@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
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/setup/policyDiscovery.ts
|
|
2
|
+
import { existsSync, statSync } from "fs";
|
|
3
|
+
import { resolve, join, dirname } from "path";
|
|
4
|
+
function isTrustedPolicyStat(stats, processUid) {
|
|
5
|
+
if ((stats.mode & 2) !== 0) {
|
|
6
|
+
return { trusted: false, reason: "file is world-writable" };
|
|
7
|
+
}
|
|
8
|
+
if (processUid !== void 0 && stats.uid !== processUid) {
|
|
9
|
+
return {
|
|
10
|
+
trusted: false,
|
|
11
|
+
reason: `file is owned by uid ${stats.uid}, not the current user (uid ${processUid})`
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return { trusted: true, reason: null };
|
|
15
|
+
}
|
|
16
|
+
function isTrustedOnDisk(candidate) {
|
|
17
|
+
if (process.platform === "win32") return true;
|
|
18
|
+
let stats;
|
|
19
|
+
try {
|
|
20
|
+
stats = statSync(candidate);
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const processUid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
25
|
+
const verdict = isTrustedPolicyStat({ uid: stats.uid, mode: stats.mode }, processUid);
|
|
26
|
+
if (!verdict.trusted) {
|
|
27
|
+
console.warn(`[Sentinel] Ignoring untrusted policy file ${candidate}: ${verdict.reason}`);
|
|
28
|
+
}
|
|
29
|
+
return verdict.trusted;
|
|
30
|
+
}
|
|
31
|
+
function discoverPolicy(startDir, home) {
|
|
32
|
+
let dir = resolve(startDir);
|
|
33
|
+
const homeAbs = resolve(home);
|
|
34
|
+
while (true) {
|
|
35
|
+
const candidate = join(dir, ".sentinel.yaml");
|
|
36
|
+
if (existsSync(candidate) && isTrustedOnDisk(candidate)) return candidate;
|
|
37
|
+
if (dir === homeAbs) return null;
|
|
38
|
+
const parent = dirname(dir);
|
|
39
|
+
if (parent === dir) return null;
|
|
40
|
+
dir = parent;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
discoverPolicy
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=chunk-B6S2PBS4.js.map
|
|
@@ -1,15 +1,62 @@
|
|
|
1
|
-
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_DANGEROUS_SCHEMES,
|
|
3
|
+
DEFAULT_FORBIDDEN_PATTERNS,
|
|
4
|
+
DEFAULT_NETWORK_DENYLIST_CIDRS,
|
|
5
|
+
checkSaneNumber,
|
|
6
|
+
suggestKey
|
|
7
|
+
} from "./chunk-SKE74CYZ.js";
|
|
8
|
+
|
|
9
|
+
// src/repoSensitivityOverlay.ts
|
|
2
10
|
import * as fs from "fs/promises";
|
|
3
11
|
import * as path from "path";
|
|
4
12
|
import * as os from "os";
|
|
5
|
-
var
|
|
6
|
-
|
|
7
|
-
|
|
13
|
+
var VALID_DECISION_TYPES = /* @__PURE__ */ new Set(["reject", "accept", "override"]);
|
|
14
|
+
var OVERLAY_TOP_KEYS = ["version", "repoRoot", "decisions", "updatedAt"];
|
|
15
|
+
var DECISION_KEYS = [
|
|
16
|
+
"path",
|
|
17
|
+
"decision",
|
|
18
|
+
"reason",
|
|
19
|
+
"sensitivity",
|
|
20
|
+
"category",
|
|
21
|
+
"decidedAt"
|
|
22
|
+
];
|
|
23
|
+
function sanitizeSensitivityScore(fieldLabel, value, throwError) {
|
|
24
|
+
const violation = checkSaneNumber(value, {});
|
|
25
|
+
if (violation) {
|
|
26
|
+
throwError(`${fieldLabel} ${violation} \u2014 sensitivity must be a finite number in [0, 1]`);
|
|
27
|
+
}
|
|
28
|
+
const n = value;
|
|
29
|
+
if (n < 0 || n > 1) {
|
|
30
|
+
const clamped = Math.min(1, Math.max(0, n));
|
|
31
|
+
console.warn(
|
|
32
|
+
`[Sentinel] ${fieldLabel} is ${n}, outside [0, 1] \u2014 clamped to ${clamped}. Fix the file to silence this warning.`
|
|
33
|
+
);
|
|
34
|
+
return clamped;
|
|
35
|
+
}
|
|
36
|
+
return n;
|
|
37
|
+
}
|
|
38
|
+
function rejectUnknownJsonKeys(section, obj, allowed, throwError) {
|
|
39
|
+
for (const key of Object.keys(obj)) {
|
|
40
|
+
if (!allowed.includes(key)) {
|
|
41
|
+
const suggestion = suggestKey(key, allowed);
|
|
42
|
+
throwError(
|
|
43
|
+
`unknown key "${key}" in ${section}.` + (suggestion ? ` Did you mean "${suggestion}"?` : "") + ` Valid keys: ${allowed.join(", ")}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
var DEFAULT_OVERLAY_PATH = path.join(
|
|
49
|
+
os.homedir(),
|
|
50
|
+
".dahlia",
|
|
51
|
+
"repo-sensitivity.review.json"
|
|
52
|
+
);
|
|
53
|
+
async function saveOverlay(overlay, customPath) {
|
|
54
|
+
const target = customPath ?? DEFAULT_OVERLAY_PATH;
|
|
8
55
|
const parent = path.dirname(target);
|
|
9
56
|
const tempPath = target + ".tmp";
|
|
10
57
|
try {
|
|
11
58
|
await fs.mkdir(parent, { recursive: true });
|
|
12
|
-
await fs.writeFile(tempPath, JSON.stringify(
|
|
59
|
+
await fs.writeFile(tempPath, JSON.stringify(overlay, null, 2), "utf-8");
|
|
13
60
|
await fs.rename(tempPath, target);
|
|
14
61
|
} catch (err) {
|
|
15
62
|
try {
|
|
@@ -17,13 +64,16 @@ async function saveMap(map, customPath) {
|
|
|
17
64
|
} catch {
|
|
18
65
|
}
|
|
19
66
|
const message = err instanceof Error ? err.message : String(err);
|
|
20
|
-
throw new Error(`
|
|
67
|
+
throw new Error(`SensitivityOverlay.saveOverlay: failed to save to ${target}: ${message}`, {
|
|
21
68
|
cause: err
|
|
22
69
|
});
|
|
23
70
|
}
|
|
24
71
|
}
|
|
25
|
-
async function
|
|
26
|
-
const target = customPath ??
|
|
72
|
+
async function loadOverlay(customPath) {
|
|
73
|
+
const target = customPath ?? DEFAULT_OVERLAY_PATH;
|
|
74
|
+
const failLoad = (message) => {
|
|
75
|
+
throw new Error(`SensitivityOverlay.loadOverlay: file at ${target} is invalid: ${message}`);
|
|
76
|
+
};
|
|
27
77
|
let raw;
|
|
28
78
|
try {
|
|
29
79
|
raw = await fs.readFile(target, "utf-8");
|
|
@@ -38,21 +88,56 @@ async function loadMap(customPath) {
|
|
|
38
88
|
parsed = JSON.parse(raw);
|
|
39
89
|
} catch (err) {
|
|
40
90
|
const message = err instanceof Error ? err.message : String(err);
|
|
41
|
-
throw new Error(`
|
|
91
|
+
throw new Error(`SensitivityOverlay.loadOverlay: file at ${target} is malformed: ${message}`, {
|
|
42
92
|
cause: err
|
|
43
93
|
});
|
|
44
94
|
}
|
|
45
|
-
|
|
95
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
96
|
+
failLoad("top level must be a JSON object");
|
|
97
|
+
}
|
|
98
|
+
rejectUnknownJsonKeys("overlay", parsed, OVERLAY_TOP_KEYS, failLoad);
|
|
99
|
+
const version = typeof parsed.version === "string" ? parsed.version : "1.0";
|
|
46
100
|
const repoRoot = typeof parsed.repoRoot === "string" ? parsed.repoRoot : "";
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
101
|
+
const rawDecisions = Array.isArray(parsed.decisions) ? parsed.decisions : [];
|
|
102
|
+
const updatedAt = typeof parsed.updatedAt === "string" ? parsed.updatedAt : "unknown";
|
|
103
|
+
const decisions = rawDecisions.map((d, i) => {
|
|
104
|
+
const prefix = `decisions[${i}]`;
|
|
105
|
+
if (typeof d !== "object" || d === null || Array.isArray(d)) {
|
|
106
|
+
failLoad(`${prefix} must be an object`);
|
|
107
|
+
}
|
|
108
|
+
const entry = d;
|
|
109
|
+
rejectUnknownJsonKeys(prefix, entry, DECISION_KEYS, failLoad);
|
|
110
|
+
if (typeof entry.path !== "string" || entry.path.length === 0) {
|
|
111
|
+
failLoad(`${prefix}.path must be a non-empty string`);
|
|
112
|
+
}
|
|
113
|
+
if (typeof entry.decision !== "string" || !VALID_DECISION_TYPES.has(entry.decision)) {
|
|
114
|
+
failLoad(
|
|
115
|
+
`${prefix}.decision is "${String(entry.decision)}" \u2014 must be one of: reject, accept, override`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const result = {
|
|
119
|
+
path: entry.path,
|
|
120
|
+
decision: entry.decision,
|
|
121
|
+
decidedAt: typeof entry.decidedAt === "string" ? entry.decidedAt : "unknown"
|
|
122
|
+
};
|
|
123
|
+
if (entry.reason !== void 0) result.reason = String(entry.reason);
|
|
124
|
+
if (entry.category !== void 0) result.category = String(entry.category);
|
|
125
|
+
if (entry.sensitivity !== void 0) {
|
|
126
|
+
result.sensitivity = sanitizeSensitivityScore(
|
|
127
|
+
`${prefix}.sensitivity`,
|
|
128
|
+
entry.sensitivity,
|
|
129
|
+
failLoad
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
});
|
|
134
|
+
return { version, repoRoot, decisions, updatedAt };
|
|
50
135
|
}
|
|
51
|
-
function
|
|
136
|
+
function lookupOverlayDecision(overlay, target, repoRoot) {
|
|
52
137
|
if (!target || typeof target !== "string" || target.trim() === "") {
|
|
53
138
|
return null;
|
|
54
139
|
}
|
|
55
|
-
const lookupRoot = repoRoot ??
|
|
140
|
+
const lookupRoot = repoRoot ?? overlay.repoRoot;
|
|
56
141
|
let canonical;
|
|
57
142
|
if (path.isAbsolute(target)) {
|
|
58
143
|
if (!lookupRoot) return null;
|
|
@@ -66,48 +151,76 @@ function lookupEntry(map, target, repoRoot) {
|
|
|
66
151
|
canonical = canonical.slice(2);
|
|
67
152
|
}
|
|
68
153
|
canonical = canonical.replace(/\\/g, "/");
|
|
69
|
-
for (const
|
|
70
|
-
if (
|
|
154
|
+
for (const decision of overlay.decisions) {
|
|
155
|
+
if (decision.path === canonical) return decision;
|
|
71
156
|
}
|
|
72
157
|
return null;
|
|
73
158
|
}
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
159
|
+
function applyOverlay(overlay, decisionPath, decisionType, options) {
|
|
160
|
+
let canonical = decisionPath;
|
|
161
|
+
if (canonical.startsWith("./")) {
|
|
162
|
+
canonical = canonical.slice(2);
|
|
163
|
+
}
|
|
164
|
+
canonical = canonical.replace(/\\/g, "/");
|
|
165
|
+
const newDecision = {
|
|
166
|
+
path: canonical,
|
|
167
|
+
decision: decisionType,
|
|
168
|
+
decidedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
80
169
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
170
|
+
if (options?.reason !== void 0) newDecision.reason = options.reason;
|
|
171
|
+
if (options?.sensitivity !== void 0) newDecision.sensitivity = options.sensitivity;
|
|
172
|
+
if (options?.category !== void 0) newDecision.category = options.category;
|
|
173
|
+
const existing = overlay.decisions.findIndex((d) => d.path === canonical);
|
|
174
|
+
const decisions = existing >= 0 ? [
|
|
175
|
+
...overlay.decisions.slice(0, existing),
|
|
176
|
+
newDecision,
|
|
177
|
+
...overlay.decisions.slice(existing + 1)
|
|
178
|
+
] : [...overlay.decisions, newDecision];
|
|
179
|
+
return {
|
|
180
|
+
...overlay,
|
|
181
|
+
decisions,
|
|
182
|
+
updatedAt: newDecision.decidedAt
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function removeOverlayDecision(overlay, decisionPath) {
|
|
186
|
+
let canonical = decisionPath;
|
|
187
|
+
if (canonical.startsWith("./")) {
|
|
188
|
+
canonical = canonical.slice(2);
|
|
189
|
+
}
|
|
190
|
+
canonical = canonical.replace(/\\/g, "/");
|
|
191
|
+
const filtered = overlay.decisions.filter((d) => d.path !== canonical);
|
|
192
|
+
if (filtered.length === overlay.decisions.length) {
|
|
193
|
+
return overlay;
|
|
86
194
|
}
|
|
87
195
|
return {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
196
|
+
...overlay,
|
|
197
|
+
decisions: filtered,
|
|
198
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function createEmptyOverlay(repoRoot) {
|
|
202
|
+
return {
|
|
203
|
+
version: "1.0",
|
|
204
|
+
repoRoot,
|
|
205
|
+
decisions: [],
|
|
206
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
92
207
|
};
|
|
93
208
|
}
|
|
94
209
|
|
|
95
|
-
// src/
|
|
210
|
+
// src/repoSensitivityMap.ts
|
|
96
211
|
import * as fs2 from "fs/promises";
|
|
97
212
|
import * as path2 from "path";
|
|
98
213
|
import * as os2 from "os";
|
|
99
|
-
var
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
async function saveOverlay(overlay, customPath) {
|
|
105
|
-
const target = customPath ?? DEFAULT_OVERLAY_PATH;
|
|
214
|
+
var MAP_TOP_KEYS = ["scannedAt", "repoRoot", "entries", "summary"];
|
|
215
|
+
var ENTRY_KEYS = ["path", "sensitivity", "category", "confidence", "matchedRules"];
|
|
216
|
+
var DEFAULT_MAP_PATH = path2.join(os2.homedir(), ".dahlia", "repo-sensitivity.json");
|
|
217
|
+
async function saveMap(map, customPath) {
|
|
218
|
+
const target = customPath ?? DEFAULT_MAP_PATH;
|
|
106
219
|
const parent = path2.dirname(target);
|
|
107
220
|
const tempPath = target + ".tmp";
|
|
108
221
|
try {
|
|
109
222
|
await fs2.mkdir(parent, { recursive: true });
|
|
110
|
-
await fs2.writeFile(tempPath, JSON.stringify(
|
|
223
|
+
await fs2.writeFile(tempPath, JSON.stringify(map, null, 2), "utf-8");
|
|
111
224
|
await fs2.rename(tempPath, target);
|
|
112
225
|
} catch (err) {
|
|
113
226
|
try {
|
|
@@ -115,13 +228,16 @@ async function saveOverlay(overlay, customPath) {
|
|
|
115
228
|
} catch {
|
|
116
229
|
}
|
|
117
230
|
const message = err instanceof Error ? err.message : String(err);
|
|
118
|
-
throw new Error(`
|
|
231
|
+
throw new Error(`RepoSensitivityMap.saveMap: failed to save to ${target}: ${message}`, {
|
|
119
232
|
cause: err
|
|
120
233
|
});
|
|
121
234
|
}
|
|
122
235
|
}
|
|
123
|
-
async function
|
|
124
|
-
const target = customPath ??
|
|
236
|
+
async function loadMap(customPath) {
|
|
237
|
+
const target = customPath ?? DEFAULT_MAP_PATH;
|
|
238
|
+
const failLoad = (message) => {
|
|
239
|
+
throw new Error(`RepoSensitivityMap.loadMap: file at ${target} is invalid: ${message}`);
|
|
240
|
+
};
|
|
125
241
|
let raw;
|
|
126
242
|
try {
|
|
127
243
|
raw = await fs2.readFile(target, "utf-8");
|
|
@@ -136,21 +252,48 @@ async function loadOverlay(customPath) {
|
|
|
136
252
|
parsed = JSON.parse(raw);
|
|
137
253
|
} catch (err) {
|
|
138
254
|
const message = err instanceof Error ? err.message : String(err);
|
|
139
|
-
throw new Error(`
|
|
255
|
+
throw new Error(`RepoSensitivityMap.loadMap: file at ${target} is malformed: ${message}`, {
|
|
140
256
|
cause: err
|
|
141
257
|
});
|
|
142
258
|
}
|
|
143
|
-
|
|
259
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
260
|
+
failLoad("top level must be a JSON object");
|
|
261
|
+
}
|
|
262
|
+
rejectUnknownJsonKeys("map", parsed, MAP_TOP_KEYS, failLoad);
|
|
263
|
+
const scannedAt = typeof parsed.scannedAt === "string" ? parsed.scannedAt : "unknown";
|
|
144
264
|
const repoRoot = typeof parsed.repoRoot === "string" ? parsed.repoRoot : "";
|
|
145
|
-
const
|
|
146
|
-
const
|
|
147
|
-
|
|
265
|
+
const rawEntries = Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
266
|
+
const entries = rawEntries.map((e, i) => {
|
|
267
|
+
const prefix = `entries[${i}]`;
|
|
268
|
+
if (typeof e !== "object" || e === null || Array.isArray(e)) {
|
|
269
|
+
failLoad(`${prefix} must be an object`);
|
|
270
|
+
}
|
|
271
|
+
const entry = e;
|
|
272
|
+
rejectUnknownJsonKeys(prefix, entry, ENTRY_KEYS, failLoad);
|
|
273
|
+
if (typeof entry.path !== "string" || entry.path.length === 0) {
|
|
274
|
+
failLoad(`${prefix}.path must be a non-empty string`);
|
|
275
|
+
}
|
|
276
|
+
const sensitivity = sanitizeSensitivityScore(
|
|
277
|
+
`${prefix}.sensitivity`,
|
|
278
|
+
entry.sensitivity,
|
|
279
|
+
failLoad
|
|
280
|
+
);
|
|
281
|
+
return {
|
|
282
|
+
path: entry.path,
|
|
283
|
+
sensitivity,
|
|
284
|
+
category: typeof entry.category === "string" ? entry.category : "general",
|
|
285
|
+
confidence: entry.confidence === "low" || entry.confidence === "medium" || entry.confidence === "high" ? entry.confidence : "low",
|
|
286
|
+
matchedRules: Array.isArray(entry.matchedRules) ? entry.matchedRules.map(String) : []
|
|
287
|
+
};
|
|
288
|
+
});
|
|
289
|
+
const summary = parsed.summary && typeof parsed.summary === "object" && !Array.isArray(parsed.summary) ? parsed.summary : computeSummaryFromEntries(entries);
|
|
290
|
+
return { scannedAt, repoRoot, entries, summary };
|
|
148
291
|
}
|
|
149
|
-
function
|
|
292
|
+
function lookupEntry(map, target, repoRoot) {
|
|
150
293
|
if (!target || typeof target !== "string" || target.trim() === "") {
|
|
151
294
|
return null;
|
|
152
295
|
}
|
|
153
|
-
const lookupRoot = repoRoot ??
|
|
296
|
+
const lookupRoot = repoRoot ?? map.repoRoot;
|
|
154
297
|
let canonical;
|
|
155
298
|
if (path2.isAbsolute(target)) {
|
|
156
299
|
if (!lookupRoot) return null;
|
|
@@ -164,134 +307,32 @@ function lookupOverlayDecision(overlay, target, repoRoot) {
|
|
|
164
307
|
canonical = canonical.slice(2);
|
|
165
308
|
}
|
|
166
309
|
canonical = canonical.replace(/\\/g, "/");
|
|
167
|
-
for (const
|
|
168
|
-
if (
|
|
310
|
+
for (const entry of map.entries) {
|
|
311
|
+
if (entry.path === canonical) return entry;
|
|
169
312
|
}
|
|
170
313
|
return null;
|
|
171
314
|
}
|
|
172
|
-
function
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const newDecision = {
|
|
179
|
-
path: canonical,
|
|
180
|
-
decision: decisionType,
|
|
181
|
-
decidedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
182
|
-
};
|
|
183
|
-
if (options?.reason !== void 0) newDecision.reason = options.reason;
|
|
184
|
-
if (options?.sensitivity !== void 0) newDecision.sensitivity = options.sensitivity;
|
|
185
|
-
if (options?.category !== void 0) newDecision.category = options.category;
|
|
186
|
-
const existing = overlay.decisions.findIndex((d) => d.path === canonical);
|
|
187
|
-
const decisions = existing >= 0 ? [
|
|
188
|
-
...overlay.decisions.slice(0, existing),
|
|
189
|
-
newDecision,
|
|
190
|
-
...overlay.decisions.slice(existing + 1)
|
|
191
|
-
] : [...overlay.decisions, newDecision];
|
|
192
|
-
return {
|
|
193
|
-
...overlay,
|
|
194
|
-
decisions,
|
|
195
|
-
updatedAt: newDecision.decidedAt
|
|
315
|
+
function computeSummaryFromEntries(entries) {
|
|
316
|
+
const byCategory = {};
|
|
317
|
+
const byConfidence = {
|
|
318
|
+
low: 0,
|
|
319
|
+
medium: 0,
|
|
320
|
+
high: 0
|
|
196
321
|
};
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
canonical = canonical.replace(/\\/g, "/");
|
|
204
|
-
const filtered = overlay.decisions.filter((d) => d.path !== canonical);
|
|
205
|
-
if (filtered.length === overlay.decisions.length) {
|
|
206
|
-
return overlay;
|
|
322
|
+
for (const entry of entries) {
|
|
323
|
+
byCategory[entry.category] = (byCategory[entry.category] ?? 0) + 1;
|
|
324
|
+
if (entry.confidence === "low" || entry.confidence === "medium" || entry.confidence === "high") {
|
|
325
|
+
byConfidence[entry.confidence]++;
|
|
326
|
+
}
|
|
207
327
|
}
|
|
208
328
|
return {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
function createEmptyOverlay(repoRoot) {
|
|
215
|
-
return {
|
|
216
|
-
version: "1.0",
|
|
217
|
-
repoRoot,
|
|
218
|
-
decisions: [],
|
|
219
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
329
|
+
totalFiles: entries.length,
|
|
330
|
+
sensitiveFiles: entries.length,
|
|
331
|
+
byCategory,
|
|
332
|
+
byConfidence
|
|
220
333
|
};
|
|
221
334
|
}
|
|
222
335
|
|
|
223
|
-
// src/defaults.ts
|
|
224
|
-
var DEFAULT_FORBIDDEN_PATTERNS = [
|
|
225
|
-
"**/.env",
|
|
226
|
-
"**/.env.*",
|
|
227
|
-
"**/.ssh/**",
|
|
228
|
-
"**/.aws/**",
|
|
229
|
-
"**/secrets/**",
|
|
230
|
-
"**/credentials/**",
|
|
231
|
-
"**/id_rsa*",
|
|
232
|
-
"**/id_dsa*",
|
|
233
|
-
"**/id_ecdsa*",
|
|
234
|
-
"**/id_ed25519*",
|
|
235
|
-
"**/*.pem",
|
|
236
|
-
"**/*.key",
|
|
237
|
-
"/etc/**",
|
|
238
|
-
// Sprint 26 FIX 1 (A) — common credential stores. DRIFT: keep in sync with
|
|
239
|
-
// STARTER_POLICY.forbid.targets in setup/initClaudeCode.ts (template↔defaults
|
|
240
|
-
// unification tracked in a separate ticket — do not refactor here).
|
|
241
|
-
"**/.netrc",
|
|
242
|
-
"**/.npmrc",
|
|
243
|
-
"**/.git-credentials",
|
|
244
|
-
"**/.pgpass",
|
|
245
|
-
"**/.zsh_history",
|
|
246
|
-
"**/.config/gh/**",
|
|
247
|
-
"**/.docker/config.json",
|
|
248
|
-
"**/.gnupg/**",
|
|
249
|
-
"**/.config/gcloud/**",
|
|
250
|
-
"**/.kube/**",
|
|
251
|
-
"**/Library/Keychains/**",
|
|
252
|
-
// Sprint 26 FIX 1 (B) — Sentinel's own state dir (current path only; the
|
|
253
|
-
// ~/.dahlia → ~/.sentinel rename is a separate re-arch).
|
|
254
|
-
"**/.dahlia/**",
|
|
255
|
-
// Sprint 26 FIX 3 — the live policy file and cc's hook-wiring settings files.
|
|
256
|
-
// Denies the agent's own tool-writes (policy rewrite / unhook vectors); reads
|
|
257
|
-
// stay allowed via DEFAULT_POLICY_READ_EXCEPTIONS below. The settings glob
|
|
258
|
-
// covers project AND user-level (~/.claude/settings*.json) in one pattern.
|
|
259
|
-
// DRIFT: keep in sync with STARTER_POLICY.forbid.targets in setup/initClaudeCode.ts.
|
|
260
|
-
"**/.sentinel.yaml",
|
|
261
|
-
"**/.claude/settings*.json"
|
|
262
|
-
];
|
|
263
|
-
var DEFAULT_POLICY_READ_EXCEPTIONS = [
|
|
264
|
-
{ target: "**/.sentinel.yaml", allowedActions: ["file_read"] },
|
|
265
|
-
{ target: "**/.claude/settings*.json", allowedActions: ["file_read"] }
|
|
266
|
-
];
|
|
267
|
-
function withPolicyReadExceptions(existing) {
|
|
268
|
-
const merged = existing ? [...existing] : [];
|
|
269
|
-
for (const exc of DEFAULT_POLICY_READ_EXCEPTIONS) {
|
|
270
|
-
if (!merged.some((e) => e.target === exc.target)) merged.push(exc);
|
|
271
|
-
}
|
|
272
|
-
return merged;
|
|
273
|
-
}
|
|
274
|
-
var DEFAULT_MEDIUM_DISPOSITION = {
|
|
275
|
-
network_request: "deny"
|
|
276
|
-
};
|
|
277
|
-
var DEFAULT_NETWORK_DENYLIST_CIDRS = [
|
|
278
|
-
"10.0.0.0/8",
|
|
279
|
-
// RFC1918 private (Class A)
|
|
280
|
-
"172.16.0.0/12",
|
|
281
|
-
// RFC1918 private (Class B)
|
|
282
|
-
"192.168.0.0/16",
|
|
283
|
-
// RFC1918 private (Class C)
|
|
284
|
-
"127.0.0.0/8",
|
|
285
|
-
// loopback v4
|
|
286
|
-
"169.254.0.0/16",
|
|
287
|
-
// link-local v4 (cloud metadata endpoints)
|
|
288
|
-
"fe80::/10",
|
|
289
|
-
// link-local v6
|
|
290
|
-
"::1/128"
|
|
291
|
-
// loopback v6
|
|
292
|
-
];
|
|
293
|
-
var DEFAULT_DANGEROUS_SCHEMES = ["file:", "data:", "javascript:", "vbscript:"];
|
|
294
|
-
|
|
295
336
|
// src/roleValidator.ts
|
|
296
337
|
import { normalize as normalize2, basename as basename2, dirname as dirname4, join as join4 } from "path";
|
|
297
338
|
import { lstatSync, readdirSync, realpathSync as realpathSync2 } from "fs";
|
|
@@ -648,8 +689,8 @@ function scanBashCommand(command, forbiddenBasenames) {
|
|
|
648
689
|
}
|
|
649
690
|
function buildPattern(basename3) {
|
|
650
691
|
const escaped = escapeRegex(basename3);
|
|
651
|
-
if (
|
|
652
|
-
return new RegExp(
|
|
692
|
+
if (EXTENSION_BASENAMES.has(basename3.toLowerCase())) {
|
|
693
|
+
return new RegExp(`(?:^|\\w)${escaped}(?=$|[\\s;&|<>()'"=\\/])`, "i");
|
|
653
694
|
}
|
|
654
695
|
if (basename3.startsWith(".")) {
|
|
655
696
|
return new RegExp(`(?:^|[\\s;&|<>()\\/'"=])${escaped}(?=$|[\\s;&|<>()\\/'"=.])`, "i");
|
|
@@ -668,17 +709,15 @@ function scanContentForForbiddenBasenames(content, forbiddenBasenames) {
|
|
|
668
709
|
}
|
|
669
710
|
function buildContentPattern(basename3) {
|
|
670
711
|
const escaped = escapeRegex(basename3);
|
|
671
|
-
if (
|
|
672
|
-
return new RegExp(
|
|
712
|
+
if (EXTENSION_BASENAMES.has(basename3.toLowerCase())) {
|
|
713
|
+
return new RegExp(`(?:^|\\w)${escaped}(?=$|[\\s;&|<>()'"=\\/])`, "i");
|
|
673
714
|
}
|
|
674
715
|
if (basename3.startsWith(".")) {
|
|
675
716
|
return new RegExp(`(?:^|[\\s;&|<>()\\/'"=])${escaped}(?=$|[\\s;&|<>()\\/'"=.])`, "i");
|
|
676
717
|
}
|
|
677
718
|
return new RegExp(`(?<=[/\\\\]\\.?)${escaped}\\b`, "i");
|
|
678
719
|
}
|
|
679
|
-
|
|
680
|
-
return /^\.[a-zA-Z]+$/.test(s);
|
|
681
|
-
}
|
|
720
|
+
var EXTENSION_BASENAMES = /* @__PURE__ */ new Set([".pem", ".key"]);
|
|
682
721
|
function escapeRegex(s) {
|
|
683
722
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
684
723
|
}
|
|
@@ -696,8 +735,8 @@ function scanGlobPattern(pattern, forbiddenBasenames) {
|
|
|
696
735
|
function buildGlobContextPattern(basename3) {
|
|
697
736
|
const escaped = escapeRegex(basename3);
|
|
698
737
|
const GLOB_DELIM = String.raw`\s;&|<>()\\/'"=.*?{}\[\]`;
|
|
699
|
-
if (
|
|
700
|
-
return new RegExp(`[\\w*]${escaped}(?=$|[${GLOB_DELIM}])`, "i");
|
|
738
|
+
if (EXTENSION_BASENAMES.has(basename3.toLowerCase())) {
|
|
739
|
+
return new RegExp(`(?:^|[\\w*])${escaped}(?=$|[${GLOB_DELIM}])`, "i");
|
|
701
740
|
}
|
|
702
741
|
if (basename3.startsWith(".")) {
|
|
703
742
|
return new RegExp(`(?:^|[${GLOB_DELIM}])${escaped}(?=$|[${GLOB_DELIM}])`, "i");
|
|
@@ -1868,18 +1907,15 @@ var TargetSensitivityScorer = class {
|
|
|
1868
1907
|
};
|
|
1869
1908
|
|
|
1870
1909
|
export {
|
|
1871
|
-
DEFAULT_MAP_PATH,
|
|
1872
|
-
saveMap,
|
|
1873
|
-
loadMap,
|
|
1874
1910
|
DEFAULT_OVERLAY_PATH,
|
|
1875
1911
|
saveOverlay,
|
|
1876
1912
|
loadOverlay,
|
|
1877
1913
|
applyOverlay,
|
|
1878
1914
|
removeOverlayDecision,
|
|
1879
1915
|
createEmptyOverlay,
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1916
|
+
DEFAULT_MAP_PATH,
|
|
1917
|
+
saveMap,
|
|
1918
|
+
loadMap,
|
|
1883
1919
|
TargetSensitivityScorer,
|
|
1884
1920
|
tokenizePaths,
|
|
1885
1921
|
FORBIDDEN_BASENAMES,
|
|
@@ -1897,4 +1933,4 @@ export {
|
|
|
1897
1933
|
findMatchingException,
|
|
1898
1934
|
RoleValidator
|
|
1899
1935
|
};
|
|
1900
|
-
//# sourceMappingURL=chunk-
|
|
1936
|
+
//# sourceMappingURL=chunk-M5EEVMLU.js.map
|