@tuent/sentinel 0.1.0 → 0.1.1

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.
@@ -17,7 +17,7 @@ import {
17
17
  saveMap,
18
18
  saveOverlay,
19
19
  walkForbiddenInodeRoots
20
- } from "./chunk-6MHWJATS.js";
20
+ } from "./chunk-QHE56MEO.js";
21
21
  import {
22
22
  loadPolicy,
23
23
  policyToConfig,
@@ -7318,13 +7318,13 @@ var Sentinel = class _Sentinel {
7318
7318
  type: "agent_quarantined",
7319
7319
  agentId,
7320
7320
  agentName: event.agentName ?? agentId,
7321
- description: `Agent ${agentId} is quarantined. All actions blocked.`,
7321
+ description: `Agent ${agentId} is quarantined. All actions blocked. Run 'sentinel release' to restore access.`,
7322
7322
  evidence: {
7323
7323
  action: event.action,
7324
7324
  target: event.primaryTarget,
7325
7325
  timestamp: event.timestamp
7326
7326
  },
7327
- recommendation: "Agent must be manually released before it can perform any actions.",
7327
+ recommendation: `Agent must be manually released before it can perform any actions. Run 'sentinel release' in this workspace (or 'sentinel release --agent=${agentId}') to restore access.`,
7328
7328
  timestamp: event.timestamp
7329
7329
  }
7330
7330
  };
@@ -7339,13 +7339,13 @@ var Sentinel = class _Sentinel {
7339
7339
  type: "agent_restricted",
7340
7340
  agentId,
7341
7341
  agentName: event.agentName ?? agentId,
7342
- description: `Agent ${agentId} is restricted. Action '${event.action}' is not permitted in restricted mode.`,
7342
+ description: `Agent ${agentId} is restricted. Action '${event.action}' is not permitted in restricted mode. Run 'sentinel release' to restore full access.`,
7343
7343
  evidence: {
7344
7344
  action: event.action,
7345
7345
  target: event.primaryTarget,
7346
7346
  timestamp: event.timestamp
7347
7347
  },
7348
- recommendation: "Only file_read and tool_invocation are allowed in restricted mode. Release the agent to restore full access.",
7348
+ recommendation: `Only file_read and tool_invocation are allowed in restricted mode. Run 'sentinel release' in this workspace (or 'sentinel release --agent=${agentId}') to restore full access.`,
7349
7349
  timestamp: event.timestamp
7350
7350
  }
7351
7351
  };
@@ -7426,4 +7426,4 @@ export {
7426
7426
  createCliApproval,
7427
7427
  Sentinel
7428
7428
  };
7429
- //# sourceMappingURL=chunk-QFRDEISP.js.map
7429
+ //# sourceMappingURL=chunk-NS6ZLMDK.js.map
@@ -258,23 +258,508 @@ var DEFAULT_NETWORK_DENYLIST_CIDRS = [
258
258
  var DEFAULT_DANGEROUS_SCHEMES = ["file:", "data:", "javascript:", "vbscript:"];
259
259
 
260
260
  // src/roleValidator.ts
261
- import { normalize, basename, dirname as dirname3, join as join3 } from "path";
262
- import { lstatSync, readdirSync, realpathSync } from "fs";
261
+ import { normalize as normalize2, basename as basename2, dirname as dirname4, join as join4 } from "path";
262
+ import { lstatSync, readdirSync, realpathSync as realpathSync2 } from "fs";
263
263
  import { homedir as homedir3 } from "os";
264
+
265
+ // src/gateway/bashScanner.ts
266
+ import { parse as shellParse } from "shell-quote";
267
+ import { realpathSync } from "fs";
268
+ import { dirname as dirname3, join as join3, basename, normalize } from "path";
269
+ var BRACE_PATTERN_RE = /\{[^}]*,[^}]*\}/;
270
+ var MAX_BRACE_EXPANSION = 64;
271
+ function fnmatchBasename(pattern, candidate) {
272
+ if (pattern.length !== candidate.length) return false;
273
+ for (let i = 0; i < pattern.length; i++) {
274
+ const p = pattern[i].toLowerCase();
275
+ const c = candidate[i].toLowerCase();
276
+ if (p === "?") continue;
277
+ if (p !== c) return false;
278
+ }
279
+ return true;
280
+ }
281
+ function bracketTokenMatchesForbidden(token, forbiddenBasenames) {
282
+ const literals = [];
283
+ let current = "";
284
+ let inBracket = false;
285
+ for (let i = 0; i < token.length; i++) {
286
+ if (token[i] === "[" && !inBracket) {
287
+ if (current) literals.push(current);
288
+ current = "";
289
+ inBracket = true;
290
+ } else if (token[i] === "]" && inBracket) {
291
+ inBracket = false;
292
+ } else if (!inBracket) {
293
+ current += token[i];
294
+ }
295
+ }
296
+ if (current) literals.push(current);
297
+ for (const forbidden of forbiddenBasenames) {
298
+ const fl = forbidden.toLowerCase();
299
+ for (const lit of literals) {
300
+ if (lit.length === 0) continue;
301
+ if (fl.includes(lit.toLowerCase())) return forbidden;
302
+ }
303
+ }
304
+ return null;
305
+ }
306
+ function resolveBraceExpansion(token) {
307
+ const match = token.match(/^(.*?)\{([^}]*,[^}]*)\}(.*)$/);
308
+ if (!match) return null;
309
+ const [, prefix, alternatives, suffix] = match;
310
+ const parts = alternatives.split(",");
311
+ if (parts.length > MAX_BRACE_EXPANSION) return null;
312
+ return parts.map((p) => prefix + p + suffix);
313
+ }
314
+ function wildcardDispatch(token, forbiddenBasenames, metadataField) {
315
+ const result = {
316
+ resolvedBasenames: [],
317
+ unparseable: false,
318
+ metadata: {}
319
+ };
320
+ if (token === "*" || token === "**" || token === "?") {
321
+ return result;
322
+ }
323
+ if (BRACE_PATTERN_RE.test(token)) {
324
+ const expanded = resolveBraceExpansion(token);
325
+ if (expanded === null) {
326
+ result.unparseable = true;
327
+ return result;
328
+ }
329
+ for (const alt of expanded) {
330
+ const hasWildcard = /[?*[]/.test(alt);
331
+ if (hasWildcard) {
332
+ const sub = wildcardDispatch(alt, forbiddenBasenames, metadataField);
333
+ if (sub.resolvedBasenames.length > 0) {
334
+ result.resolvedBasenames.push(...sub.resolvedBasenames);
335
+ result.metadata["resolvedFromBrace"] = token;
336
+ Object.assign(result.metadata, sub.metadata);
337
+ }
338
+ if (sub.unparseable) result.unparseable = true;
339
+ } else {
340
+ const altLower = alt.toLowerCase();
341
+ for (const forbidden of forbiddenBasenames) {
342
+ if (altLower === forbidden.toLowerCase()) {
343
+ result.resolvedBasenames.push(forbidden);
344
+ result.metadata["resolvedFromBrace"] = token;
345
+ break;
346
+ }
347
+ }
348
+ }
349
+ }
350
+ return result;
351
+ }
352
+ const hasStar = token.includes("*");
353
+ const hasQuestion = token.includes("?");
354
+ const hasBracket = token.includes("[");
355
+ if (hasBracket) {
356
+ const matched = bracketTokenMatchesForbidden(token, forbiddenBasenames);
357
+ if (matched) {
358
+ result.resolvedBasenames.push(matched);
359
+ result.metadata["resolvedFromBracket"] = token;
360
+ } else {
361
+ result.unparseable = true;
362
+ }
363
+ return result;
364
+ }
365
+ if (hasStar && !hasQuestion) {
366
+ const matched = starLiteralSubstringCheck(token, forbiddenBasenames);
367
+ if (matched) {
368
+ result.resolvedBasenames.push(matched);
369
+ result.metadata[metadataField] = token;
370
+ }
371
+ return result;
372
+ }
373
+ if (hasQuestion && !hasStar) {
374
+ for (const forbidden of forbiddenBasenames) {
375
+ if (fnmatchBasename(token, forbidden)) {
376
+ result.resolvedBasenames.push(forbidden);
377
+ result.metadata[metadataField] = token;
378
+ break;
379
+ }
380
+ }
381
+ return result;
382
+ }
383
+ if (hasStar && hasQuestion) {
384
+ const starMatch = starLiteralSubstringCheck(token, forbiddenBasenames);
385
+ if (starMatch) {
386
+ result.resolvedBasenames.push(starMatch);
387
+ result.metadata[metadataField] = token;
388
+ return result;
389
+ }
390
+ const segments = token.split("*").filter((s) => s.includes("?"));
391
+ for (const seg of segments) {
392
+ for (const forbidden of forbiddenBasenames) {
393
+ if (fnmatchBasename(seg, forbidden)) {
394
+ result.resolvedBasenames.push(forbidden);
395
+ result.metadata[metadataField] = token;
396
+ return result;
397
+ }
398
+ }
399
+ }
400
+ return result;
401
+ }
402
+ return result;
403
+ }
404
+ function starLiteralSubstringCheck(token, forbiddenBasenames) {
405
+ const literals = token.split("*").filter((s) => s.length > 0);
406
+ for (const forbidden of forbiddenBasenames) {
407
+ const fl = forbidden.toLowerCase();
408
+ for (const lit of literals) {
409
+ if (fl.includes(lit.toLowerCase())) return forbidden;
410
+ }
411
+ }
412
+ return null;
413
+ }
414
+ function shouldDispatchWildcard(token) {
415
+ const hasMetachar = /[?*[{]/.test(token);
416
+ if (!hasMetachar) return false;
417
+ if (isPathShaped(token)) return true;
418
+ if (token.includes("[")) return true;
419
+ if (BRACE_PATTERN_RE.test(token)) return true;
420
+ return false;
421
+ }
422
+ var SENSITIVE_BASENAME_RE = /(?:\.env|\.ssh|secrets|credentials|id_rsa|id_dsa|id_ecdsa|id_ed25519|\.pem|\.key)/i;
423
+ var DANGEROUS_COMMAND_TOKENS = /* @__PURE__ */ new Set(["eval"]);
424
+ var COMMAND_SUBSTITUTION_RE = /\$\(|`/;
425
+ var DANGEROUS_RAW_RE = /<<<|<\(|>\(/;
426
+ function isVarMarker(token) {
427
+ return typeof token === "object" && token !== null && "__sentinel_var" in token && typeof token.__sentinel_var === "string";
428
+ }
429
+ function tokenizePaths(command) {
430
+ const result = {
431
+ paths: [],
432
+ unparseable: false,
433
+ hasDangerousConstruct: false
434
+ };
435
+ if (DANGEROUS_RAW_RE.test(command)) {
436
+ result.hasDangerousConstruct = true;
437
+ }
438
+ if (COMMAND_SUBSTITUTION_RE.test(command)) {
439
+ result.hasDangerousConstruct = true;
440
+ }
441
+ let tokens;
442
+ try {
443
+ tokens = shellParse(command, (key) => ({ __sentinel_var: key }));
444
+ } catch {
445
+ result.unparseable = true;
446
+ return result;
447
+ }
448
+ if (!Array.isArray(tokens)) {
449
+ result.unparseable = true;
450
+ return result;
451
+ }
452
+ let prevToken = null;
453
+ for (let i = 0; i < tokens.length; i++) {
454
+ const token = tokens[i];
455
+ if (isVarMarker(token)) {
456
+ const nextToken = tokens[i + 1];
457
+ const nextIsPathRelevant = nextToken === void 0 || // end of tokens — var is complete argument
458
+ typeof nextToken === "object" && nextToken !== null && "op" in nextToken || // followed by operator — var is complete argument
459
+ typeof nextToken === "string" && isPathShaped(nextToken);
460
+ const prevIsPathRelevant = prevToken !== null && isPathShaped(prevToken);
461
+ if (nextIsPathRelevant || prevIsPathRelevant) {
462
+ result.unparseable = true;
463
+ }
464
+ prevToken = null;
465
+ continue;
466
+ }
467
+ if (typeof token === "object" && token !== null) {
468
+ if ("pattern" in token) {
469
+ const globPattern = token.pattern;
470
+ const lastSlash = globPattern.lastIndexOf("/");
471
+ const dispatchTarget = lastSlash >= 0 ? globPattern.slice(lastSlash + 1) : globPattern;
472
+ const dispatch = wildcardDispatch(dispatchTarget, FORBIDDEN_BASENAMES, "resolvedFromGlob");
473
+ if (dispatch.resolvedBasenames.length > 0) {
474
+ for (const resolved of dispatch.resolvedBasenames) {
475
+ result.paths.push(resolved);
476
+ }
477
+ }
478
+ if (dispatch.unparseable) {
479
+ result.unparseable = true;
480
+ }
481
+ if (SENSITIVE_BASENAME_RE.test(globPattern)) {
482
+ result.unparseable = true;
483
+ }
484
+ prevToken = null;
485
+ continue;
486
+ }
487
+ if ("op" in token) {
488
+ if (token.op === "<(") {
489
+ result.hasDangerousConstruct = true;
490
+ }
491
+ prevToken = null;
492
+ continue;
493
+ }
494
+ prevToken = null;
495
+ continue;
496
+ }
497
+ if (typeof token !== "string") {
498
+ prevToken = null;
499
+ continue;
500
+ }
501
+ if (DANGEROUS_COMMAND_TOKENS.has(token.toLowerCase())) {
502
+ result.hasDangerousConstruct = true;
503
+ }
504
+ if ((prevToken === "sh" || prevToken === "bash" || prevToken === "/bin/sh" || prevToken === "/bin/bash") && token === "-c") {
505
+ result.hasDangerousConstruct = true;
506
+ }
507
+ if (shouldDispatchWildcard(token)) {
508
+ const metaField = "resolvedFromQuotedGlob";
509
+ const dispatch = wildcardDispatch(token, FORBIDDEN_BASENAMES, metaField);
510
+ if (dispatch.resolvedBasenames.length > 0) {
511
+ for (const resolved of dispatch.resolvedBasenames) {
512
+ result.paths.push(resolved);
513
+ }
514
+ }
515
+ if (dispatch.unparseable) {
516
+ result.unparseable = true;
517
+ }
518
+ } else if (isPathShaped(token)) {
519
+ const resolved = resolvePathToken(token);
520
+ result.paths.push(resolved);
521
+ }
522
+ prevToken = token;
523
+ }
524
+ return result;
525
+ }
526
+ function isPathShaped(token) {
527
+ if (token.includes("/")) return true;
528
+ if (token.startsWith(".")) return true;
529
+ if (SENSITIVE_BASENAME_RE.test(token)) return true;
530
+ return false;
531
+ }
532
+ function resolvePathToken(token) {
533
+ const normalized = normalize(token);
534
+ try {
535
+ return realpathSync(normalized);
536
+ } catch (err) {
537
+ const code = err.code;
538
+ if (code === "ENOENT") {
539
+ return resolveNonexistentPathToken(normalized);
540
+ }
541
+ return normalized;
542
+ }
543
+ }
544
+ function resolveNonexistentPathToken(normalizedPath) {
545
+ let current = normalizedPath;
546
+ let suffix = "";
547
+ for (let i = 0; i < 50; i++) {
548
+ const parent = dirname3(current);
549
+ if (parent === current) {
550
+ return normalizedPath;
551
+ }
552
+ if (parent === ".") {
553
+ return normalizedPath;
554
+ }
555
+ suffix = suffix ? join3(basename(current), suffix) : basename(current);
556
+ current = parent;
557
+ try {
558
+ const resolved = realpathSync(current);
559
+ if (resolved !== current) {
560
+ return join3(resolved, suffix);
561
+ }
562
+ return join3(resolved, suffix);
563
+ } catch {
564
+ continue;
565
+ }
566
+ }
567
+ return normalizedPath;
568
+ }
569
+ var FORBIDDEN_BASENAMES = [
570
+ ".env",
571
+ ".ssh",
572
+ ".aws",
573
+ "secrets",
574
+ "credentials",
575
+ "id_rsa",
576
+ "id_dsa",
577
+ "id_ecdsa",
578
+ "id_ed25519",
579
+ ".pem",
580
+ ".key"
581
+ ];
582
+ function scanBashCommand(command, forbiddenBasenames) {
583
+ const basenames = forbiddenBasenames ?? FORBIDDEN_BASENAMES;
584
+ const hits = [];
585
+ for (const basename3 of basenames) {
586
+ const pattern = buildPattern(basename3);
587
+ if (pattern.test(command)) {
588
+ hits.push(basename3);
589
+ }
590
+ }
591
+ return { matched: hits.length > 0, hits };
592
+ }
593
+ function buildPattern(basename3) {
594
+ const escaped = escapeRegex(basename3);
595
+ if (basename3.startsWith(".") && basename3.length <= 4 && !isAlphaAfterDot(basename3)) {
596
+ return new RegExp(`\\w${escaped}(?=$|[\\s;&|<>()'"=\\/])`, "i");
597
+ }
598
+ if (basename3.startsWith(".")) {
599
+ return new RegExp(`(?:^|[\\s;&|<>()\\/'"=])${escaped}(?=$|[\\s;&|<>()\\/'"=.])`, "i");
600
+ }
601
+ return new RegExp(`\\b${escaped}\\b`, "i");
602
+ }
603
+ function scanContentForForbiddenBasenames(content, forbiddenBasenames) {
604
+ const hits = [];
605
+ for (const basename3 of forbiddenBasenames) {
606
+ const pattern = buildContentPattern(basename3);
607
+ if (pattern.test(content)) {
608
+ hits.push(basename3);
609
+ }
610
+ }
611
+ return { matched: hits.length > 0, hits };
612
+ }
613
+ function buildContentPattern(basename3) {
614
+ const escaped = escapeRegex(basename3);
615
+ if (basename3.startsWith(".") && basename3.length <= 4 && !isAlphaAfterDot(basename3)) {
616
+ return new RegExp(`\\w${escaped}(?=$|[\\s;&|<>()'"=\\/])`, "i");
617
+ }
618
+ if (basename3.startsWith(".")) {
619
+ return new RegExp(`(?:^|[\\s;&|<>()\\/'"=])${escaped}(?=$|[\\s;&|<>()\\/'"=.])`, "i");
620
+ }
621
+ return new RegExp(`(?<=[/\\\\]\\.?)${escaped}\\b`, "i");
622
+ }
623
+ function isAlphaAfterDot(s) {
624
+ return /^\.[a-zA-Z]+$/.test(s);
625
+ }
626
+ function escapeRegex(s) {
627
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
628
+ }
629
+ function scanGlobPattern(pattern, forbiddenBasenames) {
630
+ const basenames = forbiddenBasenames ?? FORBIDDEN_BASENAMES;
631
+ const hits = [];
632
+ for (const basename3 of basenames) {
633
+ const re = buildGlobContextPattern(basename3);
634
+ if (re.test(pattern)) {
635
+ hits.push(basename3);
636
+ }
637
+ }
638
+ return { matched: hits.length > 0, hits };
639
+ }
640
+ function buildGlobContextPattern(basename3) {
641
+ const escaped = escapeRegex(basename3);
642
+ const GLOB_DELIM = String.raw`\s;&|<>()\\/'"=.*?{}\[\]`;
643
+ if (basename3.startsWith(".") && basename3.length <= 4 && !isAlphaAfterDot(basename3)) {
644
+ return new RegExp(`[\\w*]${escaped}(?=$|[${GLOB_DELIM}])`, "i");
645
+ }
646
+ if (basename3.startsWith(".")) {
647
+ return new RegExp(`(?:^|[${GLOB_DELIM}])${escaped}(?=$|[${GLOB_DELIM}])`, "i");
648
+ }
649
+ return new RegExp(`\\b${escaped}\\b`, "i");
650
+ }
651
+ var SAFE_VERBS = /* @__PURE__ */ new Set(["echo", "printf", ":", "true"]);
652
+ var SEGMENT_OPS = /* @__PURE__ */ new Set([";", "&&", "||", "|", "&", "\n"]);
653
+ var REDIRECT_OPS = /* @__PURE__ */ new Set([">", ">>", "<", "&>", ">&", "<&"]);
654
+ function positionallySafeBasenames(command) {
655
+ const safe = /* @__PURE__ */ new Set();
656
+ if (typeof command !== "string" || command.length === 0) return safe;
657
+ if (COMMAND_SUBSTITUTION_RE.test(command) || DANGEROUS_RAW_RE.test(command) || command.includes("<<")) {
658
+ return safe;
659
+ }
660
+ let tokens;
661
+ try {
662
+ tokens = shellParse(command, (key) => ({ __sentinel_var: key }));
663
+ } catch {
664
+ return safe;
665
+ }
666
+ if (!Array.isArray(tokens)) return safe;
667
+ const tally = /* @__PURE__ */ new Map();
668
+ const mark = (hits, isSafe) => {
669
+ for (const b of hits) {
670
+ const e = tally.get(b) ?? { safe: 0, unsafe: 0 };
671
+ if (isSafe) e.safe++;
672
+ else e.unsafe++;
673
+ tally.set(b, e);
674
+ }
675
+ };
676
+ let verb = null;
677
+ let expectVerb = true;
678
+ let prevRedirect = false;
679
+ for (const tok of tokens) {
680
+ if (tok && typeof tok === "object") {
681
+ if (isVarMarker(tok)) {
682
+ if (expectVerb) {
683
+ verb = null;
684
+ expectVerb = false;
685
+ }
686
+ prevRedirect = false;
687
+ continue;
688
+ }
689
+ if ("comment" in tok) {
690
+ const hits2 = scanBashCommand(
691
+ String(tok.comment),
692
+ FORBIDDEN_BASENAMES
693
+ ).hits;
694
+ if (hits2.length) mark(hits2, true);
695
+ continue;
696
+ }
697
+ if ("pattern" in tok) {
698
+ const hits2 = scanGlobPattern(
699
+ String(tok.pattern),
700
+ FORBIDDEN_BASENAMES
701
+ ).hits;
702
+ if (hits2.length) mark(hits2, false);
703
+ prevRedirect = false;
704
+ continue;
705
+ }
706
+ if ("op" in tok) {
707
+ const op = String(tok.op);
708
+ prevRedirect = REDIRECT_OPS.has(op);
709
+ if (SEGMENT_OPS.has(op)) {
710
+ expectVerb = true;
711
+ verb = null;
712
+ }
713
+ continue;
714
+ }
715
+ prevRedirect = false;
716
+ continue;
717
+ }
718
+ if (typeof tok !== "string") {
719
+ prevRedirect = false;
720
+ continue;
721
+ }
722
+ const isRedirectTarget = prevRedirect;
723
+ prevRedirect = false;
724
+ const hits = scanBashCommand(tok, FORBIDDEN_BASENAMES).hits;
725
+ if (expectVerb) {
726
+ verb = tok;
727
+ expectVerb = false;
728
+ if (hits.length) mark(hits, false);
729
+ continue;
730
+ }
731
+ if (hits.length) {
732
+ const safeVerb = verb !== null && SAFE_VERBS.has(verb);
733
+ mark(hits, safeVerb && !isRedirectTarget);
734
+ }
735
+ }
736
+ for (const [b, e] of tally) {
737
+ if (e.unsafe === 0 && e.safe > 0) safe.add(b);
738
+ }
739
+ return safe;
740
+ }
741
+ function isPositionallySafeMention(command) {
742
+ const hits = scanBashCommand(command, FORBIDDEN_BASENAMES).hits;
743
+ if (hits.length === 0) return false;
744
+ const safe = positionallySafeBasenames(command);
745
+ return hits.every((h) => safe.has(h));
746
+ }
747
+
748
+ // src/roleValidator.ts
264
749
  var SUSPICIOUS_BASENAME_RE = /^\.|(\.env|secret|credential|key|config|token)/i;
265
750
  function resolveSymlinks(normalizedPath) {
266
751
  if (!normalizedPath || normalizedPath.includes("://")) return normalizedPath;
267
752
  if (normalizedPath.includes("node_modules/") || normalizedPath.includes("node_modules\\")) {
268
753
  return normalizedPath;
269
754
  }
270
- const base = basename(normalizedPath);
755
+ const base = basename2(normalizedPath);
271
756
  const needsRealpath = SUSPICIOUS_BASENAME_RE.test(base);
272
757
  if (!needsRealpath) {
273
758
  try {
274
759
  const stat = lstatSync(normalizedPath);
275
760
  if (!stat.isSymbolicLink()) {
276
761
  try {
277
- const resolved = realpathSync(normalizedPath);
762
+ const resolved = realpathSync2(normalizedPath);
278
763
  return resolved === normalizedPath ? normalizedPath : resolved;
279
764
  } catch {
280
765
  return normalizedPath;
@@ -289,7 +774,7 @@ function resolveSymlinks(normalizedPath) {
289
774
  }
290
775
  }
291
776
  try {
292
- return realpathSync(normalizedPath);
777
+ return realpathSync2(normalizedPath);
293
778
  } catch (err) {
294
779
  const code = err.code;
295
780
  if (code === "ENOENT") {
@@ -302,19 +787,19 @@ function resolveNonexistentPath(normalizedPath) {
302
787
  let current = normalizedPath;
303
788
  let suffix = "";
304
789
  for (let i = 0; i < 50; i++) {
305
- const parent = dirname3(current);
790
+ const parent = dirname4(current);
306
791
  if (parent === current) {
307
792
  return normalizedPath;
308
793
  }
309
794
  if (parent === ".") {
310
795
  return normalizedPath;
311
796
  }
312
- suffix = suffix ? join3(basename(current), suffix) : basename(current);
797
+ suffix = suffix ? join4(basename2(current), suffix) : basename2(current);
313
798
  current = parent;
314
799
  try {
315
- const resolved = realpathSync(current);
800
+ const resolved = realpathSync2(current);
316
801
  if (resolved !== current) {
317
- return join3(resolved, suffix);
802
+ return join4(resolved, suffix);
318
803
  }
319
804
  return normalizedPath;
320
805
  } catch {
@@ -347,7 +832,7 @@ function normalizeForbiddenPattern(pattern) {
347
832
  if (pattern.startsWith("**/") || pattern.startsWith("/")) return pattern;
348
833
  return "**/" + pattern;
349
834
  }
350
- function isPathShaped(value) {
835
+ function isPathShaped2(value) {
351
836
  if (value.length === 0 || value.length > 4096) return false;
352
837
  if (/\s/.test(value)) return false;
353
838
  if (value.includes("/") || value.includes("\\")) return true;
@@ -465,7 +950,7 @@ function walkForbiddenInodeRoots(forbiddenPatterns, cwd) {
465
950
  collectForbiddenInodes(home, deepPatterns, inodes, false);
466
951
  const sensitiveDirs = [".ssh", ".aws", ".gnupg", ".config"];
467
952
  for (const dir of sensitiveDirs) {
468
- collectForbiddenInodes(join3(home, dir), deepPatterns, inodes, true);
953
+ collectForbiddenInodes(join4(home, dir), deepPatterns, inodes, true);
469
954
  }
470
955
  collectForbiddenInodes(cwd, deepPatterns, inodes, true, true);
471
956
  return inodes;
@@ -479,7 +964,7 @@ function collectForbiddenInodes(dirPath, forbiddenPatterns, inodes, recursive, s
479
964
  }
480
965
  for (const entry of entries) {
481
966
  if (skipNodeModules && entry === "node_modules") continue;
482
- const fullPath = join3(dirPath, entry);
967
+ const fullPath = join4(dirPath, entry);
483
968
  let stat;
484
969
  try {
485
970
  stat = lstatSync(fullPath);
@@ -547,7 +1032,7 @@ var RoleValidator = class {
547
1032
  }
548
1033
  validateEvent(event, activeTask) {
549
1034
  const eventTarget = event.primaryTarget;
550
- const pathNormalized = isUrlShaped(eventTarget) ? eventTarget : normalize(eventTarget);
1035
+ const pathNormalized = isUrlShaped(eventTarget) ? eventTarget : normalize2(eventTarget);
551
1036
  const resolvedTarget = resolveSymlinks(pathNormalized);
552
1037
  const normalizedPrimaryTarget = resolvedTarget;
553
1038
  const primaryTargetPaths = pathNormalized !== resolvedTarget ? [resolvedTarget, pathNormalized] : [resolvedTarget];
@@ -570,7 +1055,7 @@ var RoleValidator = class {
570
1055
  for (let i = 1; i < event.targets.length; i++) {
571
1056
  const st = event.targets[i];
572
1057
  if (!isUrlShaped(st)) {
573
- const stNorm = normalize(st);
1058
+ const stNorm = normalize2(st);
574
1059
  inodeTargets.push(resolveSymlinks(stNorm));
575
1060
  }
576
1061
  }
@@ -588,9 +1073,10 @@ var RoleValidator = class {
588
1073
  }
589
1074
  }
590
1075
  }
1076
+ const safeCommandMention = event.action === "command_exec" && isPositionallySafeMention(event.primaryTarget);
591
1077
  for (const pattern of this.role.forbiddenTargetPatterns) {
592
1078
  let matchedTargetValue = null;
593
- if (primaryTargetPaths.some((tp) => matchGlobInsensitive(pattern, tp))) {
1079
+ if (!safeCommandMention && primaryTargetPaths.some((tp) => matchGlobInsensitive(pattern, tp))) {
594
1080
  matchedTargetValue = normalizedPrimaryTarget;
595
1081
  }
596
1082
  if (!matchedTargetValue) {
@@ -598,7 +1084,7 @@ var RoleValidator = class {
598
1084
  for (let i = 1; i < event.targets.length; i++) {
599
1085
  const st = event.targets[i];
600
1086
  if (mcpTool && /\s/.test(st)) continue;
601
- const stNorm = isUrlShaped(st) ? st : normalize(st);
1087
+ const stNorm = isUrlShaped(st) ? st : normalize2(st);
602
1088
  const stResolved = resolveSymlinks(stNorm);
603
1089
  const stPaths = stNorm !== stResolved ? [stResolved, stNorm] : [stResolved];
604
1090
  if (stPaths.some((tp) => matchGlobInsensitive(pattern, tp))) {
@@ -664,7 +1150,7 @@ var RoleValidator = class {
664
1150
  if (this.sensitivityScorer && event.metadata?._mcpTool === "true") {
665
1151
  for (let i = 1; i < event.targets.length; i++) {
666
1152
  const val = event.targets[i];
667
- if (!(isUrlShaped(val) || isPathShaped(val))) continue;
1153
+ if (!(isUrlShaped(val) || isPathShaped2(val))) continue;
668
1154
  const sens = this.sensitivityScorer.scoreTarget(val, event.action);
669
1155
  if (sens.effectiveScore < 0.6) continue;
670
1156
  return this.makeFinding(event, {
@@ -1210,6 +1696,12 @@ export {
1210
1696
  DEFAULT_FORBIDDEN_PATTERNS,
1211
1697
  DEFAULT_MEDIUM_DISPOSITION,
1212
1698
  TargetSensitivityScorer,
1699
+ tokenizePaths,
1700
+ FORBIDDEN_BASENAMES,
1701
+ scanBashCommand,
1702
+ scanContentForForbiddenBasenames,
1703
+ scanGlobPattern,
1704
+ isPositionallySafeMention,
1213
1705
  matchGlob,
1214
1706
  normalizeForbiddenPattern,
1215
1707
  matchGlobInsensitive,
@@ -1218,4 +1710,4 @@ export {
1218
1710
  findMatchingException,
1219
1711
  RoleValidator
1220
1712
  };
1221
- //# sourceMappingURL=chunk-6MHWJATS.js.map
1713
+ //# sourceMappingURL=chunk-QHE56MEO.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  acquireGatewayLock,
3
3
  writePidFile
4
- } from "./chunk-CUJKNIKT.js";
4
+ } from "./chunk-LATQNIRW.js";
5
5
  import {
6
6
  discoverPolicy
7
7
  } from "./chunk-FMZWHT4M.js";
@@ -536,4 +536,4 @@ export {
536
536
  runInitClaudeCode,
537
537
  runSessionStart
538
538
  };
539
- //# sourceMappingURL=chunk-3U3PKD4N.js.map
539
+ //# sourceMappingURL=chunk-WPTJBRX5.js.map