@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.
@@ -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
- // src/repoSensitivityMap.ts
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 DEFAULT_MAP_PATH = path.join(os.homedir(), ".dahlia", "repo-sensitivity.json");
6
- async function saveMap(map, customPath) {
7
- const target = customPath ?? DEFAULT_MAP_PATH;
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(map, null, 2), "utf-8");
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(`RepoSensitivityMap.saveMap: failed to save to ${target}: ${message}`, {
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 loadMap(customPath) {
26
- const target = customPath ?? DEFAULT_MAP_PATH;
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(`RepoSensitivityMap.loadMap: file at ${target} is malformed: ${message}`, {
91
+ throw new Error(`SensitivityOverlay.loadOverlay: file at ${target} is malformed: ${message}`, {
42
92
  cause: err
43
93
  });
44
94
  }
45
- const scannedAt = typeof parsed.scannedAt === "string" ? parsed.scannedAt : "unknown";
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 entries = Array.isArray(parsed.entries) ? parsed.entries : [];
48
- const summary = parsed.summary && typeof parsed.summary === "object" && !Array.isArray(parsed.summary) ? parsed.summary : computeSummaryFromEntries(entries);
49
- return { scannedAt, repoRoot, entries, summary };
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 lookupEntry(map, target, repoRoot) {
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 ?? map.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 entry of map.entries) {
70
- if (entry.path === canonical) return entry;
154
+ for (const decision of overlay.decisions) {
155
+ if (decision.path === canonical) return decision;
71
156
  }
72
157
  return null;
73
158
  }
74
- function computeSummaryFromEntries(entries) {
75
- const byCategory = {};
76
- const byConfidence = {
77
- low: 0,
78
- medium: 0,
79
- high: 0
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
- for (const entry of entries) {
82
- byCategory[entry.category] = (byCategory[entry.category] ?? 0) + 1;
83
- if (entry.confidence === "low" || entry.confidence === "medium" || entry.confidence === "high") {
84
- byConfidence[entry.confidence]++;
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
- totalFiles: entries.length,
89
- sensitiveFiles: entries.length,
90
- byCategory,
91
- byConfidence
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/repoSensitivityOverlay.ts
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 DEFAULT_OVERLAY_PATH = path2.join(
100
- os2.homedir(),
101
- ".dahlia",
102
- "repo-sensitivity.review.json"
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(overlay, null, 2), "utf-8");
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(`SensitivityOverlay.saveOverlay: failed to save to ${target}: ${message}`, {
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 loadOverlay(customPath) {
124
- const target = customPath ?? DEFAULT_OVERLAY_PATH;
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(`SensitivityOverlay.loadOverlay: file at ${target} is malformed: ${message}`, {
255
+ throw new Error(`RepoSensitivityMap.loadMap: file at ${target} is malformed: ${message}`, {
140
256
  cause: err
141
257
  });
142
258
  }
143
- const version = typeof parsed.version === "string" ? parsed.version : "1.0";
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 decisions = Array.isArray(parsed.decisions) ? parsed.decisions : [];
146
- const updatedAt = typeof parsed.updatedAt === "string" ? parsed.updatedAt : "unknown";
147
- return { version, repoRoot, decisions, updatedAt };
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 lookupOverlayDecision(overlay, target, repoRoot) {
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 ?? overlay.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 decision of overlay.decisions) {
168
- if (decision.path === canonical) return decision;
310
+ for (const entry of map.entries) {
311
+ if (entry.path === canonical) return entry;
169
312
  }
170
313
  return null;
171
314
  }
172
- function applyOverlay(overlay, decisionPath, decisionType, options) {
173
- let canonical = decisionPath;
174
- if (canonical.startsWith("./")) {
175
- canonical = canonical.slice(2);
176
- }
177
- canonical = canonical.replace(/\\/g, "/");
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
- function removeOverlayDecision(overlay, decisionPath) {
199
- let canonical = decisionPath;
200
- if (canonical.startsWith("./")) {
201
- canonical = canonical.slice(2);
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
- ...overlay,
210
- decisions: filtered,
211
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
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 (basename3.startsWith(".") && basename3.length <= 4 && !isAlphaAfterDot(basename3)) {
652
- return new RegExp(`\\w${escaped}(?=$|[\\s;&|<>()'"=\\/])`, "i");
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 (basename3.startsWith(".") && basename3.length <= 4 && !isAlphaAfterDot(basename3)) {
672
- return new RegExp(`\\w${escaped}(?=$|[\\s;&|<>()'"=\\/])`, "i");
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
- function isAlphaAfterDot(s) {
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 (basename3.startsWith(".") && basename3.length <= 4 && !isAlphaAfterDot(basename3)) {
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
- DEFAULT_FORBIDDEN_PATTERNS,
1881
- withPolicyReadExceptions,
1882
- DEFAULT_MEDIUM_DISPOSITION,
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-JTR2E7RD.js.map
1936
+ //# sourceMappingURL=chunk-M5EEVMLU.js.map