zelari-code 2.13.0 → 2.14.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/dist/cli/headless/policyGate.js +101 -0
- package/dist/cli/headless/policyGate.js.map +1 -0
- package/dist/cli/headless.js.map +1 -1
- package/dist/cli/kraken/completionProof.js +89 -41
- package/dist/cli/kraken/completionProof.js.map +1 -1
- package/dist/cli/kraken/completionProofAttestation.js +157 -0
- package/dist/cli/kraken/completionProofAttestation.js.map +1 -0
- package/dist/cli/kraken/completionProofPersist.js +219 -0
- package/dist/cli/kraken/completionProofPersist.js.map +1 -0
- package/dist/cli/kraken/completionProofProbe.js +135 -0
- package/dist/cli/kraken/completionProofProbe.js.map +1 -0
- package/dist/cli/kraken/contractCompiler.js +142 -0
- package/dist/cli/kraken/contractCompiler.js.map +1 -0
- package/dist/cli/kraken/delegationPolicy.js +40 -3
- package/dist/cli/kraken/delegationPolicy.js.map +1 -1
- package/dist/cli/kraken/executor.js +462 -9
- package/dist/cli/kraken/executor.js.map +1 -1
- package/dist/cli/kraken/fileOwnership.js +190 -0
- package/dist/cli/kraken/fileOwnership.js.map +1 -0
- package/dist/cli/kraken/modelReputation.js +208 -0
- package/dist/cli/kraken/modelReputation.js.map +1 -0
- package/dist/cli/kraken/nativeVerification.js +53 -11
- package/dist/cli/kraken/nativeVerification.js.map +1 -1
- package/dist/cli/kraken/planner.js +2 -0
- package/dist/cli/kraken/planner.js.map +1 -1
- package/dist/cli/kraken/reputationStore.js +139 -0
- package/dist/cli/kraken/reputationStore.js.map +1 -0
- package/dist/cli/kraken/semanticOwnership.js +184 -0
- package/dist/cli/kraken/semanticOwnership.js.map +1 -0
- package/dist/cli/kraken/spawnRoi.js +209 -0
- package/dist/cli/kraken/spawnRoi.js.map +1 -0
- package/dist/cli/kraken/transactional.js +104 -0
- package/dist/cli/kraken/transactional.js.map +1 -0
- package/dist/cli/kraken/verificationAdapters/dotnet.js +49 -0
- package/dist/cli/kraken/verificationAdapters/dotnet.js.map +1 -0
- package/dist/cli/kraken/verificationAdapters/go.js +36 -0
- package/dist/cli/kraken/verificationAdapters/go.js.map +1 -0
- package/dist/cli/kraken/verificationAdapters/index.js +47 -0
- package/dist/cli/kraken/verificationAdapters/index.js.map +1 -0
- package/dist/cli/kraken/verificationAdapters/java.js +102 -0
- package/dist/cli/kraken/verificationAdapters/java.js.map +1 -0
- package/dist/cli/kraken/verificationAdapters/node.js +90 -0
- package/dist/cli/kraken/verificationAdapters/node.js.map +1 -0
- package/dist/cli/kraken/verificationAdapters/python.js +86 -0
- package/dist/cli/kraken/verificationAdapters/python.js.map +1 -0
- package/dist/cli/kraken/verificationAdapters/rust.js +39 -0
- package/dist/cli/kraken/verificationAdapters/rust.js.map +1 -0
- package/dist/cli/kraken/verificationAdapters/types.js +2 -0
- package/dist/cli/kraken/verificationAdapters/types.js.map +1 -0
- package/dist/cli/kraken/verificationBridge.js +40 -3
- package/dist/cli/kraken/verificationBridge.js.map +1 -1
- package/dist/cli/kraken/verifierLifecycle.js +51 -36
- package/dist/cli/kraken/verifierLifecycle.js.map +1 -1
- package/dist/cli/kraken/verifierRouting.js +264 -0
- package/dist/cli/kraken/verifierRouting.js.map +1 -0
- package/dist/cli/kraken/worktreeScheduling.js +201 -0
- package/dist/cli/kraken/worktreeScheduling.js.map +1 -0
- package/dist/cli/main.bundled.js +3880 -889
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/orchestration/facts.js +127 -0
- package/dist/cli/orchestration/facts.js.map +1 -0
- package/dist/cli/orchestration/policy.js +128 -75
- package/dist/cli/orchestration/policy.js.map +1 -1
- package/dist/cli/orchestration/signals.js +96 -0
- package/dist/cli/orchestration/signals.js.map +1 -0
- package/dist/cli/runHeadless.js +92 -19
- package/dist/cli/runHeadless.js.map +1 -1
- package/dist/cli/safety/policyEngine.js +159 -20
- package/dist/cli/safety/policyEngine.js.map +1 -1
- package/dist/cli/safety/policyLoadMode.js +59 -0
- package/dist/cli/safety/policyLoadMode.js.map +1 -0
- package/dist/cli/safety/resourceClaims.js +373 -0
- package/dist/cli/safety/resourceClaims.js.map +1 -0
- package/dist/cli/safety/sandboxPath.js +160 -17
- package/dist/cli/safety/sandboxPath.js.map +1 -1
- package/dist/cli/toolRegistry.js +98 -8
- package/dist/cli/toolRegistry.js.map +1 -1
- package/dist/cli/tools/execProcess.js +121 -0
- package/dist/cli/tools/execProcess.js.map +1 -0
- package/dist/cli/tools/krakenRadio.js +9 -0
- package/dist/cli/tools/krakenRadio.js.map +1 -1
- package/dist/cli/tools/taskTool.js +8 -3
- package/dist/cli/tools/taskTool.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { pathCandidates, resolvePolicyRule } from './policyEngine.js';
|
|
2
|
+
import { intersectEffects } from './policyLayers.js';
|
|
3
|
+
function asArgs(args) {
|
|
4
|
+
return args !== null && typeof args === 'object' ? args : {};
|
|
5
|
+
}
|
|
6
|
+
function str(v) {
|
|
7
|
+
return typeof v === 'string' && v !== '' ? v : undefined;
|
|
8
|
+
}
|
|
9
|
+
/** The primary path argument, mirroring matchAgentPolicyRule: `path` then `file_path`. */
|
|
10
|
+
function primaryPath(a) {
|
|
11
|
+
return str(a['path']) ?? str(a['file_path']);
|
|
12
|
+
}
|
|
13
|
+
/** Paths referenced by a diff's `---`/`+++` headers (best-effort, `/dev/null`
|
|
14
|
+
* skipped, `a/` `b/` + quotes stripped) — write claims so none dodges a deny. */
|
|
15
|
+
function pathsFromUnifiedDiff(diff) {
|
|
16
|
+
const out = new Set();
|
|
17
|
+
for (const line of diff.split(/\r?\n/)) {
|
|
18
|
+
const m = /^(?:---|\+\+\+)\s+(\S+)/.exec(line);
|
|
19
|
+
if (!m)
|
|
20
|
+
continue;
|
|
21
|
+
let p = m[1].replace(/^"|"$/g, '');
|
|
22
|
+
if (p === '/dev/null')
|
|
23
|
+
continue;
|
|
24
|
+
if (p.startsWith('a/') || p.startsWith('b/'))
|
|
25
|
+
p = p.slice(2);
|
|
26
|
+
if (p !== '')
|
|
27
|
+
out.add(p);
|
|
28
|
+
}
|
|
29
|
+
return [...out];
|
|
30
|
+
}
|
|
31
|
+
/** Host (+optional port) claims for every absolute URL in the args. */
|
|
32
|
+
function networkClaimsForUrls(urls) {
|
|
33
|
+
const out = [];
|
|
34
|
+
for (const u of urls) {
|
|
35
|
+
if (typeof u !== 'string' || u === '')
|
|
36
|
+
continue;
|
|
37
|
+
try {
|
|
38
|
+
const parsed = new URL(u);
|
|
39
|
+
const port = parsed.port !== '' ? Number(parsed.port) : undefined;
|
|
40
|
+
out.push(port !== undefined && Number.isInteger(port)
|
|
41
|
+
? { kind: 'network', host: parsed.hostname, port }
|
|
42
|
+
: { kind: 'network', host: parsed.hostname });
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Not an absolute URL — the tool itself will reject it later; no claim.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/** Best-effort `mcp_<server>_<tool>` reverse-parse (first underscore split;
|
|
51
|
+
* ambiguous for underscored server names — accepted). */
|
|
52
|
+
function mcpClaimFromName(registryName) {
|
|
53
|
+
const rest = registryName.startsWith('mcp_') ? registryName.slice('mcp_'.length) : '';
|
|
54
|
+
const sep = rest.indexOf('_');
|
|
55
|
+
return sep <= 0 || sep === rest.length - 1
|
|
56
|
+
? null
|
|
57
|
+
: { kind: 'mcp', server: rest.slice(0, sep), tool: rest.slice(sep + 1) };
|
|
58
|
+
}
|
|
59
|
+
/** Nested action URLs a browser_check call may navigate to. */
|
|
60
|
+
function browserActionUrls(a) {
|
|
61
|
+
const actions = Array.isArray(a['actions']) ? a['actions'] : [];
|
|
62
|
+
return actions.map((x) => (x !== null && typeof x === 'object' ? x['url'] : undefined));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Raw-shell normalization (P0.C2/t17) — BEST-EFFORT, deliberately not a shell
|
|
66
|
+
* parser: strip common wrapper prefixes so `env FOO=x git push`,
|
|
67
|
+
* `command git push`, `exec git push`, `bash -lc 'git push'`,
|
|
68
|
+
* `cmd.exe /c git push` and extra whitespace classify as the program they
|
|
69
|
+
* actually run (`git push`), letting a `git push*` rule hit what a plain
|
|
70
|
+
* `command: 'git push …'` would have hit. Quoted strings are re-tokenized
|
|
71
|
+
* quote-aware; recursion is depth-capped and ANY ambiguity keeps the original.
|
|
72
|
+
*/
|
|
73
|
+
/** Cap for nested wrappers (`bash -lc 'bash -lc …'`). */
|
|
74
|
+
const MAX_RAW_SHELL_STRIP_DEPTH = 3;
|
|
75
|
+
/** Prefixes that prepend context around the real command. */
|
|
76
|
+
const RAW_SHELL_WRAPPERS = new Set(['command', 'exec', 'nice', 'nohup', 'time']);
|
|
77
|
+
/** Interpreters whose `-c`-style flag executes the NEXT argument as a string. */
|
|
78
|
+
const RAW_SHELL_INTERPRETERS = new Set(['sh', 'bash', 'dash', 'ksh', 'zsh']);
|
|
79
|
+
function stripWindowsExecutableExt(name) {
|
|
80
|
+
return name.replace(/\.(exe|cmd|bat|com)$/i, '');
|
|
81
|
+
}
|
|
82
|
+
/** Whitespace tokenizer honoring single/double quotes; quotes are dropped. */
|
|
83
|
+
function tokenizeCommandString(command) {
|
|
84
|
+
const out = [];
|
|
85
|
+
let cur = '';
|
|
86
|
+
let quote = null;
|
|
87
|
+
let started = false;
|
|
88
|
+
for (const ch of command.trim()) {
|
|
89
|
+
if (quote) {
|
|
90
|
+
if (ch === quote)
|
|
91
|
+
quote = null;
|
|
92
|
+
else
|
|
93
|
+
cur += ch;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (ch === '"' || ch === "'") {
|
|
97
|
+
quote = ch;
|
|
98
|
+
started = true;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (/\s/.test(ch)) {
|
|
102
|
+
if (started || cur !== '')
|
|
103
|
+
out.push(cur);
|
|
104
|
+
cur = '';
|
|
105
|
+
started = false;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
cur += ch;
|
|
109
|
+
started = true;
|
|
110
|
+
}
|
|
111
|
+
if (started || cur !== '')
|
|
112
|
+
out.push(cur);
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
function isEnvAssignment(token) {
|
|
116
|
+
const eq = token.indexOf('=');
|
|
117
|
+
return eq > 0 && /^[A-Za-z_][A-Za-z0-9_]*$/.test(token.slice(0, eq));
|
|
118
|
+
}
|
|
119
|
+
/** Strip one wrapper layer per call; returns argv for the program it runs. */
|
|
120
|
+
function stripRawShellLayer(argv, depth) {
|
|
121
|
+
if (depth >= MAX_RAW_SHELL_STRIP_DEPTH || argv.length === 0)
|
|
122
|
+
return [...argv];
|
|
123
|
+
const bare = stripWindowsExecutableExt(argv[0]).toLowerCase();
|
|
124
|
+
// env [VAR=val|-flag]* [--] cmd …
|
|
125
|
+
if (bare === 'env') {
|
|
126
|
+
let i = 1;
|
|
127
|
+
while (i < argv.length && (isEnvAssignment(argv[i]) || argv[i].startsWith('-')))
|
|
128
|
+
i++;
|
|
129
|
+
if (argv[i] === '--')
|
|
130
|
+
i++;
|
|
131
|
+
if (i >= argv.length)
|
|
132
|
+
return [...argv]; // nothing left — keep original
|
|
133
|
+
return stripRawShellLayer(argv.slice(i), depth + 1);
|
|
134
|
+
}
|
|
135
|
+
if (RAW_SHELL_WRAPPERS.has(bare)) {
|
|
136
|
+
let i = 1;
|
|
137
|
+
while (i < argv.length && argv[i].startsWith('-'))
|
|
138
|
+
i++;
|
|
139
|
+
if (i >= argv.length)
|
|
140
|
+
return [...argv];
|
|
141
|
+
return stripRawShellLayer(argv.slice(i), depth + 1);
|
|
142
|
+
}
|
|
143
|
+
// sh/bash -lc '<command string>' — execute the string, not the interpreter.
|
|
144
|
+
if (RAW_SHELL_INTERPRETERS.has(bare)) {
|
|
145
|
+
if (argv.length >= 3 && /^-\w*c\w*$/.test(argv[1])) {
|
|
146
|
+
return stripRawShellLayer(tokenizeCommandString(argv[2]), depth + 1);
|
|
147
|
+
}
|
|
148
|
+
return [...argv]; // `bash script.sh` — can't know what the script runs
|
|
149
|
+
}
|
|
150
|
+
// cmd.exe /c <string>
|
|
151
|
+
if ((bare === 'cmd' || bare === 'cmd.exe') && argv.length >= 3 && /^\/c$/i.test(argv[1])) {
|
|
152
|
+
return stripRawShellLayer(tokenizeCommandString(argv.slice(2).join(' ')), depth + 1);
|
|
153
|
+
}
|
|
154
|
+
return [...argv];
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Best-effort wrapper stripping over an already-whitespace-split token list —
|
|
158
|
+
* the entry point used by the tool → claims table below.
|
|
159
|
+
*/
|
|
160
|
+
export function normalizeProcessArgs(tokens) {
|
|
161
|
+
const stripped = stripRawShellLayer(tokens, 0);
|
|
162
|
+
return { argv: stripped, changed: stripped.join(' ') !== tokens.join(' ') };
|
|
163
|
+
}
|
|
164
|
+
/** Match values for ONE process claim: canonical chain first (+ raw original). */
|
|
165
|
+
function processClaimValues(claim) {
|
|
166
|
+
const main = claim.argv && claim.argv.length > 0
|
|
167
|
+
? [claim.executable, ...claim.argv].join(' ')
|
|
168
|
+
: claim.executable;
|
|
169
|
+
return claim.raw && claim.raw !== main ? [main, claim.raw] : [main];
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Central tool → claims table (best-effort throughout). `web_search` yields
|
|
173
|
+
* NO claim: its destination host is the search backend, not argument-chosen
|
|
174
|
+
* (category policy still gates it). Unknown tool → [].
|
|
175
|
+
*/
|
|
176
|
+
export function resourceClaimsFor(toolName, args) {
|
|
177
|
+
const a = asArgs(args);
|
|
178
|
+
switch (toolName) {
|
|
179
|
+
case 'write_file':
|
|
180
|
+
case 'edit_file': {
|
|
181
|
+
const p = primaryPath(a);
|
|
182
|
+
return p ? [{ kind: 'path', operation: 'write', path: p }] : [];
|
|
183
|
+
}
|
|
184
|
+
case 'apply_diff': {
|
|
185
|
+
// Cover EVERY path it can touch: primary `path` + all diff headers.
|
|
186
|
+
const uniq = [];
|
|
187
|
+
for (const p of [primaryPath(a), ...pathsFromUnifiedDiff(str(a['diff']) ?? '')]) {
|
|
188
|
+
if (p && !uniq.includes(p))
|
|
189
|
+
uniq.push(p);
|
|
190
|
+
}
|
|
191
|
+
return uniq.map((p) => ({ kind: 'path', operation: 'write', path: p }));
|
|
192
|
+
}
|
|
193
|
+
// READ tools — match only explicit v2 `claims` rules, never the edit list.
|
|
194
|
+
case 'read_file':
|
|
195
|
+
case 'show_diff':
|
|
196
|
+
case 'list_files':
|
|
197
|
+
case 'grep_content': {
|
|
198
|
+
const p = primaryPath(a);
|
|
199
|
+
return p ? [{ kind: 'path', operation: 'read', path: p }] : [];
|
|
200
|
+
}
|
|
201
|
+
// Shell-like — raw-shell string normalized into the process it runs
|
|
202
|
+
// (best-effort wrapper stripping, P0.C2/t17; NOT a shell parser).
|
|
203
|
+
// Tokenize quote-aware FIRST so `bash -lc '<string>'` keeps its command
|
|
204
|
+
// argument intact for the interpreter branch.
|
|
205
|
+
case 'bash':
|
|
206
|
+
case 'inspect_command': {
|
|
207
|
+
const cmd = str(a['command']);
|
|
208
|
+
if (!cmd)
|
|
209
|
+
return [];
|
|
210
|
+
const tokens = tokenizeCommandString(cmd);
|
|
211
|
+
const { argv, changed } = normalizeProcessArgs(tokens);
|
|
212
|
+
const claim = {
|
|
213
|
+
kind: 'process',
|
|
214
|
+
executable: argv[0],
|
|
215
|
+
...(argv.length > 1 ? { argv: argv.slice(1) } : {}),
|
|
216
|
+
...(changed ? { raw: cmd.trim() } : {}),
|
|
217
|
+
};
|
|
218
|
+
return [claim];
|
|
219
|
+
}
|
|
220
|
+
// Structured execution (t17): no shell ever involved — the claim IS the
|
|
221
|
+
// invocation. program basename (Windows extension stripped) matches
|
|
222
|
+
// `git push*`-style rules; the literal path is kept as a second value.
|
|
223
|
+
case 'exec_process': {
|
|
224
|
+
const program = str(a['program']);
|
|
225
|
+
if (!program)
|
|
226
|
+
return [];
|
|
227
|
+
const name = stripWindowsExecutableExt(program.split(/[\\/]/).pop() ?? program);
|
|
228
|
+
const argv = Array.isArray(a['args'])
|
|
229
|
+
? a['args'].filter((x) => typeof x === 'string')
|
|
230
|
+
: [];
|
|
231
|
+
return [{ kind: 'process', executable: name, ...(argv.length > 0 ? { argv } : {}) }];
|
|
232
|
+
}
|
|
233
|
+
case 'fetch_url':
|
|
234
|
+
return networkClaimsForUrls([str(a['url'])]);
|
|
235
|
+
case 'browser_check': {
|
|
236
|
+
const urls = [a['url'], ...browserActionUrls(a)];
|
|
237
|
+
return networkClaimsForUrls(urls.filter((u) => typeof u === 'string'));
|
|
238
|
+
}
|
|
239
|
+
case 'web_search':
|
|
240
|
+
return []; // no argument-controlled destination host (see table doc)
|
|
241
|
+
case 'ssh_run': {
|
|
242
|
+
const target = str(a['targetId']);
|
|
243
|
+
const command = str(a['command']);
|
|
244
|
+
return target ? [command ? { kind: 'ssh', target, command } : { kind: 'ssh', target }] : [];
|
|
245
|
+
}
|
|
246
|
+
case 'ssh_status': {
|
|
247
|
+
const target = str(a['targetId']);
|
|
248
|
+
return target ? [{ kind: 'ssh', target }] : [];
|
|
249
|
+
}
|
|
250
|
+
case 'observe_batch': {
|
|
251
|
+
// Nested observations fan out into the wrapped read tools.
|
|
252
|
+
const ops = Array.isArray(a['operations']) ? a['operations'] : [];
|
|
253
|
+
const out = [];
|
|
254
|
+
for (const op of ops) {
|
|
255
|
+
if (op === null || typeof op !== 'object')
|
|
256
|
+
continue;
|
|
257
|
+
const o = op;
|
|
258
|
+
const p = primaryPath(asArgs(o['args']));
|
|
259
|
+
if (p && ['read_file', 'grep_content', 'list_files'].includes(String(o['tool']))) {
|
|
260
|
+
out.push({ kind: 'path', operation: 'read', path: p });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
default: {
|
|
266
|
+
const mcp = mcpClaimFromName(toolName);
|
|
267
|
+
return mcp ? [mcp] : [];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** Values a claim's rules glob-match (anchored; paths try root-relative first). */
|
|
272
|
+
export function claimMatchValues(claim, root) {
|
|
273
|
+
switch (claim.kind) {
|
|
274
|
+
case 'path':
|
|
275
|
+
return pathCandidates(claim.path, root);
|
|
276
|
+
case 'process':
|
|
277
|
+
return processClaimValues(claim);
|
|
278
|
+
case 'network':
|
|
279
|
+
return claim.port !== undefined ? [`${claim.host}:${claim.port}`, claim.host] : [claim.host];
|
|
280
|
+
case 'mcp':
|
|
281
|
+
return [`${claim.server}.${claim.tool}`];
|
|
282
|
+
case 'ssh':
|
|
283
|
+
return claim.command !== undefined ? [`${claim.target} ${claim.command}`] : [claim.target];
|
|
284
|
+
case 'ui':
|
|
285
|
+
return [claim.action];
|
|
286
|
+
case 'agent':
|
|
287
|
+
return [claim.role];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/** Glob hits when ANY candidate value full-matches the anchored pattern. */
|
|
291
|
+
function globHits(pattern, values) {
|
|
292
|
+
// resolvePolicyRule only reads `.match`; a deny placeholder keeps this a pure probe.
|
|
293
|
+
return values.some((v) => resolvePolicyRule([{ match: pattern, effect: 'deny' }], v) !== null);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Match ONE claim against ONE layer: the v2 `claims` section first (ordered,
|
|
297
|
+
* kind+operation-aware), then the legacy fallback for retrocompat — path
|
|
298
|
+
* WRITE claims reuse the v1 `edit` list and process claims the v1 `shell`
|
|
299
|
+
* list with EXACTLY today's semantics. Read/network/mcp/ssh only hit
|
|
300
|
+
* explicit `claims` rules, so v1 files never start denying them. Null =
|
|
301
|
+
* this layer says nothing.
|
|
302
|
+
*/
|
|
303
|
+
export function claimRuleFor(rules, claim, root) {
|
|
304
|
+
if (!rules)
|
|
305
|
+
return null;
|
|
306
|
+
for (const cr of rules.claims ?? []) {
|
|
307
|
+
if (cr.kind !== claim.kind)
|
|
308
|
+
continue;
|
|
309
|
+
if (claim.kind === 'path' && cr.operation !== undefined && cr.operation !== claim.operation) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (globHits(cr.pattern, claimMatchValues(claim, root))) {
|
|
313
|
+
return cr.reason
|
|
314
|
+
? { match: cr.pattern, effect: cr.effect, reason: cr.reason }
|
|
315
|
+
: { match: cr.pattern, effect: cr.effect };
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// Legacy single-value fallbacks (identical behavior to the P0.A matcher).
|
|
319
|
+
if (claim.kind === 'path' && claim.operation === 'write') {
|
|
320
|
+
for (const candidate of pathCandidates(claim.path, root)) {
|
|
321
|
+
const hit = resolvePolicyRule(rules.edit, candidate);
|
|
322
|
+
if (hit)
|
|
323
|
+
return hit;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (claim.kind === 'process') {
|
|
327
|
+
return resolvePolicyRule(rules.shell, claimMatchValues(claim)[0]);
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
/** Combine BOTH layers for ONE claim — restrict-only per {@link matchAgentPolicyRuleLayered}
|
|
332
|
+
* (stricter wins, ties keep project; legacy = project-first first-match). A
|
|
333
|
+
* global-floor deny can NEVER be relaxed by an agent-local allow — this IS the
|
|
334
|
+
* subagent ⊆ parent property through claims. */
|
|
335
|
+
export function matchResourceClaimLayered(layers, precedence, claim, root) {
|
|
336
|
+
if (!layers)
|
|
337
|
+
return null;
|
|
338
|
+
if (precedence === 'legacy') {
|
|
339
|
+
const merged = {
|
|
340
|
+
shell: [...layers.project.shell, ...layers.global.shell],
|
|
341
|
+
edit: [...layers.project.edit, ...layers.global.edit],
|
|
342
|
+
};
|
|
343
|
+
if (layers.project.claims || layers.global.claims) {
|
|
344
|
+
merged.claims = [...(layers.project.claims ?? []), ...(layers.global.claims ?? [])];
|
|
345
|
+
}
|
|
346
|
+
return claimRuleFor(merged, claim, root);
|
|
347
|
+
}
|
|
348
|
+
const g = claimRuleFor(layers.global, claim, root);
|
|
349
|
+
const p = claimRuleFor(layers.project, claim, root);
|
|
350
|
+
if (!g)
|
|
351
|
+
return p;
|
|
352
|
+
if (!p)
|
|
353
|
+
return g;
|
|
354
|
+
const win = intersectEffects(p.effect, g.effect);
|
|
355
|
+
return p.effect === win ? p : g;
|
|
356
|
+
}
|
|
357
|
+
/** Expand the invocation to claims, match EACH on the layered rules, intersect. */
|
|
358
|
+
export function resolveClaimsVerdict(layers, precedence, toolName, args, root) {
|
|
359
|
+
const claims = resourceClaimsFor(toolName, args);
|
|
360
|
+
const matchedRules = [];
|
|
361
|
+
if (!layers || claims.length === 0)
|
|
362
|
+
return { matchedRules };
|
|
363
|
+
const effects = [];
|
|
364
|
+
for (const claim of claims) {
|
|
365
|
+
const hit = matchResourceClaimLayered(layers, precedence, claim, root);
|
|
366
|
+
if (hit) {
|
|
367
|
+
effects.push(hit.effect);
|
|
368
|
+
matchedRules.push(hit);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return effects.length > 0 ? { effect: intersectEffects(...effects), matchedRules } : { matchedRules };
|
|
372
|
+
}
|
|
373
|
+
//# sourceMappingURL=resourceClaims.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resourceClaims.js","sourceRoot":"","sources":["../../../src/cli/safety/resourceClaims.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,14 +2,37 @@
|
|
|
2
2
|
* sandboxPath — enforce that filesystem tool paths stay inside an allowed
|
|
3
3
|
* root directory (default: process.cwd()).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* `..` segments or absolute paths outside the sandbox.
|
|
5
|
+
* Task A2 of AnathemaCoder v3-A; symlink-safe resolution added by P0.D (t18).
|
|
7
6
|
*
|
|
8
|
-
*
|
|
7
|
+
* Two-layer containment:
|
|
8
|
+
* 1. TEXTUAL — normalize `..` segments and absolute forms, then prefix-match
|
|
9
|
+
* against the root. Fast path (zero syscalls), catches obvious escapes.
|
|
10
|
+
* 2. SYMLINK-SAFE — realpath the DEEPEST EXISTING ANCESTOR of the target
|
|
11
|
+
* and require its REAL location to sit inside the REAL root
|
|
12
|
+
* (`fs.realpathSync` of the root). Defeats junction/symlink escapes
|
|
13
|
+
* — including chains a→b→outside and cross-drive junctions on win32 —
|
|
14
|
+
* that textual checks cannot see. Comparison folds character case only
|
|
15
|
+
* on platforms whose default FS is case-insensitive (win32, darwin);
|
|
16
|
+
* linux keeps exact comparison.
|
|
17
|
+
*
|
|
18
|
+
* Return-value stability: the resolver still returns the LEXICALLY resolved
|
|
19
|
+
* path so rewritten tool args stay byte-identical across versions; a link
|
|
20
|
+
* escape surfaces as SandboxViolationError instead of a silently different
|
|
21
|
+
* target. When the sandbox root itself does not exist there is nothing
|
|
22
|
+
* between it and the target that could be a link, so layer 2 degrades to
|
|
23
|
+
* layer 1 (documented behaviour, keeps legacy fake-root usage working).
|
|
24
|
+
*
|
|
25
|
+
* TOCTOU 〔PW §5〕: `verifyContainment()` re-runs layer 2 on fresh syscalls.
|
|
26
|
+
* Callers that mutate the FS may call it as the LAST step right before a
|
|
27
|
+
* write to catch links swapped after an earlier permission check. The CLI
|
|
28
|
+
* guarantee itself lives in `wrapWithSandbox` (toolRegistry.ts): resolve →
|
|
29
|
+
* execute are adjacent, so for write_file/edit_file/apply_diff/read_file the
|
|
30
|
+
* verified resolution IS the last step before the factory touches disk.
|
|
9
31
|
*
|
|
10
32
|
* @see docs/plans/2026-06-29-anathema-coder-v3.md (Task A2)
|
|
11
33
|
*/
|
|
12
34
|
import path from 'node:path';
|
|
35
|
+
import fs from 'node:fs';
|
|
13
36
|
export class SandboxViolationError extends Error {
|
|
14
37
|
attemptedPath;
|
|
15
38
|
resolvedPath;
|
|
@@ -20,17 +43,85 @@ export class SandboxViolationError extends Error {
|
|
|
20
43
|
this.name = 'SandboxViolationError';
|
|
21
44
|
}
|
|
22
45
|
}
|
|
46
|
+
/** Platforms whose default filesystem is case-insensitive. */
|
|
47
|
+
const CASE_FOLDING_PLATFORMS = ['win32', 'darwin'];
|
|
48
|
+
/** True when path comparison must fold character case (win32/darwin only). */
|
|
49
|
+
const IS_CASE_FOLDING = CASE_FOLDING_PLATFORMS.includes(process.platform);
|
|
50
|
+
/** Fold case only where the default filesystem is case-insensitive. */
|
|
51
|
+
function normalizedCase(p) {
|
|
52
|
+
return IS_CASE_FOLDING ? p.toLowerCase() : p;
|
|
53
|
+
}
|
|
23
54
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
55
|
+
* True when `candidate` equals or sits under `base`. Case folding is applied
|
|
56
|
+
* ONLY on case-insensitive platforms (guard: process.platform). Cross-drive
|
|
57
|
+
* targets on win32 fail here naturally — `C:\...` never starts with `E:\`.
|
|
58
|
+
*/
|
|
59
|
+
function isWithin(candidate, base) {
|
|
60
|
+
const c = normalizedCase(candidate);
|
|
61
|
+
const b = normalizedCase(base);
|
|
62
|
+
if (c === b)
|
|
63
|
+
return true;
|
|
64
|
+
const baseWithSep = b.endsWith(path.sep) ? b : b + path.sep;
|
|
65
|
+
return c.startsWith(baseWithSep);
|
|
66
|
+
}
|
|
67
|
+
function escapeError(userPath, attempted, real, realRoot) {
|
|
68
|
+
return new SandboxViolationError(`Path escapes sandbox root through a symlink/junction: ${userPath} → real ${real} is outside real root ${realRoot}`, userPath, attempted);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* TEXTUAL containment check (layer 1) — unchanged Task A2 semantics.
|
|
72
|
+
*/
|
|
73
|
+
function assertLexicalContainment(resolved, root, userPath) {
|
|
74
|
+
// Ensure trailing separator for prefix check so `/foo/bar` does not match
|
|
75
|
+
// `/foo/barbaz`.
|
|
76
|
+
const rootWithSep = root.endsWith(path.sep) ? root : root + path.sep;
|
|
77
|
+
if (resolved !== root && !resolved.startsWith(rootWithSep)) {
|
|
78
|
+
throw new SandboxViolationError(`Path escapes sandbox root: ${userPath} → ${resolved} (root: ${root})`, userPath, resolved);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* SYMLINK-SAFE check (layer 2). Walks up from the lexically-resolved target
|
|
83
|
+
* until realpath succeeds (deepest existing ancestor — fs.realpathSync
|
|
84
|
+
* resolves whole chains natively, e.g. a→b→outside), verifies its REAL
|
|
85
|
+
* location stays inside the REAL root, then re-applies textual containment
|
|
86
|
+
* for the not-yet-existing suffix below that ancestor (it lands next to a
|
|
87
|
+
* verified-inside parent, so this holds trivially — kept explicit as defense
|
|
88
|
+
* in depth).
|
|
26
89
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* - Symlink resolution is the caller's responsibility (the FS tool
|
|
30
|
-
* resolves on read/write); this function only checks textual containment
|
|
31
|
-
* after normalization.
|
|
90
|
+
* Unexpected FS errors (EPERM & co.) degrade to layer 1 rather than crashing
|
|
91
|
+
* unrelated operations — same failure surface as pre-P0.D.
|
|
32
92
|
*/
|
|
33
|
-
|
|
93
|
+
function assertRealAncestorContained(resolvedLexical, realRoot, userPath) {
|
|
94
|
+
let probe = resolvedLexical;
|
|
95
|
+
let realProbe = null;
|
|
96
|
+
for (;;) {
|
|
97
|
+
if (probe === path.dirname(probe))
|
|
98
|
+
break; // filesystem apex reached
|
|
99
|
+
try {
|
|
100
|
+
realProbe = fs.realpathSync(probe);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
if (err?.code === 'ENOENT') {
|
|
105
|
+
probe = path.dirname(probe);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
return; // degrade to textual layer only
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (realProbe === null)
|
|
112
|
+
return; // fully nonexistent chain → layer 1 already passed
|
|
113
|
+
if (!isWithin(realProbe, realRoot)) {
|
|
114
|
+
throw escapeError(userPath, resolvedLexical, realProbe, realRoot);
|
|
115
|
+
}
|
|
116
|
+
// Containment re-check for the non-existent remainder of the path.
|
|
117
|
+
const suffix = resolvedLexical.startsWith(probe)
|
|
118
|
+
? resolvedLexical.slice(probe.length)
|
|
119
|
+
: '';
|
|
120
|
+
if (suffix.length > 0 && !isWithin(realProbe + suffix, realRoot)) {
|
|
121
|
+
throw escapeError(userPath, resolvedLexical, realProbe + suffix, realRoot);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function resolveSandboxedCore(userPath, options = {}) {
|
|
34
125
|
if (typeof userPath !== 'string' || userPath.length === 0) {
|
|
35
126
|
throw new SandboxViolationError('Empty path', userPath, '');
|
|
36
127
|
}
|
|
@@ -38,17 +129,69 @@ export function resolveSandboxedPath(userPath, options = {}) {
|
|
|
38
129
|
const resolved = path.isAbsolute(userPath)
|
|
39
130
|
? path.resolve(userPath)
|
|
40
131
|
: path.resolve(root, userPath);
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
132
|
+
// Layer 1: lexical containment after normalization.
|
|
133
|
+
assertLexicalContainment(resolved, root, userPath);
|
|
134
|
+
// Layer 2: symlink/junction safety. Skipped when the root itself does not
|
|
135
|
+
// exist (ENOENT) — no ancestor between a nonexistent root and the target
|
|
136
|
+
// can hold a link yet, and legacy fake-root callers keep working.
|
|
137
|
+
let realRoot = null;
|
|
138
|
+
try {
|
|
139
|
+
realRoot = fs.realpathSync(root);
|
|
46
140
|
}
|
|
141
|
+
catch {
|
|
142
|
+
return resolved;
|
|
143
|
+
}
|
|
144
|
+
assertRealAncestorContained(resolved, realRoot, userPath);
|
|
47
145
|
return resolved;
|
|
48
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Resolve a user-supplied path against an allowed root, throwing
|
|
149
|
+
* SandboxViolationError if the result escapes the root.
|
|
150
|
+
*
|
|
151
|
+
* - Absolute paths are taken as-is but must be inside the root.
|
|
152
|
+
* - Relative paths are joined to the root.
|
|
153
|
+
* - P0.D: symlinks/junctions are now resolved via realpath of the deepest
|
|
154
|
+
* existing ancestor; any link resolving OUTSIDE the real root is rejected.
|
|
155
|
+
* Internal links (inside the workspace) keep working. The returned value
|
|
156
|
+
* is still the lexically normalized path.
|
|
157
|
+
*/
|
|
158
|
+
export function resolveSandboxedPath(userPath, options = {}) {
|
|
159
|
+
return resolveSandboxedCore(userPath, options);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Explicit name for the symlink-safe resolver (P0.D t18) — IDENTICAL
|
|
163
|
+
* behavior to resolveSandboxedPath since P0.D upgraded it in place so every
|
|
164
|
+
* existing caller inherits safety transparently (zero call-site churn).
|
|
165
|
+
* Kept as a stable, self-documenting export for new call sites and tests.
|
|
166
|
+
*/
|
|
167
|
+
export const resolveSandboxedPathReal = resolveSandboxedPath;
|
|
168
|
+
/**
|
|
169
|
+
* TOCTOU re-check 〔PW §5〕: re-run BOTH layers on fresh syscalls for an
|
|
170
|
+
* ALREADY-resolved absolute path. Call this immediately before performing a
|
|
171
|
+
* filesystem mutation when state may have changed since the original
|
|
172
|
+
* resolveSandboxedPath call (e.g. a directory swapped for a junction between
|
|
173
|
+
* the permission check and the write). Throws SandboxViolationError if the
|
|
174
|
+
* candidate would now leave the real root; returns normally otherwise.
|
|
175
|
+
*/
|
|
176
|
+
export function verifyContainment(resolvedAbsolute, options = {}) {
|
|
177
|
+
if (typeof resolvedAbsolute !== 'string' || resolvedAbsolute.length === 0) {
|
|
178
|
+
throw new SandboxViolationError('Empty path', resolvedAbsolute, '');
|
|
179
|
+
}
|
|
180
|
+
const root = path.resolve(options.root ?? process.cwd());
|
|
181
|
+
const resolved = path.resolve(resolvedAbsolute);
|
|
182
|
+
assertLexicalContainment(resolved, root, resolvedAbsolute);
|
|
183
|
+
let realRoot = null;
|
|
184
|
+
try {
|
|
185
|
+
realRoot = fs.realpathSync(root);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return; // root vanished / nonexistent → textual layer is all we have
|
|
189
|
+
}
|
|
190
|
+
assertRealAncestorContained(resolved, realRoot, resolvedAbsolute);
|
|
191
|
+
}
|
|
49
192
|
/**
|
|
50
193
|
* Lightweight check that does NOT throw — returns true if the path
|
|
51
|
-
* would be allowed by resolveSandboxedPath.
|
|
194
|
+
* would be allowed by resolveSandboxedPath (symlink-aware since P0.D).
|
|
52
195
|
*/
|
|
53
196
|
export function isPathInsideSandbox(userPath, options = {}) {
|
|
54
197
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandboxPath.js","sourceRoot":"","sources":["../../../src/cli/safety/sandboxPath.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sandboxPath.js","sourceRoot":"","sources":["../../../src/cli/safety/sandboxPath.ts"],"names":[],"mappings":"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"}
|