@tuent/sentinel 0.1.3 → 0.1.5
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/{Sentinel-BVoMEF3F.d.ts → Sentinel-CJJ4iYDh.d.ts} +122 -11
- package/dist/Sentinel-XP6NFG6Z.js +10 -0
- package/dist/{chunk-PDWWRZXF.js → chunk-2IPSTUNH.js} +18 -7
- package/dist/{chunk-G74MMDKA.js → chunk-3WT3K5TH.js} +151 -24
- package/dist/{chunk-SSDIBY52.js → chunk-7R6EA7JG.js} +223 -90
- package/dist/chunk-B6S2PBS4.js +47 -0
- package/dist/{chunk-JTR2E7RD.js → chunk-M5EEVMLU.js} +222 -186
- package/dist/{chunk-WLIDSTS4.js → chunk-SKE74CYZ.js} +231 -28
- package/dist/{chunk-2TJ5Z53T.js → chunk-UVNRPML4.js} +59 -20
- package/dist/cli.js +33 -35
- package/dist/gateway/index.d.ts +26 -1
- package/dist/gateway/index.js +4 -4
- package/dist/gatewayDaemon.js +5 -5
- package/dist/index.d.ts +12 -12
- package/dist/index.js +5 -5
- package/dist/logAdapter-WM43W3S7.js +7 -0
- package/dist/{mcpAdapter-R47GX2P3.js → mcpAdapter-WYAXUE7T.js} +2 -2
- package/dist/{policyLoader-KZL2U4M2.js → policyLoader-NUPBBRKH.js} +8 -4
- package/package.json +1 -1
- package/dist/Sentinel-5CQ6HKXS.js +0 -10
- package/dist/chunk-FMZWHT4M.js +0 -20
- package/dist/logAdapter-IB6ZDEV2.js +0 -7
|
@@ -1,5 +1,81 @@
|
|
|
1
1
|
// src/policyLoader.ts
|
|
2
2
|
import yaml from "js-yaml";
|
|
3
|
+
|
|
4
|
+
// src/defaults.ts
|
|
5
|
+
var DEFAULT_FORBIDDEN_PATTERNS = [
|
|
6
|
+
"**/.env",
|
|
7
|
+
"**/.env.*",
|
|
8
|
+
"**/.ssh/**",
|
|
9
|
+
"**/.aws/**",
|
|
10
|
+
"**/secrets/**",
|
|
11
|
+
"**/credentials/**",
|
|
12
|
+
"**/id_rsa*",
|
|
13
|
+
"**/id_dsa*",
|
|
14
|
+
"**/id_ecdsa*",
|
|
15
|
+
"**/id_ed25519*",
|
|
16
|
+
"**/*.pem",
|
|
17
|
+
"**/*.key",
|
|
18
|
+
"/etc/**",
|
|
19
|
+
// Sprint 26 FIX 1 (A) — common credential stores. DRIFT: keep in sync with
|
|
20
|
+
// STARTER_POLICY.forbid.targets in setup/initClaudeCode.ts (template↔defaults
|
|
21
|
+
// unification tracked in a separate ticket — do not refactor here).
|
|
22
|
+
"**/.netrc",
|
|
23
|
+
"**/.npmrc",
|
|
24
|
+
"**/.git-credentials",
|
|
25
|
+
"**/.pgpass",
|
|
26
|
+
"**/.zsh_history",
|
|
27
|
+
"**/.config/gh/**",
|
|
28
|
+
"**/.docker/config.json",
|
|
29
|
+
"**/.gnupg/**",
|
|
30
|
+
"**/.config/gcloud/**",
|
|
31
|
+
"**/.kube/**",
|
|
32
|
+
"**/Library/Keychains/**",
|
|
33
|
+
// Sprint 26 FIX 1 (B) — Sentinel's own state dir (current path only; the
|
|
34
|
+
// ~/.dahlia → ~/.sentinel rename is a separate re-arch).
|
|
35
|
+
"**/.dahlia/**",
|
|
36
|
+
// Sprint 26 FIX 3 — the live policy file and cc's hook-wiring settings files.
|
|
37
|
+
// Denies the agent's own tool-writes (policy rewrite / unhook vectors); reads
|
|
38
|
+
// stay allowed via DEFAULT_POLICY_READ_EXCEPTIONS below. The settings glob
|
|
39
|
+
// covers project AND user-level (~/.claude/settings*.json) in one pattern.
|
|
40
|
+
// DRIFT: keep in sync with STARTER_POLICY.forbid.targets in setup/initClaudeCode.ts.
|
|
41
|
+
"**/.sentinel.yaml",
|
|
42
|
+
"**/.claude/settings*.json"
|
|
43
|
+
];
|
|
44
|
+
var DEFAULT_POLICY_READ_EXCEPTIONS = [
|
|
45
|
+
{ target: "**/.sentinel.yaml", allowedActions: ["file_read"] },
|
|
46
|
+
{ target: "**/.claude/settings*.json", allowedActions: ["file_read"] }
|
|
47
|
+
];
|
|
48
|
+
function withPolicyReadExceptions(existing) {
|
|
49
|
+
const merged = existing ? [...existing] : [];
|
|
50
|
+
for (const exc of DEFAULT_POLICY_READ_EXCEPTIONS) {
|
|
51
|
+
if (!merged.some((e) => e.target === exc.target)) merged.push(exc);
|
|
52
|
+
}
|
|
53
|
+
return merged;
|
|
54
|
+
}
|
|
55
|
+
var DEFAULT_MEDIUM_DISPOSITION = {
|
|
56
|
+
network_request: "deny"
|
|
57
|
+
};
|
|
58
|
+
var DEFAULT_RESTRICT_AFTER = 2;
|
|
59
|
+
var DEFAULT_QUARANTINE_AFTER = 3;
|
|
60
|
+
var DEFAULT_NETWORK_DENYLIST_CIDRS = [
|
|
61
|
+
"10.0.0.0/8",
|
|
62
|
+
// RFC1918 private (Class A)
|
|
63
|
+
"172.16.0.0/12",
|
|
64
|
+
// RFC1918 private (Class B)
|
|
65
|
+
"192.168.0.0/16",
|
|
66
|
+
// RFC1918 private (Class C)
|
|
67
|
+
"127.0.0.0/8",
|
|
68
|
+
// loopback v4
|
|
69
|
+
"169.254.0.0/16",
|
|
70
|
+
// link-local v4 (cloud metadata endpoints)
|
|
71
|
+
"fe80::/10",
|
|
72
|
+
// link-local v6
|
|
73
|
+
"::1/128"
|
|
74
|
+
// loopback v6
|
|
75
|
+
];
|
|
76
|
+
var DEFAULT_DANGEROUS_SCHEMES = ["file:", "data:", "javascript:", "vbscript:"];
|
|
77
|
+
|
|
78
|
+
// src/policyLoader.ts
|
|
3
79
|
var LOCKED_ACTIONABLE_TYPES = /* @__PURE__ */ new Set([
|
|
4
80
|
"role_violation",
|
|
5
81
|
"unauthorized_target",
|
|
@@ -29,15 +105,88 @@ var VALID_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
29
105
|
function fail(message) {
|
|
30
106
|
throw new Error(`Policy validation error: ${message}`);
|
|
31
107
|
}
|
|
108
|
+
function checkSaneNumber(value, opts = {}) {
|
|
109
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
110
|
+
return `must be a finite number; got ${String(value)}`;
|
|
111
|
+
}
|
|
112
|
+
if (opts.integer && !Number.isInteger(value)) {
|
|
113
|
+
return `must be an integer; got ${value}`;
|
|
114
|
+
}
|
|
115
|
+
if (opts.min !== void 0 && value < opts.min) {
|
|
116
|
+
return `must be >= ${opts.min}; got ${value}`;
|
|
117
|
+
}
|
|
118
|
+
if (opts.max !== void 0 && value > opts.max) {
|
|
119
|
+
return `must be <= ${opts.max}; got ${value}`;
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
function requireSaneNumber(name, value, opts = {}) {
|
|
124
|
+
const violation = checkSaneNumber(value, opts);
|
|
125
|
+
if (violation) fail(`${name} ${violation}`);
|
|
126
|
+
}
|
|
127
|
+
function levenshtein(a, b) {
|
|
128
|
+
const m = a.length;
|
|
129
|
+
const n = b.length;
|
|
130
|
+
const row = Array.from({ length: n + 1 }, (_, j) => j);
|
|
131
|
+
for (let i = 1; i <= m; i++) {
|
|
132
|
+
let prev = row[0];
|
|
133
|
+
row[0] = i;
|
|
134
|
+
for (let j = 1; j <= n; j++) {
|
|
135
|
+
const tmp = row[j];
|
|
136
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
137
|
+
prev = tmp;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return row[n];
|
|
141
|
+
}
|
|
142
|
+
function suggestKey(unknown, valid) {
|
|
143
|
+
const lower = unknown.toLowerCase();
|
|
144
|
+
for (const v of valid) {
|
|
145
|
+
if (v.toLowerCase() === lower) return v;
|
|
146
|
+
}
|
|
147
|
+
for (const v of valid) {
|
|
148
|
+
const vl = v.toLowerCase();
|
|
149
|
+
if (lower.includes(vl) || vl.includes(lower)) return v;
|
|
150
|
+
}
|
|
151
|
+
let best = null;
|
|
152
|
+
let bestDist = 3;
|
|
153
|
+
for (const v of valid) {
|
|
154
|
+
const d = levenshtein(lower, v.toLowerCase());
|
|
155
|
+
if (d < bestDist) {
|
|
156
|
+
bestDist = d;
|
|
157
|
+
best = v;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return best;
|
|
161
|
+
}
|
|
162
|
+
function rejectUnknownKeys(section, obj, allowed) {
|
|
163
|
+
for (const key of Object.keys(obj)) {
|
|
164
|
+
if (!allowed.includes(key)) {
|
|
165
|
+
const suggestion = suggestKey(key, allowed);
|
|
166
|
+
fail(
|
|
167
|
+
`unknown key "${key}" in ${section}.` + (suggestion ? ` Did you mean "${suggestion}"?` : "") + ` Valid keys: ${allowed.join(", ")}`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
32
172
|
function validatePolicy(data) {
|
|
33
173
|
if (typeof data !== "object" || data === null) {
|
|
34
174
|
fail("policy must be a YAML object");
|
|
35
175
|
}
|
|
36
176
|
const doc = data;
|
|
177
|
+
rejectUnknownKeys("policy document", doc, [
|
|
178
|
+
"version",
|
|
179
|
+
"agent",
|
|
180
|
+
"policy",
|
|
181
|
+
"enforcement",
|
|
182
|
+
"alerts",
|
|
183
|
+
"repo"
|
|
184
|
+
]);
|
|
37
185
|
if (doc.version === void 0) fail("version is required");
|
|
38
186
|
if (String(doc.version) !== "1.0") fail('version must be "1.0"');
|
|
39
187
|
if (typeof doc.agent !== "object" || doc.agent === null) fail("agent section is required");
|
|
40
188
|
const agent = doc.agent;
|
|
189
|
+
rejectUnknownKeys("agent", agent, ["id", "name", "description"]);
|
|
41
190
|
if (typeof agent.id !== "string" || agent.id.length === 0) fail("agent.id is required");
|
|
42
191
|
if (typeof agent.name !== "string" || agent.name.length === 0) fail("agent.name is required");
|
|
43
192
|
if (agent.description !== void 0 && typeof agent.description !== "string") {
|
|
@@ -45,10 +194,12 @@ function validatePolicy(data) {
|
|
|
45
194
|
}
|
|
46
195
|
if (typeof doc.policy !== "object" || doc.policy === null) fail("policy section is required");
|
|
47
196
|
const policy = doc.policy;
|
|
197
|
+
rejectUnknownKeys("policy", policy, ["allow", "forbid", "exceptions", "schedule", "limits"]);
|
|
48
198
|
if (typeof policy.allow !== "object" || policy.allow === null) {
|
|
49
199
|
fail("policy.allow section is required");
|
|
50
200
|
}
|
|
51
201
|
const allow = policy.allow;
|
|
202
|
+
rejectUnknownKeys("policy.allow", allow, ["actions", "targets", "networkHosts"]);
|
|
52
203
|
if (!Array.isArray(allow.actions) || allow.actions.length === 0) {
|
|
53
204
|
fail("policy.allow.actions must be a non-empty array of strings");
|
|
54
205
|
}
|
|
@@ -84,6 +235,7 @@ function validatePolicy(data) {
|
|
|
84
235
|
fail("policy.forbid section is required");
|
|
85
236
|
}
|
|
86
237
|
const forbid = policy.forbid;
|
|
238
|
+
rejectUnknownKeys("policy.forbid", forbid, ["targets"]);
|
|
87
239
|
if (!Array.isArray(forbid.targets) || forbid.targets.length === 0) {
|
|
88
240
|
fail("policy.forbid.targets must be a non-empty array of strings");
|
|
89
241
|
}
|
|
@@ -100,6 +252,14 @@ function validatePolicy(data) {
|
|
|
100
252
|
if (typeof ex !== "object" || ex === null) {
|
|
101
253
|
fail(`${prefix}: must be an object`);
|
|
102
254
|
}
|
|
255
|
+
rejectUnknownKeys(prefix, ex, [
|
|
256
|
+
"target",
|
|
257
|
+
"allowedActions",
|
|
258
|
+
"requiresTask",
|
|
259
|
+
"requiresApproval",
|
|
260
|
+
"expiresAfter",
|
|
261
|
+
"downgradeKindTo"
|
|
262
|
+
]);
|
|
103
263
|
if (typeof ex.target !== "string" || ex.target.length === 0) {
|
|
104
264
|
fail(`${prefix}: target is required and must be a non-empty string`);
|
|
105
265
|
}
|
|
@@ -144,6 +304,7 @@ function validatePolicy(data) {
|
|
|
144
304
|
fail("policy.schedule must be an object");
|
|
145
305
|
}
|
|
146
306
|
const schedule = policy.schedule;
|
|
307
|
+
rejectUnknownKeys("policy.schedule", schedule, ["hours", "days"]);
|
|
147
308
|
if (!Array.isArray(schedule.hours) || schedule.hours.length !== 2) {
|
|
148
309
|
fail("schedule.hours must be [start, end] with values 0-23");
|
|
149
310
|
}
|
|
@@ -165,35 +326,54 @@ function validatePolicy(data) {
|
|
|
165
326
|
}
|
|
166
327
|
}
|
|
167
328
|
if (policy.limits !== void 0) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const limits = policy.limits;
|
|
172
|
-
if (limits.maxEventsPerHour !== void 0 && typeof limits.maxEventsPerHour !== "number") {
|
|
173
|
-
fail("policy.limits.maxEventsPerHour must be a number");
|
|
174
|
-
}
|
|
175
|
-
if (limits.maxSessionDuration !== void 0 && typeof limits.maxSessionDuration !== "number") {
|
|
176
|
-
fail("policy.limits.maxSessionDuration must be a number");
|
|
177
|
-
}
|
|
329
|
+
fail(
|
|
330
|
+
"policy.limits is not supported \u2014 rate/duration limiting (maxEventsPerHour, maxSessionDuration) is not currently enforced by any runtime check. Remove this section. If you need rate limiting, track it as a feature request."
|
|
331
|
+
);
|
|
178
332
|
}
|
|
179
333
|
if (doc.enforcement !== void 0) {
|
|
180
334
|
if (typeof doc.enforcement !== "object" || doc.enforcement === null) {
|
|
181
335
|
fail("enforcement must be an object");
|
|
182
336
|
}
|
|
183
337
|
const enforcement = doc.enforcement;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
338
|
+
rejectUnknownKeys("enforcement", enforcement, [
|
|
339
|
+
"restrictAfter",
|
|
340
|
+
"quarantineAfter",
|
|
341
|
+
"approvalRequired",
|
|
342
|
+
"minKind",
|
|
343
|
+
"promote",
|
|
344
|
+
"baselineMaturity",
|
|
345
|
+
"unknownTools",
|
|
346
|
+
"allowUnknownTools",
|
|
347
|
+
"onInternalError"
|
|
348
|
+
]);
|
|
349
|
+
if (enforcement.restrictAfter !== void 0) {
|
|
350
|
+
requireSaneNumber("enforcement.restrictAfter", enforcement.restrictAfter, {
|
|
351
|
+
integer: true,
|
|
352
|
+
min: 1
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
if (enforcement.quarantineAfter !== void 0) {
|
|
356
|
+
requireSaneNumber("enforcement.quarantineAfter", enforcement.quarantineAfter, {
|
|
357
|
+
integer: true,
|
|
358
|
+
min: 1
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
{
|
|
362
|
+
const effRestrict = enforcement.restrictAfter ?? DEFAULT_RESTRICT_AFTER;
|
|
363
|
+
const effQuarantine = enforcement.quarantineAfter ?? DEFAULT_QUARANTINE_AFTER;
|
|
364
|
+
if (effRestrict >= effQuarantine) {
|
|
365
|
+
fail(
|
|
366
|
+
`enforcement.restrictAfter (${effRestrict}) must be strictly less than enforcement.quarantineAfter (${effQuarantine}) \u2014 otherwise the restricted tier is unreachable and agents jump straight to quarantine. (Unset values default to restrictAfter ${DEFAULT_RESTRICT_AFTER}, quarantineAfter ${DEFAULT_QUARANTINE_AFTER}.)`
|
|
367
|
+
);
|
|
368
|
+
}
|
|
189
369
|
}
|
|
190
370
|
if (enforcement.approvalRequired !== void 0 && typeof enforcement.approvalRequired !== "boolean") {
|
|
191
371
|
fail("enforcement.approvalRequired must be a boolean");
|
|
192
372
|
}
|
|
193
373
|
if (enforcement.minKind !== void 0) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
374
|
+
fail(
|
|
375
|
+
"enforcement.minKind is not supported \u2014 enforcement gates on finding kind 'actionable' and this field was never wired. Use enforcement.promote to promote specific finding types to actionable, or remove this field."
|
|
376
|
+
);
|
|
197
377
|
}
|
|
198
378
|
if (enforcement.promote !== void 0) {
|
|
199
379
|
if (!Array.isArray(enforcement.promote)) {
|
|
@@ -220,11 +400,21 @@ function validatePolicy(data) {
|
|
|
220
400
|
fail("enforcement.allowUnknownTools must be an array of tool name strings");
|
|
221
401
|
}
|
|
222
402
|
}
|
|
403
|
+
if (enforcement.onInternalError !== void 0) {
|
|
404
|
+
if (enforcement.onInternalError !== "fail-open" && enforcement.onInternalError !== "fail-closed") {
|
|
405
|
+
fail('enforcement.onInternalError must be "fail-open" or "fail-closed"');
|
|
406
|
+
}
|
|
407
|
+
}
|
|
223
408
|
if (enforcement.baselineMaturity !== void 0) {
|
|
224
409
|
if (typeof enforcement.baselineMaturity !== "object" || enforcement.baselineMaturity === null) {
|
|
225
410
|
fail("enforcement.baselineMaturity must be an object");
|
|
226
411
|
}
|
|
227
412
|
const bm = enforcement.baselineMaturity;
|
|
413
|
+
rejectUnknownKeys("enforcement.baselineMaturity", bm, [
|
|
414
|
+
"minSessions",
|
|
415
|
+
"minDaysObserved",
|
|
416
|
+
"minCategoryDiversity"
|
|
417
|
+
]);
|
|
228
418
|
for (const key of ["minSessions", "minDaysObserved", "minCategoryDiversity"]) {
|
|
229
419
|
if (bm[key] !== void 0) {
|
|
230
420
|
if (typeof bm[key] !== "number" || !Number.isFinite(bm[key])) {
|
|
@@ -242,6 +432,13 @@ function validatePolicy(data) {
|
|
|
242
432
|
fail("alerts must be an object");
|
|
243
433
|
}
|
|
244
434
|
const alerts = doc.alerts;
|
|
435
|
+
rejectUnknownKeys("alerts", alerts, [
|
|
436
|
+
"channels",
|
|
437
|
+
"webhookUrl",
|
|
438
|
+
"filePath",
|
|
439
|
+
"minSeverity",
|
|
440
|
+
"minKind"
|
|
441
|
+
]);
|
|
245
442
|
if (!Array.isArray(alerts.channels) || alerts.channels.length === 0) {
|
|
246
443
|
fail("alerts.channels must be a non-empty array");
|
|
247
444
|
}
|
|
@@ -252,6 +449,7 @@ function validatePolicy(data) {
|
|
|
252
449
|
}
|
|
253
450
|
} else if (typeof ch === "object" && ch !== null) {
|
|
254
451
|
const chObj = ch;
|
|
452
|
+
rejectUnknownKeys("alerts.channels[]", chObj, ["type", "minKind"]);
|
|
255
453
|
if (typeof chObj.type !== "string" || !VALID_CHANNELS.has(chObj.type)) {
|
|
256
454
|
fail(
|
|
257
455
|
`alerts.channels contains invalid channel type "${chObj.type}". Valid: console, webhook, file`
|
|
@@ -288,6 +486,7 @@ function validatePolicy(data) {
|
|
|
288
486
|
fail("repo must be an object");
|
|
289
487
|
}
|
|
290
488
|
const repo = doc.repo;
|
|
489
|
+
rejectUnknownKeys("repo", repo, ["root", "scanOnStartup", "mapPath", "overlayPath"]);
|
|
291
490
|
if (repo.root !== void 0) {
|
|
292
491
|
if (typeof repo.root !== "string" || repo.root.length === 0) {
|
|
293
492
|
fail("repo.root must be a non-empty string");
|
|
@@ -368,20 +567,12 @@ function policyToRole(policy) {
|
|
|
368
567
|
activeDays: policy.policy.schedule.days
|
|
369
568
|
};
|
|
370
569
|
}
|
|
371
|
-
if (policy.policy.limits) {
|
|
372
|
-
if (policy.policy.limits.maxEventsPerHour !== void 0) {
|
|
373
|
-
role.maxEventsPerHour = policy.policy.limits.maxEventsPerHour;
|
|
374
|
-
}
|
|
375
|
-
if (policy.policy.limits.maxSessionDuration !== void 0) {
|
|
376
|
-
role.maxSessionDuration = policy.policy.limits.maxSessionDuration;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
570
|
return role;
|
|
380
571
|
}
|
|
381
572
|
function policyToConfig(policy) {
|
|
382
573
|
const config = {};
|
|
383
574
|
if (policy.enforcement) {
|
|
384
|
-
if (policy.enforcement.restrictAfter !== void 0 || policy.enforcement.quarantineAfter !== void 0 || policy.enforcement.promote !== void 0 || policy.enforcement.baselineMaturity !== void 0 || policy.enforcement.unknownTools !== void 0 || policy.enforcement.allowUnknownTools !== void 0) {
|
|
575
|
+
if (policy.enforcement.restrictAfter !== void 0 || policy.enforcement.quarantineAfter !== void 0 || policy.enforcement.promote !== void 0 || policy.enforcement.baselineMaturity !== void 0 || policy.enforcement.unknownTools !== void 0 || policy.enforcement.allowUnknownTools !== void 0 || policy.enforcement.onInternalError !== void 0) {
|
|
385
576
|
config.enforcement = {};
|
|
386
577
|
if (policy.enforcement.restrictAfter !== void 0) {
|
|
387
578
|
config.enforcement.restrictAfter = policy.enforcement.restrictAfter;
|
|
@@ -401,6 +592,9 @@ function policyToConfig(policy) {
|
|
|
401
592
|
if (policy.enforcement.allowUnknownTools !== void 0) {
|
|
402
593
|
config.enforcement.allowUnknownTools = policy.enforcement.allowUnknownTools;
|
|
403
594
|
}
|
|
595
|
+
if (policy.enforcement.onInternalError !== void 0) {
|
|
596
|
+
config.enforcement.onInternalError = policy.enforcement.onInternalError;
|
|
597
|
+
}
|
|
404
598
|
}
|
|
405
599
|
}
|
|
406
600
|
if (policy.alerts) {
|
|
@@ -435,10 +629,19 @@ function policyToConfig(policy) {
|
|
|
435
629
|
}
|
|
436
630
|
|
|
437
631
|
export {
|
|
632
|
+
DEFAULT_FORBIDDEN_PATTERNS,
|
|
633
|
+
withPolicyReadExceptions,
|
|
634
|
+
DEFAULT_MEDIUM_DISPOSITION,
|
|
635
|
+
DEFAULT_RESTRICT_AFTER,
|
|
636
|
+
DEFAULT_QUARANTINE_AFTER,
|
|
637
|
+
DEFAULT_NETWORK_DENYLIST_CIDRS,
|
|
638
|
+
DEFAULT_DANGEROUS_SCHEMES,
|
|
438
639
|
LOCKED_ACTIONABLE_TYPES,
|
|
640
|
+
checkSaneNumber,
|
|
641
|
+
suggestKey,
|
|
439
642
|
loadPolicy,
|
|
440
643
|
loadPolicyFromString,
|
|
441
644
|
policyToRole,
|
|
442
645
|
policyToConfig
|
|
443
646
|
};
|
|
444
|
-
//# sourceMappingURL=chunk-
|
|
647
|
+
//# sourceMappingURL=chunk-SKE74CYZ.js.map
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
} from "./chunk-LATQNIRW.js";
|
|
5
5
|
import {
|
|
6
6
|
discoverPolicy
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-B6S2PBS4.js";
|
|
8
8
|
import {
|
|
9
9
|
FORBIDDEN_BASENAMES
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-M5EEVMLU.js";
|
|
11
11
|
import {
|
|
12
12
|
loadPolicy,
|
|
13
13
|
loadPolicyFromString
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-SKE74CYZ.js";
|
|
15
15
|
|
|
16
16
|
// src/setup/initClaudeCode.ts
|
|
17
17
|
import { access, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
@@ -94,19 +94,45 @@ const FLOOR_HIGH = ["Bash", "Write", "Edit", "WebFetch", "NotebookEdit", "Task",
|
|
|
94
94
|
// The marker default is [] \u2014 an un-substituted script stays strictest.
|
|
95
95
|
const ALLOW_UNKNOWN_TOOLS = /* __ALLOW_UNKNOWN_TOOLS__ */ [];
|
|
96
96
|
|
|
97
|
-
// Tier config uses flat format: { high, low, mcpDefault
|
|
97
|
+
// Tier config uses flat format: { high, low, mcpDefault } (plan v3.1 spec'd nested but simplified during 5a)
|
|
98
|
+
// Validated field-by-field against safe defaults (enforcement-config sanity class):
|
|
99
|
+
// the hook must never block the cc session on a config error, so invalid values
|
|
100
|
+
// fall back to the fail-closed default AND are logged \u2014 never silently honored.
|
|
101
|
+
// In particular mcpDefault was compared with === "high", so any typo silently
|
|
102
|
+
// demoted ALL MCP tools to low tier gateway-down (allow-unlogged, the F-8 hole).
|
|
98
103
|
function loadTiers() {
|
|
104
|
+
const DEFAULT_TIERS = {
|
|
105
|
+
high: ["Bash", "Write", "Edit", "WebFetch", "NotebookEdit", "Task", "Skill"],
|
|
106
|
+
low: ["Read", "Glob", "Grep", "WebSearch"],
|
|
107
|
+
mcpDefault: "high",
|
|
108
|
+
};
|
|
109
|
+
let parsed;
|
|
99
110
|
try {
|
|
100
|
-
|
|
111
|
+
parsed = JSON.parse(readFileSync(TIERS_PATH, "utf-8"));
|
|
101
112
|
} catch {
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
high: ["Bash", "Write", "Edit", "WebFetch", "NotebookEdit", "Task", "Skill"],
|
|
105
|
-
low: ["Read", "Glob", "Grep", "WebSearch"],
|
|
106
|
-
mcpDefault: "high",
|
|
107
|
-
unknownDefault: "high",
|
|
108
|
-
};
|
|
113
|
+
return DEFAULT_TIERS; // missing/unreadable config \u2014 safe defaults
|
|
109
114
|
}
|
|
115
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
116
|
+
logFallback({ event: "tiers-config-invalid", reason: "top level must be an object", treatedAs: "defaults" });
|
|
117
|
+
return DEFAULT_TIERS;
|
|
118
|
+
}
|
|
119
|
+
const tiers = { ...DEFAULT_TIERS };
|
|
120
|
+
if (Array.isArray(parsed.high)) tiers.high = parsed.high.filter((t) => typeof t === "string");
|
|
121
|
+
else if (parsed.high !== undefined) logFallback({ event: "tiers-config-invalid", field: "high", reason: "must be an array of tool names", treatedAs: "defaults" });
|
|
122
|
+
if (Array.isArray(parsed.low)) tiers.low = parsed.low.filter((t) => typeof t === "string");
|
|
123
|
+
else if (parsed.low !== undefined) logFallback({ event: "tiers-config-invalid", field: "low", reason: "must be an array of tool names", treatedAs: "defaults" });
|
|
124
|
+
if (parsed.mcpDefault === "high" || parsed.mcpDefault === "low") tiers.mcpDefault = parsed.mcpDefault;
|
|
125
|
+
else if (parsed.mcpDefault !== undefined) logFallback({ event: "tiers-config-invalid", field: "mcpDefault", value: String(parsed.mcpDefault), reason: 'must be "high" or "low"', treatedAs: "high" });
|
|
126
|
+
// unknownDefault is NOT supported: unknown tools are ALWAYS high-tier
|
|
127
|
+
// gateway-down (see isHighSensitivity). It used to be written by init and
|
|
128
|
+
// read by nothing \u2014 a silent no-op knob. Warn so the operator knows.
|
|
129
|
+
if (parsed.unknownDefault !== undefined) logFallback({ event: "tiers-config-ignored", field: "unknownDefault", reason: "unsupported \u2014 unknown tools are always high-tier gateway-down" });
|
|
130
|
+
for (const key of Object.keys(parsed)) {
|
|
131
|
+
if (!["high", "low", "mcpDefault", "unknownDefault"].includes(key)) {
|
|
132
|
+
logFallback({ event: "tiers-config-unknown-key", key: key });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return tiers;
|
|
110
136
|
}
|
|
111
137
|
|
|
112
138
|
function isHighSensitivity(toolName, tiers) {
|
|
@@ -460,8 +486,7 @@ enforcement:
|
|
|
460
486
|
var FAIL_CLOSED_TIERS = {
|
|
461
487
|
high: ["Bash", "Write", "Edit", "WebFetch", "NotebookEdit", "Task", "Skill"],
|
|
462
488
|
low: ["Read", "Glob", "Grep", "WebSearch"],
|
|
463
|
-
mcpDefault: "high"
|
|
464
|
-
unknownDefault: "high"
|
|
489
|
+
mcpDefault: "high"
|
|
465
490
|
};
|
|
466
491
|
async function runInitClaudeCode(options) {
|
|
467
492
|
const force = options.force ?? false;
|
|
@@ -712,16 +737,30 @@ async function runSessionStart(options) {
|
|
|
712
737
|
}
|
|
713
738
|
try {
|
|
714
739
|
if (lock.pid) await terminateDaemon(lock.pid);
|
|
715
|
-
const
|
|
740
|
+
const pid = spawnDaemon(gatewayEntry, policyPath, port, home);
|
|
716
741
|
await waitForGatewayReady(port);
|
|
717
|
-
return { action: "relaunched", pid
|
|
742
|
+
return { action: "relaunched", pid, policyPath, relaunchReason: reason };
|
|
718
743
|
} finally {
|
|
719
744
|
releaseRelaunchLock(home);
|
|
720
745
|
}
|
|
721
746
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
747
|
+
if (!acquireRelaunchLock(home)) {
|
|
748
|
+
await waitForGatewayReady(port);
|
|
749
|
+
const peerPid = acquireGatewayLock(home).pid ?? void 0;
|
|
750
|
+
return { action: "reused", pid: peerPid, policyPath, relaunchReason: null };
|
|
751
|
+
}
|
|
752
|
+
try {
|
|
753
|
+
const relock = acquireGatewayLock(home);
|
|
754
|
+
if (relock.reused) {
|
|
755
|
+
await waitForGatewayReady(port);
|
|
756
|
+
return { action: "reused", pid: relock.pid, policyPath, relaunchReason: null };
|
|
757
|
+
}
|
|
758
|
+
const pid = spawnDaemon(gatewayEntry, policyPath, port, home);
|
|
759
|
+
await waitForGatewayReady(port);
|
|
760
|
+
return { action: "spawned", pid, policyPath, relaunchReason: null };
|
|
761
|
+
} finally {
|
|
762
|
+
releaseRelaunchLock(home);
|
|
763
|
+
}
|
|
725
764
|
}
|
|
726
765
|
|
|
727
766
|
export {
|
|
@@ -729,4 +768,4 @@ export {
|
|
|
729
768
|
computeBuildId,
|
|
730
769
|
runSessionStart
|
|
731
770
|
};
|
|
732
|
-
//# sourceMappingURL=chunk-
|
|
771
|
+
//# sourceMappingURL=chunk-UVNRPML4.js.map
|