zelari-code 2.11.2 → 2.13.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.js +16 -5
- package/dist/cli/headless.js.map +1 -1
- package/dist/cli/hooks/useChatTurn.js +17 -4
- package/dist/cli/hooks/useChatTurn.js.map +1 -1
- package/dist/cli/kraken/completionProof.js +176 -0
- package/dist/cli/kraken/completionProof.js.map +1 -0
- package/dist/cli/kraken/nativeVerification.js +12 -5
- package/dist/cli/kraken/nativeVerification.js.map +1 -1
- package/dist/cli/kraken/verificationBridge.js +5 -1
- package/dist/cli/kraken/verificationBridge.js.map +1 -1
- package/dist/cli/kraken/verifierLifecycle.js +72 -8
- package/dist/cli/kraken/verifierLifecycle.js.map +1 -1
- package/dist/cli/main.bundled.js +1141 -433
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/orchestration/policy.js +88 -0
- package/dist/cli/orchestration/policy.js.map +1 -0
- package/dist/cli/runHeadless.js +49 -0
- package/dist/cli/runHeadless.js.map +1 -1
- package/dist/cli/safety/policyEngine.js +315 -0
- package/dist/cli/safety/policyEngine.js.map +1 -0
- package/dist/cli/safety/policyLayers.js +55 -0
- package/dist/cli/safety/policyLayers.js.map +1 -0
- package/dist/cli/safety/toolPermissions.js +38 -0
- package/dist/cli/safety/toolPermissions.js.map +1 -1
- package/dist/cli/toolRegistry.js +49 -9
- package/dist/cli/toolRegistry.js.map +1 -1
- package/dist/cli/tools/krakenCsvFanout.js +4 -4
- package/dist/cli/tools/krakenCsvFanout.js.map +1 -1
- package/dist/cli/tools/krakenModel.js +75 -0
- package/dist/cli/tools/krakenModel.js.map +1 -1
- package/dist/cli/tools/taskTool.js +4 -3
- package/dist/cli/tools/taskTool.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy engine v2 (P0.A) — per-command and per-path permission rules per
|
|
3
|
+
* agent, layered on top of the per-CATEGORY policy in toolPermissions.ts.
|
|
4
|
+
*
|
|
5
|
+
* Rules come from two USER-authored config files (same trust level as
|
|
6
|
+
* AGENTS.MD — deliberately NO trust subsystem here; the GLOBAL file is the
|
|
7
|
+
* user's personal floor, which the project file can NO LONGER relax):
|
|
8
|
+
*
|
|
9
|
+
* <root>/.zelari/policy.json — project rules
|
|
10
|
+
* ~/.zelari/policy.json — global rules
|
|
11
|
+
*
|
|
12
|
+
* Shape:
|
|
13
|
+
* {
|
|
14
|
+
* version?: 1,
|
|
15
|
+
* agents: {
|
|
16
|
+
* lead? : { shell?: Rule[], edit?: Rule[] },
|
|
17
|
+
* explore? : { shell?: Rule[], edit?: Rule[] },
|
|
18
|
+
* general? : { shell?: Rule[], edit?: Rule[] },
|
|
19
|
+
* verify? : { shell?: Rule[], edit?: Rule[] }
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* Rule = { match: string, effect: 'allow' | 'ask' | 'deny', reason?: string }
|
|
23
|
+
*
|
|
24
|
+
* `match` is a GLOB-style prefix pattern: `git push*` matches any command
|
|
25
|
+
* starting with "git push"; `src/**` matches any path under src/; `*` alone
|
|
26
|
+
* matches everything. Within one LAYER's ordered rule list the FIRST match
|
|
27
|
+
* wins (P0.A): the two layers stay SEPARATE and combine RESTRICT-ONLY — every
|
|
28
|
+
* matching rule intersects with the category decision, most-restrictive-wins
|
|
29
|
+
* (deny > ask > allow), so a global deny can never be masked by the project.
|
|
30
|
+
* Escape hatch: ZELARI_POLICY_PRECEDENCE=legacy restores the v1 behavior
|
|
31
|
+
* (project rules concatenated before global ones, first-match overrides);
|
|
32
|
+
* the active mode is exposed as `PolicySet.precedence`.
|
|
33
|
+
*
|
|
34
|
+
* Fail-closed conventions (mirrors toolPermissions.ts): a missing file is
|
|
35
|
+
* silently empty; a broken file NEVER throws — the parse error is collected
|
|
36
|
+
* in `warnings` and the offending file/section is ignored. Opt out entirely
|
|
37
|
+
* with ZELARI_POLICY=0 (always the empty set).
|
|
38
|
+
*
|
|
39
|
+
* Hand-rolled validators on purpose: no zod, no new deps.
|
|
40
|
+
*
|
|
41
|
+
* @since v2.12.0 · restrict-only layers v2.13.0
|
|
42
|
+
*/
|
|
43
|
+
import { readFileSync } from 'node:fs';
|
|
44
|
+
import { homedir } from 'node:os';
|
|
45
|
+
import path from 'node:path';
|
|
46
|
+
/** The authoritative agent keys ('lead' = the main registry; the three
|
|
47
|
+
* sub-agent kinds mirror taskAgentToProfile in toolRegistry.ts). */
|
|
48
|
+
export const POLICY_AGENTS = ['lead', 'explore', 'general', 'verify'];
|
|
49
|
+
const KNOWN_AGENTS = new Set(POLICY_AGENTS);
|
|
50
|
+
export const EMPTY_POLICY_RULE_SET = { shell: [], edit: [] };
|
|
51
|
+
export const EMPTY_POLICY_LAYERS = {
|
|
52
|
+
global: EMPTY_POLICY_RULE_SET,
|
|
53
|
+
project: EMPTY_POLICY_RULE_SET,
|
|
54
|
+
};
|
|
55
|
+
export function emptyPolicySet() {
|
|
56
|
+
return { agents: new Map(), warnings: [], precedence: policyPrecedenceFromEnv() };
|
|
57
|
+
}
|
|
58
|
+
/** Raw layers for one agent; unknown/unlisted agents get the empty layers. */
|
|
59
|
+
export function agentLayersFor(set, agent) {
|
|
60
|
+
return set.agents.get(agent) ?? EMPTY_POLICY_LAYERS;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* COMPAT view (v1 shape): project rules then global rules in ONE list —
|
|
64
|
+
* first-match over this concatenation IS legacy precedence. Restrict-only
|
|
65
|
+
* consumers must use agentLayersFor + matchAgentPolicyRuleLayered instead.
|
|
66
|
+
*/
|
|
67
|
+
export function agentRulesFor(set, agent) {
|
|
68
|
+
const l = set.agents.get(agent);
|
|
69
|
+
if (!l)
|
|
70
|
+
return EMPTY_POLICY_RULE_SET;
|
|
71
|
+
return {
|
|
72
|
+
shell: [...l.project.shell, ...l.global.shell],
|
|
73
|
+
edit: [...l.project.edit, ...l.global.edit],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// ── Glob matching ──────────────────────────────────────────────────────────
|
|
77
|
+
/**
|
|
78
|
+
* Compile a GLOB-style pattern into an ANCHORED RegExp (full match).
|
|
79
|
+
*
|
|
80
|
+
* Wildcard semantics: `*` and `**` are both "any characters, including path
|
|
81
|
+
* separators" — these are prefix patterns, not full minimatch; `src/**`
|
|
82
|
+
* means "anything under src/", `git push*` means "starts with git push",
|
|
83
|
+
* and a bare `*` is the catch-all. Consecutive stars collapse to one `.*`.
|
|
84
|
+
* Every other character is regex-escaped (`data.json` matches literally).
|
|
85
|
+
*/
|
|
86
|
+
export function globToRegExp(pattern) {
|
|
87
|
+
let src = '^';
|
|
88
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
89
|
+
const ch = pattern[i];
|
|
90
|
+
if (ch === '*') {
|
|
91
|
+
while (pattern[i + 1] === '*')
|
|
92
|
+
i++;
|
|
93
|
+
src += '.*';
|
|
94
|
+
}
|
|
95
|
+
else if ('\\^$.|?+()[]{}'.includes(ch)) {
|
|
96
|
+
src += '\\' + ch;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
src += ch;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return new RegExp(src + '$');
|
|
103
|
+
}
|
|
104
|
+
/** Backslashes (Windows paths) normalize to `/` so `src/**` matches `src\x.ts`. */
|
|
105
|
+
function normalizeForMatch(value) {
|
|
106
|
+
return value.replace(/\\/g, '/');
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* First match wins: return the FIRST rule whose pattern matches `value`,
|
|
110
|
+
* or null when nothing matches (caller keeps its category decision).
|
|
111
|
+
*/
|
|
112
|
+
export function resolvePolicyRule(rules, value) {
|
|
113
|
+
const v = normalizeForMatch(value);
|
|
114
|
+
for (const rule of rules) {
|
|
115
|
+
if (globToRegExp(normalizeForMatch(rule.match)).test(v))
|
|
116
|
+
return rule;
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
// ── Merge with the category decision ──────────────────────────────────────
|
|
121
|
+
/** Shared lattice with policyLayers.ts intersectEffects: deny > ask > allow. */
|
|
122
|
+
export const EFFECT_RANK = { allow: 0, ask: 1, deny: 2 };
|
|
123
|
+
/**
|
|
124
|
+
* Merge a matched rule's effect into the category-level action under the
|
|
125
|
+
* same lattice as intersectPermissionPolicy: deny > ask > allow. A rule can
|
|
126
|
+
* only ADD restriction — an `allow` rule never un-asks or un-denies a
|
|
127
|
+
* category restriction (no privilege escalation via policy.json).
|
|
128
|
+
*/
|
|
129
|
+
export function mergeRuleEffect(base, rule) {
|
|
130
|
+
if (!rule)
|
|
131
|
+
return base;
|
|
132
|
+
return EFFECT_RANK[rule.effect] > EFFECT_RANK[base] ? rule.effect : base;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Path candidates to test against edit rules: the argument as passed, plus
|
|
136
|
+
* the root-relative form when the arg lives under `root` (so `src/**` works
|
|
137
|
+
* whether the model passed `src/x.ts` or `E:\repo\src\x.ts`). Root-relative
|
|
138
|
+
* first — it is what users write in policy.json.
|
|
139
|
+
*/
|
|
140
|
+
function pathCandidates(value, root) {
|
|
141
|
+
const norm = normalizeForMatch(value);
|
|
142
|
+
if (!root)
|
|
143
|
+
return [norm];
|
|
144
|
+
const prefix = normalizeForMatch(root).replace(/\/+$/, '') + '/';
|
|
145
|
+
const stripped = norm.toLowerCase().startsWith(prefix.toLowerCase())
|
|
146
|
+
? norm.slice(prefix.length)
|
|
147
|
+
: null;
|
|
148
|
+
return stripped !== null ? [stripped, norm] : [norm];
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Find the agent rule that applies to one tool invocation:
|
|
152
|
+
* - execute-category tools (bash, inspect_command): shell rules vs the
|
|
153
|
+
* `command` string argument;
|
|
154
|
+
* - write-category tools (write_file / edit_file / apply_diff): edit rules
|
|
155
|
+
* vs the primary path argument (`path`, falling back to `file_path`),
|
|
156
|
+
* tried root-relative first (see pathCandidates).
|
|
157
|
+
* Tools without a usable argument (or no rules) match nothing → null.
|
|
158
|
+
*/
|
|
159
|
+
export function matchAgentPolicyRule(rules, required, args, root) {
|
|
160
|
+
if (!rules)
|
|
161
|
+
return null;
|
|
162
|
+
const a = (args !== null && typeof args === 'object' ? args : {});
|
|
163
|
+
if (required.includes('execute')) {
|
|
164
|
+
const cmd = a['command'];
|
|
165
|
+
if (typeof cmd === 'string' && cmd !== '') {
|
|
166
|
+
const hit = resolvePolicyRule(rules.shell, cmd);
|
|
167
|
+
if (hit)
|
|
168
|
+
return hit;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (required.includes('write')) {
|
|
172
|
+
const p = typeof a['path'] === 'string' ? a['path'] : typeof a['file_path'] === 'string' ? a['file_path'] : '';
|
|
173
|
+
if (p !== '') {
|
|
174
|
+
for (const candidate of pathCandidates(p, root)) {
|
|
175
|
+
const hit = resolvePolicyRule(rules.edit, candidate);
|
|
176
|
+
if (hit)
|
|
177
|
+
return hit;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
// ── Loading & validation (fail-closed on files, never throws) ─────────────
|
|
184
|
+
/** Env opt-out: ZELARI_POLICY=0 (also false/no/off) → always the empty set. */
|
|
185
|
+
export function isPolicyEngineDisabled() {
|
|
186
|
+
const v = process.env.ZELARI_POLICY?.trim().toLowerCase();
|
|
187
|
+
return v === '0' || v === 'false' || v === 'no' || v === 'off';
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* P0.A escape hatch: ZELARI_POLICY_PRECEDENCE=legacy selects the v1
|
|
191
|
+
* project-overrides-global first-match behavior; any other value (unset
|
|
192
|
+
* included) selects the default restrict-only layering. Read at policy-load
|
|
193
|
+
* time and exposed as PolicySet.precedence so callers/tests can assert the
|
|
194
|
+
* active mode.
|
|
195
|
+
*/
|
|
196
|
+
export function policyPrecedenceFromEnv() {
|
|
197
|
+
const v = process.env.ZELARI_POLICY_PRECEDENCE?.trim().toLowerCase();
|
|
198
|
+
return v === 'legacy' ? 'legacy' : 'restrict-only';
|
|
199
|
+
}
|
|
200
|
+
function isPlainObject(v) {
|
|
201
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
202
|
+
}
|
|
203
|
+
function parseRuleList(raw, origin, warnings) {
|
|
204
|
+
if (raw === undefined)
|
|
205
|
+
return [];
|
|
206
|
+
if (!Array.isArray(raw)) {
|
|
207
|
+
warnings.push(`${origin}: expected an array of rules, got ${typeof raw} — section ignored.`);
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
const out = [];
|
|
211
|
+
raw.forEach((item, i) => {
|
|
212
|
+
const where = `${origin}[${i}]`;
|
|
213
|
+
if (!isPlainObject(item)) {
|
|
214
|
+
warnings.push(`${where}: rule is not an object — skipped.`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const match = item['match'];
|
|
218
|
+
const effect = item['effect'];
|
|
219
|
+
const reason = item['reason'];
|
|
220
|
+
if (typeof match !== 'string' || match.trim() === '') {
|
|
221
|
+
warnings.push(`${where}: missing or empty "match" — skipped.`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (effect !== 'allow' && effect !== 'ask' && effect !== 'deny') {
|
|
225
|
+
warnings.push(`${where} ("${match}"): "effect" must be allow|ask|deny — skipped.`);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
out.push(typeof reason === 'string' && reason.trim() !== '' ? { match, effect, reason } : { match, effect });
|
|
229
|
+
});
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
/** Returns null when the WHOLE file must be ignored (bad shape / version). */
|
|
233
|
+
function parsePolicyFile(raw, origin, warnings) {
|
|
234
|
+
if (!isPlainObject(raw)) {
|
|
235
|
+
warnings.push(`${origin}: policy file is not a JSON object — file ignored.`);
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
const version = raw['version'];
|
|
239
|
+
if (version !== undefined && version !== 1) {
|
|
240
|
+
warnings.push(`${origin}: unsupported "version" ${JSON.stringify(version)} (expected 1) — file ignored.`);
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
const agentsRaw = raw['agents'];
|
|
244
|
+
if (agentsRaw === undefined) {
|
|
245
|
+
warnings.push(`${origin}: no "agents" key — file ignored.`);
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
if (!isPlainObject(agentsRaw)) {
|
|
249
|
+
warnings.push(`${origin}: "agents" is not an object — file ignored.`);
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
const out = new Map();
|
|
253
|
+
for (const [key, val] of Object.entries(agentsRaw)) {
|
|
254
|
+
const agent = key.trim().toLowerCase();
|
|
255
|
+
if (!KNOWN_AGENTS.has(agent)) {
|
|
256
|
+
warnings.push(`${origin}: unknown agent "${key}" (known: ${POLICY_AGENTS.join(' | ')}) — ignored.`);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (!isPlainObject(val)) {
|
|
260
|
+
warnings.push(`${origin}: agents.${key} is not an object — ignored.`);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const shell = parseRuleList(val['shell'], `${origin} agents.${key}.shell`, warnings);
|
|
264
|
+
const edit = parseRuleList(val['edit'], `${origin} agents.${key}.edit`, warnings);
|
|
265
|
+
out.set(agent, { shell, edit });
|
|
266
|
+
}
|
|
267
|
+
return out;
|
|
268
|
+
}
|
|
269
|
+
function readPolicyFile(file, warnings) {
|
|
270
|
+
let text;
|
|
271
|
+
try {
|
|
272
|
+
text = readFileSync(file, 'utf8');
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
// Missing file is the common case (most repos/users have none) — not a
|
|
276
|
+
// warning, just no rules from this source.
|
|
277
|
+
return new Map();
|
|
278
|
+
}
|
|
279
|
+
let parsed;
|
|
280
|
+
try {
|
|
281
|
+
parsed = JSON.parse(text);
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
warnings.push(`${file}: invalid JSON (${err instanceof Error ? err.message : String(err)}) — file ignored.`);
|
|
285
|
+
return new Map();
|
|
286
|
+
}
|
|
287
|
+
return parsePolicyFile(parsed, file, warnings) ?? new Map();
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Load the project (`<root>/.zelari/policy.json`) and global
|
|
291
|
+
* (`~/.zelari/policy.json`) rule sets into DISTINCT layers (P0.A): the two
|
|
292
|
+
* sources are NEVER concatenated at load time — evaluation intersects them
|
|
293
|
+
* restrict-only (matchAgentPolicyRuleLayered in policyLayers.ts) unless
|
|
294
|
+
* ZELARI_POLICY_PRECEDENCE=legacy restores the v1 concat view. NEVER
|
|
295
|
+
* throws: broken files yield warnings + whatever parsed cleanly. Pass
|
|
296
|
+
* `homeDir` to override the global-file location (tests).
|
|
297
|
+
*/
|
|
298
|
+
export function loadPolicySet(root, opts = {}) {
|
|
299
|
+
if (isPolicyEngineDisabled())
|
|
300
|
+
return emptyPolicySet();
|
|
301
|
+
const warnings = [];
|
|
302
|
+
const precedence = policyPrecedenceFromEnv();
|
|
303
|
+
const project = readPolicyFile(path.join(root, '.zelari', 'policy.json'), warnings);
|
|
304
|
+
const global = readPolicyFile(path.join(opts.homeDir ?? homedir(), '.zelari', 'policy.json'), warnings);
|
|
305
|
+
const agents = new Map();
|
|
306
|
+
for (const [agent, p] of project) {
|
|
307
|
+
agents.set(agent, { project: p, global: EMPTY_POLICY_RULE_SET });
|
|
308
|
+
}
|
|
309
|
+
for (const [agent, g] of global) {
|
|
310
|
+
const l = agents.get(agent);
|
|
311
|
+
agents.set(agent, l ? { ...l, global: g } : { project: EMPTY_POLICY_RULE_SET, global: g });
|
|
312
|
+
}
|
|
313
|
+
return { agents, warnings, precedence };
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=policyEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policyEngine.js","sourceRoot":"","sources":["../../../src/cli/safety/policyEngine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { EFFECT_RANK, matchAgentPolicyRule, } from './policyEngine.js';
|
|
2
|
+
/**
|
|
3
|
+
* Intersect candidate effects into ONE effect: deny > ask > allow. Absent
|
|
4
|
+
* matches arrive as `undefined` and simply do not constrain the result; with
|
|
5
|
+
* NO constraint at all the slot is unconstrained (`allow`). Feed this the
|
|
6
|
+
* category decision plus every layer's matched effect and the result is the
|
|
7
|
+
* effective action — no operand can ever RELAX another.
|
|
8
|
+
*/
|
|
9
|
+
export function intersectEffects(...effects) {
|
|
10
|
+
let best;
|
|
11
|
+
for (const effect of effects) {
|
|
12
|
+
if (effect === undefined)
|
|
13
|
+
continue;
|
|
14
|
+
if (best === undefined || EFFECT_RANK[effect] > EFFECT_RANK[best])
|
|
15
|
+
best = effect;
|
|
16
|
+
}
|
|
17
|
+
return best ?? 'allow';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve THIS agent's policy contribution across BOTH layers for one tool
|
|
21
|
+
* invocation:
|
|
22
|
+
*
|
|
23
|
+
* - `restrict-only` (default): each layer matches independently and the
|
|
24
|
+
* STRICTER effect wins; equal ranks surface the project rule (it is the
|
|
25
|
+
* more specific intent).
|
|
26
|
+
* - `legacy`: v1 semantics — project rules concatenated before global ones,
|
|
27
|
+
* FIRST match wins (a project match masks the global rule entirely).
|
|
28
|
+
*
|
|
29
|
+
* Either way a single rule carries the effect the caller must intersect into
|
|
30
|
+
* the category decision (see intersectEffects and wrapWithPermissions in
|
|
31
|
+
* toolRegistry.ts). Tools with no usable argument match nothing in both
|
|
32
|
+
* layers → null, leaving the category decision untouched.
|
|
33
|
+
*/
|
|
34
|
+
export function matchAgentPolicyRuleLayered(layers, precedence, required, args, root) {
|
|
35
|
+
if (!layers)
|
|
36
|
+
return null;
|
|
37
|
+
if (precedence === 'legacy') {
|
|
38
|
+
// v1: ONE concatenated list — project rules first, first-match-wins.
|
|
39
|
+
return matchAgentPolicyRule({
|
|
40
|
+
shell: [...layers.project.shell, ...layers.global.shell],
|
|
41
|
+
edit: [...layers.project.edit, ...layers.global.edit],
|
|
42
|
+
}, required, args, root);
|
|
43
|
+
}
|
|
44
|
+
// Default restrict-only: independent matches, stricter effect wins (ties
|
|
45
|
+
// keep the project rule as the surfaced representative).
|
|
46
|
+
const g = matchAgentPolicyRule(layers.global, required, args, root);
|
|
47
|
+
const p = matchAgentPolicyRule(layers.project, required, args, root);
|
|
48
|
+
if (!g)
|
|
49
|
+
return p;
|
|
50
|
+
if (!p)
|
|
51
|
+
return g;
|
|
52
|
+
const win = intersectEffects(p.effect, g.effect);
|
|
53
|
+
return p.effect === win ? p : g;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=policyLayers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policyLayers.js","sourceRoot":"","sources":["../../../src/cli/safety/policyLayers.ts"],"names":[],"mappings":"AAsBA,OAAO,EACL,WAAW,EACX,oBAAoB,GAIrB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAG,OAA4C;IAC9E,IAAI,IAAkC,CAAC;IACvC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,KAAK,SAAS;YAAE,SAAS;QACnC,IAAI,IAAI,KAAK,SAAS,IAAI,WAAW,CAAC,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,MAAM,CAAC;IACnF,CAAC;IACD,OAAO,IAAI,IAAI,OAAO,CAAC;AACzB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,2BAA2B,CACzC,MAAwC,EACxC,UAA4B,EAC5B,QAAmC,EACnC,IAAa,EACb,IAAa;IAEb,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;QAC5B,qEAAqE;QACrE,OAAO,oBAAoB,CACzB;YACE,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;YACxD,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;SACtD,EACD,QAAQ,EACR,IAAI,EACJ,IAAI,CACL,CAAC;IACJ,CAAC;IACD,yEAAyE;IACzE,yDAAyD;IACzD,MAAM,CAAC,GAAG,oBAAoB,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACpE,MAAM,CAAC,GAAG,oBAAoB,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACrE,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACjB,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACjB,MAAM,GAAG,GAAG,gBAAgB,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACjD,OAAO,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -66,6 +66,44 @@ export function defaultPermissionPolicy(overrides) {
|
|
|
66
66
|
...overrides,
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Restrictiveness lattice for {@link PermissionAction}: deny > ask > allow
|
|
71
|
+
* (most restrictive wins).
|
|
72
|
+
*/
|
|
73
|
+
const ACTION_RANK = { allow: 0, ask: 1, deny: 2 };
|
|
74
|
+
function moreRestrictive(a, b) {
|
|
75
|
+
return ACTION_RANK[a] >= ACTION_RANK[b] ? a : b;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Capability inheritance (P0.4): a Kraken tentacle (sub-agent spawned via
|
|
79
|
+
* the `task` tool) must NEVER hold more permission than its parent. The
|
|
80
|
+
* sub-agent's effective policy is the intersection of the parent's policy
|
|
81
|
+
* and the sub-agent profile's own policy, merged per category with the most
|
|
82
|
+
* restrictive action winning (deny > ask > allow). A category restricted in
|
|
83
|
+
* only ONE of the two policies keeps that restriction.
|
|
84
|
+
*
|
|
85
|
+
* `auto` (ask resolves as allow without UI) intersects as the AND of both:
|
|
86
|
+
* silently auto-allowing a category the parent would have had to ask about
|
|
87
|
+
* would itself be an escalation.
|
|
88
|
+
*
|
|
89
|
+
* If the intersection downgrades a category to `ask` while the sub-agent
|
|
90
|
+
* context has no interactive ask handler, resolution must FAIL CLOSED
|
|
91
|
+
* (deny). That behavior already lives in `wrapWithPermissions`
|
|
92
|
+
* (toolRegistry.ts): `ask` without `onPermissionAsk` returns a typedErr, so
|
|
93
|
+
* the escalation path stays closed with no extra handling here.
|
|
94
|
+
*
|
|
95
|
+
* Pure function, no I/O.
|
|
96
|
+
*/
|
|
97
|
+
export function intersectPermissionPolicy(parent, child) {
|
|
98
|
+
return {
|
|
99
|
+
read: moreRestrictive(parent.read, child.read),
|
|
100
|
+
write: moreRestrictive(parent.write, child.write),
|
|
101
|
+
execute: moreRestrictive(parent.execute, child.execute),
|
|
102
|
+
network: moreRestrictive(parent.network, child.network),
|
|
103
|
+
ui: moreRestrictive(parent.ui, child.ui),
|
|
104
|
+
auto: parent.auto && child.auto,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
69
107
|
/**
|
|
70
108
|
* Resolve effective action for a tool given its required permission tags.
|
|
71
109
|
* Empty permissions → allow. Most restrictive action wins: deny > ask > allow.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolPermissions.js","sourceRoot":"","sources":["../../../src/cli/safety/toolPermissions.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"toolPermissions.js","sourceRoot":"","sources":["../../../src/cli/safety/toolPermissions.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/toolRegistry.js
CHANGED
|
@@ -43,8 +43,10 @@ import { createWorldModelTools } from './workspace/worldModel.js';
|
|
|
43
43
|
import { providerFromEnv, providerConfigFor, } from './provider/openai-compatible.js';
|
|
44
44
|
import { buildProviderStream } from './provider/resolveStream.js';
|
|
45
45
|
import { getKrakenVerifierOverride } from './providerConfig.js';
|
|
46
|
-
import { defaultPermissionPolicy, resolveToolPermission, } from './safety/toolPermissions.js';
|
|
46
|
+
import { defaultPermissionPolicy, intersectPermissionPolicy, resolveToolPermission, } from './safety/toolPermissions.js';
|
|
47
47
|
import { createDefaultLifecycleHooks } from './safety/lifecycleHooks.js';
|
|
48
|
+
import { agentLayersFor, loadPolicySet, mergeRuleEffect, } from './safety/policyEngine.js';
|
|
49
|
+
import { matchAgentPolicyRuleLayered } from './safety/policyLayers.js';
|
|
48
50
|
import { withResultCache } from './toolResultCache.js';
|
|
49
51
|
import { typedErr } from '@zelari/core/harness/tools/toolTypes';
|
|
50
52
|
import { cliToolToEnhanced, registerCustomTool } from '@zelari/core/skills';
|
|
@@ -105,7 +107,15 @@ export function createBuiltinToolRegistry(options = {}) {
|
|
|
105
107
|
const allowMutators = !readOnly && !verifyMode && !gauntletParent;
|
|
106
108
|
const allowBash = (allowMutators || verifyMode) && !gauntletParent;
|
|
107
109
|
const permPolicy = options.permissionPolicy ?? defaultPermissionPolicy();
|
|
108
|
-
|
|
110
|
+
// P0.A layered policy engine: per-command/per-path rules for THIS agent
|
|
111
|
+
// identity with global and project kept as DISTINCT layers. Default
|
|
112
|
+
// evaluation is restrict-only (a global deny/ask can never be relaxed by the
|
|
113
|
+
// project file); ZELARI_POLICY_PRECEDENCE=legacy restores the v1
|
|
114
|
+
// project-first override. Loaded once per registry build; empty when
|
|
115
|
+
// absent/invalid/disabled (ZELARI_POLICY=0).
|
|
116
|
+
const agentPolicySet = loadPolicySet(root);
|
|
117
|
+
const agentPolicyLayers = agentLayersFor(agentPolicySet, options.policyAgent ?? 'lead');
|
|
118
|
+
const withPerm = (t) => wrapWithPermissions(t, permPolicy, options.onPermissionAsk, agentPolicyLayers, agentPolicySet.precedence, root);
|
|
109
119
|
// Observe tools — always registered.
|
|
110
120
|
registry.register(withPerm(safeReadFile));
|
|
111
121
|
registry.register(withPerm(safeGrepContent));
|
|
@@ -300,6 +310,10 @@ export function createBuiltinToolRegistry(options = {}) {
|
|
|
300
310
|
root,
|
|
301
311
|
audit,
|
|
302
312
|
sessionId,
|
|
313
|
+
// P0.4 capability inheritance: tentacles intersect THIS
|
|
314
|
+
// registry's own policy (permPolicy above) — they can never
|
|
315
|
+
// exceed it.
|
|
316
|
+
parentPolicy: permPolicy,
|
|
303
317
|
...(options.subAgentProvider ? { provider: options.subAgentProvider } : {}),
|
|
304
318
|
...(options.subAgentModel ? { model: options.subAgentModel } : {}),
|
|
305
319
|
}),
|
|
@@ -468,7 +482,7 @@ function taskAgentToProfile(agent) {
|
|
|
468
482
|
* (F6) — can build a `TaskToolDeps` without duplicating this wiring.
|
|
469
483
|
*/
|
|
470
484
|
export function createKrakenSubAgentContextFactory(opts) {
|
|
471
|
-
const { root, audit, sessionId, provider: providerOverride, model: modelOverride } = opts;
|
|
485
|
+
const { root, audit, sessionId, provider: providerOverride, model: modelOverride, parentPolicy } = opts;
|
|
472
486
|
return async ({ agent, cwd: subCwd }) => {
|
|
473
487
|
const cfg = providerOverride
|
|
474
488
|
? await providerConfigFor(providerOverride)
|
|
@@ -496,6 +510,18 @@ export function createKrakenSubAgentContextFactory(opts) {
|
|
|
496
510
|
const subCfg = { ...effCfg, model };
|
|
497
511
|
const subProfile = taskAgentToProfile(agent);
|
|
498
512
|
const subRoot = subCwd || root;
|
|
513
|
+
// P0.4 capability inheritance: a tentacle NEVER holds more permission
|
|
514
|
+
// than its parent. The sub-profile policy this factory builds is
|
|
515
|
+
// headless-style auto-allow (a tentacle has no interactive prompt to
|
|
516
|
+
// ask at); the effective policy is its intersection with the parent
|
|
517
|
+
// policy — deny > ask > allow per category, `auto` is the AND of both.
|
|
518
|
+
// If the intersection downgrades a category to `ask`, resolution must
|
|
519
|
+
// FAIL CLOSED: the sub-agent context has no interactive ask handler, and
|
|
520
|
+
// `wrapWithPermissions` already returns typedErr for `ask` without
|
|
521
|
+
// `onPermissionAsk`, so that guarantee is reused as-is here (no extra
|
|
522
|
+
// handling needed).
|
|
523
|
+
const agentPolicyForSubProfile = defaultPermissionPolicy({ auto: true });
|
|
524
|
+
const effectiveSubPolicy = intersectPermissionPolicy(parentPolicy ?? agentPolicyForSubProfile, agentPolicyForSubProfile);
|
|
499
525
|
const { registry: subRegistry } = createBuiltinToolRegistry({
|
|
500
526
|
root: subRoot,
|
|
501
527
|
audit,
|
|
@@ -505,7 +531,9 @@ export function createKrakenSubAgentContextFactory(opts) {
|
|
|
505
531
|
enableSkill: agent === 'general',
|
|
506
532
|
diagnostics: false,
|
|
507
533
|
lspProvider: null,
|
|
508
|
-
permissionPolicy:
|
|
534
|
+
permissionPolicy: effectiveSubPolicy,
|
|
535
|
+
// P0.5: the tentacle's agent identity drives per-agent policy rules.
|
|
536
|
+
policyAgent: agent,
|
|
509
537
|
});
|
|
510
538
|
return {
|
|
511
539
|
providerStream: buildProviderStream(subCfg),
|
|
@@ -525,7 +553,7 @@ export function createKrakenSubAgentContextFactory(opts) {
|
|
|
525
553
|
/**
|
|
526
554
|
* Gate tool execution with allow/ask/deny policy (OpenCode-style permissions).
|
|
527
555
|
*/
|
|
528
|
-
function wrapWithPermissions(original, policy, onAsk) {
|
|
556
|
+
function wrapWithPermissions(original, policy, onAsk, agentLayers, precedence = 'restrict-only', root) {
|
|
529
557
|
const required = (original.permissions ?? []);
|
|
530
558
|
// Pure read tools under allow policy — no wrap overhead.
|
|
531
559
|
const decisionProbe = resolveToolPermission(original.name, required, policy);
|
|
@@ -537,12 +565,24 @@ function wrapWithPermissions(original, policy, onAsk) {
|
|
|
537
565
|
...original,
|
|
538
566
|
execute: async (input, ctx) => {
|
|
539
567
|
const decision = resolveToolPermission(original.name, required, policy);
|
|
540
|
-
|
|
541
|
-
|
|
568
|
+
// P0.A: resolve this agent's rule across the global+project layers —
|
|
569
|
+
// restrict-only by default (deny > ask > allow; a global deny/ask can
|
|
570
|
+
// never be relaxed by the project file; legacy precedence restores the
|
|
571
|
+
// v1 project-first override). The survivor only ever ADDS restriction
|
|
572
|
+
// to the category decision (mergeRuleEffect).
|
|
573
|
+
const rule = agentLayers
|
|
574
|
+
? matchAgentPolicyRuleLayered(agentLayers, precedence, required, (input ?? {}), root ?? process.cwd())
|
|
575
|
+
: null;
|
|
576
|
+
const action = mergeRuleEffect(decision.action, rule);
|
|
577
|
+
const rulePrefix = rule
|
|
578
|
+
? `[policy] rule '${rule.match}'${rule.reason ? ` — ${rule.reason}` : ''}`
|
|
579
|
+
: '';
|
|
580
|
+
if (action === 'deny') {
|
|
581
|
+
return typedErr(`[permission] ${rulePrefix || decision.reason}`);
|
|
542
582
|
}
|
|
543
|
-
if (
|
|
583
|
+
if (action === 'ask') {
|
|
544
584
|
if (!onAsk) {
|
|
545
|
-
return typedErr(`[permission] ${decision.reason} No interactive approval available ` +
|
|
585
|
+
return typedErr(`[permission] ${rulePrefix ? `${rulePrefix} ` : ''}${decision.reason} No interactive approval available ` +
|
|
546
586
|
`(set ZELARI_AUTO=1 to auto-allow, or configure onPermissionAsk).`);
|
|
547
587
|
}
|
|
548
588
|
try {
|