dsh-web-fetch-enhanced 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +14 -0
- package/README.en.md +235 -0
- package/README.md +235 -0
- package/cordis.patch.yml +11 -0
- package/docs/design.zh-CN.md +140 -0
- package/docs/security.zh-CN.md +82 -0
- package/examples/coexist.cordis.yml +10 -0
- package/examples/drop-in.cordis.yml +16 -0
- package/examples/manual.cordis.patch.yml +13 -0
- package/lib/client.js +341 -0
- package/lib/index.js +645 -0
- package/lib/types/address-policy.d.ts +24 -0
- package/lib/types/client/AllowlistCard.d.ts +16 -0
- package/lib/types/client/index.d.ts +13 -0
- package/lib/types/client/locales.d.ts +26 -0
- package/lib/types/index.d.ts +44 -0
- package/lib/types/policy.d.ts +16 -0
- package/lib/types/provider.d.ts +24 -0
- package/lib/types/resolver.d.ts +31 -0
- package/package.json +122 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { type LocaleKey } from './locales.ts';
|
|
3
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
4
|
+
interface LocaleNamespaceMap {
|
|
5
|
+
'settings.webFetchEnhanced': LocaleKey;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export declare const name = "web-fetch-enhanced-client";
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
export declare function apply(ctx: Context): void;
|
|
11
|
+
export { AllowlistCard, parseLines } from './AllowlistCard.tsx';
|
|
12
|
+
export type { AllowlistCardProps, AllowlistSettings } from './AllowlistCard.tsx';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const en: {
|
|
2
|
+
readonly title: "Enhanced web fetch allowlist";
|
|
3
|
+
readonly description: "Allow selected non-public destinations for web_fetch. Public addresses remain available by default.";
|
|
4
|
+
readonly expand: "Expand";
|
|
5
|
+
readonly collapse: "Collapse";
|
|
6
|
+
readonly unsaved: "Unsaved";
|
|
7
|
+
readonly cidrs: "Allowed CIDRs";
|
|
8
|
+
readonly cidrsHint: "One IPv4 or IPv6 CIDR per line. Keep the range as narrow as possible.";
|
|
9
|
+
readonly cidrsPlaceholder: "198.18.0.0/15";
|
|
10
|
+
readonly hostnames: "Allowed hostnames";
|
|
11
|
+
readonly hostnamesHint: "Optional second factor. Use an exact hostname or a left-most *. wildcard; omit protocols, paths, and ports.";
|
|
12
|
+
readonly hostnamesPlaceholder: "api.example.com\n*.docs.example.com";
|
|
13
|
+
readonly save: "Save";
|
|
14
|
+
readonly reset: "Reset to Profile";
|
|
15
|
+
readonly discard: "Discard";
|
|
16
|
+
readonly saving: "Saving…";
|
|
17
|
+
readonly saved: "Saved";
|
|
18
|
+
readonly failed: "The Host rejected the change. Review the entries and try again.";
|
|
19
|
+
readonly unavailable: "This settings namespace is not available in the current Profile.";
|
|
20
|
+
readonly readOnly: "This connection cannot persist Host Profile settings.";
|
|
21
|
+
readonly duplicate: "Duplicate entries are not allowed.";
|
|
22
|
+
readonly blank: "Blank entries are ignored.";
|
|
23
|
+
};
|
|
24
|
+
export type LocaleKey = keyof typeof en;
|
|
25
|
+
export declare const zh: Record<LocaleKey, string>;
|
|
26
|
+
//# sourceMappingURL=locales.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Harness HTTP fetch provider with explicit non-public CIDR exceptions.
|
|
3
|
+
* It preserves the native security model while making non-public exceptions explicit.
|
|
4
|
+
*
|
|
5
|
+
* @module dsh-web-fetch-enhanced
|
|
6
|
+
*/
|
|
7
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
8
|
+
import z from '@deepseek-ai/schemastery';
|
|
9
|
+
import { EnhancedHttpFetchProvider } from './provider.ts';
|
|
10
|
+
/** Explicit product User-Agent used by default. */
|
|
11
|
+
export declare const DEFAULT_USER_AGENT = "dsh-web-fetch-enhanced/0.1.0";
|
|
12
|
+
/** Default provider id; select it in the dsh-web row with fetchProvider. */
|
|
13
|
+
export declare const DEFAULT_PROVIDER_ID = "http-enhanced";
|
|
14
|
+
/** Settings namespace paired with the Web Profile configuration card. */
|
|
15
|
+
export declare const SETTINGS_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
|
|
16
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
17
|
+
export declare const name = "web-fetch-enhanced";
|
|
18
|
+
/** The web seam this provider contributes to. */
|
|
19
|
+
export declare const inject: string[];
|
|
20
|
+
/** Plugin configuration. Every non-public exception is explicit and deny-by-default. */
|
|
21
|
+
export interface Config {
|
|
22
|
+
/** Provider id registered in ctx.web. Defaults to http-enhanced. */
|
|
23
|
+
providerId?: string;
|
|
24
|
+
/** Non-public IPv4/IPv6 CIDRs that may bypass the public-address filter. */
|
|
25
|
+
allowCidrs?: string[];
|
|
26
|
+
/** Optional exact hosts or left-most wildcard rules required in addition to allowCidrs. */
|
|
27
|
+
allowHostnames?: string[];
|
|
28
|
+
/** Maximum response body size in bytes. */
|
|
29
|
+
maxResponseBytes?: number;
|
|
30
|
+
/** Maximum decoded body length in characters. */
|
|
31
|
+
maxBodyChars?: number;
|
|
32
|
+
/** Default fetch timeout in milliseconds, within Node's timer range. */
|
|
33
|
+
timeoutMs?: number;
|
|
34
|
+
/** Maximum number of same-origin redirect hops to follow. */
|
|
35
|
+
maxRedirects?: number;
|
|
36
|
+
/** User-Agent header sent on every request. */
|
|
37
|
+
userAgent?: string;
|
|
38
|
+
}
|
|
39
|
+
export declare const Config: z<Config>;
|
|
40
|
+
/** Construct the provider without mounting it, useful for tests and custom compositions. */
|
|
41
|
+
export declare function createProvider(config?: Config): EnhancedHttpFetchProvider;
|
|
42
|
+
/** Register the enhanced fetch provider and its live Web Profile settings section. */
|
|
43
|
+
export declare function apply(ctx: Context, config: Config): void;
|
|
44
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Maximum accepted request URL length. */
|
|
2
|
+
export declare const WEB_FETCH_MAX_URL_LENGTH = 2048;
|
|
3
|
+
export type FetchableKind = 'html' | 'text';
|
|
4
|
+
/** Parse a URL and enforce HTTP(S)-only, anonymous requests. */
|
|
5
|
+
export declare function parseFetchUrl(input: string): URL;
|
|
6
|
+
/** Validate URL length before parsing. */
|
|
7
|
+
export declare function validateFetchUrl(input: string): URL;
|
|
8
|
+
/** Same origin means the scheme, hostname, and port are all equal. */
|
|
9
|
+
export declare function isSameOrigin(a: URL, b: URL): boolean;
|
|
10
|
+
/** Classify supported textual response types. */
|
|
11
|
+
export declare function classifyContentType(contentType: string | null): FetchableKind | undefined;
|
|
12
|
+
/** Return the declared charset, normalized to lower case. */
|
|
13
|
+
export declare function parseCharset(contentType: string | null): string | undefined;
|
|
14
|
+
/** Create a decoder, rejecting unsupported charset labels. */
|
|
15
|
+
export declare function decoderForCharset(charset: string | undefined): TextDecoder;
|
|
16
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { WebFetchProvider, WebFetchRequest, WebFetchResult } from '@deepseek-ai/dsh-web';
|
|
2
|
+
import type { FetchResolver } from './resolver.ts';
|
|
3
|
+
/** Resolved transport and response limits. */
|
|
4
|
+
export interface HttpFetchLimits {
|
|
5
|
+
maxResponseBytes: number;
|
|
6
|
+
maxBodyChars: number;
|
|
7
|
+
timeoutMs: number;
|
|
8
|
+
maxRedirects: number;
|
|
9
|
+
userAgent: string;
|
|
10
|
+
}
|
|
11
|
+
/** Anonymous HTTP(S) provider with allowlist-aware address validation. */
|
|
12
|
+
export declare class EnhancedHttpFetchProvider implements WebFetchProvider {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
private readonly limits;
|
|
15
|
+
private readonly resolveAddresses;
|
|
16
|
+
constructor(id: string, limits: HttpFetchLimits, resolveAddresses: FetchResolver);
|
|
17
|
+
available(): boolean;
|
|
18
|
+
fetch(request: WebFetchRequest, signal?: AbortSignal): Promise<WebFetchResult>;
|
|
19
|
+
private followAndRead;
|
|
20
|
+
private requestOnce;
|
|
21
|
+
private readBody;
|
|
22
|
+
private readCapped;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { LookupAddress, LookupOptions } from 'node:dns';
|
|
2
|
+
import type { Response } from 'undici';
|
|
3
|
+
import { AddressPolicy } from './address-policy.ts';
|
|
4
|
+
/** DNS resolver shape used by Node and by focused tests. */
|
|
5
|
+
export type AddressResolver = (hostname: string, options: {
|
|
6
|
+
all: true;
|
|
7
|
+
order: 'verbatim';
|
|
8
|
+
}) => Promise<LookupAddress[]>;
|
|
9
|
+
/** One validated address that the pinned transport may connect to. */
|
|
10
|
+
export interface ValidatedAddress {
|
|
11
|
+
readonly address: string;
|
|
12
|
+
readonly family: 4 | 6;
|
|
13
|
+
}
|
|
14
|
+
/** Resolver accepted by the enhanced fetch provider. */
|
|
15
|
+
export type FetchResolver = (hostname: string, signal: AbortSignal) => Promise<ValidatedAddress[]>;
|
|
16
|
+
/** A response and the disposer for its request-local dispatcher. */
|
|
17
|
+
export interface PinnedResponse {
|
|
18
|
+
readonly response: Response;
|
|
19
|
+
close(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
/** Build the resolver injected into the address-pinned HTTP provider. */
|
|
22
|
+
export declare function createAllowlistResolver(policy: AddressPolicy, resolver?: AddressResolver): FetchResolver;
|
|
23
|
+
/** Resolve once and reject the complete answer set when any entry is disallowed. */
|
|
24
|
+
export declare function resolveAllowedAddresses(hostname: string, signal: AbortSignal, policy: AddressPolicy, resolver?: AddressResolver): Promise<ValidatedAddress[]>;
|
|
25
|
+
/** Fetch while preserving the URL hostname for Host and TLS SNI. */
|
|
26
|
+
export declare function requestPinned(url: URL, addresses: readonly ValidatedAddress[], headers: Record<string, string>, signal: AbortSignal): Promise<PinnedResponse>;
|
|
27
|
+
type LookupCallback = (error: NodeJS.ErrnoException | null, address: string | LookupAddress[], family?: number) => void;
|
|
28
|
+
/** Create a Node lookup callback that returns only the prevalidated answer set. */
|
|
29
|
+
export declare function createPinnedLookup(addresses: readonly ValidatedAddress[]): (hostname: string, options: LookupOptions, callback: LookupCallback) => void;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=resolver.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-web-fetch-enhanced",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Configurable non-public address allowlists for DeepSeek Harness web_fetch",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./lib/index.js",
|
|
7
|
+
"types": "./lib/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/types/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./client": {
|
|
14
|
+
"types": "./lib/types/client/index.d.ts",
|
|
15
|
+
"default": "./lib/client.js"
|
|
16
|
+
},
|
|
17
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"lib/index.js",
|
|
22
|
+
"lib/client.js",
|
|
23
|
+
"lib/types/**/*.d.ts",
|
|
24
|
+
"cordis.patch.yml",
|
|
25
|
+
"docs",
|
|
26
|
+
"examples",
|
|
27
|
+
"README.md",
|
|
28
|
+
"README.en.md",
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"NOTICE"
|
|
31
|
+
],
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"dsh": {
|
|
34
|
+
"bundle": {
|
|
35
|
+
"patch": "./cordis.patch.yml"
|
|
36
|
+
},
|
|
37
|
+
"client": {
|
|
38
|
+
"inject": [
|
|
39
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins",
|
|
40
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
41
|
+
"@deepseek-ai/dsh-client-locale",
|
|
42
|
+
"@deepseek-ai/dsh-client-ui-renderer"
|
|
43
|
+
],
|
|
44
|
+
"platform": "web"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"deepseek-harness",
|
|
49
|
+
"dsh-plugin",
|
|
50
|
+
"web-fetch",
|
|
51
|
+
"ssrf",
|
|
52
|
+
"fake-ip",
|
|
53
|
+
"allowlist"
|
|
54
|
+
],
|
|
55
|
+
"license": "MIT",
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "git+https://github.com/Yurzi/dsh-web-fetch-enhanced.git"
|
|
59
|
+
},
|
|
60
|
+
"homepage": "https://github.com/Yurzi/dsh-web-fetch-enhanced#readme",
|
|
61
|
+
"bugs": {
|
|
62
|
+
"url": "https://github.com/Yurzi/dsh-web-fetch-enhanced/issues"
|
|
63
|
+
},
|
|
64
|
+
"publishConfig": {
|
|
65
|
+
"access": "public"
|
|
66
|
+
},
|
|
67
|
+
"engines": {
|
|
68
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
72
|
+
"@deepseek-ai/dsh-timeout": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
73
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
74
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
75
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
77
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
78
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
79
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
80
|
+
"@deepseek-ai/dsh-web": "^0.1.1-rc.2 || ^0.1.2-alpha.1"
|
|
81
|
+
},
|
|
82
|
+
"dependencies": {
|
|
83
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
84
|
+
"ipaddr.js": "^2.5.0",
|
|
85
|
+
"undici": "^8.10.0"
|
|
86
|
+
},
|
|
87
|
+
"devDependencies": {
|
|
88
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
89
|
+
"@deepseek-ai/dsh-timeout": "^0.1.1-rc.2",
|
|
90
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
|
|
91
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
|
|
92
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
|
|
93
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.1-rc.2",
|
|
94
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
|
|
95
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.1-rc.2",
|
|
96
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.2",
|
|
97
|
+
"@deepseek-ai/dsh-web": "^0.1.1-rc.2",
|
|
98
|
+
"@types/js-yaml": "^4.0.9",
|
|
99
|
+
"@types/node": "^22.19.13",
|
|
100
|
+
"@types/react": "~18.3.1",
|
|
101
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
102
|
+
"js-yaml": "^4.1.0",
|
|
103
|
+
"oxlint": "^1.51.0",
|
|
104
|
+
"oxlint-tsgolint": "7.0.2001",
|
|
105
|
+
"publint": "^0.3.21",
|
|
106
|
+
"react": "^18.2.0",
|
|
107
|
+
"tsdown": "^0.21.0",
|
|
108
|
+
"typescript": "^6.0.3",
|
|
109
|
+
"vitest": "^4.1.8"
|
|
110
|
+
},
|
|
111
|
+
"scripts": {
|
|
112
|
+
"build": "pnpm run build:types && pnpm run bundle",
|
|
113
|
+
"build:types": "tsc -p tsconfig.json",
|
|
114
|
+
"bundle": "tsdown",
|
|
115
|
+
"watch": "tsdown --watch",
|
|
116
|
+
"typecheck": "tsc -p tsconfig.check.json --noEmit",
|
|
117
|
+
"lint": "oxlint --type-aware --type-check -c .oxlintrc.json src tests",
|
|
118
|
+
"test": "vitest run",
|
|
119
|
+
"test:coverage": "vitest run --coverage",
|
|
120
|
+
"check": "pnpm run typecheck && pnpm run lint && pnpm run build && pnpm run test && publint"
|
|
121
|
+
}
|
|
122
|
+
}
|