@asterxsk/kiln 0.1.0 → 0.2.0
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 -21
- package/README.md +170 -170
- package/agent/AGENTS.md +67 -67
- package/agent/README.md +5 -5
- package/agent/extensions/AGENTS.md +68 -68
- package/agent/extensions/ask-user/index.ts +418 -418
- package/agent/extensions/ask-user/package-lock.json +769 -769
- package/agent/extensions/ask-user/package.json +19 -19
- package/agent/extensions/ask-user/prompt.ts +45 -45
- package/agent/extensions/ask-user/tsconfig.json +7 -7
- package/agent/extensions/background-terminals/docs/implementation-guide.md +942 -942
- package/agent/extensions/background-terminals/index.ts +627 -627
- package/agent/extensions/background-terminals/manager.test.ts +735 -735
- package/agent/extensions/background-terminals/output.test.ts +109 -109
- package/agent/extensions/background-terminals/package-lock.json +769 -769
- package/agent/extensions/background-terminals/package.json +17 -17
- package/agent/extensions/background-terminals/prompt.test.ts +125 -125
- package/agent/extensions/background-terminals/ps.test.ts +82 -82
- package/agent/extensions/background-terminals/result-delivery.test.ts +44 -44
- package/agent/extensions/background-terminals/src/domain.ts +87 -87
- package/agent/extensions/background-terminals/src/manager.ts +907 -907
- package/agent/extensions/background-terminals/src/output.ts +84 -84
- package/agent/extensions/background-terminals/src/prompt.ts +142 -142
- package/agent/extensions/background-terminals/src/result-delivery.ts +27 -27
- package/agent/extensions/background-terminals/src/runtime.ts +36 -36
- package/agent/extensions/background-terminals/src/ui/output-view.ts +79 -79
- package/agent/extensions/background-terminals/src/ui/ps.ts +621 -621
- package/agent/extensions/background-terminals/tsconfig.json +7 -7
- package/agent/extensions/file-search/index.spec.ts +443 -443
- package/agent/extensions/file-search/index.ts +459 -459
- package/agent/extensions/file-search/package-lock.json +2253 -2253
- package/agent/extensions/file-search/package.json +23 -23
- package/agent/extensions/file-search/src/args.ts +122 -122
- package/agent/extensions/file-search/src/binaries.ts +422 -422
- package/agent/extensions/file-search/src/output.ts +126 -126
- package/agent/extensions/file-search/src/process.ts +146 -146
- package/agent/extensions/file-search/src/prompt.ts +52 -52
- package/agent/extensions/file-search/tsconfig.json +7 -7
- package/agent/extensions/modelconf/PLAN.md +915 -915
- package/agent/extensions/modelconf/index.ts +296 -296
- package/agent/extensions/modelconf/src/ui/ModelConfView.ts +1101 -1101
- package/agent/extensions/pi-web-access/CHANGELOG.md +690 -690
- package/agent/extensions/pi-web-access/LICENSE +21 -21
- package/agent/extensions/pi-web-access/README.md +470 -470
- package/agent/extensions/pi-web-access/SECURITY.md +5 -5
- package/agent/extensions/pi-web-access/activity.ts +101 -101
- package/agent/extensions/pi-web-access/auth-fetch.ts +148 -148
- package/agent/extensions/pi-web-access/brightdata-unlocker.ts +272 -272
- package/agent/extensions/pi-web-access/chrome-cookies.ts +669 -669
- package/agent/extensions/pi-web-access/content-find.ts +139 -139
- package/agent/extensions/pi-web-access/credential-source.ts +191 -191
- package/agent/extensions/pi-web-access/data-uri-sanitize.ts +406 -406
- package/agent/extensions/pi-web-access/datalab-pdf-extract.ts +568 -568
- package/agent/extensions/pi-web-access/declared-web-links.ts +173 -173
- package/agent/extensions/pi-web-access/evidence/CONTRACT-EVIDENCE.md +496 -496
- package/agent/extensions/pi-web-access/evidence/contract-probe.mjs +140 -140
- package/agent/extensions/pi-web-access/exa.ts +526 -526
- package/agent/extensions/pi-web-access/extract.ts +1196 -1196
- package/agent/extensions/pi-web-access/feature-config.ts +29 -29
- package/agent/extensions/pi-web-access/fetch-params.ts +111 -111
- package/agent/extensions/pi-web-access/gemini-adc.ts +298 -298
- package/agent/extensions/pi-web-access/gemini-api.ts +353 -353
- package/agent/extensions/pi-web-access/gemini-pdf-extract.ts +108 -108
- package/agent/extensions/pi-web-access/gemini-url-context.ts +128 -128
- package/agent/extensions/pi-web-access/gemini-web-config.ts +101 -101
- package/agent/extensions/pi-web-access/gemini-web.ts +487 -487
- package/agent/extensions/pi-web-access/github-api.ts +197 -197
- package/agent/extensions/pi-web-access/github-extract.ts +746 -746
- package/agent/extensions/pi-web-access/github-issue-pr.ts +700 -700
- package/agent/extensions/pi-web-access/index.ts +1737 -1737
- package/agent/extensions/pi-web-access/package-lock.json +5808 -5808
- package/agent/extensions/pi-web-access/package.json +64 -64
- package/agent/extensions/pi-web-access/page-query.ts +96 -96
- package/agent/extensions/pi-web-access/pdf-extract.ts +409 -409
- package/agent/extensions/pi-web-access/promise-try.d.ts +7 -7
- package/agent/extensions/pi-web-access/query-rewrite.ts +51 -51
- package/agent/extensions/pi-web-access/render-search-error.ts +170 -170
- package/agent/extensions/pi-web-access/rsc-extract.ts +338 -338
- package/agent/extensions/pi-web-access/source-check.ts +282 -282
- package/agent/extensions/pi-web-access/ssrf-protection.ts +526 -526
- package/agent/extensions/pi-web-access/storage.ts +521 -521
- package/agent/extensions/pi-web-access/summary-model-scope.ts +125 -125
- package/agent/extensions/pi-web-access/test/auth-fetch.test.mjs +208 -208
- package/agent/extensions/pi-web-access/test/brightdata-unlocker.test.mjs +840 -840
- package/agent/extensions/pi-web-access/test/chrome-cookie-extraction.test.mjs +441 -441
- package/agent/extensions/pi-web-access/test/config-path.test.mjs +283 -283
- package/agent/extensions/pi-web-access/test/content-find.test.mjs +25 -25
- package/agent/extensions/pi-web-access/test/credential-source.test.mjs +118 -118
- package/agent/extensions/pi-web-access/test/data-uri-sanitize.test.mjs +210 -210
- package/agent/extensions/pi-web-access/test/datalab-pdf-extract.test.mjs +552 -552
- package/agent/extensions/pi-web-access/test/declared-web-links.test.mjs +212 -212
- package/agent/extensions/pi-web-access/test/fetch-answer-storage.test.mjs +40 -40
- package/agent/extensions/pi-web-access/test/fetch-cache-storage.test.mjs +334 -334
- package/agent/extensions/pi-web-access/test/fetch-content-domain-policy.test.mjs +95 -95
- package/agent/extensions/pi-web-access/test/fetch-modes.test.mjs +53 -53
- package/agent/extensions/pi-web-access/test/fetch-not-found-guidance.test.mjs +92 -92
- package/agent/extensions/pi-web-access/test/fetch-params.test.mjs +86 -86
- package/agent/extensions/pi-web-access/test/fetch-render-call.test.mjs +34 -34
- package/agent/extensions/pi-web-access/test/fetch-routing.test.mjs +173 -173
- package/agent/extensions/pi-web-access/test/gemini-adc-auth.test.mjs +257 -257
- package/agent/extensions/pi-web-access/test/gemini-api-transport.test.mjs +170 -170
- package/agent/extensions/pi-web-access/test/gemini-pdf-extract.test.mjs +133 -133
- package/agent/extensions/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +178 -178
- package/agent/extensions/pi-web-access/test/gemini-web-header-overflow.test.mjs +148 -148
- package/agent/extensions/pi-web-access/test/get-search-content.test.mjs +223 -223
- package/agent/extensions/pi-web-access/test/github-extract.test.mjs +378 -378
- package/agent/extensions/pi-web-access/test/github-issue-pr.test.mjs +565 -565
- package/agent/extensions/pi-web-access/test/inline-content-config.test.mjs +99 -99
- package/agent/extensions/pi-web-access/test/lazy-extract-load.test.mjs +118 -118
- package/agent/extensions/pi-web-access/test/local-video-oversize.test.mjs +52 -52
- package/agent/extensions/pi-web-access/test/package-typebox-dependency.test.mjs +50 -50
- package/agent/extensions/pi-web-access/test/page-query.test.mjs +51 -51
- package/agent/extensions/pi-web-access/test/pdf-config.test.mjs +140 -140
- package/agent/extensions/pi-web-access/test/pdf-extract.test.mjs +500 -500
- package/agent/extensions/pi-web-access/test/proxy-transport.test.mjs +286 -286
- package/agent/extensions/pi-web-access/test/query-rewrite.test.mjs +52 -52
- package/agent/extensions/pi-web-access/test/rsc-fallback.test.mjs +102 -102
- package/agent/extensions/pi-web-access/test/search-error-render.test.mjs +152 -152
- package/agent/extensions/pi-web-access/test/search-providers.test.mjs +274 -274
- package/agent/extensions/pi-web-access/test/source-check.test.mjs +179 -179
- package/agent/extensions/pi-web-access/test/ssrf-allow-ranges-config.test.mjs +205 -205
- package/agent/extensions/pi-web-access/test/ssrf-protection.test.mjs +456 -456
- package/agent/extensions/pi-web-access/test/tool-registration-config.test.mjs +182 -182
- package/agent/extensions/pi-web-access/test/youtube-extract-errors.test.mjs +64 -64
- package/agent/extensions/pi-web-access/tsconfig.json +11 -11
- package/agent/extensions/pi-web-access/utils.ts +451 -451
- package/agent/extensions/pi-web-access/video-extract.ts +392 -392
- package/agent/extensions/pi-web-access/youtube-extract.ts +328 -328
- package/agent/extensions/shared/activity-status.ts +31 -31
- package/agent/extensions/shared/child-session.test.ts +270 -270
- package/agent/extensions/shared/child-session.ts +148 -148
- package/agent/extensions/shared/context-utilization.test.ts +48 -48
- package/agent/extensions/shared/context-utilization.ts +47 -47
- package/agent/extensions/shared/dashboard-state.ts +99 -99
- package/agent/extensions/shared/tool-call-timeout.test.ts +117 -117
- package/agent/extensions/shared/tool-call-timeout.ts +104 -104
- package/agent/extensions/subagents/by-the-way.test.ts +29 -29
- package/agent/extensions/subagents/claude.test.ts +119 -119
- package/agent/extensions/subagents/codex.test.ts +102 -102
- package/agent/extensions/subagents/context-usage.test.ts +107 -107
- package/agent/extensions/subagents/docs/design-plan.md +568 -568
- package/agent/extensions/subagents/docs/effect-v4-extension-guide.md +354 -354
- package/agent/extensions/subagents/docs/effect-v4-notes.md +571 -571
- package/agent/extensions/subagents/index.ts +779 -779
- package/agent/extensions/subagents/manager.test.ts +276 -276
- package/agent/extensions/subagents/package-lock.json +2244 -2244
- package/agent/extensions/subagents/package.json +19 -19
- package/agent/extensions/subagents/result-delivery.test.ts +27 -27
- package/agent/extensions/subagents/src/backend.ts +73 -73
- package/agent/extensions/subagents/src/backends/claude.ts +701 -701
- package/agent/extensions/subagents/src/backends/codex.ts +1060 -1060
- package/agent/extensions/subagents/src/backends/pi.ts +575 -575
- package/agent/extensions/subagents/src/backends/stub.ts +300 -300
- package/agent/extensions/subagents/src/by-the-way.ts +21 -21
- package/agent/extensions/subagents/src/domain.ts +253 -253
- package/agent/extensions/subagents/src/format.ts +74 -74
- package/agent/extensions/subagents/src/manager.ts +736 -736
- package/agent/extensions/subagents/src/prompt.ts +92 -92
- package/agent/extensions/subagents/src/result-delivery.ts +20 -20
- package/agent/extensions/subagents/src/runtime.ts +53 -53
- package/agent/extensions/subagents/src/ui/takeover.ts +583 -583
- package/agent/extensions/subagents/src/ui/transcript.ts +201 -201
- package/agent/extensions/subagents/takeover.test.ts +29 -29
- package/agent/extensions/subagents/tsconfig.json +7 -7
- package/agent/extensions/todo/AGENTS.md +38 -38
- package/agent/extensions/todo/LICENSE +21 -21
- package/agent/extensions/todo/config.ts +55 -55
- package/agent/extensions/todo/index.ts +151 -151
- package/agent/extensions/todo/locales/de.json +17 -17
- package/agent/extensions/todo/locales/en.json +15 -15
- package/agent/extensions/todo/locales/es.json +17 -17
- package/agent/extensions/todo/locales/fr.json +17 -17
- package/agent/extensions/todo/locales/pt-BR.json +17 -17
- package/agent/extensions/todo/locales/pt.json +17 -17
- package/agent/extensions/todo/locales/ru.json +17 -17
- package/agent/extensions/todo/locales/uk.json +17 -17
- package/agent/extensions/todo/locales/zh.json +17 -17
- package/agent/extensions/todo/package-lock.json +3358 -3358
- package/agent/extensions/todo/package.json +67 -67
- package/agent/extensions/todo/state/i18n-bridge.ts +64 -64
- package/agent/extensions/todo/state/invariants.ts +20 -20
- package/agent/extensions/todo/state/replay.ts +38 -38
- package/agent/extensions/todo/state/selectors.ts +107 -107
- package/agent/extensions/todo/state/state-reducer.ts +326 -326
- package/agent/extensions/todo/state/state.ts +18 -18
- package/agent/extensions/todo/state/store.ts +82 -82
- package/agent/extensions/todo/state/task-graph.ts +57 -57
- package/agent/extensions/todo/todo-overlay.ts +200 -200
- package/agent/extensions/todo/todo.ts +155 -155
- package/agent/extensions/todo/tool/response-envelope.ts +109 -109
- package/agent/extensions/todo/tool/types.ts +206 -206
- package/agent/extensions/todo/view/format.ts +177 -177
- package/agent/install.ps1 +637 -527
- package/agent/install.sh +620 -511
- package/agent/keybindings.json +7 -7
- package/bin/kiln.js +124 -11
- package/package.json +8 -2
- package/agent/extensions/taste/index.ts +0 -443
- package/agent/extensions/taste/install.ps1 +0 -23
- package/agent/extensions/taste/install.sh +0 -21
- /package/agent/extensions/{status line → statusline}/index.ts +0 -0
- /package/agent/extensions/{status line → statusline}/install.ps1 +0 -0
- /package/agent/extensions/{status line → statusline}/install.sh +0 -0
|
@@ -1,205 +1,205 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { test } from "node:test";
|
|
7
|
-
|
|
8
|
-
const extractUrl = new URL("../extract.ts", import.meta.url).href;
|
|
9
|
-
|
|
10
|
-
// `loadSsrfAllowRanges` reads the config path captured at module load, so each
|
|
11
|
-
// case runs in a child process with PI_CODING_AGENT_DIR pointed at a temp dir.
|
|
12
|
-
function runLoad(env) {
|
|
13
|
-
const childEnv = { ...process.env };
|
|
14
|
-
delete childEnv.PI_CODING_AGENT_DIR;
|
|
15
|
-
delete childEnv.XDG_CONFIG_HOME;
|
|
16
|
-
for (const [key, value] of Object.entries(env)) {
|
|
17
|
-
if (value === undefined) delete childEnv[key];
|
|
18
|
-
else childEnv[key] = value;
|
|
19
|
-
}
|
|
20
|
-
const script = `
|
|
21
|
-
const { loadSsrfAllowRanges } = await import(${JSON.stringify(extractUrl)});
|
|
22
|
-
try {
|
|
23
|
-
console.log(JSON.stringify({ ok: true, ranges: loadSsrfAllowRanges() }));
|
|
24
|
-
} catch (err) {
|
|
25
|
-
console.log(JSON.stringify({ ok: false, error: err instanceof Error ? err.message : String(err) }));
|
|
26
|
-
}
|
|
27
|
-
`;
|
|
28
|
-
const child = spawnSync(process.execPath, ["--input-type=module"], {
|
|
29
|
-
input: script,
|
|
30
|
-
encoding: "utf8",
|
|
31
|
-
env: childEnv,
|
|
32
|
-
});
|
|
33
|
-
assert.equal(child.status, 0, child.stderr);
|
|
34
|
-
return JSON.parse(child.stdout);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function runLoadConfig(env) {
|
|
38
|
-
const childEnv = { ...process.env };
|
|
39
|
-
delete childEnv.PI_CODING_AGENT_DIR;
|
|
40
|
-
delete childEnv.XDG_CONFIG_HOME;
|
|
41
|
-
for (const [key, value] of Object.entries(env)) {
|
|
42
|
-
if (value === undefined) delete childEnv[key];
|
|
43
|
-
else childEnv[key] = value;
|
|
44
|
-
}
|
|
45
|
-
const script = `
|
|
46
|
-
const { loadSsrfConfig } = await import(${JSON.stringify(extractUrl)});
|
|
47
|
-
try {
|
|
48
|
-
console.log(JSON.stringify({ ok: true, config: loadSsrfConfig() }));
|
|
49
|
-
} catch (err) {
|
|
50
|
-
console.log(JSON.stringify({ ok: false, error: err instanceof Error ? err.message : String(err) }));
|
|
51
|
-
}
|
|
52
|
-
`;
|
|
53
|
-
const child = spawnSync(process.execPath, ["--input-type=module"], {
|
|
54
|
-
input: script,
|
|
55
|
-
encoding: "utf8",
|
|
56
|
-
env: childEnv,
|
|
57
|
-
});
|
|
58
|
-
assert.equal(child.status, 0, child.stderr);
|
|
59
|
-
return JSON.parse(child.stdout);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function makeConfigDir(prefix) {
|
|
63
|
-
const root = await mkdtemp(join(tmpdir(), prefix));
|
|
64
|
-
const agentDir = join(root, "agent-dir");
|
|
65
|
-
await mkdir(agentDir, { recursive: true });
|
|
66
|
-
return { root, agentDir, configPath: join(agentDir, "web-search.json") };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function envFor(root, agentDir) {
|
|
70
|
-
return {
|
|
71
|
-
PI_CODING_AGENT_DIR: agentDir,
|
|
72
|
-
HOME: join(root, "home"),
|
|
73
|
-
USERPROFILE: join(root, "home"),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
test("loadSsrfConfig defaults trustEnvProxy to false", async () => {
|
|
78
|
-
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-proxy-default-");
|
|
79
|
-
await writeFile(configPath, JSON.stringify({ ssrf: { allowRanges: ["198.18.0.0/15"] } }), "utf8");
|
|
80
|
-
|
|
81
|
-
const result = runLoadConfig(envFor(root, agentDir));
|
|
82
|
-
assert.equal(result.ok, true, result.error);
|
|
83
|
-
assert.deepEqual(result.config, { allowRanges: ["198.18.0.0/15"], trustEnvProxy: false });
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test("loadFetchContentDomainPolicy reads normalized allow and deny hostnames", async () => {
|
|
87
|
-
const { root, agentDir, configPath } = await makeConfigDir("pi-domain-policy-valid-");
|
|
88
|
-
await writeFile(configPath, JSON.stringify({ fetchContent: { domainPolicy: { allow: [" Example.COM. "], deny: ["blocked.example.com"] } } }), "utf8");
|
|
89
|
-
|
|
90
|
-
const childEnv = envFor(root, agentDir);
|
|
91
|
-
const child = spawnSync(process.execPath, ["--input-type=module"], {
|
|
92
|
-
input: `
|
|
93
|
-
const { loadFetchContentDomainPolicy } = await import(${JSON.stringify(extractUrl.replace("/extract.ts", "/ssrf-protection.ts"))});
|
|
94
|
-
console.log(JSON.stringify(loadFetchContentDomainPolicy()));
|
|
95
|
-
`,
|
|
96
|
-
encoding: "utf8",
|
|
97
|
-
env: childEnv,
|
|
98
|
-
});
|
|
99
|
-
assert.equal(child.status, 0, child.stderr);
|
|
100
|
-
assert.deepEqual(JSON.parse(child.stdout), { allow: ["example.com"], deny: ["blocked.example.com"] });
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("loadFetchContentDomainPolicy rejects malformed policy values", async () => {
|
|
104
|
-
for (const domainPolicy of [
|
|
105
|
-
{ allow: "example.com" },
|
|
106
|
-
{ deny: ["*.example.com"] },
|
|
107
|
-
{ allow: ["example.com", 42] },
|
|
108
|
-
]) {
|
|
109
|
-
const { root, agentDir, configPath } = await makeConfigDir("pi-domain-policy-invalid-");
|
|
110
|
-
await writeFile(configPath, JSON.stringify({ fetchContent: { domainPolicy } }), "utf8");
|
|
111
|
-
const childEnv = envFor(root, agentDir);
|
|
112
|
-
const child = spawnSync(process.execPath, ["--input-type=module"], {
|
|
113
|
-
input: `
|
|
114
|
-
const { loadFetchContentDomainPolicy } = await import(${JSON.stringify(extractUrl.replace("/extract.ts", "/ssrf-protection.ts"))});
|
|
115
|
-
try { loadFetchContentDomainPolicy(); console.log(JSON.stringify({ ok: true })); }
|
|
116
|
-
catch (err) { console.log(JSON.stringify({ ok: false, error: err.message })); }
|
|
117
|
-
`,
|
|
118
|
-
encoding: "utf8",
|
|
119
|
-
env: childEnv,
|
|
120
|
-
});
|
|
121
|
-
assert.equal(child.status, 0, child.stderr);
|
|
122
|
-
const result = JSON.parse(child.stdout);
|
|
123
|
-
assert.equal(result.ok, false, JSON.stringify(domainPolicy));
|
|
124
|
-
assert.match(result.error, /fetchContent\.domainPolicy\.(allow|deny)/);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test("loadSsrfConfig accepts an explicit trustEnvProxy opt-in", async () => {
|
|
129
|
-
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-proxy-enabled-");
|
|
130
|
-
await writeFile(configPath, JSON.stringify({ ssrf: { trustEnvProxy: true } }), "utf8");
|
|
131
|
-
|
|
132
|
-
const result = runLoadConfig(envFor(root, agentDir));
|
|
133
|
-
assert.equal(result.ok, true, result.error);
|
|
134
|
-
assert.deepEqual(result.config, { allowRanges: [], trustEnvProxy: true });
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("loadSsrfConfig rejects a non-boolean trustEnvProxy value", async () => {
|
|
138
|
-
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-proxy-invalid-");
|
|
139
|
-
await writeFile(configPath, JSON.stringify({ ssrf: { trustEnvProxy: "yes" } }), "utf8");
|
|
140
|
-
|
|
141
|
-
const result = runLoadConfig(envFor(root, agentDir));
|
|
142
|
-
assert.equal(result.ok, false);
|
|
143
|
-
assert.match(result.error, /ssrf\.trustEnvProxy in .* must be a boolean/);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test("loadSsrfAllowRanges throws when ssrf.allowRanges is not an array", async () => {
|
|
147
|
-
// Regression: a mistyped non-array value (bare string, object, number) must
|
|
148
|
-
// fail loudly instead of being silently ignored as "no exemptions".
|
|
149
|
-
for (const allowRanges of ["198.18.0.0/15", { "198.18.0.0/15": true }, 42]) {
|
|
150
|
-
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-nonarray-");
|
|
151
|
-
await writeFile(configPath, JSON.stringify({ ssrf: { allowRanges } }), "utf8");
|
|
152
|
-
|
|
153
|
-
const result = runLoad(envFor(root, agentDir));
|
|
154
|
-
assert.equal(result.ok, false, `expected throw for ${JSON.stringify(allowRanges)}`);
|
|
155
|
-
assert.match(result.error, /ssrf\.allowRanges in .* must be an array of CIDR strings/);
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test("loadSsrfAllowRanges throws when an ssrf.allowRanges entry is not a string", async () => {
|
|
160
|
-
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-entry-type-");
|
|
161
|
-
await writeFile(configPath, JSON.stringify({ ssrf: { allowRanges: ["198.18.0.0/15", 123] } }), "utf8");
|
|
162
|
-
|
|
163
|
-
const result = runLoad(envFor(root, agentDir));
|
|
164
|
-
assert.equal(result.ok, false);
|
|
165
|
-
assert.match(result.error, /ssrf\.allowRanges in .* must contain only CIDR strings; entry 2 is number/);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("loadSsrfAllowRanges returns trimmed, non-empty CIDR strings for a valid array", async () => {
|
|
169
|
-
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-valid-");
|
|
170
|
-
await writeFile(
|
|
171
|
-
configPath,
|
|
172
|
-
JSON.stringify({ ssrf: { allowRanges: ["198.18.0.0/15", " fd00::/8 ", "", " "] } }),
|
|
173
|
-
"utf8",
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
const result = runLoad(envFor(root, agentDir));
|
|
177
|
-
assert.equal(result.ok, true, result.error);
|
|
178
|
-
assert.deepEqual(result.ranges, ["198.18.0.0/15", "fd00::/8"]);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test("loadSsrfAllowRanges returns [] when the config file is missing", async () => {
|
|
182
|
-
const { root, agentDir } = await makeConfigDir("pi-ssrf-missing-");
|
|
183
|
-
// Intentionally do not write web-search.json.
|
|
184
|
-
const result = runLoad(envFor(root, agentDir));
|
|
185
|
-
assert.equal(result.ok, true, result.error);
|
|
186
|
-
assert.deepEqual(result.ranges, []);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test("loadSsrfAllowRanges returns [] when ssrf.allowRanges is unset", async () => {
|
|
190
|
-
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-unset-");
|
|
191
|
-
await writeFile(configPath, JSON.stringify({ perplexityApiKey: "pplx-x" }), "utf8");
|
|
192
|
-
|
|
193
|
-
const result = runLoad(envFor(root, agentDir));
|
|
194
|
-
assert.equal(result.ok, true, result.error);
|
|
195
|
-
assert.deepEqual(result.ranges, []);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test("loadSsrfAllowRanges reports malformed config JSON", async () => {
|
|
199
|
-
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-badjson-");
|
|
200
|
-
await writeFile(configPath, "{ not valid json", "utf8");
|
|
201
|
-
|
|
202
|
-
const result = runLoad(envFor(root, agentDir));
|
|
203
|
-
assert.equal(result.ok, false);
|
|
204
|
-
assert.match(result.error, /Failed to parse .*web-search\.json/);
|
|
205
|
-
});
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { test } from "node:test";
|
|
7
|
+
|
|
8
|
+
const extractUrl = new URL("../extract.ts", import.meta.url).href;
|
|
9
|
+
|
|
10
|
+
// `loadSsrfAllowRanges` reads the config path captured at module load, so each
|
|
11
|
+
// case runs in a child process with PI_CODING_AGENT_DIR pointed at a temp dir.
|
|
12
|
+
function runLoad(env) {
|
|
13
|
+
const childEnv = { ...process.env };
|
|
14
|
+
delete childEnv.PI_CODING_AGENT_DIR;
|
|
15
|
+
delete childEnv.XDG_CONFIG_HOME;
|
|
16
|
+
for (const [key, value] of Object.entries(env)) {
|
|
17
|
+
if (value === undefined) delete childEnv[key];
|
|
18
|
+
else childEnv[key] = value;
|
|
19
|
+
}
|
|
20
|
+
const script = `
|
|
21
|
+
const { loadSsrfAllowRanges } = await import(${JSON.stringify(extractUrl)});
|
|
22
|
+
try {
|
|
23
|
+
console.log(JSON.stringify({ ok: true, ranges: loadSsrfAllowRanges() }));
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.log(JSON.stringify({ ok: false, error: err instanceof Error ? err.message : String(err) }));
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
const child = spawnSync(process.execPath, ["--input-type=module"], {
|
|
29
|
+
input: script,
|
|
30
|
+
encoding: "utf8",
|
|
31
|
+
env: childEnv,
|
|
32
|
+
});
|
|
33
|
+
assert.equal(child.status, 0, child.stderr);
|
|
34
|
+
return JSON.parse(child.stdout);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function runLoadConfig(env) {
|
|
38
|
+
const childEnv = { ...process.env };
|
|
39
|
+
delete childEnv.PI_CODING_AGENT_DIR;
|
|
40
|
+
delete childEnv.XDG_CONFIG_HOME;
|
|
41
|
+
for (const [key, value] of Object.entries(env)) {
|
|
42
|
+
if (value === undefined) delete childEnv[key];
|
|
43
|
+
else childEnv[key] = value;
|
|
44
|
+
}
|
|
45
|
+
const script = `
|
|
46
|
+
const { loadSsrfConfig } = await import(${JSON.stringify(extractUrl)});
|
|
47
|
+
try {
|
|
48
|
+
console.log(JSON.stringify({ ok: true, config: loadSsrfConfig() }));
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.log(JSON.stringify({ ok: false, error: err instanceof Error ? err.message : String(err) }));
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
const child = spawnSync(process.execPath, ["--input-type=module"], {
|
|
54
|
+
input: script,
|
|
55
|
+
encoding: "utf8",
|
|
56
|
+
env: childEnv,
|
|
57
|
+
});
|
|
58
|
+
assert.equal(child.status, 0, child.stderr);
|
|
59
|
+
return JSON.parse(child.stdout);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function makeConfigDir(prefix) {
|
|
63
|
+
const root = await mkdtemp(join(tmpdir(), prefix));
|
|
64
|
+
const agentDir = join(root, "agent-dir");
|
|
65
|
+
await mkdir(agentDir, { recursive: true });
|
|
66
|
+
return { root, agentDir, configPath: join(agentDir, "web-search.json") };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function envFor(root, agentDir) {
|
|
70
|
+
return {
|
|
71
|
+
PI_CODING_AGENT_DIR: agentDir,
|
|
72
|
+
HOME: join(root, "home"),
|
|
73
|
+
USERPROFILE: join(root, "home"),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
test("loadSsrfConfig defaults trustEnvProxy to false", async () => {
|
|
78
|
+
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-proxy-default-");
|
|
79
|
+
await writeFile(configPath, JSON.stringify({ ssrf: { allowRanges: ["198.18.0.0/15"] } }), "utf8");
|
|
80
|
+
|
|
81
|
+
const result = runLoadConfig(envFor(root, agentDir));
|
|
82
|
+
assert.equal(result.ok, true, result.error);
|
|
83
|
+
assert.deepEqual(result.config, { allowRanges: ["198.18.0.0/15"], trustEnvProxy: false });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("loadFetchContentDomainPolicy reads normalized allow and deny hostnames", async () => {
|
|
87
|
+
const { root, agentDir, configPath } = await makeConfigDir("pi-domain-policy-valid-");
|
|
88
|
+
await writeFile(configPath, JSON.stringify({ fetchContent: { domainPolicy: { allow: [" Example.COM. "], deny: ["blocked.example.com"] } } }), "utf8");
|
|
89
|
+
|
|
90
|
+
const childEnv = envFor(root, agentDir);
|
|
91
|
+
const child = spawnSync(process.execPath, ["--input-type=module"], {
|
|
92
|
+
input: `
|
|
93
|
+
const { loadFetchContentDomainPolicy } = await import(${JSON.stringify(extractUrl.replace("/extract.ts", "/ssrf-protection.ts"))});
|
|
94
|
+
console.log(JSON.stringify(loadFetchContentDomainPolicy()));
|
|
95
|
+
`,
|
|
96
|
+
encoding: "utf8",
|
|
97
|
+
env: childEnv,
|
|
98
|
+
});
|
|
99
|
+
assert.equal(child.status, 0, child.stderr);
|
|
100
|
+
assert.deepEqual(JSON.parse(child.stdout), { allow: ["example.com"], deny: ["blocked.example.com"] });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("loadFetchContentDomainPolicy rejects malformed policy values", async () => {
|
|
104
|
+
for (const domainPolicy of [
|
|
105
|
+
{ allow: "example.com" },
|
|
106
|
+
{ deny: ["*.example.com"] },
|
|
107
|
+
{ allow: ["example.com", 42] },
|
|
108
|
+
]) {
|
|
109
|
+
const { root, agentDir, configPath } = await makeConfigDir("pi-domain-policy-invalid-");
|
|
110
|
+
await writeFile(configPath, JSON.stringify({ fetchContent: { domainPolicy } }), "utf8");
|
|
111
|
+
const childEnv = envFor(root, agentDir);
|
|
112
|
+
const child = spawnSync(process.execPath, ["--input-type=module"], {
|
|
113
|
+
input: `
|
|
114
|
+
const { loadFetchContentDomainPolicy } = await import(${JSON.stringify(extractUrl.replace("/extract.ts", "/ssrf-protection.ts"))});
|
|
115
|
+
try { loadFetchContentDomainPolicy(); console.log(JSON.stringify({ ok: true })); }
|
|
116
|
+
catch (err) { console.log(JSON.stringify({ ok: false, error: err.message })); }
|
|
117
|
+
`,
|
|
118
|
+
encoding: "utf8",
|
|
119
|
+
env: childEnv,
|
|
120
|
+
});
|
|
121
|
+
assert.equal(child.status, 0, child.stderr);
|
|
122
|
+
const result = JSON.parse(child.stdout);
|
|
123
|
+
assert.equal(result.ok, false, JSON.stringify(domainPolicy));
|
|
124
|
+
assert.match(result.error, /fetchContent\.domainPolicy\.(allow|deny)/);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("loadSsrfConfig accepts an explicit trustEnvProxy opt-in", async () => {
|
|
129
|
+
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-proxy-enabled-");
|
|
130
|
+
await writeFile(configPath, JSON.stringify({ ssrf: { trustEnvProxy: true } }), "utf8");
|
|
131
|
+
|
|
132
|
+
const result = runLoadConfig(envFor(root, agentDir));
|
|
133
|
+
assert.equal(result.ok, true, result.error);
|
|
134
|
+
assert.deepEqual(result.config, { allowRanges: [], trustEnvProxy: true });
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("loadSsrfConfig rejects a non-boolean trustEnvProxy value", async () => {
|
|
138
|
+
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-proxy-invalid-");
|
|
139
|
+
await writeFile(configPath, JSON.stringify({ ssrf: { trustEnvProxy: "yes" } }), "utf8");
|
|
140
|
+
|
|
141
|
+
const result = runLoadConfig(envFor(root, agentDir));
|
|
142
|
+
assert.equal(result.ok, false);
|
|
143
|
+
assert.match(result.error, /ssrf\.trustEnvProxy in .* must be a boolean/);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("loadSsrfAllowRanges throws when ssrf.allowRanges is not an array", async () => {
|
|
147
|
+
// Regression: a mistyped non-array value (bare string, object, number) must
|
|
148
|
+
// fail loudly instead of being silently ignored as "no exemptions".
|
|
149
|
+
for (const allowRanges of ["198.18.0.0/15", { "198.18.0.0/15": true }, 42]) {
|
|
150
|
+
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-nonarray-");
|
|
151
|
+
await writeFile(configPath, JSON.stringify({ ssrf: { allowRanges } }), "utf8");
|
|
152
|
+
|
|
153
|
+
const result = runLoad(envFor(root, agentDir));
|
|
154
|
+
assert.equal(result.ok, false, `expected throw for ${JSON.stringify(allowRanges)}`);
|
|
155
|
+
assert.match(result.error, /ssrf\.allowRanges in .* must be an array of CIDR strings/);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("loadSsrfAllowRanges throws when an ssrf.allowRanges entry is not a string", async () => {
|
|
160
|
+
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-entry-type-");
|
|
161
|
+
await writeFile(configPath, JSON.stringify({ ssrf: { allowRanges: ["198.18.0.0/15", 123] } }), "utf8");
|
|
162
|
+
|
|
163
|
+
const result = runLoad(envFor(root, agentDir));
|
|
164
|
+
assert.equal(result.ok, false);
|
|
165
|
+
assert.match(result.error, /ssrf\.allowRanges in .* must contain only CIDR strings; entry 2 is number/);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("loadSsrfAllowRanges returns trimmed, non-empty CIDR strings for a valid array", async () => {
|
|
169
|
+
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-valid-");
|
|
170
|
+
await writeFile(
|
|
171
|
+
configPath,
|
|
172
|
+
JSON.stringify({ ssrf: { allowRanges: ["198.18.0.0/15", " fd00::/8 ", "", " "] } }),
|
|
173
|
+
"utf8",
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const result = runLoad(envFor(root, agentDir));
|
|
177
|
+
assert.equal(result.ok, true, result.error);
|
|
178
|
+
assert.deepEqual(result.ranges, ["198.18.0.0/15", "fd00::/8"]);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("loadSsrfAllowRanges returns [] when the config file is missing", async () => {
|
|
182
|
+
const { root, agentDir } = await makeConfigDir("pi-ssrf-missing-");
|
|
183
|
+
// Intentionally do not write web-search.json.
|
|
184
|
+
const result = runLoad(envFor(root, agentDir));
|
|
185
|
+
assert.equal(result.ok, true, result.error);
|
|
186
|
+
assert.deepEqual(result.ranges, []);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("loadSsrfAllowRanges returns [] when ssrf.allowRanges is unset", async () => {
|
|
190
|
+
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-unset-");
|
|
191
|
+
await writeFile(configPath, JSON.stringify({ perplexityApiKey: "pplx-x" }), "utf8");
|
|
192
|
+
|
|
193
|
+
const result = runLoad(envFor(root, agentDir));
|
|
194
|
+
assert.equal(result.ok, true, result.error);
|
|
195
|
+
assert.deepEqual(result.ranges, []);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("loadSsrfAllowRanges reports malformed config JSON", async () => {
|
|
199
|
+
const { root, agentDir, configPath } = await makeConfigDir("pi-ssrf-badjson-");
|
|
200
|
+
await writeFile(configPath, "{ not valid json", "utf8");
|
|
201
|
+
|
|
202
|
+
const result = runLoad(envFor(root, agentDir));
|
|
203
|
+
assert.equal(result.ok, false);
|
|
204
|
+
assert.match(result.error, /Failed to parse .*web-search\.json/);
|
|
205
|
+
});
|