dsh-perm-gate 2.1.0 → 2.1.2

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/INSTALL.ja.md +57 -1
  3. package/INSTALL.ko.md +54 -1
  4. package/INSTALL.md +53 -1
  5. package/INSTALL.zh.md +48 -1
  6. package/README.ja.md +84 -10
  7. package/README.ko.md +81 -10
  8. package/README.md +118 -22
  9. package/README.zh.md +86 -8
  10. package/cordis.patch.yml +34 -3
  11. package/dsh.plugin.json +22 -0
  12. package/lib/agent-identity.d.ts +43 -0
  13. package/lib/agent-identity.js +95 -0
  14. package/lib/client.js +114 -0
  15. package/lib/client.js.map +1 -1
  16. package/lib/compiler.d.ts +39 -0
  17. package/lib/compiler.js +106 -0
  18. package/lib/config.d.ts +95 -2
  19. package/lib/config.js +28 -3
  20. package/lib/engine.js +9 -2
  21. package/lib/evaluate.d.ts +9 -0
  22. package/lib/evaluate.js +189 -0
  23. package/lib/events.d.ts +13 -0
  24. package/lib/events.js +31 -0
  25. package/lib/index.d.ts +1 -1
  26. package/lib/index.js +235 -6
  27. package/lib/network-lifecycle.d.ts +107 -0
  28. package/lib/network-lifecycle.js +180 -0
  29. package/lib/network.d.ts +109 -0
  30. package/lib/network.js +196 -0
  31. package/lib/proxy.d.ts +160 -0
  32. package/lib/proxy.js +529 -0
  33. package/lib/risk.js +2 -3
  34. package/lib/rule-chain.d.ts +39 -0
  35. package/lib/rule-chain.js +222 -0
  36. package/lib/rule-dims.d.ts +139 -0
  37. package/lib/rule-dims.js +194 -0
  38. package/lib/rule.d.ts +25 -0
  39. package/lib/rule.js +15 -1
  40. package/lib/runtime.d.ts +87 -0
  41. package/lib/runtime.js +229 -2
  42. package/lib/session-sweep.d.ts +45 -0
  43. package/lib/session-sweep.js +153 -0
  44. package/lib/shadow.d.ts +34 -0
  45. package/lib/shadow.js +143 -0
  46. package/lib/watch.d.ts +42 -0
  47. package/lib/watch.js +170 -0
  48. package/package.json +8 -2
  49. package/scripts/patch-permission-glyph.mjs +209 -0
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Pure network-policy orchestration for dsh-perm-gate.
3
+ *
4
+ * Shared by the `tools/pre-execute` gate (URL candidate static check) and
5
+ * the local HTTP/CONNECT proxy (real subprocess traffic). Three policy modes
6
+ * mapped onto DSH sandbox presets, first-match evaluation over the loaded
7
+ * CompiledRuleset, and mode-default fallback decision. No I/O, no process
8
+ * state — unit-testable and replayable.
9
+ *
10
+ * Network is a **parallel execution surface** (NOT a P-layer): it shares the
11
+ * same CompiledRuleset / verdict vocabulary as the P0–P4 chain but does not
12
+ * enter the prefix numbering. The proxy layer has no session context, so
13
+ * `ask` decisions degrade to block + audit.
14
+ */
15
+ import type { CompiledRuleEntry, CompiledRuleset } from './rule.js';
16
+ /** The three network policy modes. */
17
+ export type NetworkMode = 'deny-all' | 'whitelist' | 'allow-all';
18
+ /** Closed mode list for runtime normalization. */
19
+ export declare const NETWORK_MODES: readonly NetworkMode[];
20
+ /** The official file-sandbox presets the mode maps onto. */
21
+ export type SandboxModeName = 'read-only' | 'workspace-write' | 'danger-full-access';
22
+ /** How a whitelist-mode unlisted target is handled. */
23
+ export type UnlistedAction = 'ask' | 'deny';
24
+ /**
25
+ * How traffic with **no in-flight shell attribution** is handled.
26
+ *
27
+ * - `'allow'` (default) — pass through unreviewed. Such traffic is not a shell
28
+ * subprocess: it is DSH's own client (a built-in network tool, the LLM
29
+ * transport). The proxy is a *subprocess* policy surface, so reviewing the
30
+ * host's own traffic risks the host blocking itself — a far worse failure
31
+ * than a missed block. This is what keeps the built-in network tools working.
32
+ * - `'deny'` — review it like any other connection. Stricter, but if a
33
+ * built-in client ever honors the proxy environment (e.g. Node 24+ with
34
+ * `NODE_USE_ENV_PROXY=1`), DSH's own calls would be blocked and the harness
35
+ * would stop working.
36
+ */
37
+ export type UnattributedAction = 'allow' | 'deny';
38
+ /** A parsed network target for one connection/URL. */
39
+ export interface NetworkTarget {
40
+ readonly host: string;
41
+ readonly port?: number;
42
+ readonly scheme?: string;
43
+ /** Resolved IP addresses (populated after DNS lookup). */
44
+ readonly ips: readonly string[];
45
+ }
46
+ /** One network decision: a rule hit or the mode-default fallback. */
47
+ export interface NetworkDecision {
48
+ readonly action: 'allow' | 'deny' | 'ask';
49
+ /** Whether a rule (true) or the mode default (false) produced the decision. */
50
+ readonly matched: boolean;
51
+ readonly mode: NetworkMode;
52
+ readonly ruleIndex?: number;
53
+ readonly rule?: CompiledRuleEntry;
54
+ /** Source file path for audit attribution. */
55
+ readonly source?: string;
56
+ /** True when the connection carried no shell attribution and was exempted. */
57
+ readonly unattributed?: true;
58
+ }
59
+ /** Proxy-layer evaluation options. */
60
+ export interface NetworkDecisionOptions {
61
+ readonly mode: NetworkMode;
62
+ readonly unlisted: UnlistedAction;
63
+ /** `allow` short-circuits loopback targets before rules; `policy` evaluates them normally. */
64
+ readonly loopback: 'allow' | 'policy';
65
+ /**
66
+ * Whether an in-flight shell execution can be attributed to this
67
+ * connection. `false` means the connection did not come from a subprocess
68
+ * the gate is tracking. Omitted = treat as attributed (pre-change behavior).
69
+ */
70
+ readonly attributed?: boolean;
71
+ /** Handling for unattributed traffic. Default `'allow'`. */
72
+ readonly unattributed?: UnattributedAction;
73
+ }
74
+ /**
75
+ * Map one official sandbox preset onto its network mode.
76
+ * `read-only` → deny-all, `workspace-write` → whitelist,
77
+ * `danger-full-access` → allow-all. Unknown → fallback.
78
+ */
79
+ export declare function networkModeForSandbox(sandbox: string | undefined, fallback: NetworkMode): NetworkMode;
80
+ /** The default decision a mode yields for an unmatched target. */
81
+ export declare function defaultDecision(mode: NetworkMode, unlisted: UnlistedAction): NetworkDecision;
82
+ /**
83
+ * Evaluate the network policy for one target against a compiled ruleset.
84
+ * First-match wins across deny → allow → ask partitions (deny-first).
85
+ *
86
+ * Proxy-layer tool attribution: shell subprocess connections carry
87
+ * `bash`/`pwsh` identity; rules scoped to other tools never fire.
88
+ * Agent identity is unknown at the proxy → agent-scoped rules fail-closed.
89
+ *
90
+ * **Unattributed traffic is exempt by default.** A connection that cannot be
91
+ * tied to an in-flight shell execution did not come from a subprocess the gate
92
+ * manages — it is DSH's own client. Reviewing it would let the host block
93
+ * itself, so it passes through (see {@link UnattributedAction}).
94
+ */
95
+ export declare function decideNetworkTarget(ruleset: CompiledRuleset, target: NetworkTarget, options: NetworkDecisionOptions): NetworkDecision;
96
+ /** Whether a target addresses the loopback range. */
97
+ export declare function isLoopbackTarget(target: NetworkTarget): boolean;
98
+ /** Whether a string is an IP literal (v4 or v6). */
99
+ export declare function isIpLiteral(host: string): boolean;
100
+ /**
101
+ * Parse a URL string into a NetworkTarget.
102
+ * Returns undefined for malformed URLs.
103
+ */
104
+ export declare function parseUrlTarget(url: string): NetworkTarget | undefined;
105
+ /**
106
+ * The short structured message a blocked proxy connection receives.
107
+ * `ask` at proxy layer degrades to block + audit (no session context).
108
+ */
109
+ export declare function blockMessage(decision: NetworkDecision): string;
package/lib/network.js ADDED
@@ -0,0 +1,196 @@
1
+ import { compileCidr, compilePortSpec, compileDomainPattern } from './compiler.js';
2
+ /** Closed mode list for runtime normalization. */
3
+ export const NETWORK_MODES = ['deny-all', 'whitelist', 'allow-all'];
4
+ // ─── Mode mapping ──────────────────────────────────────────────────────────
5
+ /**
6
+ * Map one official sandbox preset onto its network mode.
7
+ * `read-only` → deny-all, `workspace-write` → whitelist,
8
+ * `danger-full-access` → allow-all. Unknown → fallback.
9
+ */
10
+ export function networkModeForSandbox(sandbox, fallback) {
11
+ switch (sandbox) {
12
+ case 'read-only': return 'deny-all';
13
+ case 'workspace-write': return 'whitelist';
14
+ case 'danger-full-access': return 'allow-all';
15
+ default: return fallback;
16
+ }
17
+ }
18
+ /** The default decision a mode yields for an unmatched target. */
19
+ export function defaultDecision(mode, unlisted) {
20
+ switch (mode) {
21
+ case 'deny-all':
22
+ return { action: 'deny', matched: false, mode };
23
+ case 'whitelist':
24
+ return { action: unlisted, matched: false, mode };
25
+ case 'allow-all':
26
+ return { action: 'allow', matched: false, mode };
27
+ }
28
+ }
29
+ // ─── Target matching ───────────────────────────────────────────────────────
30
+ /**
31
+ * Evaluate the network policy for one target against a compiled ruleset.
32
+ * First-match wins across deny → allow → ask partitions (deny-first).
33
+ *
34
+ * Proxy-layer tool attribution: shell subprocess connections carry
35
+ * `bash`/`pwsh` identity; rules scoped to other tools never fire.
36
+ * Agent identity is unknown at the proxy → agent-scoped rules fail-closed.
37
+ *
38
+ * **Unattributed traffic is exempt by default.** A connection that cannot be
39
+ * tied to an in-flight shell execution did not come from a subprocess the gate
40
+ * manages — it is DSH's own client. Reviewing it would let the host block
41
+ * itself, so it passes through (see {@link UnattributedAction}).
42
+ */
43
+ export function decideNetworkTarget(ruleset, target, options) {
44
+ if (options.loopback === 'allow' && isLoopbackTarget(target)) {
45
+ return { action: 'allow', matched: false, mode: options.mode };
46
+ }
47
+ // Not from a shell subprocess → not this surface's to police.
48
+ if (options.attributed === false && (options.unattributed ?? 'allow') === 'allow') {
49
+ return { action: 'allow', matched: false, mode: options.mode, unattributed: true };
50
+ }
51
+ // Scan deny → allow → ask (deny-first).
52
+ for (const partition of [ruleset.deny, ruleset.allow, ruleset.ask]) {
53
+ for (const rule of partition) {
54
+ if (!rule.enabled)
55
+ continue;
56
+ if (rule.network === undefined)
57
+ continue;
58
+ if (!matchToolsProxy(rule))
59
+ continue;
60
+ if (!targetMatchesNetwork(target, rule.network))
61
+ continue;
62
+ return {
63
+ action: rule.action,
64
+ matched: true,
65
+ mode: options.mode,
66
+ ruleIndex: rule.index,
67
+ rule,
68
+ };
69
+ }
70
+ }
71
+ return defaultDecision(options.mode, options.unlisted);
72
+ }
73
+ /**
74
+ * Tool-scope check for proxy traffic: shell subprocess connections carry
75
+ * `bash`/`pwsh` identity. Rules scoped to other tools never fire at proxy.
76
+ */
77
+ function matchToolsProxy(rule) {
78
+ if (rule.tools.length === 0)
79
+ return true;
80
+ // Match if any tool pattern matches 'bash' or 'pwsh'.
81
+ return rule.tools.some((t) => t.re.test('bash') || t.re.test('pwsh'));
82
+ }
83
+ /**
84
+ * Check if a network target matches a rule's network dimension.
85
+ * All present sub-dimensions must match (AND); within a sub-dimension, entries are OR.
86
+ */
87
+ function targetMatchesNetwork(target, network) {
88
+ // domains
89
+ if (network.domains !== undefined && network.domains.length > 0) {
90
+ const matchers = network.domains.map((d) => compileDomainPattern(d));
91
+ if (!matchers.some((m) => m.re.test(target.host)))
92
+ return false;
93
+ }
94
+ // ips
95
+ if (network.ips !== undefined && network.ips.length > 0) {
96
+ const allIps = [...target.ips];
97
+ if (target.host !== undefined)
98
+ allIps.push(target.host);
99
+ const cidrs = network.ips.filter((s) => s.includes('/'));
100
+ const literals = network.ips.filter((s) => !s.includes('/'));
101
+ const ipMatch = allIps.some((ip) => literals.includes(ip) || cidrs.some((c) => {
102
+ try {
103
+ return compileCidr(c)(ip);
104
+ }
105
+ catch {
106
+ return false;
107
+ }
108
+ }));
109
+ if (!ipMatch)
110
+ return false;
111
+ }
112
+ // ports
113
+ if (network.ports !== undefined && network.ports.length > 0) {
114
+ if (target.port === undefined)
115
+ return false;
116
+ const matchers = network.ports.map((p) => {
117
+ try {
118
+ return compilePortSpec(p);
119
+ }
120
+ catch {
121
+ return (_) => false;
122
+ }
123
+ });
124
+ if (!matchers.some((m) => m(target.port)))
125
+ return false;
126
+ }
127
+ // schemes
128
+ if (network.schemes !== undefined && network.schemes.length > 0) {
129
+ if (target.scheme === undefined)
130
+ return false;
131
+ if (!network.schemes.includes(target.scheme))
132
+ return false;
133
+ }
134
+ return true;
135
+ }
136
+ // ─── Helpers ───────────────────────────────────────────────────────────────
137
+ /** Whether a target addresses the loopback range. */
138
+ export function isLoopbackTarget(target) {
139
+ const host = target.host;
140
+ if (host === 'localhost' || host === '::1' || host === '0:0:0:0:0:0:0:1')
141
+ return true;
142
+ const parts = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
143
+ if (parts === null)
144
+ return false;
145
+ return parts.slice(1).every((o) => Number(o) <= 255) && parts[1] === '127';
146
+ }
147
+ /** Whether a string is an IP literal (v4 or v6). */
148
+ export function isIpLiteral(host) {
149
+ return /^[\d.:a-fA-F]+$/.test(host) && (host.includes(':') || /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host));
150
+ }
151
+ /**
152
+ * Parse a URL string into a NetworkTarget.
153
+ * Returns undefined for malformed URLs.
154
+ */
155
+ export function parseUrlTarget(url) {
156
+ try {
157
+ const u = new URL(url);
158
+ const host = u.hostname.toLowerCase().replace(/\.+$/, '');
159
+ if (host.length === 0)
160
+ return undefined;
161
+ const port = u.port !== '' ? Number(u.port) : (u.protocol === 'https:' ? 443 : u.protocol === 'http:' ? 80 : undefined);
162
+ return {
163
+ host,
164
+ port: port !== undefined && Number.isFinite(port) ? port : undefined,
165
+ scheme: u.protocol.replace(/:$/, ''),
166
+ ips: isIpLiteral(host) ? [host] : [],
167
+ };
168
+ }
169
+ catch {
170
+ return undefined;
171
+ }
172
+ }
173
+ /**
174
+ * The short structured message a blocked proxy connection receives.
175
+ * `ask` at proxy layer degrades to block + audit (no session context).
176
+ */
177
+ export function blockMessage(decision) {
178
+ const reason = decision.rule?.reason ?? '';
179
+ if (decision.matched) {
180
+ const rulePart = decision.ruleIndex !== undefined ? ` by rule ${decision.ruleIndex + 1}` : '';
181
+ if (decision.action === 'ask') {
182
+ return `[network: blocked pending approval${rulePart}] ${reason}\n(subprocess connections cannot ride the interactive approval seam — ask your user or add an allow rule for this target)`;
183
+ }
184
+ return `[network: denied${rulePart}] ${reason}`;
185
+ }
186
+ switch (decision.mode) {
187
+ case 'deny-all':
188
+ return '[network: denied] network mode deny-all (read-only sandbox preset): only whitelisted targets are reachable';
189
+ case 'whitelist':
190
+ return decision.action === 'ask'
191
+ ? '[network: blocked pending approval] whitelist mode: the target is not matched by an allow rule'
192
+ : '[network: denied] whitelist mode: the target is not matched by an allow rule';
193
+ case 'allow-all':
194
+ return '[network: denied]';
195
+ }
196
+ }
package/lib/proxy.d.ts ADDED
@@ -0,0 +1,160 @@
1
+ import type { NetworkDecision, NetworkMode, NetworkTarget } from './network.js';
2
+ /** Proxy env var names the injector sets/restores (upper + lower for mixed-ecosystem CLIs). */
3
+ export declare const PROXY_ENV_NAMES: readonly string[];
4
+ /** NO_PROXY env var names cleared by the injector. */
5
+ export declare const NO_PROXY_ENV_NAMES: readonly string[];
6
+ /** Default DNS lookup bound (ms). A black-holed resolver must not hang a CONNECT. */
7
+ export declare const DNS_TIMEOUT_MS = 3000;
8
+ /** Default connection-setup bound (ms) — slowloris guard. */
9
+ export declare const HEADERS_TIMEOUT_MS = 60000;
10
+ /** Default cap on concurrent accepted connections. */
11
+ export declare const MAX_CONNECTIONS = 256;
12
+ /** Bound on how long close() waits for a server to finish closing (ms). */
13
+ export declare const CLOSE_TIMEOUT_MS = 2000;
14
+ /** One recorded proxy-layer block. */
15
+ export interface NetworkBlockRecord {
16
+ readonly time: number;
17
+ readonly tool: string;
18
+ readonly attributed: boolean;
19
+ readonly callId?: string;
20
+ readonly domain: string;
21
+ readonly scheme?: string;
22
+ readonly port?: number;
23
+ readonly action: 'deny' | 'ask';
24
+ readonly mode: NetworkMode;
25
+ readonly matched: boolean;
26
+ readonly source: string;
27
+ readonly ruleIndex?: number;
28
+ readonly reason?: string;
29
+ }
30
+ /** Cumulative proxy-layer block counters. */
31
+ export interface NetworkStats {
32
+ denied: number;
33
+ askBlocked: number;
34
+ }
35
+ /** Attribution the runtime supplies for one connection. */
36
+ export interface ProxyAttribution {
37
+ readonly tool: string;
38
+ readonly callId?: string;
39
+ }
40
+ /** Construction options for NetworkProxy. */
41
+ export interface NetworkProxyOptions {
42
+ /** Bind address (loopback by config). */
43
+ readonly bind: string;
44
+ /** Requested port; `0` binds an ephemeral port. */
45
+ readonly port: number;
46
+ /** Cap on recent-block records kept in memory. */
47
+ readonly maxRecent: number;
48
+ /** Decision function supplied by the runtime. */
49
+ readonly decide: (target: NetworkTarget) => NetworkDecision | Promise<NetworkDecision>;
50
+ /**
51
+ * Escalate an `ask` decision to the interactive approval seam. Called ONLY
52
+ * when {@link decide} returns `ask` — a `deny` is never escalated, so the
53
+ * rule review cannot be bypassed by approving a connection.
54
+ *
55
+ * Absent, throwing, or timing out all mean "not allowed": the connection is
56
+ * blocked exactly as it would have been.
57
+ */
58
+ readonly escalate?: (target: NetworkTarget, decision: NetworkDecision) => Promise<'allow' | 'deny'>;
59
+ /** Current attribution (newest in-flight shell execution). */
60
+ readonly attribution?: () => ProxyAttribution | undefined;
61
+ /** Called for every blocked connection. */
62
+ readonly onBlock?: (record: NetworkBlockRecord, attribution: ProxyAttribution | undefined) => void;
63
+ /** Bind a DNS lookup with this timeout (ms). Default {@link DNS_TIMEOUT_MS}. */
64
+ readonly dnsTimeoutMs?: number;
65
+ /** Connection-setup bound (ms). Default {@link HEADERS_TIMEOUT_MS}. */
66
+ readonly headersTimeoutMs?: number;
67
+ /** Cap on concurrent connections. Default {@link MAX_CONNECTIONS}. */
68
+ readonly maxConnections?: number;
69
+ /** Logger sink (proxy failures must never crash the host). */
70
+ readonly logger: {
71
+ warn(message: string): void;
72
+ };
73
+ }
74
+ /**
75
+ * The local HTTP/CONNECT policy proxy. Binds on demand; every live tunnel
76
+ * socket is tracked and destroyed on close so updates and uninstalls leave
77
+ * no orphaned connections.
78
+ */
79
+ export declare class NetworkProxy {
80
+ private readonly options;
81
+ private server;
82
+ private readonly sockets;
83
+ private readonly recent;
84
+ private readonly stats;
85
+ private actualPort;
86
+ /** In-flight start(); guards against a second concurrent bind. */
87
+ private starting;
88
+ /** Set by close(); a bind resolving afterwards tears itself down. */
89
+ private closed;
90
+ constructor(options: NetworkProxyOptions);
91
+ /**
92
+ * Logger sink that can never throw. The logger is called from inside
93
+ * 'error' handlers, so a throwing logger would itself become an
94
+ * unhandled error and defeat the whole point of the guard.
95
+ */
96
+ private safeWarn;
97
+ /** The bound port (valid after start resolves). */
98
+ get port(): number;
99
+ /** Deny/ask blocks recorded since mount, newest first. */
100
+ recentBlocks(): readonly NetworkBlockRecord[];
101
+ /** Cumulative block counters. */
102
+ blockStats(): NetworkStats;
103
+ /** Number of active sockets (tunnels + client connections). */
104
+ activeSocketCount(): number;
105
+ /**
106
+ * Bind the server and return the actual port.
107
+ * On bind failure: warns and returns -1 (degraded mode — no proxy).
108
+ * Concurrent calls share the single in-flight bind.
109
+ */
110
+ start(): Promise<number>;
111
+ private bind;
112
+ /**
113
+ * Stop the server and destroy every tunnel socket.
114
+ *
115
+ * Safe against a concurrent (in-flight) {@link start}: the `closed` flag
116
+ * makes a bind that resolves after this call tear itself down instead of
117
+ * leaving an orphaned listening server behind.
118
+ */
119
+ close(): Promise<void>;
120
+ private handleRequest;
121
+ private handleConnect;
122
+ private forwardOrBlock;
123
+ /**
124
+ * DNS-resolve a hostname so `ips`-scoped rules see real addresses, then
125
+ * decide. Resolution failure → decide on the literal name.
126
+ *
127
+ * The lookup is **time-bounded**: an unbounded `lookup()` on a black-holed
128
+ * resolver would hold the CONNECT socket open indefinitely, so a slow
129
+ * resolver degrades to literal-name evaluation instead of hanging the
130
+ * client. `ips`-scoped rules simply cannot fire in that case.
131
+ */
132
+ private decideWithResolution;
133
+ /**
134
+ * The final decision for one target: the rule/mode verdict, then — only for
135
+ * an `ask` — the interactive approval seam.
136
+ *
137
+ * Escalation is deliberately narrow:
138
+ * - A `deny` verdict is NEVER escalated. Approving a connection can widen
139
+ * reach for a target no rule allows, but it can never override a rule that
140
+ * says no. The rule review stays authoritative.
141
+ * - An escalation that throws, is absent, or returns anything but `allow`
142
+ * leaves the verdict blocked.
143
+ */
144
+ private resolveDecision;
145
+ private recordBlock;
146
+ }
147
+ /**
148
+ * Inject proxy environment variables for subprocesses and return a disposer
149
+ * restoring every previous value exactly.
150
+ *
151
+ * The snapshot pass covers EVERY name BEFORE any write: on Windows
152
+ * `process.env` is case-insensitive, so writing `HTTP_PROXY` mid-loop
153
+ * would poison the later snapshot of `http_proxy`.
154
+ *
155
+ * @param port - the bound proxy port.
156
+ * @param noProxy - `'clear'` empties NO_PROXY so policy cannot be bypassed;
157
+ * `'preserve'` keeps ambient values.
158
+ * @returns the restore disposer.
159
+ */
160
+ export declare function injectProxyEnv(port: number, noProxy: 'clear' | 'preserve'): () => void;