@node9/proxy 2.37.1 → 2.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +7112 -3805
- package/dist/cli.mjs +7002 -3694
- package/dist/dashboard.mjs +1351 -369
- package/dist/index.js +2333 -710
- package/dist/index.mjs +2326 -703
- package/dist/scan-ink.mjs +1340 -279
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -71,6 +71,93 @@ var init_hasher = __esm({
|
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
// packages/policy-engine/dist/index.mjs
|
|
74
|
+
function validateRegex(pattern) {
|
|
75
|
+
if (!pattern) return "Pattern is required";
|
|
76
|
+
if (pattern.length > MAX_REGEX_LENGTH) return `Pattern exceeds max length of ${MAX_REGEX_LENGTH}`;
|
|
77
|
+
try {
|
|
78
|
+
new RegExp(pattern);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
return `Invalid regex syntax: ${e.message}`;
|
|
81
|
+
}
|
|
82
|
+
if (/\\\d+[*+{]/.test(pattern)) return "Quantified backreferences are forbidden (ReDoS risk)";
|
|
83
|
+
if (!(0, import_safe_regex2.default)(pattern)) return "Pattern rejected: potential ReDoS vulnerability detected";
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function getCompiledRegex(pattern, flags = "") {
|
|
87
|
+
if (flags && !/^[gimsuy]+$/.test(flags)) return null;
|
|
88
|
+
const key = `${pattern}\0${flags}`;
|
|
89
|
+
if (regexCache.has(key)) {
|
|
90
|
+
const cached = regexCache.get(key);
|
|
91
|
+
regexCache.delete(key);
|
|
92
|
+
regexCache.set(key, cached);
|
|
93
|
+
return cached;
|
|
94
|
+
}
|
|
95
|
+
if (validateRegex(pattern) !== null) return null;
|
|
96
|
+
try {
|
|
97
|
+
const re = new RegExp(pattern, flags);
|
|
98
|
+
if (regexCache.size >= REGEX_CACHE_MAX) {
|
|
99
|
+
const oldest = regexCache.keys().next().value;
|
|
100
|
+
if (oldest) regexCache.delete(oldest);
|
|
101
|
+
}
|
|
102
|
+
regexCache.set(key, re);
|
|
103
|
+
return re;
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function isToolRule(rule) {
|
|
109
|
+
return rule.tool.startsWith("{,mcp__*__}");
|
|
110
|
+
}
|
|
111
|
+
function actionPart(ruleName) {
|
|
112
|
+
const m = /^shield:[^:]+:(.+)$/.exec(ruleName ?? "");
|
|
113
|
+
return m ? m[1] : ruleName ?? "rule";
|
|
114
|
+
}
|
|
115
|
+
function ownReason(reason, owner) {
|
|
116
|
+
if (!reason) return reason;
|
|
117
|
+
return reason.replace(/\((?:Finance|Identity|Publishing) shield\)\.?$/, `(${owner}).`);
|
|
118
|
+
}
|
|
119
|
+
function buildAppShield(spec) {
|
|
120
|
+
const owner = spec.label;
|
|
121
|
+
const toolRules = ACTION_TEMPLATES[spec.template].filter(isToolRule).map((rule) => ({
|
|
122
|
+
...rule,
|
|
123
|
+
name: `shield:${spec.name}:${actionPart(rule.name)}`,
|
|
124
|
+
server: spec.server,
|
|
125
|
+
reason: ownReason(rule.reason, owner)
|
|
126
|
+
}));
|
|
127
|
+
const ownTools = (spec.mcpTools ?? []).map((rule) => ({
|
|
128
|
+
...rule,
|
|
129
|
+
server: spec.server
|
|
130
|
+
}));
|
|
131
|
+
return {
|
|
132
|
+
name: spec.name,
|
|
133
|
+
description: spec.description,
|
|
134
|
+
aliases: [],
|
|
135
|
+
smartRules: [...toolRules, ...ownTools, ...spec.doors],
|
|
136
|
+
dangerousWords: []
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function buildGeneralRules() {
|
|
140
|
+
const out = [];
|
|
141
|
+
for (const name of Object.keys(ACTION_TEMPLATES)) {
|
|
142
|
+
const { slug: slug2, label: label2 } = ACTION_CHECKS[name];
|
|
143
|
+
for (const rule of ACTION_TEMPLATES[name].filter(isToolRule)) {
|
|
144
|
+
const part = actionPart(rule.name).replace(/^review-/, "");
|
|
145
|
+
out.push({
|
|
146
|
+
...rule,
|
|
147
|
+
name: `review-action-${slug2}-${part}`,
|
|
148
|
+
notServer: APP_SERVER_GLOB,
|
|
149
|
+
reason: ownReason(rule.reason, label2)
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
function generalActionRuleNames(template) {
|
|
156
|
+
const { slug: slug2 } = ACTION_CHECKS[template];
|
|
157
|
+
return GENERAL_ACTION_RULES.filter((r) => r.name?.startsWith(`review-action-${slug2}-`)).map(
|
|
158
|
+
(r) => r.name
|
|
159
|
+
);
|
|
160
|
+
}
|
|
74
161
|
function isShieldVerdict(v) {
|
|
75
162
|
return v === "allow" || v === "review" || v === "block";
|
|
76
163
|
}
|
|
@@ -108,16 +195,25 @@ function validateOverrides(raw) {
|
|
|
108
195
|
return { overrides: result, warnings };
|
|
109
196
|
}
|
|
110
197
|
function assertBuiltinShieldRegexesAreSafe() {
|
|
111
|
-
|
|
198
|
+
const sets = [
|
|
199
|
+
...Object.values(BUILTIN_SHIELDS).map((sh) => ({
|
|
200
|
+
name: sh.name,
|
|
201
|
+
smartRules: sh.smartRules
|
|
202
|
+
})),
|
|
203
|
+
...Object.entries(ACTION_TEMPLATES).map(([name, smartRules]) => ({ name, smartRules })),
|
|
204
|
+
{ name: "general-action-checks", smartRules: GENERAL_ACTION_RULES }
|
|
205
|
+
];
|
|
206
|
+
for (const shield of sets) {
|
|
112
207
|
for (const rule of shield.smartRules) {
|
|
113
208
|
const conditions = rule.conditions ?? [];
|
|
114
209
|
for (const cond of conditions) {
|
|
115
210
|
if (cond.op !== "matches" && cond.op !== "notMatches") continue;
|
|
116
211
|
const pattern = cond.value;
|
|
117
212
|
if (!pattern) continue;
|
|
118
|
-
|
|
213
|
+
const problem = validateRegex(pattern);
|
|
214
|
+
if (problem) {
|
|
119
215
|
throw new Error(
|
|
120
|
-
`[node9 engine] Shield '${shield.name}' rule '${rule.name ?? rule.tool}' has
|
|
216
|
+
`[node9 engine] Shield '${shield.name}' rule '${rule.name ?? rule.tool}' has a regex the runtime refuses (${problem}): ${pattern}`
|
|
121
217
|
);
|
|
122
218
|
}
|
|
123
219
|
}
|
|
@@ -353,6 +449,32 @@ function ssrfFloorDecision(m, asWritten, opts) {
|
|
|
353
449
|
}
|
|
354
450
|
return "block";
|
|
355
451
|
}
|
|
452
|
+
function isModeValue(v) {
|
|
453
|
+
return typeof v === "string" && MODE_VALUES.includes(v);
|
|
454
|
+
}
|
|
455
|
+
function levelOf(mode) {
|
|
456
|
+
if (mode === "observe") return "audit";
|
|
457
|
+
return LEVELS.includes(mode ?? "") ? mode : "standard";
|
|
458
|
+
}
|
|
459
|
+
function isRecordOnly(mode) {
|
|
460
|
+
return levelOf(mode) === "audit";
|
|
461
|
+
}
|
|
462
|
+
function reviewsEveryInline(mode) {
|
|
463
|
+
const level = levelOf(mode);
|
|
464
|
+
return level === "strict" || level === "block-all";
|
|
465
|
+
}
|
|
466
|
+
function isBlockAll(mode) {
|
|
467
|
+
return levelOf(mode) === "block-all";
|
|
468
|
+
}
|
|
469
|
+
function levelCheckValue(mode, id) {
|
|
470
|
+
return LEVEL_CHECKS[levelOf(mode)][id];
|
|
471
|
+
}
|
|
472
|
+
function levelChecks(mode) {
|
|
473
|
+
return LEVEL_CHECKS[levelOf(mode)];
|
|
474
|
+
}
|
|
475
|
+
function levelPackageSignals(mode) {
|
|
476
|
+
return levelOf(mode) === "soft" ? { newPackage: "off", installScript: "off" } : void 0;
|
|
477
|
+
}
|
|
356
478
|
function isVerdict(v) {
|
|
357
479
|
return v === "off" || v === "log" || v === "review" || v === "block";
|
|
358
480
|
}
|
|
@@ -403,7 +525,7 @@ function isLockedCheck(def) {
|
|
|
403
525
|
function ssrfCheckId(tier) {
|
|
404
526
|
return isStrictGatedTier(tier) ? "network.internal-addresses" : "network.metadata";
|
|
405
527
|
}
|
|
406
|
-
function
|
|
528
|
+
function ruleNameInLabel(s) {
|
|
407
529
|
let rest = s;
|
|
408
530
|
const override = rest.lastIndexOf(OVERRIDE_PREFIX);
|
|
409
531
|
if (override >= 0) rest = rest.slice(override + OVERRIDE_PREFIX.length).trimStart();
|
|
@@ -419,7 +541,7 @@ function checkIdForRule(nameOrLabel) {
|
|
|
419
541
|
const s = nameOrLabel.trim();
|
|
420
542
|
const exact = LEGACY_TO_ID.get(s);
|
|
421
543
|
if (exact) return exact;
|
|
422
|
-
const unwrapped =
|
|
544
|
+
const unwrapped = ruleNameInLabel(s);
|
|
423
545
|
if (unwrapped !== void 0) return checkIdForRule(unwrapped);
|
|
424
546
|
if (s.startsWith("shield:project-jail:")) {
|
|
425
547
|
return PROJECT_JAIL_BLOCK_READ.test(s) ? "data.credential-files" : "data.credential-files-other";
|
|
@@ -498,19 +620,21 @@ function resolveCheck(settings, id) {
|
|
|
498
620
|
if (!def) return void 0;
|
|
499
621
|
if (isLockedCheck(def)) return { id, value: "block", source: "locked" };
|
|
500
622
|
const floor = def.floor ? CHECK_VERDICT_RANK[def.floor] : 0;
|
|
623
|
+
const levelDefault = levelCheckValue(settings.mode, id) ?? def.defaultValue;
|
|
624
|
+
const sourceOf = (value2) => value2 === levelDefault ? "default" : "configured";
|
|
501
625
|
const explicit = settings.checks?.[id];
|
|
502
626
|
if (isVerdict(explicit)) {
|
|
503
627
|
const value2 = CHECK_VERDICT_RANK[explicit] < floor ? def.floor : explicit;
|
|
504
|
-
return { id, value: value2, source: value2
|
|
628
|
+
return { id, value: value2, source: sourceOf(value2) };
|
|
505
629
|
}
|
|
506
630
|
if (def.pack) {
|
|
507
631
|
const on = settings.appliedShields?.includes(def.pack) ?? false;
|
|
508
632
|
return { id, value: on ? def.defaultValue : "off", source: "default" };
|
|
509
633
|
}
|
|
510
|
-
const
|
|
511
|
-
if (
|
|
512
|
-
const value = CHECK_VERDICT_RANK[
|
|
513
|
-
return { id, value, source: value
|
|
634
|
+
const read2 = LEGACY_READERS[id]?.(settings);
|
|
635
|
+
if (read2 === void 0) return { id, value: levelDefault, source: "default" };
|
|
636
|
+
const value = CHECK_VERDICT_RANK[read2] < floor ? def.floor : read2;
|
|
637
|
+
return { id, value, source: sourceOf(value) };
|
|
514
638
|
}
|
|
515
639
|
function checkValue(settings, id) {
|
|
516
640
|
return resolveCheck(settings, id)?.value ?? "off";
|
|
@@ -577,6 +701,102 @@ function scanViews(text) {
|
|
|
577
701
|
}
|
|
578
702
|
return views;
|
|
579
703
|
}
|
|
704
|
+
function isAgentInstructionPath(absPath, root) {
|
|
705
|
+
const r = root.replace(/\/$/, "");
|
|
706
|
+
if (!(absPath === r || absPath.startsWith(r + "/"))) return false;
|
|
707
|
+
const rel = absPath.slice(r.length + 1);
|
|
708
|
+
if (AGENT_INSTRUCTION_FILES.includes(rel)) return true;
|
|
709
|
+
return AGENT_INSTRUCTION_DIRS.some((d) => rel === d || rel.startsWith(d + "/"));
|
|
710
|
+
}
|
|
711
|
+
function isInside(root, p) {
|
|
712
|
+
const r = root.replace(/\/$/, "") || "/";
|
|
713
|
+
return p === r || p.startsWith(r === "/" ? "/" : r + "/");
|
|
714
|
+
}
|
|
715
|
+
function isPasswordHashPath(p) {
|
|
716
|
+
return PASSWORD_HASH_FILES.includes(p);
|
|
717
|
+
}
|
|
718
|
+
function isLegacySystemPath(p) {
|
|
719
|
+
return LEGACY_SYSTEM_PATH_RE.test(p);
|
|
720
|
+
}
|
|
721
|
+
function normalizeFilePath(p, cwd = "", home = "~") {
|
|
722
|
+
if (!p.startsWith("/") && !p.startsWith("~") && cwd) p = `${cwd}/${p}`;
|
|
723
|
+
if (p === "~" || p.startsWith("~/")) p = home + p.slice(1);
|
|
724
|
+
const rooted = p.startsWith("/");
|
|
725
|
+
const parts = [];
|
|
726
|
+
for (const part of p.split("/")) {
|
|
727
|
+
if (!part || part === ".") continue;
|
|
728
|
+
if (part === ".." && parts.length && parts.at(-1) !== "..") parts.pop();
|
|
729
|
+
else if (part !== ".." || !rooted) parts.push(part);
|
|
730
|
+
}
|
|
731
|
+
return (rooted ? "/" : "") + parts.join("/");
|
|
732
|
+
}
|
|
733
|
+
function inlineLanguageOf(interp) {
|
|
734
|
+
const i = interp.toLowerCase();
|
|
735
|
+
if (/^python[\d.]*$/.test(i)) return "python";
|
|
736
|
+
if (/^(?:node|tsx|ts-node|deno|bun)$/.test(i)) return "node";
|
|
737
|
+
if (i === "perl") return "perl";
|
|
738
|
+
if (i === "ruby" || i === "irb") return "ruby";
|
|
739
|
+
if (i === "php") return "php";
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
function resolvePythonAliases(code) {
|
|
743
|
+
const aliases = [...code.matchAll(/\bimport\s+([\w.]+)\s+as\s+(\w+)/g)].slice(0, 32);
|
|
744
|
+
let out = code;
|
|
745
|
+
for (const [, mod, alias] of aliases) {
|
|
746
|
+
if (alias === mod) continue;
|
|
747
|
+
out = out.replace(new RegExp(String.raw`(?<![\w.])${alias}\b`, "g"), mod);
|
|
748
|
+
}
|
|
749
|
+
return out;
|
|
750
|
+
}
|
|
751
|
+
function protectedLiteral(code, isProtected) {
|
|
752
|
+
let n = 0;
|
|
753
|
+
for (const m of code.matchAll(STRING_LITERAL)) {
|
|
754
|
+
if (++n > 2048) break;
|
|
755
|
+
const value = m[2].trim();
|
|
756
|
+
if (value && isProtected(value)) return value;
|
|
757
|
+
}
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
function amplifies(language, code) {
|
|
761
|
+
const loop = language === "python" ? /\bwhile\s+(?:True|1)\s*:/ : language === "node" ? /\bwhile\s*\(\s*(?:true|1)\s*\)|\bfor\s*\(\s*;\s*;\s*\)|\bsetInterval\s*\(/ : /\bwhile\s*\(?\s*(?:1|true)\s*\)?|\bloop\s+do\b|\bfor\s*\(\s*;\s*;\s*\)/;
|
|
762
|
+
const m = loop.exec(code);
|
|
763
|
+
if (!m) return null;
|
|
764
|
+
const body = code.slice(m.index);
|
|
765
|
+
return /\.write\s*\(|\.(?:append|write)File(?:Sync)?\s*\(|\bopen\s*\(|\bprint\s*\(\s*[^)\n]*\bfile\s*=|\.send(?:all|to)?\s*\(|\bfwrite\s*\(|\bfile_put_contents\s*\(|\bsyswrite\b/.test(
|
|
766
|
+
body
|
|
767
|
+
) ? m[0] : null;
|
|
768
|
+
}
|
|
769
|
+
function judgeInlineCode(language, code, isProtected) {
|
|
770
|
+
if (code.length > MAX_INLINE_CODE) {
|
|
771
|
+
return { outcome: "unreadable", what: "is too long for node9 to read" };
|
|
772
|
+
}
|
|
773
|
+
const text = language === "python" ? resolvePythonAliases(code) : code;
|
|
774
|
+
for (const [category, re] of TABLES[language]) {
|
|
775
|
+
const m = re.exec(text);
|
|
776
|
+
if (!m) continue;
|
|
777
|
+
const evidence = m[0].trim().replace(/\s+/g, " ").slice(0, 60);
|
|
778
|
+
return {
|
|
779
|
+
outcome: category === "builds-code" ? "unreadable" : "dangerous",
|
|
780
|
+
what: WHAT[category],
|
|
781
|
+
evidence,
|
|
782
|
+
category
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
const path28 = protectedLiteral(text, isProtected);
|
|
786
|
+
if (path28) {
|
|
787
|
+
return {
|
|
788
|
+
outcome: "dangerous",
|
|
789
|
+
what: WHAT["reads-protected"],
|
|
790
|
+
evidence: path28.slice(0, 60),
|
|
791
|
+
category: "reads-protected"
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
const loop = amplifies(language, text);
|
|
795
|
+
if (loop) {
|
|
796
|
+
return { outcome: "dangerous", what: WHAT.amplifies, evidence: loop, category: "amplifies" };
|
|
797
|
+
}
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
580
800
|
function isCatHeredocOrLit(part) {
|
|
581
801
|
if (!part) return false;
|
|
582
802
|
const t = syntax.NodeType(part);
|
|
@@ -645,6 +865,55 @@ function cachedNormalize(command, compute) {
|
|
|
645
865
|
function normalizeCommandForPolicy(command) {
|
|
646
866
|
return commandReadingsImpl(command).posix;
|
|
647
867
|
}
|
|
868
|
+
function fileAccessView(command) {
|
|
869
|
+
const hit = accessViewCache.get(command);
|
|
870
|
+
if (hit !== void 0) return hit;
|
|
871
|
+
const view = fileAccessViewImpl(command);
|
|
872
|
+
if (accessViewCache.size >= ACCESS_VIEW_CACHE_MAX) {
|
|
873
|
+
const oldest = accessViewCache.keys().next().value;
|
|
874
|
+
if (oldest !== void 0) accessViewCache.delete(oldest);
|
|
875
|
+
}
|
|
876
|
+
accessViewCache.set(command, view);
|
|
877
|
+
return view;
|
|
878
|
+
}
|
|
879
|
+
function fileAccessViewImpl(command) {
|
|
880
|
+
const text = command.replace(INTERPRETER_LINE, "");
|
|
881
|
+
const f = parseShared(text);
|
|
882
|
+
if (f === PARSE_FAIL) return text;
|
|
883
|
+
const toCharIndex = byteOffsetToCharIndex(text);
|
|
884
|
+
const at = (byteOffset) => toCharIndex === null ? byteOffset : toCharIndex(byteOffset);
|
|
885
|
+
const edits = [];
|
|
886
|
+
try {
|
|
887
|
+
syntax.Walk(f, (node) => {
|
|
888
|
+
if (!node) return false;
|
|
889
|
+
const n = node;
|
|
890
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
891
|
+
const args = n.Args || [];
|
|
892
|
+
const words = args.map((a) => resolveWordLiteral(a));
|
|
893
|
+
const head = unwrapCommandHead(words);
|
|
894
|
+
for (const i of /* @__PURE__ */ new Set([0, head])) {
|
|
895
|
+
const word = words[i];
|
|
896
|
+
if (i >= args.length || word === null || word === void 0 || !/[\\/]/.test(word)) {
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
if (!PROGRAM_DIR.test(word)) continue;
|
|
900
|
+
const name = word.split(/[\\/]/).pop() ?? "";
|
|
901
|
+
if (!name) continue;
|
|
902
|
+
const s = at(args[i].Pos().Offset());
|
|
903
|
+
const e = at(args[i].End().Offset());
|
|
904
|
+
if (s < 0 || e < 0) continue;
|
|
905
|
+
edits.push([s, e, name]);
|
|
906
|
+
}
|
|
907
|
+
return true;
|
|
908
|
+
});
|
|
909
|
+
} catch {
|
|
910
|
+
return text;
|
|
911
|
+
}
|
|
912
|
+
edits.sort((a, b) => b[0] - a[0]);
|
|
913
|
+
let out = text;
|
|
914
|
+
for (const [s, e, rep] of edits) out = out.slice(0, s) + rep + out.slice(e);
|
|
915
|
+
return out;
|
|
916
|
+
}
|
|
648
917
|
function commandReadings(command) {
|
|
649
918
|
const r = commandReadingsImpl(command);
|
|
650
919
|
const out = /* @__PURE__ */ new Set([r.posix, r.separator, r.folded]);
|
|
@@ -1100,53 +1369,119 @@ function unwrapCommandHead(words, stopAtFind = false, stopAtDynamic = false) {
|
|
|
1100
1369
|
}
|
|
1101
1370
|
return i;
|
|
1102
1371
|
}
|
|
1103
|
-
function
|
|
1372
|
+
function heredocText(word) {
|
|
1373
|
+
const parts = word?.Parts ?? [];
|
|
1374
|
+
let out = "";
|
|
1375
|
+
for (const p of parts) {
|
|
1376
|
+
if (syntax.NodeType(p) !== "Lit") return null;
|
|
1377
|
+
out += p.Value ?? "";
|
|
1378
|
+
}
|
|
1379
|
+
return out;
|
|
1380
|
+
}
|
|
1381
|
+
function stdinCodeOf(redirs) {
|
|
1382
|
+
const heredoc = /* @__PURE__ */ new Set([deriveRedirOp("cat <<X\nX"), deriveRedirOp("cat <<-X\nX")]);
|
|
1383
|
+
const herestring = deriveRedirOp("cat <<< x");
|
|
1384
|
+
for (const r of redirs) {
|
|
1385
|
+
if (!r || !redirStdinOps().has(r.Op)) continue;
|
|
1386
|
+
if (heredoc.has(r.Op)) {
|
|
1387
|
+
const text = heredocText(r.Hdoc);
|
|
1388
|
+
return text === null ? { code: null, source: "variable" } : { code: text };
|
|
1389
|
+
}
|
|
1390
|
+
if (r.Op === herestring) {
|
|
1391
|
+
const text = resolveWordLiteral(r.Word);
|
|
1392
|
+
return text === null ? { code: null, source: "variable" } : { code: text };
|
|
1393
|
+
}
|
|
1394
|
+
return { code: null, source: "file" };
|
|
1395
|
+
}
|
|
1396
|
+
return { code: null, source: "file" };
|
|
1397
|
+
}
|
|
1398
|
+
function attachedCode(interp, flag) {
|
|
1399
|
+
const eq = flag.indexOf("=");
|
|
1400
|
+
if (flag.startsWith("--")) return eq >= 0 ? flag.slice(eq + 1) : null;
|
|
1401
|
+
if (/^(pwsh|powershell)/.test(interp)) return null;
|
|
1402
|
+
const letters = /^(perl|ruby|lua|bun|osascript|rscript|irb)$/.test(interp) ? "e" : /^(node|tsx|ts-node)$/.test(interp) ? "ep" : interp === "php" ? "r" : "c";
|
|
1403
|
+
const body = flag.slice(1);
|
|
1404
|
+
const at = [...body].findIndex((ch) => letters.includes(ch.toLowerCase()));
|
|
1405
|
+
if (at < 0) return null;
|
|
1406
|
+
let rest = body.slice(at + 1);
|
|
1407
|
+
if (letters === "ep" && /^[ep]*$/i.test(rest)) rest = "";
|
|
1408
|
+
return rest === "" ? null : rest;
|
|
1409
|
+
}
|
|
1410
|
+
function inlineExecCall(stmt, pipeFed) {
|
|
1104
1411
|
const cmd = stmt?.Cmd;
|
|
1105
|
-
if (!cmd || syntax.NodeType(cmd) !== "CallExpr") return
|
|
1412
|
+
if (!cmd || syntax.NodeType(cmd) !== "CallExpr") return null;
|
|
1106
1413
|
const words = (cmd.Args || []).map((a) => resolveWordLiteral(a));
|
|
1107
|
-
if (words.length === 0) return
|
|
1414
|
+
if (words.length === 0) return null;
|
|
1108
1415
|
const headIdx = unwrapCommandHead(words);
|
|
1109
1416
|
const rawHead = words[headIdx];
|
|
1110
|
-
if (rawHead == null) return
|
|
1417
|
+
if (rawHead == null) return null;
|
|
1111
1418
|
const interp = (rawHead.split("/").pop() ?? "").toLowerCase();
|
|
1112
|
-
if (!INLINE_INTERPRETER.test(interp)) return
|
|
1419
|
+
if (!INLINE_INTERPRETER.test(interp)) return null;
|
|
1113
1420
|
let args = words.slice(headIdx + 1);
|
|
1114
|
-
if (interp === "deno" && (args[0] ?? "").toLowerCase() === "eval")
|
|
1421
|
+
if (interp === "deno" && (args[0] ?? "").toLowerCase() === "eval") {
|
|
1422
|
+
const code = args[1] ?? null;
|
|
1423
|
+
return { interp, code: [code], source: code === null ? "variable" : void 0 };
|
|
1424
|
+
}
|
|
1115
1425
|
if (INTERP_LEADING_TARGET.has(interp)) {
|
|
1116
1426
|
const firstFlag = args.findIndex((a) => a == null || a.startsWith("-"));
|
|
1117
1427
|
args = firstFlag >= 0 ? args.slice(firstFlag) : [];
|
|
1118
1428
|
}
|
|
1429
|
+
const redirs = stmt.Redirs || cmd.Redirs || [];
|
|
1430
|
+
const stdinFed = redirs.some((r) => r && redirStdinOps().has(r.Op));
|
|
1431
|
+
const fromStdin = () => {
|
|
1432
|
+
if (stdinFed) {
|
|
1433
|
+
const { code, source } = stdinCodeOf(redirs);
|
|
1434
|
+
return { interp, code: [code], source };
|
|
1435
|
+
}
|
|
1436
|
+
return { interp, code: [null], source: "pipe" };
|
|
1437
|
+
};
|
|
1119
1438
|
let positionals2 = 0;
|
|
1120
1439
|
let selectedProgram = false;
|
|
1440
|
+
const codes = [];
|
|
1121
1441
|
for (let i = 0; i < args.length; i++) {
|
|
1122
1442
|
const a = args[i];
|
|
1123
1443
|
if (a == null) {
|
|
1444
|
+
if (codes.length > 0) break;
|
|
1124
1445
|
positionals2++;
|
|
1125
1446
|
selectedProgram = true;
|
|
1126
1447
|
continue;
|
|
1127
1448
|
}
|
|
1128
1449
|
if (!selectedProgram && isInlineCodeFlag(interp, a)) {
|
|
1129
|
-
if (
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1450
|
+
if (/^(bash|sh|zsh|dash|ksh)$/.test(interp)) {
|
|
1451
|
+
const payload = args[i + 1];
|
|
1452
|
+
if (payload == null) return { interp, code: [null], source: "variable" };
|
|
1453
|
+
return shellPayloadNeedsReview(payload) ? { interp, code: [null], source: "payload" } : null;
|
|
1454
|
+
}
|
|
1455
|
+
const attached = attachedCode(interp, a);
|
|
1456
|
+
if (attached !== null) {
|
|
1457
|
+
codes.push(attached);
|
|
1458
|
+
} else {
|
|
1459
|
+
codes.push(args[i + 1] ?? null);
|
|
1460
|
+
i++;
|
|
1461
|
+
}
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
if (codes.length > 0) {
|
|
1465
|
+
if (a.startsWith("-")) continue;
|
|
1466
|
+
break;
|
|
1133
1467
|
}
|
|
1134
1468
|
if (a === "-m") {
|
|
1135
1469
|
selectedProgram = true;
|
|
1136
1470
|
continue;
|
|
1137
1471
|
}
|
|
1138
|
-
if (a === "-" && !selectedProgram) return
|
|
1472
|
+
if (a === "-" && !selectedProgram) return fromStdin();
|
|
1139
1473
|
if (!a.startsWith("-")) {
|
|
1140
1474
|
positionals2++;
|
|
1141
1475
|
selectedProgram = true;
|
|
1142
1476
|
}
|
|
1143
1477
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1478
|
+
if (codes.length > 0) {
|
|
1479
|
+
return { interp, code: codes, source: codes.includes(null) ? "variable" : void 0 };
|
|
1480
|
+
}
|
|
1146
1481
|
if (positionals2 === 0 && (stdinFed || pipeFed)) {
|
|
1147
|
-
if (!/^(bash|sh|zsh)$/i.test(interp)) return
|
|
1482
|
+
if (!/^(bash|sh|zsh)$/i.test(interp)) return fromStdin();
|
|
1148
1483
|
}
|
|
1149
|
-
return
|
|
1484
|
+
return null;
|
|
1150
1485
|
}
|
|
1151
1486
|
function shellPayloadNeedsReview(payload) {
|
|
1152
1487
|
if (payloadNesting >= MAX_PAYLOAD_NESTING) return true;
|
|
@@ -1179,7 +1514,7 @@ function shellPayloadNeedsReview(payload) {
|
|
|
1179
1514
|
payload
|
|
1180
1515
|
))
|
|
1181
1516
|
review = true;
|
|
1182
|
-
if (!review &&
|
|
1517
|
+
if (!review && inlineExecReview(payload) !== null) review = true;
|
|
1183
1518
|
} catch {
|
|
1184
1519
|
review = true;
|
|
1185
1520
|
} finally {
|
|
@@ -1187,35 +1522,77 @@ function shellPayloadNeedsReview(payload) {
|
|
|
1187
1522
|
}
|
|
1188
1523
|
return review;
|
|
1189
1524
|
}
|
|
1190
|
-
function
|
|
1525
|
+
function inlineExecCalls(command) {
|
|
1191
1526
|
const f = parseShared(command);
|
|
1192
|
-
if (f === PARSE_FAIL)
|
|
1193
|
-
|
|
1194
|
-
command
|
|
1195
|
-
);
|
|
1196
|
-
}
|
|
1197
|
-
let found = false;
|
|
1527
|
+
if (f === PARSE_FAIL) return PARSE_FAIL;
|
|
1528
|
+
const calls = [];
|
|
1198
1529
|
try {
|
|
1199
1530
|
syntax.Walk(f, (node) => {
|
|
1200
|
-
if (!node ||
|
|
1531
|
+
if (!node || calls.length >= MAX_INLINE_CALLS) return false;
|
|
1201
1532
|
const n = node;
|
|
1202
1533
|
const t = syntax.NodeType(n);
|
|
1203
1534
|
if (t === "BinaryCmd" && n.Y && !listOps().has(n.Op)) {
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
return false;
|
|
1207
|
-
}
|
|
1535
|
+
const call = inlineExecCall(n.Y, true);
|
|
1536
|
+
if (call) calls.push(call);
|
|
1208
1537
|
}
|
|
1209
|
-
if (t === "Stmt"
|
|
1210
|
-
|
|
1211
|
-
|
|
1538
|
+
if (t === "Stmt") {
|
|
1539
|
+
const call = inlineExecCall(n, false);
|
|
1540
|
+
if (call) calls.push(call);
|
|
1212
1541
|
}
|
|
1213
1542
|
return true;
|
|
1214
1543
|
});
|
|
1215
1544
|
} catch {
|
|
1216
|
-
return found;
|
|
1217
1545
|
}
|
|
1218
|
-
return
|
|
1546
|
+
return calls;
|
|
1547
|
+
}
|
|
1548
|
+
function detectInlineExec(command) {
|
|
1549
|
+
const calls = inlineExecCalls(command);
|
|
1550
|
+
if (calls === PARSE_FAIL) return INLINE_EXEC_FALLBACK.test(command);
|
|
1551
|
+
return calls.length > 0;
|
|
1552
|
+
}
|
|
1553
|
+
function isProtectedCodePath(p) {
|
|
1554
|
+
if (matchSensitivePath(p) !== null) return true;
|
|
1555
|
+
return SYSTEM_AUTH_FILES.includes(p) || SYSTEM_AUTH_DIRS.some((d) => p === d || p.startsWith(d + "/"));
|
|
1556
|
+
}
|
|
1557
|
+
function inlineExecDescription(r) {
|
|
1558
|
+
const lead = r ? `The AI is running code directly from the command line: ${inlineExecReason(r).replace(/^This/, "this")}.` : "The AI is running code directly from the command line.";
|
|
1559
|
+
return `${lead} Review the full script below before allowing it to execute.`;
|
|
1560
|
+
}
|
|
1561
|
+
function inlineExecReason(r) {
|
|
1562
|
+
const who = r.interp ? `This inline ${r.interp} code` : "This inline code";
|
|
1563
|
+
return r.evidence ? `${who} ${r.what} (${r.evidence})` : `${who} ${r.what}`;
|
|
1564
|
+
}
|
|
1565
|
+
function inlineExecReview(command) {
|
|
1566
|
+
const calls = inlineExecCalls(command);
|
|
1567
|
+
if (calls === PARSE_FAIL) {
|
|
1568
|
+
return INLINE_EXEC_FALLBACK.test(command) ? { outcome: "unreadable", interp: "", what: "could not be parsed" } : null;
|
|
1569
|
+
}
|
|
1570
|
+
let unreadable = calls.length >= MAX_INLINE_CALLS ? { outcome: "unreadable", interp: "", what: "runs more inline programs than node9 reads" } : null;
|
|
1571
|
+
for (const call of calls) {
|
|
1572
|
+
const language = inlineLanguageOf(call.interp);
|
|
1573
|
+
for (const code of call.code) {
|
|
1574
|
+
let finding = null;
|
|
1575
|
+
if (code === null) {
|
|
1576
|
+
finding = {
|
|
1577
|
+
outcome: "unreadable",
|
|
1578
|
+
interp: call.interp,
|
|
1579
|
+
what: UNREADABLE_SOURCE[call.source ?? "variable"]
|
|
1580
|
+
};
|
|
1581
|
+
} else if (language === null) {
|
|
1582
|
+
finding = {
|
|
1583
|
+
outcome: "unreadable",
|
|
1584
|
+
interp: call.interp,
|
|
1585
|
+
what: `is ${call.interp} code, which node9 does not read`
|
|
1586
|
+
};
|
|
1587
|
+
} else {
|
|
1588
|
+
const judged = judgeInlineCode(language, code, isProtectedCodePath);
|
|
1589
|
+
if (judged) finding = { ...judged, interp: call.interp };
|
|
1590
|
+
}
|
|
1591
|
+
if (finding?.outcome === "dangerous") return finding;
|
|
1592
|
+
if (finding && !unreadable) unreadable = finding;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
return unreadable;
|
|
1219
1596
|
}
|
|
1220
1597
|
function analyzeChmod777(command) {
|
|
1221
1598
|
if (!/chmod/i.test(command.replace(/[\\'"]/g, ""))) return null;
|
|
@@ -2464,40 +2841,6 @@ function analyzeShellCommand(command) {
|
|
|
2464
2841
|
}
|
|
2465
2842
|
return { actions, paths, allTokens };
|
|
2466
2843
|
}
|
|
2467
|
-
function validateRegex(pattern) {
|
|
2468
|
-
if (!pattern) return "Pattern is required";
|
|
2469
|
-
if (pattern.length > MAX_REGEX_LENGTH) return `Pattern exceeds max length of ${MAX_REGEX_LENGTH}`;
|
|
2470
|
-
try {
|
|
2471
|
-
new RegExp(pattern);
|
|
2472
|
-
} catch (e) {
|
|
2473
|
-
return `Invalid regex syntax: ${e.message}`;
|
|
2474
|
-
}
|
|
2475
|
-
if (/\\\d+[*+{]/.test(pattern)) return "Quantified backreferences are forbidden (ReDoS risk)";
|
|
2476
|
-
if (!(0, import_safe_regex22.default)(pattern)) return "Pattern rejected: potential ReDoS vulnerability detected";
|
|
2477
|
-
return null;
|
|
2478
|
-
}
|
|
2479
|
-
function getCompiledRegex(pattern, flags = "") {
|
|
2480
|
-
if (flags && !/^[gimsuy]+$/.test(flags)) return null;
|
|
2481
|
-
const key = `${pattern}\0${flags}`;
|
|
2482
|
-
if (regexCache.has(key)) {
|
|
2483
|
-
const cached = regexCache.get(key);
|
|
2484
|
-
regexCache.delete(key);
|
|
2485
|
-
regexCache.set(key, cached);
|
|
2486
|
-
return cached;
|
|
2487
|
-
}
|
|
2488
|
-
if (validateRegex(pattern) !== null) return null;
|
|
2489
|
-
try {
|
|
2490
|
-
const re = new RegExp(pattern, flags);
|
|
2491
|
-
if (regexCache.size >= REGEX_CACHE_MAX) {
|
|
2492
|
-
const oldest = regexCache.keys().next().value;
|
|
2493
|
-
if (oldest) regexCache.delete(oldest);
|
|
2494
|
-
}
|
|
2495
|
-
regexCache.set(key, re);
|
|
2496
|
-
return re;
|
|
2497
|
-
} catch {
|
|
2498
|
-
return null;
|
|
2499
|
-
}
|
|
2500
|
-
}
|
|
2501
2844
|
function matchesPattern(text, patterns) {
|
|
2502
2845
|
const p = Array.isArray(patterns) ? patterns : [patterns];
|
|
2503
2846
|
if (p.length === 0) return false;
|
|
@@ -2508,9 +2851,21 @@ function matchesPattern(text, patterns) {
|
|
|
2508
2851
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
2509
2852
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
2510
2853
|
}
|
|
2511
|
-
function
|
|
2854
|
+
function mcpServerFromToolName(toolName) {
|
|
2855
|
+
const match = toolName.match(/^mcp__([^_](?:[^_]|_(?!_))*?)__/i);
|
|
2856
|
+
return match?.[1];
|
|
2857
|
+
}
|
|
2858
|
+
function ruleServerMatches(rule, serverLabel) {
|
|
2859
|
+
const label2 = typeof serverLabel === "string" ? serverLabel.trim().replace(/[\\/]/g, "-") || void 0 : void 0;
|
|
2860
|
+
if (rule.server) {
|
|
2861
|
+
if (!label2 || !matchesPattern(label2, rule.server)) return false;
|
|
2862
|
+
}
|
|
2863
|
+
if (rule.notServer && label2 && matchesPattern(label2, rule.notServer)) return false;
|
|
2864
|
+
return true;
|
|
2865
|
+
}
|
|
2866
|
+
function getNestedValue(obj, path28) {
|
|
2512
2867
|
if (!obj || typeof obj !== "object") return null;
|
|
2513
|
-
const segments2 =
|
|
2868
|
+
const segments2 = path28.split(".");
|
|
2514
2869
|
for (const seg of segments2) {
|
|
2515
2870
|
if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
|
|
2516
2871
|
}
|
|
@@ -2530,23 +2885,26 @@ function evaluateSmartConditions(args, rule) {
|
|
|
2530
2885
|
return val;
|
|
2531
2886
|
};
|
|
2532
2887
|
const readingsCache = /* @__PURE__ */ new Map();
|
|
2533
|
-
const resolveFieldReadings = (field) => {
|
|
2534
|
-
const
|
|
2888
|
+
const resolveFieldReadings = (field, reading) => {
|
|
2889
|
+
const access = reading === "access" && field === "command";
|
|
2890
|
+
const key = access ? `${field}\0access` : field;
|
|
2891
|
+
const cached = readingsCache.get(key);
|
|
2535
2892
|
if (cached) return cached;
|
|
2536
2893
|
const primary = resolveField(field);
|
|
2537
2894
|
if (primary === null) {
|
|
2538
|
-
readingsCache.set(
|
|
2895
|
+
readingsCache.set(key, []);
|
|
2539
2896
|
return [];
|
|
2540
2897
|
}
|
|
2541
2898
|
let out = [primary];
|
|
2542
2899
|
if (field === "command") {
|
|
2543
2900
|
const raw = getNestedValue(args, field);
|
|
2544
2901
|
if (typeof raw === "string") {
|
|
2545
|
-
const
|
|
2546
|
-
|
|
2902
|
+
const text = access ? fileAccessView(raw) : raw;
|
|
2903
|
+
const collapsed = commandReadings(text).map((r) => r.replace(/\s+/g, " ").trim());
|
|
2904
|
+
out = access ? [...new Set(collapsed)] : [.../* @__PURE__ */ new Set([primary, ...collapsed])];
|
|
2547
2905
|
}
|
|
2548
2906
|
}
|
|
2549
|
-
readingsCache.set(
|
|
2907
|
+
readingsCache.set(key, out);
|
|
2550
2908
|
return out;
|
|
2551
2909
|
};
|
|
2552
2910
|
const results = rule.conditions.map((cond) => {
|
|
@@ -2564,14 +2922,14 @@ function evaluateSmartConditions(args, rule) {
|
|
|
2564
2922
|
if (val === null || !cond.value) return false;
|
|
2565
2923
|
const reM = getCompiledRegex(cond.value, cond.flags ?? "");
|
|
2566
2924
|
if (!reM) return false;
|
|
2567
|
-
return resolveFieldReadings(cond.field).some((v) => reM.test(v));
|
|
2925
|
+
return resolveFieldReadings(cond.field, cond.reading).some((v) => reM.test(v));
|
|
2568
2926
|
}
|
|
2569
2927
|
case "notMatches": {
|
|
2570
2928
|
if (!cond.value) return false;
|
|
2571
2929
|
if (val === null) return true;
|
|
2572
2930
|
const reN = getCompiledRegex(cond.value, cond.flags ?? "");
|
|
2573
2931
|
if (!reN) return false;
|
|
2574
|
-
return !resolveFieldReadings(cond.field).some((v) => reN.test(v));
|
|
2932
|
+
return !resolveFieldReadings(cond.field, cond.reading).some((v) => reN.test(v));
|
|
2575
2933
|
}
|
|
2576
2934
|
case "matchesGlob":
|
|
2577
2935
|
return val !== null && cond.value ? import_picomatch2.default.isMatch(val, cond.value) : false;
|
|
@@ -3413,35 +3771,6 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
3413
3771
|
}
|
|
3414
3772
|
return out;
|
|
3415
3773
|
}
|
|
3416
|
-
function isAgentInstructionPath(absPath, root) {
|
|
3417
|
-
const r = root.replace(/\/$/, "");
|
|
3418
|
-
if (!(absPath === r || absPath.startsWith(r + "/"))) return false;
|
|
3419
|
-
const rel = absPath.slice(r.length + 1);
|
|
3420
|
-
if (AGENT_INSTRUCTION_FILES.includes(rel)) return true;
|
|
3421
|
-
return AGENT_INSTRUCTION_DIRS.some((d) => rel === d || rel.startsWith(d + "/"));
|
|
3422
|
-
}
|
|
3423
|
-
function isInside(root, p) {
|
|
3424
|
-
const r = root.replace(/\/$/, "") || "/";
|
|
3425
|
-
return p === r || p.startsWith(r === "/" ? "/" : r + "/");
|
|
3426
|
-
}
|
|
3427
|
-
function isPasswordHashPath(p) {
|
|
3428
|
-
return PASSWORD_HASH_FILES.includes(p);
|
|
3429
|
-
}
|
|
3430
|
-
function isLegacySystemPath(p) {
|
|
3431
|
-
return LEGACY_SYSTEM_PATH_RE.test(p);
|
|
3432
|
-
}
|
|
3433
|
-
function normalizeFilePath(p, cwd = "", home = "~") {
|
|
3434
|
-
if (!p.startsWith("/") && !p.startsWith("~") && cwd) p = `${cwd}/${p}`;
|
|
3435
|
-
if (p === "~" || p.startsWith("~/")) p = home + p.slice(1);
|
|
3436
|
-
const rooted = p.startsWith("/");
|
|
3437
|
-
const parts = [];
|
|
3438
|
-
for (const part of p.split("/")) {
|
|
3439
|
-
if (!part || part === ".") continue;
|
|
3440
|
-
if (part === ".." && parts.length && parts.at(-1) !== "..") parts.pop();
|
|
3441
|
-
else if (part !== ".." || !rooted) parts.push(part);
|
|
3442
|
-
}
|
|
3443
|
-
return (rooted ? "/" : "") + parts.join("/");
|
|
3444
|
-
}
|
|
3445
3774
|
function incompleteFileVerdict(operation = "write") {
|
|
3446
3775
|
return {
|
|
3447
3776
|
decision: "review",
|
|
@@ -3493,8 +3822,8 @@ function collectFileAccesses(tool, args, context = {}, inspection = {}) {
|
|
|
3493
3822
|
}
|
|
3494
3823
|
return out;
|
|
3495
3824
|
}
|
|
3496
|
-
function within(
|
|
3497
|
-
return
|
|
3825
|
+
function within(path28, root) {
|
|
3826
|
+
return path28 === root || path28.startsWith(root === "/" ? "/" : root + "/");
|
|
3498
3827
|
}
|
|
3499
3828
|
function hits(access, paths, directories, context) {
|
|
3500
3829
|
const home = context.homeDirs?.[0] ?? "~";
|
|
@@ -4302,14 +4631,14 @@ function sensitivePathMatch(originalPath) {
|
|
|
4302
4631
|
}
|
|
4303
4632
|
function assertBuiltinPatternsAreSafe() {
|
|
4304
4633
|
for (const p of DLP_PATTERNS) {
|
|
4305
|
-
if (!(0,
|
|
4634
|
+
if (!(0, import_safe_regex22.default)(p.regex.source)) {
|
|
4306
4635
|
throw new Error(
|
|
4307
4636
|
`[node9 engine] Builtin DLP pattern '${p.name}' is vulnerable to ReDoS: ${p.regex.source}`
|
|
4308
4637
|
);
|
|
4309
4638
|
}
|
|
4310
4639
|
}
|
|
4311
4640
|
for (const re of SENSITIVE_PATH_PATTERNS) {
|
|
4312
|
-
if (!(0,
|
|
4641
|
+
if (!(0, import_safe_regex22.default)(re.source)) {
|
|
4313
4642
|
throw new Error(
|
|
4314
4643
|
`[node9 engine] Builtin sensitive-path pattern is vulnerable to ReDoS: ${re.source}`
|
|
4315
4644
|
);
|
|
@@ -5055,6 +5384,20 @@ function extractPackageInstalls(command) {
|
|
|
5055
5384
|
return true;
|
|
5056
5385
|
});
|
|
5057
5386
|
}
|
|
5387
|
+
function tuningFromLegacyPolicy(policy) {
|
|
5388
|
+
const out = {};
|
|
5389
|
+
const p = policy;
|
|
5390
|
+
if (policy.packageCheck?.registrySignals === false) {
|
|
5391
|
+
out["loading.malicious-package"] = { newPackage: "off", installScript: "off" };
|
|
5392
|
+
}
|
|
5393
|
+
for (const f of TUNING_FIELDS) {
|
|
5394
|
+
const [block, key] = f.legacy;
|
|
5395
|
+
const value = p[block]?.[key];
|
|
5396
|
+
if (value === void 0) continue;
|
|
5397
|
+
(out[f.checkId] ??= {})[f.name] = value;
|
|
5398
|
+
}
|
|
5399
|
+
return out;
|
|
5400
|
+
}
|
|
5058
5401
|
function tuningToLegacyPolicy(policy, tuning) {
|
|
5059
5402
|
const unknown = [];
|
|
5060
5403
|
for (const [id, fields] of Object.entries(tuning)) {
|
|
@@ -5169,6 +5512,25 @@ function checkToLegacyPolicy(policy, id, v) {
|
|
|
5169
5512
|
return false;
|
|
5170
5513
|
}
|
|
5171
5514
|
}
|
|
5515
|
+
function knobStatedChecks(policy) {
|
|
5516
|
+
const out = /* @__PURE__ */ new Set();
|
|
5517
|
+
const has = (o, key) => !!o && typeof o === "object" && o[key] !== void 0;
|
|
5518
|
+
const { dlp, egress, loopDetection, injectionScan, skillPinning, packageCheck } = policy;
|
|
5519
|
+
const uc = policy.untrustedContent;
|
|
5520
|
+
if (has(dlp, "enabled")) out.add("data.secrets");
|
|
5521
|
+
if (has(dlp, "reviewAction") || dlp?.enabled === false) out.add("data.secrets-weak");
|
|
5522
|
+
if (has(dlp, "pii")) out.add("data.pii");
|
|
5523
|
+
if (has(egress, "enabled") || has(egress, "mode")) out.add("network.unknown-host");
|
|
5524
|
+
if (has(egress, "ssrfStrict")) out.add("network.internal-addresses");
|
|
5525
|
+
if (has(loopDetection, "enabled")) out.add("behavior.loops");
|
|
5526
|
+
if (has(injectionScan, "enabled")) out.add("behavior.prompt-injection");
|
|
5527
|
+
if (has(skillPinning, "enabled") || has(skillPinning, "mode")) out.add("loading.skill-tamper");
|
|
5528
|
+
if (has(packageCheck, "enabled") || has(packageCheck, "onMalicious"))
|
|
5529
|
+
out.add("loading.malicious-package");
|
|
5530
|
+
if (uc && typeof uc === "object" && (uc.enabled === false || has(uc, "afterExposure")))
|
|
5531
|
+
out.add("behavior.untrusted-content");
|
|
5532
|
+
return out;
|
|
5533
|
+
}
|
|
5172
5534
|
function isKnobGoverned(id) {
|
|
5173
5535
|
return id in KNOB_GOVERNED;
|
|
5174
5536
|
}
|
|
@@ -5433,9 +5795,9 @@ function bareToolName(toolName) {
|
|
|
5433
5795
|
const parts = toolName.split("__");
|
|
5434
5796
|
return (parts.length >= 3 ? parts.slice(2).join("__") : toolName).toLowerCase();
|
|
5435
5797
|
}
|
|
5436
|
-
function valuesAt(args,
|
|
5798
|
+
function valuesAt(args, path28) {
|
|
5437
5799
|
let cursors = [args];
|
|
5438
|
-
for (const rawSegment of
|
|
5800
|
+
for (const rawSegment of path28.split(".")) {
|
|
5439
5801
|
const isArray = rawSegment.endsWith("[]");
|
|
5440
5802
|
const key = isArray ? rawSegment.slice(0, -2) : rawSegment;
|
|
5441
5803
|
const next = [];
|
|
@@ -5466,8 +5828,8 @@ function extractToolDestinations(toolName, args) {
|
|
|
5466
5828
|
try {
|
|
5467
5829
|
const out = [];
|
|
5468
5830
|
const paths = DESTINATION_ARGS.get(bareToolName(toolName)) ?? [];
|
|
5469
|
-
for (const
|
|
5470
|
-
for (const value of valuesAt(args,
|
|
5831
|
+
for (const path28 of paths) {
|
|
5832
|
+
for (const value of valuesAt(args, path28)) {
|
|
5471
5833
|
const host2 = hostOf(value);
|
|
5472
5834
|
if (host2) out.push({ host: host2, binary: toolName, raw: value });
|
|
5473
5835
|
}
|
|
@@ -5493,8 +5855,8 @@ function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
|
5493
5855
|
try {
|
|
5494
5856
|
const paths = DESTINATION_ARGS.get(bareToolName(toolName));
|
|
5495
5857
|
if (!paths) return null;
|
|
5496
|
-
for (const
|
|
5497
|
-
for (const value of valuesAt(args,
|
|
5858
|
+
for (const path28 of paths) {
|
|
5859
|
+
for (const value of valuesAt(args, path28)) {
|
|
5498
5860
|
const host2 = hostOf(value);
|
|
5499
5861
|
if (!host2) continue;
|
|
5500
5862
|
const m = classifySsrf(host2);
|
|
@@ -5503,7 +5865,7 @@ function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
|
5503
5865
|
const decision = ssrfFloorDecision(m, written, opts);
|
|
5504
5866
|
if (decision === null) continue;
|
|
5505
5867
|
const reason = decision === "review" ? ssrfDisguisedReason(m, written) : ssrfReason(m, host2);
|
|
5506
|
-
return { ...m, argPath:
|
|
5868
|
+
return { ...m, argPath: path28, host: host2, reason, decision };
|
|
5507
5869
|
}
|
|
5508
5870
|
}
|
|
5509
5871
|
return null;
|
|
@@ -5883,11 +6245,15 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
5883
6245
|
};
|
|
5884
6246
|
const ruleCheck = (rule) => {
|
|
5885
6247
|
if (rule.verdict === "allow") return void 0;
|
|
5886
|
-
if (!rule.builtin
|
|
6248
|
+
if (!rule.builtin) return void 0;
|
|
5887
6249
|
const id = checkIdForRule(rule.name);
|
|
5888
6250
|
if (!id) return void 0;
|
|
5889
6251
|
const def = getCheck(id);
|
|
5890
6252
|
if (!def) return void 0;
|
|
6253
|
+
if (rule.pinned) {
|
|
6254
|
+
const explicit = config.policy.checks?.[id];
|
|
6255
|
+
return def.pack && config.policy.checkSources?.[id] === "workspace" && isVerdict(explicit) ? { id, value: explicit } : void 0;
|
|
6256
|
+
}
|
|
5891
6257
|
if (rule.name?.startsWith("shield:") && !def.pack) return void 0;
|
|
5892
6258
|
if (def.pack) {
|
|
5893
6259
|
const explicit = config.policy.checks?.[id];
|
|
@@ -5895,8 +6261,10 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
5895
6261
|
}
|
|
5896
6262
|
return { id, value: cv(id) };
|
|
5897
6263
|
};
|
|
6264
|
+
const serverLabel = context.mcpServer ?? mcpServerFromToolName(toolName);
|
|
5898
6265
|
const matches = config.policy.smartRules.filter((rule) => {
|
|
5899
6266
|
if (!toolMatchesRule(toolName, rule.tool, layers)) return false;
|
|
6267
|
+
if (!ruleServerMatches(rule, serverLabel)) return false;
|
|
5900
6268
|
if (astSuppressed(rule)) return false;
|
|
5901
6269
|
if (rmCleanupWaiver && rule.name === "review-rm" && rule.verdict === "review") return false;
|
|
5902
6270
|
if (!evaluateSmartConditions(shellRuleArgs(toolName, args, rule.tool, layers), rule))
|
|
@@ -5973,11 +6341,13 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
5973
6341
|
if (v?.logged) noteLogged(v);
|
|
5974
6342
|
else if (v) candidates.push(v);
|
|
5975
6343
|
};
|
|
5976
|
-
|
|
6344
|
+
const inline = reviewsEveryInline(config.settings.mode) ? detectInlineExec(shellCommand) ? void 0 : null : inlineExecReview(shellCommand);
|
|
6345
|
+
if (inline !== null) {
|
|
5977
6346
|
consider("commands.inline-exec", {
|
|
5978
6347
|
decision: "review",
|
|
5979
6348
|
blockedByLabel: "Node9 Standard (Inline Execution)",
|
|
5980
|
-
|
|
6349
|
+
...inline ? { reason: inlineExecReason(inline) } : {},
|
|
6350
|
+
ruleDescription: inlineExecDescription(inline),
|
|
5981
6351
|
tier: 3,
|
|
5982
6352
|
checkId: "commands.inline-exec"
|
|
5983
6353
|
});
|
|
@@ -6031,21 +6401,17 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
6031
6401
|
if (firstToken && firstToken.startsWith("/") && checkProvenance2) {
|
|
6032
6402
|
const prov = checkProvenance2(firstToken, cwd);
|
|
6033
6403
|
if (prov.trustLevel === "suspect") {
|
|
6034
|
-
const
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
{
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
tier: 3,
|
|
6042
|
-
checkId: "commands.temp-binary"
|
|
6043
|
-
}
|
|
6044
|
-
);
|
|
6404
|
+
const v = applyCheckValue(cv("commands.temp-binary"), {
|
|
6405
|
+
decision: "review",
|
|
6406
|
+
blockedByLabel: "Node9: Suspect Binary",
|
|
6407
|
+
reason: `Binary "${firstToken}" resolved to ${prov.resolvedPath} \u2014 ${prov.reason}`,
|
|
6408
|
+
tier: 3,
|
|
6409
|
+
checkId: "commands.temp-binary"
|
|
6410
|
+
});
|
|
6045
6411
|
if (v?.logged) noteLogged(v);
|
|
6046
6412
|
else if (v) return v;
|
|
6047
6413
|
}
|
|
6048
|
-
if (prov.trustLevel === "unknown" && config.settings.mode
|
|
6414
|
+
if (prov.trustLevel === "unknown" && isBlockAll(config.settings.mode)) {
|
|
6049
6415
|
return {
|
|
6050
6416
|
decision: "review",
|
|
6051
6417
|
blockedByLabel: "Node9: Unknown Binary (strict mode)",
|
|
@@ -6136,7 +6502,7 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
|
|
|
6136
6502
|
if (v?.logged) noteLogged(v);
|
|
6137
6503
|
else if (v) return v;
|
|
6138
6504
|
}
|
|
6139
|
-
if (config.settings.mode
|
|
6505
|
+
if (isBlockAll(config.settings.mode)) {
|
|
6140
6506
|
if (activeEnvironment?.requireApproval === false) return allowed();
|
|
6141
6507
|
return {
|
|
6142
6508
|
decision: "review",
|
|
@@ -7067,7 +7433,7 @@ function matchCanaryArgs(args, values) {
|
|
|
7067
7433
|
return null;
|
|
7068
7434
|
}
|
|
7069
7435
|
}
|
|
7070
|
-
var import_picomatch, import_safe_regex2, import_picomatch2, import_mvdan_sh,
|
|
7436
|
+
var import_picomatch, import_safe_regex2, import_picomatch2, import_mvdan_sh, import_mvdan_sh2, import_safe_regex22, import_crypto2, import_crypto3, import_mvdan_sh3, import_crypto4, import_mvdan_sh4, import_crypto5, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, finance_default, identity_default, publishing_default, ACTION_TEMPLATES, ACTION_CHECKS, GAP, prog, httpTo, httpWrite, HTTP_WRITE, HTTP_WRITE_ANY, NOT_STRIPE_TEST, postTo, STRIPE_OBJECTS, APP_SHIELD_SPECS, APP_SHIELDS, APP_SHIELD_NAMES, APP_SERVER_GLOB, GENERAL_ACTION_RULES, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, LEVELS, MODE_VALUES, SOFT, STRICT, BLOCK_ALL, LEVEL_CHECKS, VERDICTS, CHECK_VERDICT_RANK, ALL, BUILTIN_DANGEROUS_WORDS, PRODUCT_CHECKS, SHIELD_RULE_NAME, SHIELD_RULES_FOLDED_INTO_PRODUCT, CHECKS, CHECK_BY_ID, LEGACY_TO_ID, PROJECT_JAIL_BLOCK_READ, OVERRIDE_PREFIX, LABEL_WRAPPERS, LEGACY_READERS, INVISIBLE_RE, INVISIBLE_ONE, HOMOGLYPHS, HOMOGLYPH_RE, MAX_BLOBS, MIN_DECODED, BASE64_RUN_RE, HEX_RUN_RE, HEX_ESCAPE_RUN_RE, SYSTEM_AUTH_FILES, SYSTEM_AUTH_DIRS, PASSWORD_HASH_FILES, STARTUP_NAMES, SYSTEM_DIRS, SYSTEM_DIRS_EXEMPT, UPLOAD_SYSTEM_DIRS, AGENT_INSTRUCTION_FILES, AGENT_INSTRUCTION_DIRS, LEGACY_SYSTEM_PATH_RE, MAX_INLINE_CODE, WHAT, OUTSIDE, pyImport, either, PYTHON, NODE, PERL, RUBY, PHP, TABLES, STRING_LITERAL, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, INTERPRETER_LINE, PROGRAM_DIR, ACCESS_VIEW_CACHE_MAX, accessViewCache, MAX_READING_PAYLOADS, defaultExpansionOps, VARIABLE_READERS, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, CURL_SOURCE_FLAGS, CURL_VALUE_LETTERS, WGET_VALUE_LETTERS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_MUTATION_TOOLS, INTERP_LEADING_TARGET, MAX_PAYLOAD_NESTING, payloadNesting, MAX_INLINE_CALLS, INLINE_EXEC_FALLBACK, UNREADABLE_SOURCE, SCRIPT_RUNNERS, SCRIPT_RUNNER_VALUE_FLAGS, MAX_SCRIPT_OPERANDS, NET_BINARIES, VALUE_FLAGS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, MAX_CALL_DEPTH, MAX_CALL_WALKS, callDepth, callWalks, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, BUILTIN_FIELDS, ARGV_FIELDS, BARE_WORD, MAX_ARGV_CHARACTERS, syntax2, MAX_ALTERNATIVES, MAX_CALL_DEPTH2, MAX_CALL_WALKS2, POSITIONAL, MAX_DEPTH, MAX_PAYLOAD_DEPTH, unique, clone, SENDING_STDIN, DOWNLOAD_SHORT_VALUE, DOWNLOAD_LONG_VALUE, DOWNLOAD_OUTPUT_FLAGS, DOWNLOAD_DIR_FLAGS, base, quoteGlob, asWord, WRITES, READS, DUP_OUT, HERE_STRING, AND, OR, DEFAULT_OPS, exempt, RANK, BIP39_ENGLISH, BIP39_PUBLIC_VECTORS, indices, lengths, hash, MAX_MNEMONIC_HASHES, SECRET_VIEW_LIMITS, IBAN_LENGTH, IBAN_BBAN_SHAPE, B58, B58_INDEX, XPRV_VERSIONS, B62, CRC32_TABLE, GITHUB_TOKEN_RE, B64URL, B64URL_INDEX, CASK_TWO_ZERO_SUFFIX, CASK_FOUR_ZERO_SUFFIX, CF_ODD, CF_EVEN, SPANISH_LETTERS, HETU_CHARS, MAX, UNTRUSTED_TOOLS, SIGNALS, MENTION_QUOTE_RE, MENTION_WORD_RE, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, REFERENCE_VALUE_RE, IDENTIFIER_VALUE_RE, PATH_VALUE_RE, DOTTED_REFERENCE_RE, CONCATENATION_RE, EMAIL_VALUE_RE, FIXTURE_NAME_RE, DERIVATIVE_NAME_RE, QUERY_CREDENTIAL_VALUE_RE, OTP_SECRET_RE, OTP_DOC_SECRETS, ASSIGNED_SECRET_VALUE_RE, ASSIGNED_SECRET_NAME_RE, NAME_KEYWORD_RE, ARN_CONTEXT_RE, CIPHER_KEY_NAME_RE, CIPHER_NAME_PARTS, CIPHER_KEY_VALUE_RE, LARAVEL_KEY_VALUE_RE, SQL_PASSWORD_VALUE_RE, GRAFANA_KEY_VALUE_RE, BASIC_VALUE_RE, BASIC_PAIR_RE, OTP_CAPTURE_RE, DB_URL_PASSWORD_RE, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, SENSITIVE_PATH_REGEXES, MAX_DEPTH2, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, BATCH_SEPARATOR, syntax3, NPM_INSTALL_VERBS, NPM_EXEC_VERBS, PNPM_INSTALL_VERBS, YARN_INSTALL_VERBS, BUN_INSTALL_VERBS, DLX_VERBS, NON_REGISTRY_RE, NPM_NAME_RE, EXACT_SEMVER_RE, NPM_VALUE_FLAGS, PNPM_VALUE_FLAGS, YARN_VALUE_FLAGS, BUN_VALUE_FLAGS, PIP_VALUE_FLAGS, UV_VALUE_FLAGS, POETRY_VALUE_FLAGS, PIPENV_VALUE_FLAGS, PIPX_VALUE_FLAGS, PY_SPEC_RE, PY_EXACT_RE, EXACT_VERSION_RE, GEM_NAME_RE, GEM_VALUE_FLAGS, BUNDLE_VALUE_FLAGS, CRATE_NAME_RE, CARGO_ADD_VALUE_FLAGS, CARGO_INSTALL_VALUE_FLAGS, GO_VALUE_FLAGS, GO_PATH_RE, NUGET_NAME_RE, DOTNET_ADD_VALUE_FLAGS, DOTNET_TOOL_VALUE_FLAGS, NUGET_VALUE_FLAGS, MAVEN_COORD_RE, COMPOSER_NAME_RE, COMPOSER_VALUE_FLAGS, EDITOR_HEADS, EXTENSION_ID_RE, MANAGER_HEADS, SHELLS, MAX_NESTING, SHELL_VALUE_FLAGS, TUNING_FIELDS, MAP_GOVERNED, KNOB_GOVERNED, CONFIGURABLE_CHECK_IDS, DEFAULT_EGRESS_ALLOWLIST, PRIVATE_HOST_SUFFIXES, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, DESTINATION_ARGS, MCP_DESTINATION_KEYS, ARGUMENT_SCAN_LIMITS, RUN_CHAR, runChar, VERDICT_RANK, SQL_DML_KEYWORDS, MAX_DESTINATIONS, MAX_SCAN_CHARS, EMAIL_RE, URL_RE, IBAN_RE, RECIPIENT_KEYS, DESTRUCTIVE_VERB_RE, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_CC14_RE, PII_CC13_RE, PII_CC19_RE, CARD_PATTERNS, PII_ROUTING_RE, ROUTING_LABEL_RE, ROUTING_LABEL_WINDOW, NATIONAL_ID_SHAPES, NATIONAL_ID_LABEL_RE, NATIONAL_ID_LABEL_WINDOW, NATIONAL_ID_GUARD_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, IBAN_DESTINATION_KEYS, WHOLE_IBAN_RE, JSON_ESCAPE_RE, JSON_ESCAPES, MASKED_KINDS, ENTRY_RE, IBAN_TEXT_RE, EMAIL_TEXT_RE, ACCOUNT_KEY_RE, ACCOUNT_VALUE_RE, MAX_MASK_DEPTH, syntax4, EMPTY, CURL_SEND_FLAGS, WGET_SEND_FLAGS, IWR_SEND_FLAGS, WRITE_METHODS, GH_READ, GH_WRITE, GH_HOSTNAME_FLAGS, GH_REPO_VALUE_FLAGS, GH_API_VALUE_FLAGS, GIT_FETCH, PUBLISH, SHELLS2, OWN_HEADS, MAX_DEPTH3, CWD_CHANGERS, TERMINAL_ESCAPE_RE, LOOP_MAX_RECORDS, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH4, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES;
|
|
7071
7437
|
var init_dist = __esm({
|
|
7072
7438
|
"packages/policy-engine/dist/index.mjs"() {
|
|
7073
7439
|
"use strict";
|
|
@@ -7075,15 +7441,17 @@ var init_dist = __esm({
|
|
|
7075
7441
|
import_safe_regex2 = __toESM(require("safe-regex2"), 1);
|
|
7076
7442
|
import_picomatch2 = __toESM(require("picomatch"), 1);
|
|
7077
7443
|
import_mvdan_sh = __toESM(require("mvdan-sh"), 1);
|
|
7078
|
-
import_safe_regex22 = __toESM(require("safe-regex2"), 1);
|
|
7079
7444
|
import_mvdan_sh2 = __toESM(require("mvdan-sh"), 1);
|
|
7080
|
-
|
|
7445
|
+
import_safe_regex22 = __toESM(require("safe-regex2"), 1);
|
|
7081
7446
|
import_crypto2 = require("crypto");
|
|
7082
7447
|
import_crypto3 = require("crypto");
|
|
7083
7448
|
import_mvdan_sh3 = __toESM(require("mvdan-sh"), 1);
|
|
7084
7449
|
import_crypto4 = require("crypto");
|
|
7085
7450
|
import_mvdan_sh4 = __toESM(require("mvdan-sh"), 1);
|
|
7086
7451
|
import_crypto5 = __toESM(require("crypto"), 1);
|
|
7452
|
+
MAX_REGEX_LENGTH = 256;
|
|
7453
|
+
REGEX_CACHE_MAX = 500;
|
|
7454
|
+
regexCache = /* @__PURE__ */ new Map();
|
|
7087
7455
|
aws_default = {
|
|
7088
7456
|
name: "aws",
|
|
7089
7457
|
description: "Protects AWS infrastructure from destructive AI operations",
|
|
@@ -7385,77 +7753,6 @@ var init_dist = __esm({
|
|
|
7385
7753
|
],
|
|
7386
7754
|
dangerousWords: ["wipefs"]
|
|
7387
7755
|
};
|
|
7388
|
-
finance_default = {
|
|
7389
|
-
name: "finance",
|
|
7390
|
-
description: "Asks before money moves through a payment or banking app (refunds, payouts, transfers, charges, invoices), whatever the request said. Reads are never asked about.",
|
|
7391
|
-
aliases: ["payments", "money"],
|
|
7392
|
-
smartRules: [
|
|
7393
|
-
{
|
|
7394
|
-
name: "shield:finance:review-refund",
|
|
7395
|
-
tool: "{,mcp__*__}{create_refund,refund_payment,refund_charge,refund_order,issue_refund}",
|
|
7396
|
-
conditions: [],
|
|
7397
|
-
verdict: "review",
|
|
7398
|
-
reason: "A refund returns money to a customer. node9 asks before money moves, whatever the request said (Finance shield)."
|
|
7399
|
-
},
|
|
7400
|
-
{
|
|
7401
|
-
name: "shield:finance:review-payout",
|
|
7402
|
-
tool: "{,mcp__*__}{create_payout,send_payout,payout,create_payouts_batch}",
|
|
7403
|
-
conditions: [],
|
|
7404
|
-
verdict: "review",
|
|
7405
|
-
reason: "A payout moves the balance to a bank account. node9 asks before money moves (Finance shield)."
|
|
7406
|
-
},
|
|
7407
|
-
{
|
|
7408
|
-
name: "shield:finance:review-transfer",
|
|
7409
|
-
tool: "{,mcp__*__}{transfer_create,transfer_authorization_create,create_transfer,transfer_money,transfer_funds,send_money,wire_transfer,create_wire,schedule_transaction,update_scheduled_transaction,create_transaction}",
|
|
7410
|
-
conditions: [],
|
|
7411
|
-
verdict: "review",
|
|
7412
|
-
reason: "A transfer moves money between accounts. node9 asks before money moves (Finance shield)."
|
|
7413
|
-
},
|
|
7414
|
-
{
|
|
7415
|
-
name: "shield:finance:review-charge",
|
|
7416
|
-
tool: "{,mcp__*__}{create_payment_intent,confirm_payment_intent,capture_payment_intent,create_charge,create_payment,capture_payment,capture_order,create_order,authorize_order,create_checkout_session}",
|
|
7417
|
-
conditions: [],
|
|
7418
|
-
verdict: "review",
|
|
7419
|
-
reason: "This charges a card or captures a payment. node9 asks before money moves (Finance shield)."
|
|
7420
|
-
},
|
|
7421
|
-
{
|
|
7422
|
-
name: "shield:finance:review-payment-link",
|
|
7423
|
-
tool: "{,mcp__*__}{create_payment_link,create_invoice,finalize_invoice,send_invoice,pay_invoice,create_invoice_item}",
|
|
7424
|
-
conditions: [],
|
|
7425
|
-
verdict: "review",
|
|
7426
|
-
reason: "This bills someone or creates a link that charges a card. node9 asks before money moves (Finance shield)."
|
|
7427
|
-
},
|
|
7428
|
-
{
|
|
7429
|
-
name: "shield:finance:review-subscription",
|
|
7430
|
-
tool: "{,mcp__*__}{create_subscription,cancel_subscription,update_subscription,pause_subscription,resume_subscription}",
|
|
7431
|
-
conditions: [],
|
|
7432
|
-
verdict: "review",
|
|
7433
|
-
reason: "A subscription starts or stops recurring charges. node9 asks before money moves (Finance shield)."
|
|
7434
|
-
},
|
|
7435
|
-
{
|
|
7436
|
-
name: "shield:finance:review-dispute",
|
|
7437
|
-
tool: "{,mcp__*__}{update_dispute,accept_dispute,close_dispute}",
|
|
7438
|
-
conditions: [],
|
|
7439
|
-
verdict: "review",
|
|
7440
|
-
reason: "This answers or concedes a chargeback. node9 asks before money moves (Finance shield)."
|
|
7441
|
-
},
|
|
7442
|
-
{
|
|
7443
|
-
name: "shield:finance:review-card",
|
|
7444
|
-
tool: "{,mcp__*__}{issue_card,create_virtual_card,create_issuing_card,update_card_limit}",
|
|
7445
|
-
conditions: [],
|
|
7446
|
-
verdict: "review",
|
|
7447
|
-
reason: "This issues a card or changes its limit. node9 asks before money moves (Finance shield)."
|
|
7448
|
-
},
|
|
7449
|
-
{
|
|
7450
|
-
name: "shield:finance:review-withdraw",
|
|
7451
|
-
tool: "{,mcp__*__}{withdraw,withdraw_funds,create_withdrawal,request_withdrawal}",
|
|
7452
|
-
conditions: [],
|
|
7453
|
-
verdict: "review",
|
|
7454
|
-
reason: "A withdrawal moves money out. node9 asks before money moves (Finance shield)."
|
|
7455
|
-
}
|
|
7456
|
-
],
|
|
7457
|
-
dangerousWords: []
|
|
7458
|
-
};
|
|
7459
7756
|
github_default = {
|
|
7460
7757
|
name: "github",
|
|
7461
7758
|
description: "Protects GitHub repositories from destructive AI operations",
|
|
@@ -7482,56 +7779,6 @@ var init_dist = __esm({
|
|
|
7482
7779
|
],
|
|
7483
7780
|
dangerousWords: []
|
|
7484
7781
|
};
|
|
7485
|
-
identity_default = {
|
|
7486
|
-
name: "identity",
|
|
7487
|
-
description: "Asks before a password, a key, a token, a role or a user changes in an identity or admin app, whatever the request said. Reads are never asked about.",
|
|
7488
|
-
aliases: ["access", "iam"],
|
|
7489
|
-
smartRules: [
|
|
7490
|
-
{
|
|
7491
|
-
name: "shield:identity:review-password",
|
|
7492
|
-
tool: "{,mcp__*__}{change_password,update_password,reset_password,set_password,expire_password,force_password_reset}",
|
|
7493
|
-
conditions: [],
|
|
7494
|
-
verdict: "review",
|
|
7495
|
-
reason: "This changes or resets a password. node9 asks before credentials change, whatever the request said (Identity shield)."
|
|
7496
|
-
},
|
|
7497
|
-
{
|
|
7498
|
-
name: "shield:identity:review-key",
|
|
7499
|
-
tool: "{,mcp__*__}{rotate_key,rotate_api_key,create_api_key,create_token,create_access_token,create_personal_access_token,regenerate_secret,rotate_secret,create_service_account_key,create_credential}",
|
|
7500
|
-
conditions: [],
|
|
7501
|
-
verdict: "review",
|
|
7502
|
-
reason: "This creates or rotates a key, token or secret. node9 asks before credentials change (Identity shield)."
|
|
7503
|
-
},
|
|
7504
|
-
{
|
|
7505
|
-
name: "shield:identity:review-access",
|
|
7506
|
-
tool: "{,mcp__*__}{grant_access,grant_permission,grant_role,revoke_access,revoke_permission,revoke_role,revoke_token,set_permission,set_permissions,set_role,assign_role,update_role,add_permission,remove_permission,attach_policy,detach_policy}",
|
|
7507
|
-
conditions: [],
|
|
7508
|
-
verdict: "review",
|
|
7509
|
-
reason: "This grants or revokes access. node9 asks before access changes (Identity shield)."
|
|
7510
|
-
},
|
|
7511
|
-
{
|
|
7512
|
-
name: "shield:identity:review-membership",
|
|
7513
|
-
tool: "{,mcp__*__}{add_member,remove_member,add_user_to_group,remove_user_from_group,add_collaborator,remove_collaborator,invite_user,invite_member,invite_user_to_slack,invite_users,add_org_member,remove_org_member,add_team_member,remove_team_member}",
|
|
7514
|
-
conditions: [],
|
|
7515
|
-
verdict: "review",
|
|
7516
|
-
reason: "This changes who belongs where. node9 asks before membership changes (Identity shield)."
|
|
7517
|
-
},
|
|
7518
|
-
{
|
|
7519
|
-
name: "shield:identity:review-user",
|
|
7520
|
-
tool: "{,mcp__*__}{create_user,delete_user,deactivate_user,suspend_user,activate_user,unlock_user,make_admin,promote_user,demote_user,update_user_role}",
|
|
7521
|
-
conditions: [],
|
|
7522
|
-
verdict: "review",
|
|
7523
|
-
reason: "This creates, removes or promotes an account. node9 asks before accounts change (Identity shield)."
|
|
7524
|
-
},
|
|
7525
|
-
{
|
|
7526
|
-
name: "shield:identity:review-mfa",
|
|
7527
|
-
tool: "{,mcp__*__}{reset_mfa,reset_factors,disable_mfa,enroll_factor,deactivate_factor}",
|
|
7528
|
-
conditions: [],
|
|
7529
|
-
verdict: "review",
|
|
7530
|
-
reason: "This changes a second factor. node9 asks before credentials change (Identity shield)."
|
|
7531
|
-
}
|
|
7532
|
-
],
|
|
7533
|
-
dangerousWords: []
|
|
7534
|
-
};
|
|
7535
7782
|
k8s_default = {
|
|
7536
7783
|
name: "k8s",
|
|
7537
7784
|
description: "Protects Kubernetes clusters from destructive AI operations",
|
|
@@ -7784,7 +8031,7 @@ var init_dist = __esm({
|
|
|
7784
8031
|
{
|
|
7785
8032
|
field: "command",
|
|
7786
8033
|
op: "matches",
|
|
7787
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim
|
|
8034
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim?|emacs|code|type|[ef]?grep|rg|ag|ack|g?awk|sed|cut|tr|[jy]q|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development)){0,2}(?=[\\s;&|>)<]|$)",
|
|
7788
8035
|
flags: "i"
|
|
7789
8036
|
}
|
|
7790
8037
|
],
|
|
@@ -7798,7 +8045,7 @@ var init_dist = __esm({
|
|
|
7798
8045
|
{
|
|
7799
8046
|
field: "command",
|
|
7800
8047
|
op: "matches",
|
|
7801
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim
|
|
8048
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim?|emacs|code|type|[ef]?grep|rg|ag|ack|g?awk|sed|cut|tr|[jy]q|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
7802
8049
|
flags: "i"
|
|
7803
8050
|
}
|
|
7804
8051
|
],
|
|
@@ -7864,272 +8111,879 @@ var init_dist = __esm({
|
|
|
7864
8111
|
],
|
|
7865
8112
|
dangerousWords: []
|
|
7866
8113
|
};
|
|
7867
|
-
|
|
7868
|
-
name: "
|
|
7869
|
-
description: "
|
|
7870
|
-
aliases: [
|
|
8114
|
+
redis_default = {
|
|
8115
|
+
name: "redis",
|
|
8116
|
+
description: "Protects Redis instances from destructive AI operations",
|
|
8117
|
+
aliases: [],
|
|
8118
|
+
_scopeNote: "Every rule requires Redis to actually be involved: a client in the command (redis-cli / valkey-cli, in either order \u2014 `redis-cli FLUSHALL` and `echo FLUSHALL | redis-cli` are both real), OR the command field holding the bare keyword, which is what the MCP redis_command tool sends ({ command: 'FLUSHALL', args: [] }). Without the client requirement these rules matched the WORD anywhere \u2014 including prose in a heredoc writing a file \u2014 which produced two observed false positives: a test fixture describing the shield, and a commit message naming the rule. Every other builtin shield already scopes this way (aws s3, docker system prune, kubectl delete, gh repo delete). See redis-scope.spec.ts.",
|
|
7871
8119
|
smartRules: [
|
|
7872
8120
|
{
|
|
7873
|
-
name: "shield:
|
|
7874
|
-
tool: "
|
|
7875
|
-
conditions: [],
|
|
7876
|
-
verdict: "review",
|
|
7877
|
-
reason: "A release ships code to everyone who installs it. node9 asks before publishing, whatever the request said (Publishing shield)."
|
|
7878
|
-
},
|
|
7879
|
-
{
|
|
7880
|
-
name: "shield:publishing:review-package",
|
|
7881
|
-
tool: "{,mcp__*__}{publish_package,npm_publish,pypi_upload,publish_version,publish_extension}",
|
|
7882
|
-
conditions: [],
|
|
7883
|
-
verdict: "review",
|
|
7884
|
-
reason: "This publishes a package. node9 asks before publishing (Publishing shield)."
|
|
7885
|
-
},
|
|
7886
|
-
{
|
|
7887
|
-
name: "shield:publishing:review-deploy",
|
|
7888
|
-
tool: "{,mcp__*__}{deploy,create_deployment,deploy_site,promote_deployment,trigger_deploy,deploy_to_production,rollout}",
|
|
7889
|
-
conditions: [],
|
|
7890
|
-
verdict: "review",
|
|
7891
|
-
reason: "This deploys to an environment other people use. node9 asks before publishing (Publishing shield)."
|
|
7892
|
-
},
|
|
7893
|
-
{
|
|
7894
|
-
name: "shield:publishing:review-post",
|
|
7895
|
-
tool: "{,mcp__*__}{post_tweet,create_tweet,post_webpage,publish_post,create_post,publish_page,send_announcement,post_announcement,create_linkedin_post,schedule_post}",
|
|
7896
|
-
conditions: [],
|
|
7897
|
-
verdict: "review",
|
|
7898
|
-
reason: "This posts outside the organisation. node9 asks before publishing (Publishing shield)."
|
|
7899
|
-
},
|
|
7900
|
-
{
|
|
7901
|
-
name: "shield:publishing:review-visibility",
|
|
7902
|
-
tool: "{,mcp__*__}{set_visibility,make_public,set_public,publish_repository,change_visibility,update_visibility}",
|
|
7903
|
-
conditions: [],
|
|
7904
|
-
verdict: "review",
|
|
7905
|
-
reason: "This makes something public. node9 asks before publishing (Publishing shield)."
|
|
7906
|
-
},
|
|
7907
|
-
{
|
|
7908
|
-
name: "shield:publishing:review-npm-publish",
|
|
7909
|
-
tool: "bash",
|
|
8121
|
+
name: "shield:redis:block-flushall",
|
|
8122
|
+
tool: "*",
|
|
7910
8123
|
conditions: [
|
|
7911
8124
|
{
|
|
7912
8125
|
field: "command",
|
|
7913
8126
|
op: "matches",
|
|
7914
|
-
value: "(
|
|
8127
|
+
value: "(redis|valkey)-cli.*\\bFLUSHALL\\b|\\bFLUSHALL\\b.*(redis|valkey)-cli|^ ?FLUSHALL\\b|\\.flushall\\s*\\(",
|
|
7915
8128
|
flags: "i"
|
|
7916
8129
|
}
|
|
7917
8130
|
],
|
|
7918
|
-
verdict: "
|
|
7919
|
-
reason: "
|
|
8131
|
+
verdict: "block",
|
|
8132
|
+
reason: "FLUSHALL deletes every key in every database \u2014 blocked by Redis shield"
|
|
7920
8133
|
},
|
|
7921
8134
|
{
|
|
7922
|
-
name: "shield:
|
|
7923
|
-
tool: "
|
|
8135
|
+
name: "shield:redis:block-flushdb",
|
|
8136
|
+
tool: "*",
|
|
7924
8137
|
conditions: [
|
|
7925
8138
|
{
|
|
7926
8139
|
field: "command",
|
|
7927
8140
|
op: "matches",
|
|
7928
|
-
value: "(
|
|
8141
|
+
value: "(redis|valkey)-cli.*\\bFLUSHDB\\b|\\bFLUSHDB\\b.*(redis|valkey)-cli|^ ?FLUSHDB\\b|\\.flushdb\\s*\\(",
|
|
7929
8142
|
flags: "i"
|
|
7930
8143
|
}
|
|
7931
8144
|
],
|
|
7932
|
-
verdict: "
|
|
7933
|
-
reason: "
|
|
8145
|
+
verdict: "block",
|
|
8146
|
+
reason: "FLUSHDB deletes all keys in the current database \u2014 blocked by Redis shield"
|
|
7934
8147
|
},
|
|
7935
8148
|
{
|
|
7936
|
-
name: "shield:
|
|
7937
|
-
tool: "
|
|
8149
|
+
name: "shield:redis:block-config-resetstat",
|
|
8150
|
+
tool: "*",
|
|
7938
8151
|
conditions: [
|
|
7939
8152
|
{
|
|
7940
8153
|
field: "command",
|
|
7941
8154
|
op: "matches",
|
|
7942
|
-
value: "(
|
|
8155
|
+
value: "(redis|valkey)-cli.*CONFIG\\s+RESETSTAT|^ ?CONFIG\\s+RESETSTAT",
|
|
7943
8156
|
flags: "i"
|
|
7944
8157
|
}
|
|
7945
8158
|
],
|
|
7946
|
-
verdict: "
|
|
7947
|
-
reason: "
|
|
8159
|
+
verdict: "block",
|
|
8160
|
+
reason: "CONFIG RESETSTAT resets server statistics irreversibly \u2014 blocked by Redis shield"
|
|
7948
8161
|
},
|
|
7949
8162
|
{
|
|
7950
|
-
name: "shield:
|
|
7951
|
-
tool: "
|
|
8163
|
+
name: "shield:redis:review-config-set",
|
|
8164
|
+
tool: "*",
|
|
7952
8165
|
conditions: [
|
|
7953
8166
|
{
|
|
7954
8167
|
field: "command",
|
|
7955
8168
|
op: "matches",
|
|
7956
|
-
value: "(
|
|
8169
|
+
value: "(redis|valkey)-cli.*\\bCONFIG\\s+SET\\b|\\bCONFIG\\s+SET\\b.*(redis|valkey)-cli|^ ?CONFIG\\s+SET\\b",
|
|
7957
8170
|
flags: "i"
|
|
7958
8171
|
}
|
|
7959
8172
|
],
|
|
7960
8173
|
verdict: "review",
|
|
7961
|
-
reason: "
|
|
8174
|
+
reason: "CONFIG SET changes live server configuration and requires human approval (Redis shield)"
|
|
7962
8175
|
},
|
|
7963
8176
|
{
|
|
7964
|
-
name: "shield:
|
|
7965
|
-
tool: "
|
|
8177
|
+
name: "shield:redis:review-del-wildcard",
|
|
8178
|
+
tool: "*",
|
|
7966
8179
|
conditions: [
|
|
7967
8180
|
{
|
|
7968
8181
|
field: "command",
|
|
7969
8182
|
op: "matches",
|
|
7970
|
-
value: "(
|
|
8183
|
+
value: "(redis|valkey)-cli.*\\bDEL\\b.*[*?\\[]|^ ?DEL\\b.*[*?\\[]|-cli.*--scan.*xargs.*del",
|
|
7971
8184
|
flags: "i"
|
|
7972
8185
|
}
|
|
7973
8186
|
],
|
|
7974
8187
|
verdict: "review",
|
|
7975
|
-
reason: "
|
|
7976
|
-
}
|
|
8188
|
+
reason: "Wildcard key deletion requires human approval (Redis shield)"
|
|
8189
|
+
}
|
|
8190
|
+
],
|
|
8191
|
+
dangerousWords: ["FLUSHALL", "FLUSHDB"]
|
|
8192
|
+
};
|
|
8193
|
+
finance_default = {
|
|
8194
|
+
name: "finance",
|
|
8195
|
+
description: "Asks before money moves through a payment or banking app (refunds, payouts, transfers, charges, invoices), whatever the request said. Reads are never asked about.",
|
|
8196
|
+
aliases: ["payments", "money"],
|
|
8197
|
+
smartRules: [
|
|
7977
8198
|
{
|
|
7978
|
-
name: "shield:
|
|
7979
|
-
tool: "
|
|
7980
|
-
conditions: [
|
|
7981
|
-
{
|
|
7982
|
-
field: "command",
|
|
7983
|
-
op: "matches",
|
|
7984
|
-
value: "(?:^|[^\\w-])(?:cargo)(?![\\w-])[^;&|]{0,200}?\\s(?:publish)(?![\\w-])",
|
|
7985
|
-
flags: "i"
|
|
7986
|
-
}
|
|
7987
|
-
],
|
|
8199
|
+
name: "shield:finance:review-refund",
|
|
8200
|
+
tool: "{,mcp__*__}{create_refund,refund_payment,refund_charge,refund_order,issue_refund}",
|
|
8201
|
+
conditions: [],
|
|
7988
8202
|
verdict: "review",
|
|
7989
|
-
reason: "
|
|
8203
|
+
reason: "A refund returns money to a customer. node9 asks before money moves, whatever the request said (Finance shield)."
|
|
7990
8204
|
},
|
|
7991
8205
|
{
|
|
7992
|
-
name: "shield:
|
|
7993
|
-
tool: "
|
|
7994
|
-
conditions: [
|
|
7995
|
-
{
|
|
7996
|
-
field: "command",
|
|
7997
|
-
op: "matches",
|
|
7998
|
-
value: "(?:^|[^\\w-])(?:gem)(?![\\w-])[^;&|]{0,200}?\\s(?:push)(?![\\w-])",
|
|
7999
|
-
flags: "i"
|
|
8000
|
-
}
|
|
8001
|
-
],
|
|
8206
|
+
name: "shield:finance:review-payout",
|
|
8207
|
+
tool: "{,mcp__*__}{create_payout,send_payout,payout,create_payouts_batch}",
|
|
8208
|
+
conditions: [],
|
|
8002
8209
|
verdict: "review",
|
|
8003
|
-
reason: "
|
|
8210
|
+
reason: "A payout moves the balance to a bank account. node9 asks before money moves (Finance shield)."
|
|
8004
8211
|
},
|
|
8005
8212
|
{
|
|
8006
|
-
name: "shield:
|
|
8213
|
+
name: "shield:finance:review-transfer",
|
|
8214
|
+
tool: "{,mcp__*__}{transfer_create,transfer_authorization_create,create_transfer,transfer_money,transfer_funds,send_money,wire_transfer,create_wire,schedule_transaction,update_scheduled_transaction}",
|
|
8215
|
+
conditions: [],
|
|
8216
|
+
verdict: "review",
|
|
8217
|
+
reason: "A transfer moves money between accounts. node9 asks before money moves (Finance shield)."
|
|
8218
|
+
},
|
|
8219
|
+
{
|
|
8220
|
+
name: "shield:finance:review-charge",
|
|
8221
|
+
tool: "{,mcp__*__}{create_payment_intent,confirm_payment_intent,capture_payment_intent,create_charge,create_payment,capture_payment,capture_order,create_order,authorize_order,create_checkout_session}",
|
|
8222
|
+
conditions: [],
|
|
8223
|
+
verdict: "review",
|
|
8224
|
+
reason: "This charges a card or captures a payment. node9 asks before money moves (Finance shield)."
|
|
8225
|
+
},
|
|
8226
|
+
{
|
|
8227
|
+
name: "shield:finance:review-payment-link",
|
|
8228
|
+
tool: "{,mcp__*__}{create_payment_link,create_invoice,finalize_invoice,send_invoice,pay_invoice,create_invoice_item}",
|
|
8229
|
+
conditions: [],
|
|
8230
|
+
verdict: "review",
|
|
8231
|
+
reason: "This bills someone or creates a link that charges a card. node9 asks before money moves (Finance shield)."
|
|
8232
|
+
},
|
|
8233
|
+
{
|
|
8234
|
+
name: "shield:finance:review-subscription",
|
|
8235
|
+
tool: "{,mcp__*__}{create_subscription,cancel_subscription,update_subscription,pause_subscription,resume_subscription}",
|
|
8236
|
+
conditions: [],
|
|
8237
|
+
verdict: "review",
|
|
8238
|
+
reason: "A subscription starts or stops recurring charges. node9 asks before money moves (Finance shield)."
|
|
8239
|
+
},
|
|
8240
|
+
{
|
|
8241
|
+
name: "shield:finance:review-dispute",
|
|
8242
|
+
tool: "{,mcp__*__}{update_dispute,accept_dispute,close_dispute}",
|
|
8243
|
+
conditions: [],
|
|
8244
|
+
verdict: "review",
|
|
8245
|
+
reason: "This answers or concedes a chargeback. node9 asks before money moves (Finance shield)."
|
|
8246
|
+
},
|
|
8247
|
+
{
|
|
8248
|
+
name: "shield:finance:review-card",
|
|
8249
|
+
tool: "{,mcp__*__}{issue_card,create_virtual_card,create_issuing_card,update_card_limit}",
|
|
8250
|
+
conditions: [],
|
|
8251
|
+
verdict: "review",
|
|
8252
|
+
reason: "This issues a card or changes its limit. node9 asks before money moves (Finance shield)."
|
|
8253
|
+
},
|
|
8254
|
+
{
|
|
8255
|
+
name: "shield:finance:review-withdraw",
|
|
8256
|
+
tool: "{,mcp__*__}{withdraw,withdraw_funds,create_withdrawal,request_withdrawal}",
|
|
8257
|
+
conditions: [],
|
|
8258
|
+
verdict: "review",
|
|
8259
|
+
reason: "A withdrawal moves money out. node9 asks before money moves (Finance shield)."
|
|
8260
|
+
}
|
|
8261
|
+
],
|
|
8262
|
+
dangerousWords: []
|
|
8263
|
+
};
|
|
8264
|
+
identity_default = {
|
|
8265
|
+
name: "identity",
|
|
8266
|
+
description: "Asks before a password, a key, a token, a role or a user changes in an identity or admin app, whatever the request said. Reads are never asked about.",
|
|
8267
|
+
aliases: ["access", "iam"],
|
|
8268
|
+
smartRules: [
|
|
8269
|
+
{
|
|
8270
|
+
name: "shield:identity:review-password",
|
|
8271
|
+
tool: "{,mcp__*__}{change_password,update_password,reset_password,set_password,expire_password,force_password_reset}",
|
|
8272
|
+
conditions: [],
|
|
8273
|
+
verdict: "review",
|
|
8274
|
+
reason: "This changes or resets a password. node9 asks before credentials change, whatever the request said (Identity shield)."
|
|
8275
|
+
},
|
|
8276
|
+
{
|
|
8277
|
+
name: "shield:identity:review-key",
|
|
8278
|
+
tool: "{,mcp__*__}{rotate_key,rotate_api_key,create_api_key,create_token,create_access_token,create_personal_access_token,regenerate_secret,rotate_secret,create_service_account_key,create_credential}",
|
|
8279
|
+
conditions: [],
|
|
8280
|
+
verdict: "review",
|
|
8281
|
+
reason: "This creates or rotates a key, token or secret. node9 asks before credentials change (Identity shield)."
|
|
8282
|
+
},
|
|
8283
|
+
{
|
|
8284
|
+
name: "shield:identity:review-access",
|
|
8285
|
+
tool: "{,mcp__*__}{grant_access,grant_permission,grant_role,revoke_access,revoke_permission,revoke_role,revoke_token,set_permission,set_permissions,set_role,assign_role,update_role,add_permission,remove_permission,attach_policy,detach_policy,share_file}",
|
|
8286
|
+
conditions: [],
|
|
8287
|
+
verdict: "review",
|
|
8288
|
+
reason: "This grants or revokes access. node9 asks before access changes (Identity shield)."
|
|
8289
|
+
},
|
|
8290
|
+
{
|
|
8291
|
+
name: "shield:identity:review-membership",
|
|
8292
|
+
tool: "{,mcp__*__}{add_member,remove_member,add_user_to_group,remove_user_from_group,add_collaborator,remove_collaborator,invite_user,invite_member,invite_user_to_slack,invite_users,add_org_member,remove_org_member,add_team_member,remove_team_member,add_user_to_channel,remove_user_from_slack}",
|
|
8293
|
+
conditions: [],
|
|
8294
|
+
verdict: "review",
|
|
8295
|
+
reason: "This changes who belongs where. node9 asks before membership changes (Identity shield)."
|
|
8296
|
+
},
|
|
8297
|
+
{
|
|
8298
|
+
name: "shield:identity:review-user",
|
|
8299
|
+
tool: "{,mcp__*__}{create_user,delete_user,delete_deactivated_user,deactivate_user,suspend_user,activate_user,unlock_user,make_admin,promote_user,demote_user,update_user_role}",
|
|
8300
|
+
conditions: [],
|
|
8301
|
+
verdict: "review",
|
|
8302
|
+
reason: "This creates, removes or promotes an account. node9 asks before accounts change (Identity shield)."
|
|
8303
|
+
},
|
|
8304
|
+
{
|
|
8305
|
+
name: "shield:identity:review-mfa",
|
|
8306
|
+
tool: "{,mcp__*__}{reset_mfa,reset_factors,disable_mfa,enroll_factor,deactivate_factor}",
|
|
8307
|
+
conditions: [],
|
|
8308
|
+
verdict: "review",
|
|
8309
|
+
reason: "This changes a second factor. node9 asks before credentials change (Identity shield)."
|
|
8310
|
+
}
|
|
8311
|
+
],
|
|
8312
|
+
dangerousWords: []
|
|
8313
|
+
};
|
|
8314
|
+
publishing_default = {
|
|
8315
|
+
name: "publishing",
|
|
8316
|
+
description: "Asks before something goes public or ships outside the organisation (a release, a package, a deploy, a post), whatever the request said. The same switch covers the shell commands that publish.",
|
|
8317
|
+
aliases: ["release", "deploy"],
|
|
8318
|
+
smartRules: [
|
|
8319
|
+
{
|
|
8320
|
+
name: "shield:publishing:review-release",
|
|
8321
|
+
tool: "{,mcp__*__}{create_release,publish_release,create_tag_release,update_release,promote_release}",
|
|
8322
|
+
conditions: [],
|
|
8323
|
+
verdict: "review",
|
|
8324
|
+
reason: "A release ships code to everyone who installs it. node9 asks before publishing, whatever the request said (Publishing shield)."
|
|
8325
|
+
},
|
|
8326
|
+
{
|
|
8327
|
+
name: "shield:publishing:review-package",
|
|
8328
|
+
tool: "{,mcp__*__}{publish_package,npm_publish,pypi_upload,publish_version,publish_extension}",
|
|
8329
|
+
conditions: [],
|
|
8330
|
+
verdict: "review",
|
|
8331
|
+
reason: "This publishes a package. node9 asks before publishing (Publishing shield)."
|
|
8332
|
+
},
|
|
8333
|
+
{
|
|
8334
|
+
name: "shield:publishing:review-deploy",
|
|
8335
|
+
tool: "{,mcp__*__}{deploy,create_deployment,deploy_site,promote_deployment,trigger_deploy,deploy_to_production,rollout}",
|
|
8336
|
+
conditions: [],
|
|
8337
|
+
verdict: "review",
|
|
8338
|
+
reason: "This deploys to an environment other people use. node9 asks before publishing (Publishing shield)."
|
|
8339
|
+
},
|
|
8340
|
+
{
|
|
8341
|
+
name: "shield:publishing:review-post",
|
|
8342
|
+
tool: "{,mcp__*__}{post_tweet,create_tweet,post_webpage,publish_post,create_post,publish_page,send_announcement,post_announcement,create_linkedin_post,schedule_post}",
|
|
8343
|
+
conditions: [],
|
|
8344
|
+
verdict: "review",
|
|
8345
|
+
reason: "This posts outside the organisation. node9 asks before publishing (Publishing shield)."
|
|
8346
|
+
},
|
|
8347
|
+
{
|
|
8348
|
+
name: "shield:publishing:review-visibility",
|
|
8349
|
+
tool: "{,mcp__*__}{set_visibility,make_public,set_public,publish_repository,change_visibility,update_visibility}",
|
|
8350
|
+
conditions: [],
|
|
8351
|
+
verdict: "review",
|
|
8352
|
+
reason: "This makes something public. node9 asks before publishing (Publishing shield)."
|
|
8353
|
+
},
|
|
8354
|
+
{
|
|
8355
|
+
name: "shield:publishing:review-npm-publish",
|
|
8007
8356
|
tool: "bash",
|
|
8008
8357
|
conditions: [
|
|
8009
8358
|
{
|
|
8010
8359
|
field: "command",
|
|
8011
8360
|
op: "matches",
|
|
8012
|
-
value: "(?:^|[^\\w-])(?:
|
|
8361
|
+
value: "(?:^|[^\\w-])(?:npm|pnpm)(?![\\w-])[^;&|]{0,200}?\\s(?:publish)(?![\\w-])",
|
|
8013
8362
|
flags: "i"
|
|
8014
8363
|
}
|
|
8015
8364
|
],
|
|
8016
8365
|
verdict: "review",
|
|
8017
|
-
reason: "
|
|
8366
|
+
reason: "npm publish ships the package to everyone who installs it. node9 asks before publishing (Publishing shield)."
|
|
8018
8367
|
},
|
|
8019
8368
|
{
|
|
8020
|
-
name: "shield:publishing:review-
|
|
8369
|
+
name: "shield:publishing:review-yarn-publish",
|
|
8021
8370
|
tool: "bash",
|
|
8022
8371
|
conditions: [
|
|
8023
8372
|
{
|
|
8024
8373
|
field: "command",
|
|
8025
8374
|
op: "matches",
|
|
8026
|
-
value: "(?:^|[^\\w-])(?:
|
|
8375
|
+
value: "(?:^|[^\\w-])(?:yarn)(?![\\w-])[^;&|]{0,200}?\\s(?:publish)(?![\\w-])",
|
|
8027
8376
|
flags: "i"
|
|
8028
8377
|
}
|
|
8029
8378
|
],
|
|
8030
8379
|
verdict: "review",
|
|
8031
|
-
reason: "
|
|
8032
|
-
}
|
|
8033
|
-
],
|
|
8034
|
-
dangerousWords: []
|
|
8035
|
-
};
|
|
8036
|
-
redis_default = {
|
|
8037
|
-
name: "redis",
|
|
8038
|
-
description: "Protects Redis instances from destructive AI operations",
|
|
8039
|
-
aliases: [],
|
|
8040
|
-
_scopeNote: "Every rule requires Redis to actually be involved: a client in the command (redis-cli / valkey-cli, in either order \u2014 `redis-cli FLUSHALL` and `echo FLUSHALL | redis-cli` are both real), OR the command field holding the bare keyword, which is what the MCP redis_command tool sends ({ command: 'FLUSHALL', args: [] }). Without the client requirement these rules matched the WORD anywhere \u2014 including prose in a heredoc writing a file \u2014 which produced two observed false positives: a test fixture describing the shield, and a commit message naming the rule. Every other builtin shield already scopes this way (aws s3, docker system prune, kubectl delete, gh repo delete). See redis-scope.spec.ts.",
|
|
8041
|
-
smartRules: [
|
|
8380
|
+
reason: "yarn publish ships the package to everyone who installs it. node9 asks before publishing (Publishing shield)."
|
|
8381
|
+
},
|
|
8042
8382
|
{
|
|
8043
|
-
name: "shield:
|
|
8044
|
-
tool: "
|
|
8383
|
+
name: "shield:publishing:review-twine-upload",
|
|
8384
|
+
tool: "bash",
|
|
8045
8385
|
conditions: [
|
|
8046
8386
|
{
|
|
8047
8387
|
field: "command",
|
|
8048
8388
|
op: "matches",
|
|
8049
|
-
value: "(
|
|
8389
|
+
value: "(?:^|[^\\w-])(?:twine)(?![\\w-])[^;&|]{0,200}?\\s(?:upload)(?![\\w-])",
|
|
8050
8390
|
flags: "i"
|
|
8051
8391
|
}
|
|
8052
8392
|
],
|
|
8053
|
-
verdict: "
|
|
8054
|
-
reason: "
|
|
8393
|
+
verdict: "review",
|
|
8394
|
+
reason: "twine upload publishes to PyPI. node9 asks before publishing (Publishing shield)."
|
|
8055
8395
|
},
|
|
8056
8396
|
{
|
|
8057
|
-
name: "shield:
|
|
8058
|
-
tool: "
|
|
8397
|
+
name: "shield:publishing:review-python-publish",
|
|
8398
|
+
tool: "bash",
|
|
8059
8399
|
conditions: [
|
|
8060
8400
|
{
|
|
8061
8401
|
field: "command",
|
|
8062
8402
|
op: "matches",
|
|
8063
|
-
value: "(
|
|
8403
|
+
value: "(?:^|[^\\w-])(?:poetry|uv|hatch|flit)(?![\\w-])[^;&|]{0,200}?\\s(?:publish)(?![\\w-])",
|
|
8064
8404
|
flags: "i"
|
|
8065
8405
|
}
|
|
8066
8406
|
],
|
|
8067
|
-
verdict: "
|
|
8068
|
-
reason: "
|
|
8407
|
+
verdict: "review",
|
|
8408
|
+
reason: "This publishes the Python package to PyPI. node9 asks before publishing (Publishing shield)."
|
|
8069
8409
|
},
|
|
8070
8410
|
{
|
|
8071
|
-
name: "shield:
|
|
8072
|
-
tool: "
|
|
8411
|
+
name: "shield:publishing:review-release-tool-publish",
|
|
8412
|
+
tool: "bash",
|
|
8073
8413
|
conditions: [
|
|
8074
8414
|
{
|
|
8075
8415
|
field: "command",
|
|
8076
8416
|
op: "matches",
|
|
8077
|
-
value: "(
|
|
8417
|
+
value: "(?:^|[^\\w-])(?:lerna|changeset|vsce|ovsx)(?![\\w-])[^;&|]{0,200}?\\s(?:publish)(?![\\w-])",
|
|
8078
8418
|
flags: "i"
|
|
8079
8419
|
}
|
|
8080
8420
|
],
|
|
8081
|
-
verdict: "
|
|
8082
|
-
reason: "
|
|
8421
|
+
verdict: "review",
|
|
8422
|
+
reason: "This publishes packages or an editor extension through a release tool. node9 asks before publishing (Publishing shield)."
|
|
8083
8423
|
},
|
|
8084
8424
|
{
|
|
8085
|
-
name: "shield:
|
|
8086
|
-
tool: "
|
|
8425
|
+
name: "shield:publishing:review-cargo-publish",
|
|
8426
|
+
tool: "bash",
|
|
8087
8427
|
conditions: [
|
|
8088
8428
|
{
|
|
8089
8429
|
field: "command",
|
|
8090
8430
|
op: "matches",
|
|
8091
|
-
value: "(
|
|
8431
|
+
value: "(?:^|[^\\w-])(?:cargo)(?![\\w-])[^;&|]{0,200}?\\s(?:publish)(?![\\w-])",
|
|
8092
8432
|
flags: "i"
|
|
8093
8433
|
}
|
|
8094
8434
|
],
|
|
8095
8435
|
verdict: "review",
|
|
8096
|
-
reason: "
|
|
8436
|
+
reason: "cargo publish ships the crate. node9 asks before publishing (Publishing shield)."
|
|
8097
8437
|
},
|
|
8098
8438
|
{
|
|
8099
|
-
name: "shield:
|
|
8100
|
-
tool: "
|
|
8439
|
+
name: "shield:publishing:review-gem-push",
|
|
8440
|
+
tool: "bash",
|
|
8101
8441
|
conditions: [
|
|
8102
8442
|
{
|
|
8103
8443
|
field: "command",
|
|
8104
8444
|
op: "matches",
|
|
8105
|
-
value: "(
|
|
8445
|
+
value: "(?:^|[^\\w-])(?:gem)(?![\\w-])[^;&|]{0,200}?\\s(?:push)(?![\\w-])",
|
|
8106
8446
|
flags: "i"
|
|
8107
8447
|
}
|
|
8108
8448
|
],
|
|
8109
8449
|
verdict: "review",
|
|
8110
|
-
reason: "
|
|
8450
|
+
reason: "gem push publishes to RubyGems. node9 asks before publishing (Publishing shield)."
|
|
8451
|
+
},
|
|
8452
|
+
{
|
|
8453
|
+
name: "shield:publishing:review-docker-push",
|
|
8454
|
+
tool: "bash",
|
|
8455
|
+
conditions: [
|
|
8456
|
+
{
|
|
8457
|
+
field: "command",
|
|
8458
|
+
op: "matches",
|
|
8459
|
+
value: "(?:^|[^\\w-])(?:docker|podman)(?![\\w-])[^;&|]{0,200}?\\s(?:push|--push)(?![\\w-])",
|
|
8460
|
+
flags: "i"
|
|
8461
|
+
}
|
|
8462
|
+
],
|
|
8463
|
+
verdict: "review",
|
|
8464
|
+
reason: "docker push publishes the image to a registry. node9 asks before publishing (Publishing shield)."
|
|
8465
|
+
},
|
|
8466
|
+
{
|
|
8467
|
+
name: "shield:publishing:review-gh-release",
|
|
8468
|
+
tool: "bash",
|
|
8469
|
+
conditions: [
|
|
8470
|
+
{
|
|
8471
|
+
field: "command",
|
|
8472
|
+
op: "matches",
|
|
8473
|
+
value: "(?:^|[^\\w-])(?:gh)(?![\\w-])[^;&|]{0,200}?\\srelease\\s+(?:create|upload|edit)(?![\\w-])",
|
|
8474
|
+
flags: "i"
|
|
8475
|
+
}
|
|
8476
|
+
],
|
|
8477
|
+
verdict: "review",
|
|
8478
|
+
reason: "gh release ships a release on GitHub. node9 asks before publishing (Publishing shield)."
|
|
8111
8479
|
}
|
|
8112
8480
|
],
|
|
8113
|
-
dangerousWords: [
|
|
8481
|
+
dangerousWords: []
|
|
8114
8482
|
};
|
|
8483
|
+
ACTION_TEMPLATES = {
|
|
8484
|
+
finance: finance_default.smartRules,
|
|
8485
|
+
identity: identity_default.smartRules,
|
|
8486
|
+
publishing: publishing_default.smartRules
|
|
8487
|
+
};
|
|
8488
|
+
ACTION_CHECKS = {
|
|
8489
|
+
finance: { checkId: "actions.money", slug: "money", label: "Money check" },
|
|
8490
|
+
identity: { checkId: "actions.access", slug: "access", label: "Accounts and access check" },
|
|
8491
|
+
publishing: { checkId: "actions.publish", slug: "publish", label: "Publishing check" }
|
|
8492
|
+
};
|
|
8493
|
+
GAP = "[^;&|]{0,200}?";
|
|
8494
|
+
prog = (alt) => `(?:^|[^\\w-])(?:${alt})(?![\\w-])`;
|
|
8495
|
+
httpTo = (host2) => prog("curl|wget|http|https|xh") + "[^;&|]{0,400}?" + host2;
|
|
8496
|
+
httpWrite = (methods) => ({
|
|
8497
|
+
field: "command",
|
|
8498
|
+
op: "matches",
|
|
8499
|
+
value: `-X\\s*(?:${methods})|--(?:request|method)[=\\s]+(?:${methods})|--post-(?:data|file)|\\s(?:${methods}|${methods.toLowerCase()})\\s|^(?!.*\\s(?:-[a-zA-Z]*G|--get)(?![\\w-])).*(?:\\s-d[\\s'"@]|\\s--data|\\s--json\\s|\\s-F\\s|\\s--form\\s)`
|
|
8500
|
+
});
|
|
8501
|
+
HTTP_WRITE = httpWrite("POST|DELETE");
|
|
8502
|
+
HTTP_WRITE_ANY = httpWrite("POST|PUT|PATCH|DELETE");
|
|
8503
|
+
NOT_STRIPE_TEST = {
|
|
8504
|
+
field: "command",
|
|
8505
|
+
op: "notMatches",
|
|
8506
|
+
value: "\\b[sr]k_test_"
|
|
8507
|
+
};
|
|
8508
|
+
postTo = (host2) => `\\.post\\(\\s*["'\`][^"'\`]{0,80}${host2}`;
|
|
8509
|
+
STRIPE_OBJECTS = "refunds|payouts|charges|transfers|payment_intents|payment_links|invoices|subscriptions|topups";
|
|
8510
|
+
APP_SHIELD_SPECS = [
|
|
8511
|
+
{
|
|
8512
|
+
name: "stripe",
|
|
8513
|
+
label: "Stripe",
|
|
8514
|
+
description: "Asks before money moves in Stripe (refunds, payouts, charges, invoices, subscriptions), whether the agent uses the Stripe MCP server, the stripe CLI, the API or the Stripe library in a script. Reads are never asked about.",
|
|
8515
|
+
template: "finance",
|
|
8516
|
+
server: "*stripe*",
|
|
8517
|
+
doors: [
|
|
8518
|
+
{
|
|
8519
|
+
name: "shield:stripe:review-cli",
|
|
8520
|
+
tool: "bash",
|
|
8521
|
+
conditions: [
|
|
8522
|
+
{
|
|
8523
|
+
field: "command",
|
|
8524
|
+
op: "matches",
|
|
8525
|
+
value: prog("stripe") + GAP + `\\s(?:${STRIPE_OBJECTS})\\s+(?:create|cancel|capture|confirm|update|pay|finalize_invoice|send_invoice|void_invoice)(?![\\w-])`,
|
|
8526
|
+
flags: "i"
|
|
8527
|
+
},
|
|
8528
|
+
{
|
|
8529
|
+
field: "command",
|
|
8530
|
+
op: "matches",
|
|
8531
|
+
// The stripe CLI works in test mode unless told otherwise.
|
|
8532
|
+
value: "\\s--live(?![\\w-])|\\b[sr]k_live_"
|
|
8533
|
+
}
|
|
8534
|
+
],
|
|
8535
|
+
conditionMode: "all",
|
|
8536
|
+
verdict: "review",
|
|
8537
|
+
reason: "The stripe CLI is about to move money. node9 asks before money moves (Stripe)."
|
|
8538
|
+
},
|
|
8539
|
+
{
|
|
8540
|
+
name: "shield:stripe:review-cli-request",
|
|
8541
|
+
tool: "bash",
|
|
8542
|
+
conditions: [
|
|
8543
|
+
{
|
|
8544
|
+
field: "command",
|
|
8545
|
+
op: "matches",
|
|
8546
|
+
value: prog("stripe") + GAP + `\\s(?:post|delete)\\s+/?v1/(?:${STRIPE_OBJECTS})(?![\\w-])`,
|
|
8547
|
+
flags: "i"
|
|
8548
|
+
},
|
|
8549
|
+
{
|
|
8550
|
+
field: "command",
|
|
8551
|
+
op: "matches",
|
|
8552
|
+
// The stripe CLI works in test mode unless told otherwise.
|
|
8553
|
+
value: "\\s--live(?![\\w-])|\\b[sr]k_live_"
|
|
8554
|
+
}
|
|
8555
|
+
],
|
|
8556
|
+
conditionMode: "all",
|
|
8557
|
+
verdict: "review",
|
|
8558
|
+
reason: "A raw stripe CLI request is about to move money. node9 asks before money moves (Stripe)."
|
|
8559
|
+
},
|
|
8560
|
+
{
|
|
8561
|
+
name: "shield:stripe:review-api",
|
|
8562
|
+
tool: "bash",
|
|
8563
|
+
conditions: [
|
|
8564
|
+
{
|
|
8565
|
+
field: "command",
|
|
8566
|
+
op: "matches",
|
|
8567
|
+
value: httpTo(`api\\.stripe\\.com/v1/(?:${STRIPE_OBJECTS})(?![\\w-])`),
|
|
8568
|
+
flags: "i"
|
|
8569
|
+
},
|
|
8570
|
+
HTTP_WRITE,
|
|
8571
|
+
NOT_STRIPE_TEST
|
|
8572
|
+
],
|
|
8573
|
+
conditionMode: "all",
|
|
8574
|
+
verdict: "review",
|
|
8575
|
+
reason: "A request to the Stripe API is about to move money. node9 asks before money moves (Stripe)."
|
|
8576
|
+
},
|
|
8577
|
+
{
|
|
8578
|
+
name: "shield:stripe:review-code",
|
|
8579
|
+
tool: "bash",
|
|
8580
|
+
conditions: [
|
|
8581
|
+
{
|
|
8582
|
+
field: "command",
|
|
8583
|
+
op: "matches",
|
|
8584
|
+
// Python: stripe.Refund.create(...)
|
|
8585
|
+
value: "\\bstripe\\.(?:Refund|Payout|Transfer|Charge|PaymentIntent|PaymentLink|Invoice|Subscription|Topup)\\.(?:create|cancel|capture|confirm|modify|pay|finalize_invoice|send_invoice|void_invoice)\\b"
|
|
8586
|
+
},
|
|
8587
|
+
NOT_STRIPE_TEST
|
|
8588
|
+
],
|
|
8589
|
+
conditionMode: "all",
|
|
8590
|
+
verdict: "review",
|
|
8591
|
+
reason: "This code moves money through the Stripe library. node9 asks before money moves (Stripe)."
|
|
8592
|
+
},
|
|
8593
|
+
{
|
|
8594
|
+
name: "shield:stripe:review-code-node",
|
|
8595
|
+
tool: "bash",
|
|
8596
|
+
conditions: [
|
|
8597
|
+
{
|
|
8598
|
+
field: "command",
|
|
8599
|
+
op: "matches",
|
|
8600
|
+
// Node: stripe.refunds.create(...)
|
|
8601
|
+
value: "\\bstripe\\.(?:refunds|payouts|transfers|charges|paymentIntents|paymentLinks|invoices|subscriptions|topups)\\.(?:create|cancel|capture|confirm|update|pay|finalizeInvoice|sendInvoice|voidInvoice)\\s*\\("
|
|
8602
|
+
},
|
|
8603
|
+
NOT_STRIPE_TEST
|
|
8604
|
+
],
|
|
8605
|
+
conditionMode: "all",
|
|
8606
|
+
verdict: "review",
|
|
8607
|
+
reason: "This code moves money through the Stripe library. node9 asks before money moves (Stripe)."
|
|
8608
|
+
},
|
|
8609
|
+
{
|
|
8610
|
+
name: "shield:stripe:review-code-http",
|
|
8611
|
+
tool: "bash",
|
|
8612
|
+
conditions: [
|
|
8613
|
+
{
|
|
8614
|
+
field: "command",
|
|
8615
|
+
op: "matches",
|
|
8616
|
+
// requests.post("https://api.stripe.com/v1/refunds", ...)
|
|
8617
|
+
value: `\\.post\\(\\s*["'\`][^"'\`]{0,80}api\\.stripe\\.com/v1/(?:${STRIPE_OBJECTS})`
|
|
8618
|
+
},
|
|
8619
|
+
NOT_STRIPE_TEST
|
|
8620
|
+
],
|
|
8621
|
+
conditionMode: "all",
|
|
8622
|
+
verdict: "review",
|
|
8623
|
+
reason: "This code posts to the Stripe API. node9 asks before money moves (Stripe)."
|
|
8624
|
+
}
|
|
8625
|
+
]
|
|
8626
|
+
},
|
|
8627
|
+
{
|
|
8628
|
+
name: "paypal",
|
|
8629
|
+
label: "PayPal",
|
|
8630
|
+
description: "Asks before money moves in PayPal (refunds, payouts, captured orders, invoices, subscriptions), whether the agent uses a PayPal MCP server, the API or the PayPal SDK in a script. The sandbox is never asked about, and neither are reads.",
|
|
8631
|
+
template: "finance",
|
|
8632
|
+
server: "*paypal*",
|
|
8633
|
+
doors: [
|
|
8634
|
+
{
|
|
8635
|
+
name: "shield:paypal:review-api",
|
|
8636
|
+
tool: "bash",
|
|
8637
|
+
conditions: [
|
|
8638
|
+
{
|
|
8639
|
+
field: "command",
|
|
8640
|
+
op: "matches",
|
|
8641
|
+
// The live hosts only: api.paypal.com and api-m.paypal.com. The
|
|
8642
|
+
// sandbox (api-m.sandbox.paypal.com) moves no money.
|
|
8643
|
+
value: httpTo("api(?:-m)?\\.paypal\\.com/v[12]/"),
|
|
8644
|
+
flags: "i"
|
|
8645
|
+
},
|
|
8646
|
+
{
|
|
8647
|
+
field: "command",
|
|
8648
|
+
op: "matches",
|
|
8649
|
+
// The endpoints that move money.
|
|
8650
|
+
value: "\\.paypal\\.com/v[12]/(?:payments/(?:captures|authorizations|sale|payouts|payment|referenced-payouts)|checkout/orders/[^\\s/]{1,64}/(?:capture|authorize)|invoicing/invoices|billing/subscriptions)",
|
|
8651
|
+
flags: "i"
|
|
8652
|
+
},
|
|
8653
|
+
HTTP_WRITE
|
|
8654
|
+
],
|
|
8655
|
+
conditionMode: "all",
|
|
8656
|
+
verdict: "review",
|
|
8657
|
+
reason: "A request to the PayPal API is about to move money. node9 asks before money moves (PayPal)."
|
|
8658
|
+
},
|
|
8659
|
+
{
|
|
8660
|
+
name: "shield:paypal:review-code",
|
|
8661
|
+
tool: "bash",
|
|
8662
|
+
conditions: [
|
|
8663
|
+
{
|
|
8664
|
+
field: "command",
|
|
8665
|
+
op: "matches",
|
|
8666
|
+
// The Node SDKs (checkout-server-sdk, paypal-server-sdk) and the
|
|
8667
|
+
// Python paypalrestsdk.
|
|
8668
|
+
value: "\\b(?:CapturesRefundRequest|OrdersCaptureRequest|AuthorizationsCaptureRequest|PayoutsPostRequest)\\b|\\b(?:captureOrder|refundCapturedPayment|captureAuthorizedPayment|createBatchPayout)\\s*\\(|\\bpaypalrestsdk\\.(?:Payout|Sale|Capture|Invoice|BillingAgreement)\\b"
|
|
8669
|
+
}
|
|
8670
|
+
],
|
|
8671
|
+
verdict: "review",
|
|
8672
|
+
reason: "This code moves money through PayPal. node9 asks before money moves (PayPal)."
|
|
8673
|
+
},
|
|
8674
|
+
{
|
|
8675
|
+
name: "shield:paypal:review-code-http",
|
|
8676
|
+
tool: "bash",
|
|
8677
|
+
conditions: [
|
|
8678
|
+
{
|
|
8679
|
+
field: "command",
|
|
8680
|
+
op: "matches",
|
|
8681
|
+
// The endpoints that move money; the token call every script
|
|
8682
|
+
// starts with (/v1/oauth2/token) is not one.
|
|
8683
|
+
value: postTo(
|
|
8684
|
+
"api(?:-m)?\\.paypal\\.com/v[12]/(?:payments/(?:captures|authorizations|sale|payouts|payment|referenced-payouts)|checkout/orders/[^\\s/\"'`]{1,64}/(?:capture|authorize)|invoicing/invoices|billing/subscriptions)"
|
|
8685
|
+
)
|
|
8686
|
+
}
|
|
8687
|
+
],
|
|
8688
|
+
verdict: "review",
|
|
8689
|
+
reason: "This code posts to the PayPal API. node9 asks before money moves (PayPal)."
|
|
8690
|
+
}
|
|
8691
|
+
]
|
|
8692
|
+
},
|
|
8693
|
+
{
|
|
8694
|
+
name: "square",
|
|
8695
|
+
label: "Square",
|
|
8696
|
+
description: "Asks before money moves in Square (refunds, payments, paid orders, invoices, subscriptions), whether the agent uses a Square MCP server, the API or the Square SDK in a script. The sandbox host is never asked about, and neither are reads.",
|
|
8697
|
+
template: "finance",
|
|
8698
|
+
// Square's servers, never Squarespace (a website builder).
|
|
8699
|
+
server: "{*square,*square[-_]*,*squareup*}",
|
|
8700
|
+
mcpTools: [
|
|
8701
|
+
{
|
|
8702
|
+
// Square's official MCP server has one tool for every API call; the
|
|
8703
|
+
// service and method name what it does.
|
|
8704
|
+
name: "shield:square:review-mcp-request",
|
|
8705
|
+
tool: "{,mcp__*__}make_api_request",
|
|
8706
|
+
conditions: [
|
|
8707
|
+
{
|
|
8708
|
+
field: "service",
|
|
8709
|
+
op: "matches",
|
|
8710
|
+
value: "^(?:refunds|payments|orders|invoices|subscriptions|disputes|gift_cards|gift_card_activities|checkout|terminal)$",
|
|
8711
|
+
flags: "i"
|
|
8712
|
+
},
|
|
8713
|
+
{
|
|
8714
|
+
field: "method",
|
|
8715
|
+
op: "notMatches",
|
|
8716
|
+
value: "^(?:list|get|retrieve|search|batch_?retrieve|calculate)",
|
|
8717
|
+
flags: "i"
|
|
8718
|
+
}
|
|
8719
|
+
],
|
|
8720
|
+
conditionMode: "all",
|
|
8721
|
+
verdict: "review",
|
|
8722
|
+
reason: "A request through the Square MCP server is about to move money. node9 asks before money moves (Square)."
|
|
8723
|
+
}
|
|
8724
|
+
],
|
|
8725
|
+
doors: [
|
|
8726
|
+
{
|
|
8727
|
+
name: "shield:square:review-api",
|
|
8728
|
+
tool: "bash",
|
|
8729
|
+
conditions: [
|
|
8730
|
+
{
|
|
8731
|
+
field: "command",
|
|
8732
|
+
op: "matches",
|
|
8733
|
+
// The live host only; the sandbox is connect.squareupsandbox.com.
|
|
8734
|
+
value: httpTo(
|
|
8735
|
+
"connect\\.squareup\\.com/v2/(?:refunds|payments|orders/[^\\s/?]{1,64}/pay|invoices|subscriptions|disputes|gift-cards/activities)(?![\\w-]|/search)"
|
|
8736
|
+
),
|
|
8737
|
+
flags: "i"
|
|
8738
|
+
},
|
|
8739
|
+
HTTP_WRITE_ANY
|
|
8740
|
+
],
|
|
8741
|
+
conditionMode: "all",
|
|
8742
|
+
verdict: "review",
|
|
8743
|
+
reason: "A request to the Square API is about to move money. node9 asks before money moves (Square)."
|
|
8744
|
+
},
|
|
8745
|
+
{
|
|
8746
|
+
name: "shield:square:review-code",
|
|
8747
|
+
tool: "bash",
|
|
8748
|
+
conditions: [
|
|
8749
|
+
{
|
|
8750
|
+
field: "command",
|
|
8751
|
+
op: "matches",
|
|
8752
|
+
// The Square SDK in the script: Node (`square`) or Python (`square`, `squareup`).
|
|
8753
|
+
value: `\\bSquareClient\\b|\\bsquare\\.client\\b|from\\s+square[.\\s]|from\\s+["']square["']|require\\(\\s*["']square["']`
|
|
8754
|
+
},
|
|
8755
|
+
{
|
|
8756
|
+
field: "command",
|
|
8757
|
+
op: "matches",
|
|
8758
|
+
value: "\\.refunds(?:Api)?\\.refund(?:Payment|_payment)\\s*\\(|\\.payments(?:Api)?\\.(?:create|complete)(?:Payment|_payment)?\\s*\\(|\\.orders(?:Api)?\\.pay(?:Order|_order)?\\s*\\(|\\.invoices(?:Api)?\\.publish(?:Invoice|_invoice)?\\s*\\("
|
|
8759
|
+
}
|
|
8760
|
+
],
|
|
8761
|
+
conditionMode: "all",
|
|
8762
|
+
verdict: "review",
|
|
8763
|
+
reason: "This code moves money through the Square SDK. node9 asks before money moves (Square)."
|
|
8764
|
+
},
|
|
8765
|
+
{
|
|
8766
|
+
name: "shield:square:review-code-http",
|
|
8767
|
+
tool: "bash",
|
|
8768
|
+
conditions: [
|
|
8769
|
+
{
|
|
8770
|
+
field: "command",
|
|
8771
|
+
op: "matches",
|
|
8772
|
+
value: postTo(
|
|
8773
|
+
"connect\\.squareup\\.com/v2/(?:refunds|payments|orders/[^\\s/?\"'`]{1,64}/pay|invoices|subscriptions|disputes|gift-cards/activities)(?![\\w-]|/search)"
|
|
8774
|
+
)
|
|
8775
|
+
}
|
|
8776
|
+
],
|
|
8777
|
+
verdict: "review",
|
|
8778
|
+
reason: "This code posts to the Square API. node9 asks before money moves (Square)."
|
|
8779
|
+
}
|
|
8780
|
+
]
|
|
8781
|
+
},
|
|
8782
|
+
{
|
|
8783
|
+
name: "plaid",
|
|
8784
|
+
label: "Plaid",
|
|
8785
|
+
description: "Asks before money moves through Plaid (transfers, transfer authorizations, payment initiation), whether the agent uses a Plaid MCP server, the API or the Plaid SDK in a script. The sandbox host is never asked about, and neither are reads such as balances or transactions.",
|
|
8786
|
+
template: "finance",
|
|
8787
|
+
server: "*plaid*",
|
|
8788
|
+
doors: [
|
|
8789
|
+
{
|
|
8790
|
+
name: "shield:plaid:review-api",
|
|
8791
|
+
tool: "bash",
|
|
8792
|
+
conditions: [
|
|
8793
|
+
{
|
|
8794
|
+
field: "command",
|
|
8795
|
+
op: "matches",
|
|
8796
|
+
// Every Plaid endpoint is a POST, so the path is what tells a
|
|
8797
|
+
// write; the live host only, never sandbox.plaid.com. A script
|
|
8798
|
+
// that posts to the URL meets this door too.
|
|
8799
|
+
value: httpTo(
|
|
8800
|
+
"production\\.plaid\\.com/(?:transfer/(?:create|authorization/create|recurring/create)|payment_initiation/payment/create|bank_transfer/create)(?![\\w-])"
|
|
8801
|
+
),
|
|
8802
|
+
flags: "i"
|
|
8803
|
+
}
|
|
8804
|
+
],
|
|
8805
|
+
verdict: "review",
|
|
8806
|
+
reason: "A request to the Plaid API is about to move money. node9 asks before money moves (Plaid)."
|
|
8807
|
+
},
|
|
8808
|
+
{
|
|
8809
|
+
name: "shield:plaid:review-code",
|
|
8810
|
+
tool: "bash",
|
|
8811
|
+
conditions: [
|
|
8812
|
+
{ field: "command", op: "matches", value: "\\bplaid", flags: "i" },
|
|
8813
|
+
{
|
|
8814
|
+
field: "command",
|
|
8815
|
+
op: "matches",
|
|
8816
|
+
value: "\\b(?:transfer(?:Authorization|Recurring)?Create|paymentInitiationPaymentCreate|bankTransferCreate|transfer_(?:authorization_|recurring_)?create|payment_initiation_payment_create|bank_transfer_create)\\s*\\("
|
|
8817
|
+
}
|
|
8818
|
+
],
|
|
8819
|
+
conditionMode: "all",
|
|
8820
|
+
verdict: "review",
|
|
8821
|
+
reason: "This code moves money through Plaid. node9 asks before money moves (Plaid)."
|
|
8822
|
+
}
|
|
8823
|
+
]
|
|
8824
|
+
},
|
|
8825
|
+
{
|
|
8826
|
+
name: "okta",
|
|
8827
|
+
label: "Okta",
|
|
8828
|
+
description: "Asks before a user, a group member, a role, a password or a key changes in Okta, whether the agent uses an Okta MCP server, the API or the Okta SDK in a script. Reads and signing in are never asked about.",
|
|
8829
|
+
template: "identity",
|
|
8830
|
+
server: "*okta*",
|
|
8831
|
+
doors: [
|
|
8832
|
+
{
|
|
8833
|
+
name: "shield:okta:review-api",
|
|
8834
|
+
tool: "bash",
|
|
8835
|
+
conditions: [
|
|
8836
|
+
{
|
|
8837
|
+
field: "command",
|
|
8838
|
+
op: "matches",
|
|
8839
|
+
// The management API only: sign-in (/api/v1/authn, /sessions,
|
|
8840
|
+
// /oauth2) is not under these paths.
|
|
8841
|
+
value: httpTo(
|
|
8842
|
+
"[\\w-]+\\.okta(?:-emea)?\\.com/api/v1/(?:users|groups|apps|iam|api-tokens|idps|authorizationServers|policies)(?![\\w-])"
|
|
8843
|
+
),
|
|
8844
|
+
flags: "i"
|
|
8845
|
+
},
|
|
8846
|
+
HTTP_WRITE_ANY
|
|
8847
|
+
],
|
|
8848
|
+
conditionMode: "all",
|
|
8849
|
+
verdict: "review",
|
|
8850
|
+
reason: "A request to the Okta API is about to change who has access. node9 asks before access changes (Okta)."
|
|
8851
|
+
},
|
|
8852
|
+
{
|
|
8853
|
+
name: "shield:okta:review-code",
|
|
8854
|
+
tool: "bash",
|
|
8855
|
+
conditions: [
|
|
8856
|
+
{
|
|
8857
|
+
field: "command",
|
|
8858
|
+
op: "matches",
|
|
8859
|
+
// The Okta SDK in the script: a method named createUser alone is
|
|
8860
|
+
// in half the applications ever written.
|
|
8861
|
+
value: "@okta/okta-sdk|\\bokta\\.Client\\b|from\\s+okta[.\\s]|\\bimport\\s+okta\\b"
|
|
8862
|
+
},
|
|
8863
|
+
{
|
|
8864
|
+
field: "command",
|
|
8865
|
+
op: "matches",
|
|
8866
|
+
value: "\\b(?:(?:create|delete|deactivate|suspend|unlock|activate)User|(?:reset|expire)Password|resetFactors|(?:assign|unassign)User(?:To|From)Group|assignRoleTo(?:User|Group))\\s*\\("
|
|
8867
|
+
}
|
|
8868
|
+
],
|
|
8869
|
+
conditionMode: "all",
|
|
8870
|
+
verdict: "review",
|
|
8871
|
+
reason: "This code changes who has access in Okta. node9 asks before access changes (Okta)."
|
|
8872
|
+
},
|
|
8873
|
+
{
|
|
8874
|
+
name: "shield:okta:review-code-py",
|
|
8875
|
+
tool: "bash",
|
|
8876
|
+
conditions: [
|
|
8877
|
+
{
|
|
8878
|
+
field: "command",
|
|
8879
|
+
op: "matches",
|
|
8880
|
+
value: "@okta/okta-sdk|\\bokta\\.Client\\b|from\\s+okta[.\\s]|\\bimport\\s+okta\\b"
|
|
8881
|
+
},
|
|
8882
|
+
{
|
|
8883
|
+
field: "command",
|
|
8884
|
+
op: "matches",
|
|
8885
|
+
value: "\\b(?:(?:create|delete|deactivate|deactivate_or_delete|suspend|unlock|activate)_user|(?:reset|expire)_password|reset_factors|(?:add|remove)_user_(?:to|from)_group|assign_role_to_(?:user|group))\\s*\\("
|
|
8886
|
+
}
|
|
8887
|
+
],
|
|
8888
|
+
conditionMode: "all",
|
|
8889
|
+
verdict: "review",
|
|
8890
|
+
reason: "This code changes who has access in Okta. node9 asks before access changes (Okta)."
|
|
8891
|
+
}
|
|
8892
|
+
]
|
|
8893
|
+
},
|
|
8894
|
+
{
|
|
8895
|
+
name: "auth0",
|
|
8896
|
+
label: "Auth0",
|
|
8897
|
+
description: "Asks before a user, a role, an application or its permissions change in Auth0, whether the agent uses an Auth0 MCP server, the auth0 CLI, the Management API or the Auth0 SDK in a script. Reads and signing in are never asked about.",
|
|
8898
|
+
template: "identity",
|
|
8899
|
+
server: "*auth0*",
|
|
8900
|
+
mcpTools: [
|
|
8901
|
+
{
|
|
8902
|
+
// Auth0's official MCP server: applications (their secrets and
|
|
8903
|
+
// callbacks), APIs and their scopes, and Actions (code that runs at
|
|
8904
|
+
// every sign-in).
|
|
8905
|
+
name: "shield:auth0:review-tenant-config",
|
|
8906
|
+
tool: "{,mcp__*__}{auth0_create_application,auth0_update_application,auth0_create_resource_server,auth0_update_resource_server,auth0_create_action,auth0_update_action,auth0_deploy_action}",
|
|
8907
|
+
conditions: [],
|
|
8908
|
+
verdict: "review",
|
|
8909
|
+
reason: "This changes an Auth0 application, API or sign-in Action. node9 asks before access changes (Auth0)."
|
|
8910
|
+
}
|
|
8911
|
+
],
|
|
8912
|
+
doors: [
|
|
8913
|
+
{
|
|
8914
|
+
name: "shield:auth0:review-cli",
|
|
8915
|
+
tool: "bash",
|
|
8916
|
+
conditions: [
|
|
8917
|
+
{
|
|
8918
|
+
field: "command",
|
|
8919
|
+
op: "matches",
|
|
8920
|
+
value: prog("auth0") + GAP + "\\s(?:(?:users|roles|apps|orgs)\\s+(?:create|update|delete|rm)|users\\s+roles\\s+(?:assign|add|remove|rm)|roles\\s+permissions\\s+(?:add|remove|rm)|users\\s+blocks\\s+unblock|api\\s+(?:post|put|patch|delete))(?![\\w-])",
|
|
8921
|
+
flags: "i"
|
|
8922
|
+
}
|
|
8923
|
+
],
|
|
8924
|
+
verdict: "review",
|
|
8925
|
+
reason: "The auth0 CLI is about to change who has access. node9 asks before access changes (Auth0)."
|
|
8926
|
+
},
|
|
8927
|
+
{
|
|
8928
|
+
name: "shield:auth0:review-api",
|
|
8929
|
+
tool: "bash",
|
|
8930
|
+
conditions: [
|
|
8931
|
+
{
|
|
8932
|
+
field: "command",
|
|
8933
|
+
op: "matches",
|
|
8934
|
+
// The Management API only; /oauth/token is signing in.
|
|
8935
|
+
value: httpTo(
|
|
8936
|
+
"\\.auth0\\.com/api/v2/(?:users|roles|clients|grants|client-grants|organizations|tickets|jobs/users-imports|device-credentials)(?![\\w-])"
|
|
8937
|
+
),
|
|
8938
|
+
flags: "i"
|
|
8939
|
+
},
|
|
8940
|
+
HTTP_WRITE_ANY
|
|
8941
|
+
],
|
|
8942
|
+
conditionMode: "all",
|
|
8943
|
+
verdict: "review",
|
|
8944
|
+
reason: "A request to the Auth0 Management API is about to change who has access. node9 asks before access changes (Auth0)."
|
|
8945
|
+
},
|
|
8946
|
+
{
|
|
8947
|
+
name: "shield:auth0:review-code",
|
|
8948
|
+
tool: "bash",
|
|
8949
|
+
conditions: [
|
|
8950
|
+
{
|
|
8951
|
+
field: "command",
|
|
8952
|
+
op: "matches",
|
|
8953
|
+
// The Auth0 SDK in the script (Node `auth0`, Python `auth0`).
|
|
8954
|
+
value: `\\bManagementClient\\b|from\\s+auth0[.\\s]|from\\s+["']auth0["']|require\\(\\s*["']auth0["']`
|
|
8955
|
+
},
|
|
8956
|
+
{
|
|
8957
|
+
field: "command",
|
|
8958
|
+
op: "matches",
|
|
8959
|
+
value: "\\.(?:users|roles|organizations|clients)\\.(?:create|delete|update|assignRoles|deleteRoles|assignUsers|addPermissions|deletePermissions|addMembers|deleteMembers|add_roles|remove_roles|add_users|add_permissions|remove_permissions)\\s*\\("
|
|
8960
|
+
}
|
|
8961
|
+
],
|
|
8962
|
+
conditionMode: "all",
|
|
8963
|
+
verdict: "review",
|
|
8964
|
+
reason: "This code changes who has access in Auth0. node9 asks before access changes (Auth0)."
|
|
8965
|
+
}
|
|
8966
|
+
]
|
|
8967
|
+
}
|
|
8968
|
+
];
|
|
8969
|
+
APP_SHIELDS = APP_SHIELD_SPECS.map(buildAppShield);
|
|
8970
|
+
APP_SHIELD_NAMES = new Set(APP_SHIELD_SPECS.map((s) => s.name));
|
|
8971
|
+
APP_SERVER_GLOB = `{${APP_SHIELD_SPECS.map((s) => s.server).join(",")}}`;
|
|
8972
|
+
GENERAL_ACTION_RULES = buildGeneralRules();
|
|
8115
8973
|
BUILTIN_SHIELDS = {
|
|
8116
8974
|
[aws_default.name]: aws_default,
|
|
8117
8975
|
[bash_safe_default.name]: bash_safe_default,
|
|
8118
8976
|
[docker_default.name]: docker_default,
|
|
8119
8977
|
[filesystem_default.name]: filesystem_default,
|
|
8120
|
-
// Dangerous actions as category shields (node9Firewall#385): an MCP app's
|
|
8121
|
-
// money, credential and publishing tools, asked about whatever the request
|
|
8122
|
-
// said. One glob per rule covers the bare name the gateway sends and the
|
|
8123
|
-
// mcp__<server>__ name the agent hook sends.
|
|
8124
|
-
[finance_default.name]: finance_default,
|
|
8125
8978
|
[github_default.name]: github_default,
|
|
8126
|
-
[identity_default.name]: identity_default,
|
|
8127
8979
|
[k8s_default.name]: k8s_default,
|
|
8128
8980
|
[mongodb_default.name]: mongodb_default,
|
|
8129
8981
|
[postgres_default.name]: postgres_default,
|
|
8130
8982
|
[project_jail_default.name]: project_jail_default,
|
|
8131
|
-
[
|
|
8132
|
-
|
|
8983
|
+
[redis_default.name]: redis_default,
|
|
8984
|
+
// App shields generated from the action templates (node9Firewall#390): one
|
|
8985
|
+
// per app, every door (MCP server, CLI, API, library), off by default.
|
|
8986
|
+
...Object.fromEntries(APP_SHIELDS.map((s) => [s.name, s]))
|
|
8133
8987
|
};
|
|
8134
8988
|
assertBuiltinShieldRegexesAreSafe();
|
|
8135
8989
|
SSRF_MAX_HOST = 253;
|
|
@@ -8185,9 +9039,9 @@ var init_dist = __esm({
|
|
|
8185
9039
|
},
|
|
8186
9040
|
// ── Commands ──────────────────────────────────────────────────────────────
|
|
8187
9041
|
"commands.inline-exec": {
|
|
8188
|
-
plain: "The agent runs a small program written inside the command itself.",
|
|
9042
|
+
plain: "The agent runs a small program written inside the command itself, and node9 reads it: ordinary code runs, code that deletes, sends data out, runs other commands or reads protected files is held, and so is code node9 cannot read.",
|
|
8189
9043
|
example: `node -e "require('fs').rmSync('dist', {recursive: true})"`,
|
|
8190
|
-
advice: "
|
|
9044
|
+
advice: "Keep Review. Strict mode reviews every inline call, harmless or not; Log or Off only if inline code never reaches anything that matters."
|
|
8191
9045
|
},
|
|
8192
9046
|
"commands.eval-dynamic": {
|
|
8193
9047
|
plain: "The agent runs code whose content is built at run time, so node9 cannot read it in advance.",
|
|
@@ -8219,6 +9073,21 @@ var init_dist = __esm({
|
|
|
8219
9073
|
example: "chmod 777 deploy.sh",
|
|
8220
9074
|
advice: "Keep Review. A narrower permission (755, 644) almost always does the job."
|
|
8221
9075
|
},
|
|
9076
|
+
"actions.money": {
|
|
9077
|
+
plain: "The agent moves money (a refund, a payout, a transfer, a charge) through an app node9 has no protection for, such as an internal billing server.",
|
|
9078
|
+
example: "refund_order on a server called acme-billing",
|
|
9079
|
+
advice: "Keep Review. When the app is Stripe or PayPal, turn on its own protection instead: it covers the CLI, the API and scripts too, and a call from it no longer comes here."
|
|
9080
|
+
},
|
|
9081
|
+
"actions.access": {
|
|
9082
|
+
plain: "The agent changes a password, a key, a role or who belongs to a team, through an app node9 has no protection for.",
|
|
9083
|
+
example: "invite_user or grant_role on a company admin server",
|
|
9084
|
+
advice: "Keep Review. Set Log on a sandbox workspace where agents manage test users all day."
|
|
9085
|
+
},
|
|
9086
|
+
"actions.publish": {
|
|
9087
|
+
plain: "The agent publishes something outside the organisation (a release, a package, a deploy, a public post) through an app node9 has no protection for.",
|
|
9088
|
+
example: "create_release or deploy_to_production on a company MCP server",
|
|
9089
|
+
advice: "Keep Review. Set Log on a release machine whose pipeline publishes on purpose, never on a developer laptop."
|
|
9090
|
+
},
|
|
8222
9091
|
"commands.sudo": {
|
|
8223
9092
|
plain: "The agent runs a command as the administrator (root).",
|
|
8224
9093
|
example: "sudo apt-get install -y nginx",
|
|
@@ -8379,11 +9248,57 @@ var init_dist = __esm({
|
|
|
8379
9248
|
advice: "Fixed at Block. A person accepts the change on the machine (node9 mcp pin update)."
|
|
8380
9249
|
},
|
|
8381
9250
|
"loading.malicious-package": {
|
|
8382
|
-
plain: "The agent installs a package that the public malicious-package database (OSV) lists, or one published minutes ago. Covers npm, PyPI, RubyGems, crates.io, Go, NuGet, Maven, Packagist and VS Code extensions; the publish-age and install-script signals are npm and PyPI only.",
|
|
9251
|
+
plain: "The agent installs a package that the public malicious-package database (OSV) lists, or one published minutes ago. Covers npm, PyPI, RubyGems, crates.io, Go, NuGet, Maven, Packagist and VS Code extensions; the publish-age and install-script signals are npm and PyPI only. A new release built from the same repository as the one before it (registry provenance) and an install script the package has always run do not stop; a release that lost the provenance its previous release had does.",
|
|
8383
9252
|
example: "npm install of a package flagged as malware",
|
|
8384
9253
|
advice: "Keep Block for known-malicious packages; brand-new packages already stop for Review."
|
|
8385
9254
|
}
|
|
8386
9255
|
};
|
|
9256
|
+
LEVELS = ["audit", "soft", "standard", "strict", "block-all"];
|
|
9257
|
+
MODE_VALUES = ["observe", ...LEVELS];
|
|
9258
|
+
SOFT = {
|
|
9259
|
+
"commands.inline-exec": "log",
|
|
9260
|
+
"commands.eval-dynamic": "log",
|
|
9261
|
+
"commands.rm": "log",
|
|
9262
|
+
"commands.chmod": "log",
|
|
9263
|
+
"commands.sudo": "log",
|
|
9264
|
+
"commands.git-destructive": "log",
|
|
9265
|
+
"commands.sql-ddl": "log",
|
|
9266
|
+
"commands.sql-no-where": "log",
|
|
9267
|
+
"commands.temp-binary": "log",
|
|
9268
|
+
"commands.disk-destroy": "log",
|
|
9269
|
+
"commands.dangerous-word": "log",
|
|
9270
|
+
"files.system-dirs": "log",
|
|
9271
|
+
"files.shell-startup": "log",
|
|
9272
|
+
"files.password-hashes": "log",
|
|
9273
|
+
"data.pipe-chain": "log",
|
|
9274
|
+
"behavior.untrusted-content": "log"
|
|
9275
|
+
};
|
|
9276
|
+
STRICT = {
|
|
9277
|
+
"commands.sql-ddl": "block",
|
|
9278
|
+
"commands.disk-destroy": "block",
|
|
9279
|
+
"commands.node9-disarm": "block",
|
|
9280
|
+
"files.system-dirs": "block",
|
|
9281
|
+
"files.shell-startup": "block",
|
|
9282
|
+
"files.password-hashes": "block",
|
|
9283
|
+
"data.secrets-weak": "block",
|
|
9284
|
+
"data.pipe-chain": "block",
|
|
9285
|
+
"network.unknown-host": "review",
|
|
9286
|
+
"network.internal-addresses": "block",
|
|
9287
|
+
"behavior.prompt-injection": "log",
|
|
9288
|
+
"loading.skill-tamper": "block"
|
|
9289
|
+
};
|
|
9290
|
+
BLOCK_ALL = {
|
|
9291
|
+
...STRICT,
|
|
9292
|
+
"commands.unknown": "review",
|
|
9293
|
+
"commands.temp-binary": "block"
|
|
9294
|
+
};
|
|
9295
|
+
LEVEL_CHECKS = {
|
|
9296
|
+
audit: {},
|
|
9297
|
+
soft: SOFT,
|
|
9298
|
+
standard: {},
|
|
9299
|
+
strict: STRICT,
|
|
9300
|
+
"block-all": BLOCK_ALL
|
|
9301
|
+
};
|
|
8387
9302
|
VERDICTS = ["off", "log", "review", "block"];
|
|
8388
9303
|
CHECK_VERDICT_RANK = { off: 0, log: 1, review: 2, block: 3 };
|
|
8389
9304
|
ALL = VERDICTS;
|
|
@@ -8431,7 +9346,7 @@ var init_dist = __esm({
|
|
|
8431
9346
|
id: "commands.inline-exec",
|
|
8432
9347
|
group: "commands",
|
|
8433
9348
|
title: "Inline code execution",
|
|
8434
|
-
catches: "
|
|
9349
|
+
catches: "inline code (python -c, node -e, a heredoc into an interpreter) that deletes, sends, opens a socket, runs commands, reads protected files or decodes and runs; unreadable inline code",
|
|
8435
9350
|
values: ALL,
|
|
8436
9351
|
defaultValue: "review",
|
|
8437
9352
|
legacy: ["Node9 Standard (Inline Execution)"]
|
|
@@ -8788,6 +9703,38 @@ var init_dist = __esm({
|
|
|
8788
9703
|
values: ALL,
|
|
8789
9704
|
defaultValue: "block",
|
|
8790
9705
|
legacy: ["\u{1F4E6} Node9 Package Check (Malicious)", "\u{1F4E6} Node9 Package Check (Review)"]
|
|
9706
|
+
},
|
|
9707
|
+
// ── Actions in apps (node9Firewall#390) ──────────────────────────────────
|
|
9708
|
+
// The safety net under the app shields: an action that moves money, changes
|
|
9709
|
+
// access or publishes, on an MCP server no app shield claims (an internal
|
|
9710
|
+
// billing server, a vendor with no shield, Zapier). Review by default; the
|
|
9711
|
+
// rules come from the action templates (shields/apps.ts).
|
|
9712
|
+
{
|
|
9713
|
+
id: "actions.money",
|
|
9714
|
+
group: "actions",
|
|
9715
|
+
title: "Money moves",
|
|
9716
|
+
catches: "a refund, payout, transfer, charge or withdrawal through an app with no protection of its own",
|
|
9717
|
+
values: ALL,
|
|
9718
|
+
defaultValue: "review",
|
|
9719
|
+
legacy: generalActionRuleNames("finance")
|
|
9720
|
+
},
|
|
9721
|
+
{
|
|
9722
|
+
id: "actions.access",
|
|
9723
|
+
group: "actions",
|
|
9724
|
+
title: "Accounts and access change",
|
|
9725
|
+
catches: "a password, key, role, permission or user changed through an app with no protection of its own",
|
|
9726
|
+
values: ALL,
|
|
9727
|
+
defaultValue: "review",
|
|
9728
|
+
legacy: generalActionRuleNames("identity")
|
|
9729
|
+
},
|
|
9730
|
+
{
|
|
9731
|
+
id: "actions.publish",
|
|
9732
|
+
group: "actions",
|
|
9733
|
+
title: "Something is published",
|
|
9734
|
+
catches: "a release, package, deploy or public post through an app with no protection of its own",
|
|
9735
|
+
values: ALL,
|
|
9736
|
+
defaultValue: "review",
|
|
9737
|
+
legacy: generalActionRuleNames("publishing")
|
|
8791
9738
|
}
|
|
8792
9739
|
];
|
|
8793
9740
|
SHIELD_RULE_NAME = /^shield:([^:]+):(block|review|allow)-(.+)$/;
|
|
@@ -8808,7 +9755,7 @@ var init_dist = __esm({
|
|
|
8808
9755
|
"commands.rm": (s) => knob(s.commandChecks?.rmAdvisory),
|
|
8809
9756
|
"commands.chmod": (s) => knob(s.commandChecks?.chmod),
|
|
8810
9757
|
"commands.sql-ddl": (s) => knob(s.commandChecks?.sqlDdl),
|
|
8811
|
-
"commands.unknown": (s) => s.mode === void 0 ? void 0 : s.mode
|
|
9758
|
+
"commands.unknown": (s) => s.mode === void 0 ? void 0 : isBlockAll(s.mode) ? "review" : "off",
|
|
8812
9759
|
"data.secrets": (s) => enabledOr(s.dlp?.enabled, "block"),
|
|
8813
9760
|
"data.secrets-weak": (s) => {
|
|
8814
9761
|
if (s.dlp?.enabled === false) return "off";
|
|
@@ -8916,6 +9863,217 @@ var init_dist = __esm({
|
|
|
8916
9863
|
BASE64_RUN_RE = /[A-Za-z0-9+/]{32,}={0,2}|[A-Za-z0-9_-]{32,}/g;
|
|
8917
9864
|
HEX_RUN_RE = /\b(?:[0-9a-fA-F]{2}){24,}\b/g;
|
|
8918
9865
|
HEX_ESCAPE_RUN_RE = /(?:\\x[0-9a-fA-F]{2}){16,}/g;
|
|
9866
|
+
SYSTEM_AUTH_FILES = [
|
|
9867
|
+
"/etc/passwd",
|
|
9868
|
+
"/etc/shadow",
|
|
9869
|
+
"/etc/group",
|
|
9870
|
+
"/etc/gshadow",
|
|
9871
|
+
"/etc/sudoers"
|
|
9872
|
+
];
|
|
9873
|
+
SYSTEM_AUTH_DIRS = ["/etc/security", "/etc/pam.d", "/etc/sudoers.d"];
|
|
9874
|
+
PASSWORD_HASH_FILES = ["/etc/shadow", "/etc/gshadow"];
|
|
9875
|
+
STARTUP_NAMES = [".bashrc", ".zshrc", ".profile", ".bash_profile"];
|
|
9876
|
+
SYSTEM_DIRS = [
|
|
9877
|
+
"/usr",
|
|
9878
|
+
"/etc",
|
|
9879
|
+
"/boot",
|
|
9880
|
+
"/bin",
|
|
9881
|
+
"/sbin",
|
|
9882
|
+
"/lib",
|
|
9883
|
+
"/lib64",
|
|
9884
|
+
"/var/log"
|
|
9885
|
+
];
|
|
9886
|
+
SYSTEM_DIRS_EXEMPT = ["/usr/local", "/usr/src"];
|
|
9887
|
+
UPLOAD_SYSTEM_DIRS = [...SYSTEM_DIRS, "/proc", "/sys", "/root"];
|
|
9888
|
+
AGENT_INSTRUCTION_FILES = [
|
|
9889
|
+
".gitlab-ci.yml",
|
|
9890
|
+
"CLAUDE.md",
|
|
9891
|
+
"AGENTS.md",
|
|
9892
|
+
".cursorrules",
|
|
9893
|
+
".mcp.json",
|
|
9894
|
+
".vscode/settings.json",
|
|
9895
|
+
"package.json"
|
|
9896
|
+
];
|
|
9897
|
+
AGENT_INSTRUCTION_DIRS = [
|
|
9898
|
+
".github/workflows",
|
|
9899
|
+
".claude",
|
|
9900
|
+
".cursor",
|
|
9901
|
+
".git/hooks",
|
|
9902
|
+
".husky"
|
|
9903
|
+
];
|
|
9904
|
+
LEGACY_SYSTEM_PATH_RE = /^(?:[a-zA-Z]:)?\/etc\/(?:passwd|shadow|sudoers)$/;
|
|
9905
|
+
MAX_INLINE_CODE = 64 * 1024;
|
|
9906
|
+
WHAT = {
|
|
9907
|
+
deletes: "deletes files",
|
|
9908
|
+
"writes-outside": "writes a file outside the project",
|
|
9909
|
+
sends: "sends data over the network",
|
|
9910
|
+
listens: "opens a listening socket or a shell",
|
|
9911
|
+
"runs-commands": "runs other commands or processes",
|
|
9912
|
+
"reads-protected": "reads a protected file",
|
|
9913
|
+
"builds-code": "runs code it builds at run time",
|
|
9914
|
+
permissions: "changes permissions or ownership",
|
|
9915
|
+
monitors: "captures the screen, the clipboard or the keyboard",
|
|
9916
|
+
amplifies: "writes or sends in an endless loop"
|
|
9917
|
+
};
|
|
9918
|
+
OUTSIDE = String.raw`(?:~|\.\.[\\/]|/(?!(?:tmp|var/tmp)/(?![^'"\n]*\.\.)|dev/(?:null|stdout|stderr)\b))`;
|
|
9919
|
+
pyImport = (mods) => new RegExp(String.raw`\b(?:import\s+(?:[\w.]+(?:\s+as\s+\w+)?\s*,\s*)*|from\s+)(?:${mods})\b`);
|
|
9920
|
+
either = (...res) => new RegExp(res.map((r) => `(?:${r.source})`).join("|"), "m");
|
|
9921
|
+
PYTHON = [
|
|
9922
|
+
[
|
|
9923
|
+
"deletes",
|
|
9924
|
+
/\bos\.(?:remove|unlink|rmdir|removedirs)\s*\(|\bshutil\.rmtree\s*\(|\.(?:unlink|rmdir)\s*\(|\bfrom\s+(?:os|shutil)\s+import\s+[^\n]*\b(?:remove|unlink|rmdir|removedirs|rmtree)\b/
|
|
9925
|
+
],
|
|
9926
|
+
[
|
|
9927
|
+
"writes-outside",
|
|
9928
|
+
new RegExp(
|
|
9929
|
+
String.raw`\bopen\s*\(\s*[rRbBuU]?['"]${OUTSIDE}[^'"\n]*['"]\s*,\s*(?:mode\s*=\s*)?[rRbB]?['"][^'"\n]*[wax+]|\bPath\s*\(\s*[rR]?['"]${OUTSIDE}[^'"\n]*['"]\s*\)\s*\.(?:write_text|write_bytes|touch|mkdir|symlink_to)\s*\(|\bshutil\.(?:copy\w*|move)\s*\([^)\n]*['"]${OUTSIDE}|\bos\.(?:rename|replace|symlink|link|truncate|makedirs|mkdir)\s*\([^)\n]*['"]${OUTSIDE}`
|
|
9930
|
+
)
|
|
9931
|
+
],
|
|
9932
|
+
[
|
|
9933
|
+
"sends",
|
|
9934
|
+
either(
|
|
9935
|
+
pyImport(
|
|
9936
|
+
String.raw`socket|requests|urllib2|urllib3|http\.client|httplib|smtplib|ftplib|paramiko|telnetlib|aiohttp|httpx|websockets?|xmlrpc|poplib|imaplib|pycurl|scapy`
|
|
9937
|
+
),
|
|
9938
|
+
/\burllib\.request\b|\burlopen\s*\(|\bfrom\s+urllib\s+import\s+[^\n;]*\brequest\b|\basyncio\.open_connection\s*\(/
|
|
9939
|
+
)
|
|
9940
|
+
],
|
|
9941
|
+
[
|
|
9942
|
+
"listens",
|
|
9943
|
+
either(
|
|
9944
|
+
pyImport(String.raw`pty|socketserver|http\.server`),
|
|
9945
|
+
/\bpty\.spawn\s*\(|\.bind\s*\(\s*\(|\basyncio\.start_server\s*\(/
|
|
9946
|
+
)
|
|
9947
|
+
],
|
|
9948
|
+
[
|
|
9949
|
+
"runs-commands",
|
|
9950
|
+
either(
|
|
9951
|
+
pyImport("subprocess|pexpect|commands|psutil"),
|
|
9952
|
+
/\bos\.(?:system|popen|exec\w*|spawn\w*|posix_spawn\w*|fork|forkpty|kill|killpg)\s*\(|\bfrom\s+os\s+import\s+[^\n;]*\b(?:system|popen|exec\w*|spawn\w*|fork|kill)\b|\bfrom\s+(?:os|shutil|pty|ctypes)\s+import\s+\*/
|
|
9953
|
+
)
|
|
9954
|
+
],
|
|
9955
|
+
[
|
|
9956
|
+
"builds-code",
|
|
9957
|
+
/(?<![\w.])(?:exec|eval|compile|execfile)\s*\(|\b__import__\s*\(|\bimportlib\b|\b(?:c?pickle|dill|marshal|shelve)\.loads?\s*\(|\byaml\.(?:unsafe_)?load\s*\((?![^)\n]*SafeLoader)|\bctypes\b|\b(?:getattr|vars)\s*\(\s*(?:os|subprocess|shutil|socket|sys|pty|builtins|__builtins__)\b|\b(?:os|subprocess|shutil|socket|sys|pty|builtins)\.__dict__\b|\b__builtins__\b|\bsys\.modules\b|\bglobals\s*\(\s*\)\s*\[/
|
|
9958
|
+
],
|
|
9959
|
+
[
|
|
9960
|
+
// Spyware shapes: a screenshot, the clipboard, a key logger.
|
|
9961
|
+
"monitors",
|
|
9962
|
+
either(
|
|
9963
|
+
pyImport(String.raw`pyperclip|pynput|keyboard|pyautogui|pyscreenshot|mss|clipboard`),
|
|
9964
|
+
/\bImageGrab\b|\bpyautogui\.screenshot\b|\bscreencapture\b/
|
|
9965
|
+
)
|
|
9966
|
+
],
|
|
9967
|
+
[
|
|
9968
|
+
"permissions",
|
|
9969
|
+
/\bos\.(?:chmod|lchmod|fchmod|chown|lchown|fchown|setuid|setgid|seteuid|setegid|setreuid|setregid|setresuid)\s*\(|\.chmod\s*\(|\bshutil\.chown\s*\(/
|
|
9970
|
+
]
|
|
9971
|
+
];
|
|
9972
|
+
NODE = [
|
|
9973
|
+
[
|
|
9974
|
+
"deletes",
|
|
9975
|
+
/\.(?:rm|rmSync|rmdir|rmdirSync|unlink|unlinkSync)\s*\(|\brimraf\b|\bDeno\.remove(?:Sync)?\s*\(/
|
|
9976
|
+
],
|
|
9977
|
+
[
|
|
9978
|
+
"writes-outside",
|
|
9979
|
+
new RegExp(
|
|
9980
|
+
String.raw`(?:\.(?:writeFile|writeFileSync|appendFile|appendFileSync|createWriteStream|copyFile|copyFileSync|cp|cpSync|rename|renameSync|truncate|truncateSync|symlink|symlinkSync|mkdir|mkdirSync)|\bDeno\.(?:writeTextFile|writeFile|rename|mkdir)(?:Sync)?|\bBun\.write)\s*\(\s*['"\x60]${OUTSIDE}`
|
|
9981
|
+
)
|
|
9982
|
+
],
|
|
9983
|
+
[
|
|
9984
|
+
"sends",
|
|
9985
|
+
/\bfetch\s*\(|(?:\brequire\s*\(\s*|\bfrom\s+|\bimport\s+)['"\x60](?:node:)?(?:http|https|http2|net|dgram|tls|axios|node-fetch|ws|request|got|undici|nodemailer)['"\x60]|\bnew\s+WebSocket\b|\bXMLHttpRequest\b|\b(?:Deno|Bun)\.connect\w*\s*\(/
|
|
9986
|
+
],
|
|
9987
|
+
["listens", /\bcreateServer\s*\(|\b(?:Deno\.(?:listen\w*|serve)|Bun\.(?:listen|serve))\s*\(/],
|
|
9988
|
+
[
|
|
9989
|
+
"runs-commands",
|
|
9990
|
+
/\bchild_process\b|\bprocess\.(?:binding|dlopen|kill)\s*\(|\bDeno\.(?:run|Command)\b|\bBun\.(?:spawn|spawnSync|\$)\b|\bexecSync\s*\(|\bspawnSync\s*\(/
|
|
9991
|
+
],
|
|
9992
|
+
[
|
|
9993
|
+
"builds-code",
|
|
9994
|
+
/(?<![\w.])eval\s*\(|\bnew\s+Function\s*\(|(?<![\w.])Function\s*\(|(?:\brequire\s*\(\s*|\bfrom\s+)['"\x60](?:node:)?vm['"\x60]|\bvm\.\w+|\brequire\s*\(\s*[^'"\x60\s)]|\bimport\s*\(\s*[^'"\x60\s)]/
|
|
9995
|
+
],
|
|
9996
|
+
[
|
|
9997
|
+
"permissions",
|
|
9998
|
+
/\.(?:chmod|chmodSync|chown|chownSync|lchown|lchownSync|fchmod|fchmodSync|fchown|fchownSync)\s*\(|\bprocess\.set(?:e)?[ug]id\s*\(|\bDeno\.(?:chmod|chown)(?:Sync)?\s*\(/
|
|
9999
|
+
]
|
|
10000
|
+
];
|
|
10001
|
+
PERL = [
|
|
10002
|
+
["deletes", /\b(?:unlink|rmdir|rmtree|remove_tree)\b/],
|
|
10003
|
+
[
|
|
10004
|
+
"writes-outside",
|
|
10005
|
+
new RegExp(
|
|
10006
|
+
String.raw`['"]\s*(?:\+?>>?|\+<)\s*['"]?\s*,?\s*['"]?${OUTSIDE}|\b(?:rename|symlink|link|copy|move)\s*\(?[^;\n]*['"]${OUTSIDE}`
|
|
10007
|
+
)
|
|
10008
|
+
],
|
|
10009
|
+
[
|
|
10010
|
+
"sends",
|
|
10011
|
+
/\b(?:IO::Socket|Socket|LWP|HTTP::Tiny|Net::\w+|Mojo::UserAgent|WWW::Mechanize|IO::Socket::INET)\b/
|
|
10012
|
+
],
|
|
10013
|
+
[
|
|
10014
|
+
"runs-commands",
|
|
10015
|
+
/\b(?:system|exec|fork|kill|syscall)\b|`[^`]*`|\bqx\s*[{([/|!'"]|\bopen\s*\(?[^;\n]*(?:['"]\s*\||\|\s*['"]|['"]-\|['"])/
|
|
10016
|
+
],
|
|
10017
|
+
["builds-code", /\beval\b|\bStorable::thaw\b|\bdo\s+['"]/],
|
|
10018
|
+
["permissions", /\b(?:chmod|chown)\b/]
|
|
10019
|
+
];
|
|
10020
|
+
RUBY = [
|
|
10021
|
+
[
|
|
10022
|
+
"deletes",
|
|
10023
|
+
/\bFile\.(?:delete|unlink)\b|\bFileUtils\.(?:rm\w*|remove\w*)\b|\bDir\.(?:rmdir|delete|unlink)\b|\.(?:rmtree|delete_all)\b/
|
|
10024
|
+
],
|
|
10025
|
+
[
|
|
10026
|
+
"writes-outside",
|
|
10027
|
+
new RegExp(
|
|
10028
|
+
String.raw`\bFile\.(?:write|binwrite|symlink|rename)\s*\(?\s*['"]${OUTSIDE}|\bFile\.open\s*\(?\s*['"]${OUTSIDE}[^'"\n]*['"]\s*,\s*['"][wa+]|\bFileUtils\.(?:cp\w*|mv|install|ln\w*|mkdir_p|touch)\s*\(?[^\n]*['"]${OUTSIDE}`
|
|
10029
|
+
)
|
|
10030
|
+
],
|
|
10031
|
+
[
|
|
10032
|
+
"sends",
|
|
10033
|
+
/\bNet::\w+|\b(?:TCPSocket|UDPSocket|UNIXSocket|Socket)\b|\bopen-uri\b|\bURI\.open\b|\b(?:HTTParty|Faraday|RestClient|Excon|Typhoeus)\b|\brequire\s+['"](?:socket|net\/\w+|open-uri)['"]/
|
|
10034
|
+
],
|
|
10035
|
+
["listens", /\b(?:TCPServer|UNIXServer|WEBrick|PTY)\b/],
|
|
10036
|
+
[
|
|
10037
|
+
"runs-commands",
|
|
10038
|
+
/(?<![\w.])(?:system|exec|spawn|fork)\b|`[^`]*`|%x[{([|!]|(?<![\w.])open\s*\(?\s*['"]\||\bIO\.popen\b|\bOpen3\b|\bProcess\.(?:spawn|kill|exec)\b|\bKernel\.(?:system|exec|spawn|open)\b/
|
|
10039
|
+
],
|
|
10040
|
+
[
|
|
10041
|
+
"builds-code",
|
|
10042
|
+
/(?<![\w.])eval\b|\b(?:instance|class|module)_eval\b|\b(?:send|public_send)\s*\(?\s*:?['"]?(?:system|eval|exec|spawn)\b|\bMarshal\.load\b|\bYAML\.(?:unsafe_)?load\b|\bconst_get\b/
|
|
10043
|
+
],
|
|
10044
|
+
["permissions", /\b(?:File|FileUtils)\.(?:chmod|chown|lchmod|lchown)\w*\b/]
|
|
10045
|
+
];
|
|
10046
|
+
PHP = [
|
|
10047
|
+
["deletes", /\b(?:unlink|rmdir)\s*\(/],
|
|
10048
|
+
[
|
|
10049
|
+
"writes-outside",
|
|
10050
|
+
new RegExp(
|
|
10051
|
+
String.raw`\b(?:file_put_contents|symlink|link|copy|rename|mkdir|touch)\s*\([^)\n]*['"]${OUTSIDE}|\bfopen\s*\(\s*['"]${OUTSIDE}[^'"\n]*['"]\s*,\s*['"][waxc+]`
|
|
10052
|
+
)
|
|
10053
|
+
],
|
|
10054
|
+
[
|
|
10055
|
+
"sends",
|
|
10056
|
+
/\b(?:curl_exec|curl_init|fsockopen|pfsockopen|stream_socket_client|socket_create|socket_connect|mail|ftp_connect)\s*\(|\b(?:file_get_contents|fopen|file|readfile)\s*\(\s*['"](?:https?|ftp):/
|
|
10057
|
+
],
|
|
10058
|
+
["listens", /\b(?:stream_socket_server|socket_bind|socket_listen)\s*\(/],
|
|
10059
|
+
[
|
|
10060
|
+
"runs-commands",
|
|
10061
|
+
/\b(?:system|exec|shell_exec|passthru|proc_open|popen|pcntl_exec|pcntl_fork|posix_kill)\s*\(|`[^`]*`/
|
|
10062
|
+
],
|
|
10063
|
+
[
|
|
10064
|
+
"builds-code",
|
|
10065
|
+
/\b(?:eval|assert|create_function|unserialize|call_user_func(?:_array)?)\s*\(|\bbase64_decode\b/
|
|
10066
|
+
],
|
|
10067
|
+
["permissions", /\b(?:chmod|chown|chgrp|lchown|lchgrp)\s*\(/]
|
|
10068
|
+
];
|
|
10069
|
+
TABLES = {
|
|
10070
|
+
python: PYTHON,
|
|
10071
|
+
node: NODE,
|
|
10072
|
+
perl: PERL,
|
|
10073
|
+
ruby: RUBY,
|
|
10074
|
+
php: PHP
|
|
10075
|
+
};
|
|
10076
|
+
STRING_LITERAL = /(['"`])((?:\\.|(?!\1)[^\\\n]){1,4096})\1/g;
|
|
8919
10077
|
({ syntax } = import_mvdan_sh.default);
|
|
8920
10078
|
sharedParser = syntax.NewParser();
|
|
8921
10079
|
MESSAGE_FLAGS = /* @__PURE__ */ new Set([
|
|
@@ -8953,6 +10111,10 @@ var init_dist = __esm({
|
|
|
8953
10111
|
AST_CACHE_MAX = 5e3;
|
|
8954
10112
|
astCache = /* @__PURE__ */ new Map();
|
|
8955
10113
|
PARSE_FAIL = /* @__PURE__ */ Symbol("parse-fail");
|
|
10114
|
+
INTERPRETER_LINE = /^#![ \t]*[\w./+-]+(?:[ \t]+[\w./+=:-]+)*[ \t]*\r?(?=\n|$)/;
|
|
10115
|
+
PROGRAM_DIR = /^(?:\/usr(?:\/local)?|\/opt\/homebrew|\/home\/linuxbrew\/\.linuxbrew)?\/s?bin\/[^/]+$|^\/snap\/bin\/[^/]+$|^(?![/~\\]|[A-Za-z]:|.*\.\.)/;
|
|
10116
|
+
ACCESS_VIEW_CACHE_MAX = 1e3;
|
|
10117
|
+
accessViewCache = /* @__PURE__ */ new Map();
|
|
8956
10118
|
MAX_READING_PAYLOADS = 8;
|
|
8957
10119
|
defaultExpansionOps = null;
|
|
8958
10120
|
VARIABLE_READERS = /* @__PURE__ */ new Set([
|
|
@@ -9662,6 +10824,14 @@ var init_dist = __esm({
|
|
|
9662
10824
|
INTERP_LEADING_TARGET = /* @__PURE__ */ new Set(["su"]);
|
|
9663
10825
|
MAX_PAYLOAD_NESTING = 8;
|
|
9664
10826
|
payloadNesting = 0;
|
|
10827
|
+
MAX_INLINE_CALLS = 16;
|
|
10828
|
+
INLINE_EXEC_FALLBACK = /(^|[|;&]|&&)\s*(?:[\w./-]*\/)?(python[\d.]*|perl|ruby|node|php|lua|deno|bun|pwsh|osascript|rscript|bash|sh|zsh)\s+-{1,2}[a-z]*[ceEr]/i;
|
|
10829
|
+
UNREADABLE_SOURCE = {
|
|
10830
|
+
pipe: "reads its code from another program through a pipe",
|
|
10831
|
+
file: "reads its code from a file on stdin",
|
|
10832
|
+
variable: "runs code the shell fills in at run time",
|
|
10833
|
+
payload: "runs a shell payload that evaluates, sources or runs further code"
|
|
10834
|
+
};
|
|
9665
10835
|
SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "ksh", "source", "."]);
|
|
9666
10836
|
SCRIPT_RUNNER_VALUE_FLAGS = /* @__PURE__ */ new Set(["-o", "+o", "-O", "+O", "--rcfile", "--init-file"]);
|
|
9667
10837
|
MAX_SCRIPT_OPERANDS = 8;
|
|
@@ -9861,9 +11031,6 @@ var init_dist = __esm({
|
|
|
9861
11031
|
positionalAfter = (words, from, to = words.length) => positionedArgs(words, from, to).map((a) => a.value);
|
|
9862
11032
|
NONE = { kind: "none" };
|
|
9863
11033
|
UNKNOWN = { kind: "unknown" };
|
|
9864
|
-
MAX_REGEX_LENGTH = 256;
|
|
9865
|
-
REGEX_CACHE_MAX = 500;
|
|
9866
|
-
regexCache = /* @__PURE__ */ new Map();
|
|
9867
11034
|
FORBIDDEN_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
9868
11035
|
BASH_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
9869
11036
|
"bash",
|
|
@@ -9978,45 +11145,6 @@ var init_dist = __esm({
|
|
|
9978
11145
|
(s) => parseShared(`echo ${s}`).Stmts[0].Cmd.Args[1].Parts[0].Exp.Op
|
|
9979
11146
|
)
|
|
9980
11147
|
);
|
|
9981
|
-
SYSTEM_AUTH_FILES = [
|
|
9982
|
-
"/etc/passwd",
|
|
9983
|
-
"/etc/shadow",
|
|
9984
|
-
"/etc/group",
|
|
9985
|
-
"/etc/gshadow",
|
|
9986
|
-
"/etc/sudoers"
|
|
9987
|
-
];
|
|
9988
|
-
SYSTEM_AUTH_DIRS = ["/etc/security", "/etc/pam.d", "/etc/sudoers.d"];
|
|
9989
|
-
PASSWORD_HASH_FILES = ["/etc/shadow", "/etc/gshadow"];
|
|
9990
|
-
STARTUP_NAMES = [".bashrc", ".zshrc", ".profile", ".bash_profile"];
|
|
9991
|
-
SYSTEM_DIRS = [
|
|
9992
|
-
"/usr",
|
|
9993
|
-
"/etc",
|
|
9994
|
-
"/boot",
|
|
9995
|
-
"/bin",
|
|
9996
|
-
"/sbin",
|
|
9997
|
-
"/lib",
|
|
9998
|
-
"/lib64",
|
|
9999
|
-
"/var/log"
|
|
10000
|
-
];
|
|
10001
|
-
SYSTEM_DIRS_EXEMPT = ["/usr/local", "/usr/src"];
|
|
10002
|
-
UPLOAD_SYSTEM_DIRS = [...SYSTEM_DIRS, "/proc", "/sys", "/root"];
|
|
10003
|
-
AGENT_INSTRUCTION_FILES = [
|
|
10004
|
-
".gitlab-ci.yml",
|
|
10005
|
-
"CLAUDE.md",
|
|
10006
|
-
"AGENTS.md",
|
|
10007
|
-
".cursorrules",
|
|
10008
|
-
".mcp.json",
|
|
10009
|
-
".vscode/settings.json",
|
|
10010
|
-
"package.json"
|
|
10011
|
-
];
|
|
10012
|
-
AGENT_INSTRUCTION_DIRS = [
|
|
10013
|
-
".github/workflows",
|
|
10014
|
-
".claude",
|
|
10015
|
-
".cursor",
|
|
10016
|
-
".git/hooks",
|
|
10017
|
-
".husky"
|
|
10018
|
-
];
|
|
10019
|
-
LEGACY_SYSTEM_PATH_RE = /^(?:[a-zA-Z]:)?\/etc\/(?:passwd|shadow|sudoers)$/;
|
|
10020
11148
|
exempt = (p) => SYSTEM_DIRS_EXEMPT.some((d) => within(p, d));
|
|
10021
11149
|
RANK = { allow: 0, review: 1, block: 2 };
|
|
10022
11150
|
BIP39_ENGLISH = `abandon
|
|
@@ -13509,7 +14637,11 @@ zoo`.split("\n");
|
|
|
13509
14637
|
"commands.disk-destroy",
|
|
13510
14638
|
"commands.dangerous-word",
|
|
13511
14639
|
"commands.node9-disarm",
|
|
13512
|
-
"data.pipe-chain"
|
|
14640
|
+
"data.pipe-chain",
|
|
14641
|
+
// The general action checks (node9Firewall#390): smart rules the map governs.
|
|
14642
|
+
"actions.money",
|
|
14643
|
+
"actions.access",
|
|
14644
|
+
"actions.publish"
|
|
13513
14645
|
]);
|
|
13514
14646
|
KNOB_GOVERNED = {
|
|
13515
14647
|
"data.secrets": ["off", "block"],
|
|
@@ -14224,7 +15356,8 @@ var SmartConditionSchema = import_zod.z.object({
|
|
|
14224
15356
|
}
|
|
14225
15357
|
),
|
|
14226
15358
|
value: import_zod.z.string().optional(),
|
|
14227
|
-
flags: import_zod.z.string().optional()
|
|
15359
|
+
flags: import_zod.z.string().optional(),
|
|
15360
|
+
reading: import_zod.z.enum(["access"]).optional()
|
|
14228
15361
|
}).refine(
|
|
14229
15362
|
(c) => {
|
|
14230
15363
|
if (c.op === "matchesGlob" || c.op === "notMatchesGlob") return c.value !== void 0;
|
|
@@ -14235,6 +15368,9 @@ var SmartConditionSchema = import_zod.z.object({
|
|
|
14235
15368
|
var SmartRuleSchema = import_zod.z.object({
|
|
14236
15369
|
name: import_zod.z.string().optional(),
|
|
14237
15370
|
tool: import_zod.z.string().min(1, "Smart rule tool must not be empty"),
|
|
15371
|
+
// Scope to an MCP server label (glob), or keep off one (node9Firewall#390).
|
|
15372
|
+
server: import_zod.z.string().min(1).optional(),
|
|
15373
|
+
notServer: import_zod.z.string().min(1).optional(),
|
|
14238
15374
|
conditions: import_zod.z.array(SmartConditionSchema).min(1, "Smart rule must have at least one condition"),
|
|
14239
15375
|
conditionMode: import_zod.z.enum(["all", "any"]).optional(),
|
|
14240
15376
|
verdict: import_zod.z.enum(["allow", "review", "block"], {
|
|
@@ -14255,7 +15391,8 @@ var SmartRuleSchema = import_zod.z.object({
|
|
|
14255
15391
|
var ConfigFileSchema = import_zod.z.object({
|
|
14256
15392
|
version: import_zod.z.string().optional(),
|
|
14257
15393
|
settings: import_zod.z.object({
|
|
14258
|
-
|
|
15394
|
+
// The protection level (levels.ts in the engine); `observe` is Audit's older name.
|
|
15395
|
+
mode: import_zod.z.enum(["observe", "audit", "soft", "standard", "strict", "block-all"]).optional(),
|
|
14259
15396
|
autoStartDaemon: import_zod.z.boolean().optional(),
|
|
14260
15397
|
// enableUndo / policy.snapshot: removed with the undo feature. The
|
|
14261
15398
|
// schema strips undeclared keys silently (plain zod object — no
|
|
@@ -14384,8 +15521,8 @@ var ConfigFileSchema = import_zod.z.object({
|
|
|
14384
15521
|
}).strict();
|
|
14385
15522
|
function formatIssues(issues) {
|
|
14386
15523
|
const lines = issues.map((issue) => {
|
|
14387
|
-
const
|
|
14388
|
-
return ` \u2022 ${
|
|
15524
|
+
const path28 = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
|
|
15525
|
+
return ` \u2022 ${path28}: ${issue.message}`;
|
|
14389
15526
|
});
|
|
14390
15527
|
return `Invalid config:
|
|
14391
15528
|
${lines.join("\n")}`;
|
|
@@ -14398,9 +15535,9 @@ function prunePaths(root, paths) {
|
|
|
14398
15535
|
const yi = y[y.length - 1];
|
|
14399
15536
|
return typeof xi === "number" && typeof yi === "number" ? yi - xi : 0;
|
|
14400
15537
|
});
|
|
14401
|
-
for (const
|
|
15538
|
+
for (const path28 of ordered) {
|
|
14402
15539
|
let cur = root;
|
|
14403
|
-
for (const key of
|
|
15540
|
+
for (const key of path28.slice(0, -1)) {
|
|
14404
15541
|
if (cur === null || typeof cur !== "object") {
|
|
14405
15542
|
cur = void 0;
|
|
14406
15543
|
break;
|
|
@@ -14408,7 +15545,7 @@ function prunePaths(root, paths) {
|
|
|
14408
15545
|
cur = cur[key];
|
|
14409
15546
|
}
|
|
14410
15547
|
if (cur === null || typeof cur !== "object") continue;
|
|
14411
|
-
const last =
|
|
15548
|
+
const last = path28[path28.length - 1];
|
|
14412
15549
|
if (Array.isArray(cur)) {
|
|
14413
15550
|
const i = Number(last);
|
|
14414
15551
|
if (Number.isInteger(i) && i >= 0 && i < cur.length) {
|
|
@@ -14444,7 +15581,7 @@ function sanitizeConfig(raw) {
|
|
|
14444
15581
|
return issue.keys.map((k) => [...at, k]);
|
|
14445
15582
|
}
|
|
14446
15583
|
return [at.slice(0, -level || void 0)];
|
|
14447
|
-
}).filter((
|
|
15584
|
+
}).filter((path28) => path28.length > 0);
|
|
14448
15585
|
if (paths.length === 0 || !prunePaths(working, paths)) break;
|
|
14449
15586
|
}
|
|
14450
15587
|
if (ConfigFileSchema.safeParse(working).success) break;
|
|
@@ -14470,6 +15607,85 @@ var import_fs2 = __toESM(require("fs"));
|
|
|
14470
15607
|
var import_path2 = __toESM(require("path"));
|
|
14471
15608
|
var import_os2 = __toESM(require("os"));
|
|
14472
15609
|
init_dist();
|
|
15610
|
+
|
|
15611
|
+
// src/shields/build.ts
|
|
15612
|
+
init_dist();
|
|
15613
|
+
function escapeRegex(s) {
|
|
15614
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15615
|
+
}
|
|
15616
|
+
function slug(s) {
|
|
15617
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "rule";
|
|
15618
|
+
}
|
|
15619
|
+
var B = "[\\s/\\\\]";
|
|
15620
|
+
var SEP = "[/\\\\]";
|
|
15621
|
+
function pathToRegexFragment(rawPath) {
|
|
15622
|
+
const tail = rawPath.trim().replace(/^~[\\/]?/, "").replace(/^\$\{?HOME\}?[\\/]?/, "").replace(/^\/(?:home|Users)\/[^\\/]+[\\/]?/, "").replace(/^[A-Za-z]:[\\/]Users[\\/][^\\/]+[\\/]?/, "").replace(/^[\\/]+/, "").replace(/[\\/]+$/, "");
|
|
15623
|
+
const segments2 = tail.split(/[\\/]+/).filter(Boolean).map(escapeRegex);
|
|
15624
|
+
if (segments2.length === 0) return "";
|
|
15625
|
+
return `(^|${B})${segments2.join(SEP)}(${B}|$)`;
|
|
15626
|
+
}
|
|
15627
|
+
function pathMatchesFragment(candidate, rawPath) {
|
|
15628
|
+
const value = pathToRegexFragment(rawPath);
|
|
15629
|
+
if (!value || !candidate) return false;
|
|
15630
|
+
const re = getCompiledRegex(value);
|
|
15631
|
+
if (!re) return false;
|
|
15632
|
+
return re.test(candidate);
|
|
15633
|
+
}
|
|
15634
|
+
function pathRules(rawPath, verdict, reason) {
|
|
15635
|
+
const value = pathToRegexFragment(rawPath);
|
|
15636
|
+
if (!value) return [];
|
|
15637
|
+
const why = reason ?? `Accessing ${rawPath} is restricted by this shield`;
|
|
15638
|
+
const s = slug(rawPath);
|
|
15639
|
+
return [
|
|
15640
|
+
// `reading: 'access'`: a script's interpreter line and the program a
|
|
15641
|
+
// command runs are not reads of the jailed path (node9Firewall#385).
|
|
15642
|
+
{
|
|
15643
|
+
name: `${verdict}-path-${s}-bash`,
|
|
15644
|
+
tool: "bash",
|
|
15645
|
+
conditions: [{ field: "command", op: "matches", value, reading: "access" }],
|
|
15646
|
+
verdict,
|
|
15647
|
+
reason: why
|
|
15648
|
+
},
|
|
15649
|
+
// Keep the historical `-anytool` name for the file_path rule: the
|
|
15650
|
+
// rule→shield attribution maps (Report SHIELDS panel) key on rule names.
|
|
15651
|
+
{
|
|
15652
|
+
name: `${verdict}-path-${s}-anytool`,
|
|
15653
|
+
tool: "*",
|
|
15654
|
+
conditions: [{ field: "file_path", op: "matches", value }],
|
|
15655
|
+
verdict,
|
|
15656
|
+
reason: why
|
|
15657
|
+
},
|
|
15658
|
+
{
|
|
15659
|
+
name: `${verdict}-path-${s}-anytool-path`,
|
|
15660
|
+
tool: "*",
|
|
15661
|
+
conditions: [{ field: "path", op: "matches", value }],
|
|
15662
|
+
verdict,
|
|
15663
|
+
reason: why
|
|
15664
|
+
},
|
|
15665
|
+
{
|
|
15666
|
+
name: `${verdict}-path-${s}-anytool-pattern`,
|
|
15667
|
+
tool: "*",
|
|
15668
|
+
conditions: [{ field: "pattern", op: "matches", value }],
|
|
15669
|
+
verdict,
|
|
15670
|
+
reason: why
|
|
15671
|
+
}
|
|
15672
|
+
];
|
|
15673
|
+
}
|
|
15674
|
+
var LEGACY_BASH_PATH_RULE = /^(?:block|review)-path-.+-bash$/;
|
|
15675
|
+
function withAccessReading(def) {
|
|
15676
|
+
let changed = false;
|
|
15677
|
+
const smartRules = def.smartRules.map((r) => {
|
|
15678
|
+
const [c, ...rest] = r.conditions ?? [];
|
|
15679
|
+
if (rest.length > 0 || !c || r.tool !== "bash" || !LEGACY_BASH_PATH_RULE.test(r.name ?? "") || c.field !== "command" || c.op !== "matches" || c.reading !== void 0) {
|
|
15680
|
+
return r;
|
|
15681
|
+
}
|
|
15682
|
+
changed = true;
|
|
15683
|
+
return { ...r, conditions: [{ ...c, reading: "access" }] };
|
|
15684
|
+
});
|
|
15685
|
+
return changed ? { ...def, smartRules } : def;
|
|
15686
|
+
}
|
|
15687
|
+
|
|
15688
|
+
// src/shields.ts
|
|
14473
15689
|
init_dist();
|
|
14474
15690
|
var USER_SHIELDS_DIR = import_path2.default.join(import_os2.default.homedir(), ".node9", "shields");
|
|
14475
15691
|
function loadUserShields() {
|
|
@@ -14496,7 +15712,7 @@ function loadUserShields() {
|
|
|
14496
15712
|
`);
|
|
14497
15713
|
continue;
|
|
14498
15714
|
}
|
|
14499
|
-
result[v.ok.name] = v.ok;
|
|
15715
|
+
result[v.ok.name] = withAccessReading(v.ok);
|
|
14500
15716
|
} catch (err) {
|
|
14501
15717
|
process.stderr.write(`[node9] Failed to load user shield ${file2}: ${String(err)}
|
|
14502
15718
|
`);
|
|
@@ -14551,7 +15767,7 @@ function readShieldOverrides() {
|
|
|
14551
15767
|
}
|
|
14552
15768
|
|
|
14553
15769
|
// src/config/managed.ts
|
|
14554
|
-
var MODE_ORDER = ["observe", "audit", "standard", "strict"];
|
|
15770
|
+
var MODE_ORDER = ["observe", "audit", "soft", "standard", "strict", "block-all"];
|
|
14555
15771
|
var EGRESS_MODE_ORDER = ["off", "review", "block"];
|
|
14556
15772
|
function rankIn(order, value) {
|
|
14557
15773
|
return value === void 0 ? -1 : order.indexOf(value);
|
|
@@ -14664,74 +15880,12 @@ function applyManagedCommandChecks(local, managed, locked) {
|
|
|
14664
15880
|
}
|
|
14665
15881
|
function applyManagedApprovers(local, managed) {
|
|
14666
15882
|
return {
|
|
14667
|
-
...local,
|
|
14668
|
-
native: typeof managed.native === "boolean" ? managed.native : local.native,
|
|
14669
|
-
browser: typeof managed.browser === "boolean" ? managed.browser : local.browser,
|
|
14670
|
-
cloud: typeof managed.cloud === "boolean" ? managed.cloud : local.cloud,
|
|
14671
|
-
terminal: typeof managed.terminal === "boolean" ? managed.terminal : local.terminal
|
|
14672
|
-
};
|
|
14673
|
-
}
|
|
14674
|
-
|
|
14675
|
-
// src/shields/build.ts
|
|
14676
|
-
init_dist();
|
|
14677
|
-
function escapeRegex(s) {
|
|
14678
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14679
|
-
}
|
|
14680
|
-
function slug(s) {
|
|
14681
|
-
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "rule";
|
|
14682
|
-
}
|
|
14683
|
-
var B = "[\\s/\\\\]";
|
|
14684
|
-
var SEP = "[/\\\\]";
|
|
14685
|
-
function pathToRegexFragment(rawPath) {
|
|
14686
|
-
const tail = rawPath.trim().replace(/^~[\\/]?/, "").replace(/^\$\{?HOME\}?[\\/]?/, "").replace(/^\/(?:home|Users)\/[^\\/]+[\\/]?/, "").replace(/^[A-Za-z]:[\\/]Users[\\/][^\\/]+[\\/]?/, "").replace(/^[\\/]+/, "").replace(/[\\/]+$/, "");
|
|
14687
|
-
const segments2 = tail.split(/[\\/]+/).filter(Boolean).map(escapeRegex);
|
|
14688
|
-
if (segments2.length === 0) return "";
|
|
14689
|
-
return `(^|${B})${segments2.join(SEP)}(${B}|$)`;
|
|
14690
|
-
}
|
|
14691
|
-
function pathMatchesFragment(candidate, rawPath) {
|
|
14692
|
-
const value = pathToRegexFragment(rawPath);
|
|
14693
|
-
if (!value || !candidate) return false;
|
|
14694
|
-
const re = getCompiledRegex(value);
|
|
14695
|
-
if (!re) return false;
|
|
14696
|
-
return re.test(candidate);
|
|
14697
|
-
}
|
|
14698
|
-
function pathRules(rawPath, verdict, reason) {
|
|
14699
|
-
const value = pathToRegexFragment(rawPath);
|
|
14700
|
-
if (!value) return [];
|
|
14701
|
-
const why = reason ?? `Accessing ${rawPath} is restricted by this shield`;
|
|
14702
|
-
const s = slug(rawPath);
|
|
14703
|
-
return [
|
|
14704
|
-
{
|
|
14705
|
-
name: `${verdict}-path-${s}-bash`,
|
|
14706
|
-
tool: "bash",
|
|
14707
|
-
conditions: [{ field: "command", op: "matches", value }],
|
|
14708
|
-
verdict,
|
|
14709
|
-
reason: why
|
|
14710
|
-
},
|
|
14711
|
-
// Keep the historical `-anytool` name for the file_path rule: the
|
|
14712
|
-
// rule→shield attribution maps (Report SHIELDS panel) key on rule names.
|
|
14713
|
-
{
|
|
14714
|
-
name: `${verdict}-path-${s}-anytool`,
|
|
14715
|
-
tool: "*",
|
|
14716
|
-
conditions: [{ field: "file_path", op: "matches", value }],
|
|
14717
|
-
verdict,
|
|
14718
|
-
reason: why
|
|
14719
|
-
},
|
|
14720
|
-
{
|
|
14721
|
-
name: `${verdict}-path-${s}-anytool-path`,
|
|
14722
|
-
tool: "*",
|
|
14723
|
-
conditions: [{ field: "path", op: "matches", value }],
|
|
14724
|
-
verdict,
|
|
14725
|
-
reason: why
|
|
14726
|
-
},
|
|
14727
|
-
{
|
|
14728
|
-
name: `${verdict}-path-${s}-anytool-pattern`,
|
|
14729
|
-
tool: "*",
|
|
14730
|
-
conditions: [{ field: "pattern", op: "matches", value }],
|
|
14731
|
-
verdict,
|
|
14732
|
-
reason: why
|
|
14733
|
-
}
|
|
14734
|
-
];
|
|
15883
|
+
...local,
|
|
15884
|
+
native: typeof managed.native === "boolean" ? managed.native : local.native,
|
|
15885
|
+
browser: typeof managed.browser === "boolean" ? managed.browser : local.browser,
|
|
15886
|
+
cloud: typeof managed.cloud === "boolean" ? managed.cloud : local.cloud,
|
|
15887
|
+
terminal: typeof managed.terminal === "boolean" ? managed.terminal : local.terminal
|
|
15888
|
+
};
|
|
14735
15889
|
}
|
|
14736
15890
|
|
|
14737
15891
|
// src/auth/trusted-hosts.ts
|
|
@@ -14792,6 +15946,7 @@ function isTrustedHost(host2) {
|
|
|
14792
15946
|
|
|
14793
15947
|
// src/config/index.ts
|
|
14794
15948
|
init_dist();
|
|
15949
|
+
init_dist();
|
|
14795
15950
|
|
|
14796
15951
|
// src/config/v2.ts
|
|
14797
15952
|
init_dist();
|
|
@@ -14875,7 +16030,7 @@ function v2ToLegacy(raw, options = {}) {
|
|
|
14875
16030
|
continue;
|
|
14876
16031
|
}
|
|
14877
16032
|
if (id === "commands.unknown") {
|
|
14878
|
-
warnings.push(`checks.${id}: set "mode": "
|
|
16033
|
+
warnings.push(`checks.${id}: set "mode": "block-all" instead`);
|
|
14879
16034
|
continue;
|
|
14880
16035
|
}
|
|
14881
16036
|
const allowed = configurableValues(id);
|
|
@@ -15104,7 +16259,9 @@ var DEFAULT_CONFIG = {
|
|
|
15104
16259
|
op: "matches",
|
|
15105
16260
|
// Single spaces on purpose: a nested `\\s+` inside the alternation
|
|
15106
16261
|
// fails the engine's ReDoS check and the rule would be dropped.
|
|
15107
|
-
|
|
16262
|
+
// `apps`: turning an app off, allowing one of its actions, or
|
|
16263
|
+
// linking a server to an app (the general checks leave it).
|
|
16264
|
+
value: "(^|&&|\\|\\||;)\\s*(npx )?node9 (session-(exposure|taint) clear|trust (add|recipient add)|pause|shield (disable|set)|apps (link|[a-z0-9-]{1,32} (off|[a-z]{1,32} allow))|egress allow|approver set)\\b",
|
|
15108
16265
|
flags: "i"
|
|
15109
16266
|
}
|
|
15110
16267
|
],
|
|
@@ -15190,7 +16347,12 @@ var DEFAULT_CONFIG = {
|
|
|
15190
16347
|
verdict: "block",
|
|
15191
16348
|
reason: "Piping remote script into a shell is a supply-chain attack vector",
|
|
15192
16349
|
description: "The AI wants to download a script from the internet and run it immediately, without you seeing what it contains. This is one of the most common ways malware gets installed."
|
|
15193
|
-
}
|
|
16350
|
+
},
|
|
16351
|
+
// ── Actions in apps (node9Firewall#390) ───────────────────────────────
|
|
16352
|
+
// actions.money / actions.access / actions.publish: the action templates
|
|
16353
|
+
// on every MCP server no app shield claims, plus the shell publishing
|
|
16354
|
+
// rules. Review by default; the check value governs them.
|
|
16355
|
+
...GENERAL_ACTION_RULES
|
|
15194
16356
|
],
|
|
15195
16357
|
dlp: { enabled: true, scanIgnoredTools: true, pii: "block" },
|
|
15196
16358
|
egress: {
|
|
@@ -15227,6 +16389,7 @@ var DEFAULT_CONFIG = {
|
|
|
15227
16389
|
trustedHosts: [],
|
|
15228
16390
|
trustedHostsManaged: false,
|
|
15229
16391
|
appPermissions: {},
|
|
16392
|
+
appLinks: {},
|
|
15230
16393
|
managedJailPaths: []
|
|
15231
16394
|
},
|
|
15232
16395
|
environments: {}
|
|
@@ -15406,6 +16569,11 @@ var KNOB_TO_CHECK = {
|
|
|
15406
16569
|
evalDynamic: "commands.eval-dynamic",
|
|
15407
16570
|
pipeChainHigh: "data.pipe-chain"
|
|
15408
16571
|
};
|
|
16572
|
+
function statedSignalFields(policy) {
|
|
16573
|
+
return Object.keys(tuningFromLegacyPolicy(policy)["loading.malicious-package"] ?? {}).filter(
|
|
16574
|
+
(k) => k === "newPackage" || k === "installScript"
|
|
16575
|
+
);
|
|
16576
|
+
}
|
|
15409
16577
|
function catalogSettingsFrom(settings, policy) {
|
|
15410
16578
|
return catalogSettingsFromConfig(settings, policy);
|
|
15411
16579
|
}
|
|
@@ -15461,6 +16629,7 @@ function getConfig(cwd) {
|
|
|
15461
16629
|
trustedHosts: [],
|
|
15462
16630
|
trustedHostsManaged: false,
|
|
15463
16631
|
appPermissions: {},
|
|
16632
|
+
appLinks: {},
|
|
15464
16633
|
managedJailPaths: []
|
|
15465
16634
|
};
|
|
15466
16635
|
const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
|
|
@@ -15472,6 +16641,8 @@ function getConfig(cwd) {
|
|
|
15472
16641
|
const keyed = !!pr2Creds?.apiKey && pr2Creds.localOnly !== true;
|
|
15473
16642
|
let ssrfStrictSource = "default";
|
|
15474
16643
|
const workspaceChecks = {};
|
|
16644
|
+
const managedStatedChecks = /* @__PURE__ */ new Set();
|
|
16645
|
+
const managedStatedSignals = /* @__PURE__ */ new Set();
|
|
15475
16646
|
const applyLayer = (source, isProject = false, isCloud = false) => {
|
|
15476
16647
|
if (!source) return;
|
|
15477
16648
|
const s = source.settings || {};
|
|
@@ -15679,9 +16850,11 @@ function getConfig(cwd) {
|
|
|
15679
16850
|
if (raw.managedConfig && typeof raw.managedConfig === "object") {
|
|
15680
16851
|
const mc = raw.managedConfig;
|
|
15681
16852
|
const locked = Array.isArray(mc.locked) ? mc.locked.filter((f) => typeof f === "string") : [];
|
|
16853
|
+
for (const id of knobStatedChecks(mc)) managedStatedChecks.add(id);
|
|
16854
|
+
for (const k of statedSignalFields(mc)) managedStatedSignals.add(k);
|
|
15682
16855
|
if (typeof mc.mode === "string") {
|
|
15683
16856
|
if (keyed) {
|
|
15684
|
-
if (
|
|
16857
|
+
if (isModeValue(mc.mode)) {
|
|
15685
16858
|
mergedSettings.mode = mc.mode;
|
|
15686
16859
|
}
|
|
15687
16860
|
} else {
|
|
@@ -15845,19 +17018,26 @@ function getConfig(cwd) {
|
|
|
15845
17018
|
}
|
|
15846
17019
|
if (Array.isArray(mc.jailPaths)) {
|
|
15847
17020
|
for (const jp of mc.jailPaths) {
|
|
15848
|
-
const
|
|
15849
|
-
if (!
|
|
17021
|
+
const path28 = typeof jp?.path === "string" ? jp.path.trim() : "";
|
|
17022
|
+
if (!path28) continue;
|
|
15850
17023
|
const verdict = jp?.verdict === "review" ? "review" : "block";
|
|
15851
|
-
for (const r of pathRules(
|
|
17024
|
+
for (const r of pathRules(path28, verdict, "org-managed jail")) {
|
|
15852
17025
|
mergedPolicy.smartRules.push({ ...r, name: `org:${r.name}` });
|
|
15853
17026
|
}
|
|
15854
|
-
mergedPolicy.managedJailPaths.push({ path:
|
|
17027
|
+
mergedPolicy.managedJailPaths.push({ path: path28, verdict });
|
|
15855
17028
|
}
|
|
15856
17029
|
}
|
|
15857
17030
|
if (Array.isArray(mc.trustedHosts)) {
|
|
15858
17031
|
mergedPolicy.trustedHostsManaged = true;
|
|
15859
17032
|
mergedPolicy.trustedHosts = mc.trustedHosts.filter((h) => typeof h === "string").map((h) => normalizeHost2(h));
|
|
15860
17033
|
}
|
|
17034
|
+
if (mc.appLinks && typeof mc.appLinks === "object" && !Array.isArray(mc.appLinks)) {
|
|
17035
|
+
const links = {};
|
|
17036
|
+
for (const [key, slug2] of Object.entries(mc.appLinks)) {
|
|
17037
|
+
if (typeof slug2 === "string" && /^[a-z0-9-]{1,40}$/.test(slug2)) links[key] = slug2;
|
|
17038
|
+
}
|
|
17039
|
+
mergedPolicy.appLinks = links;
|
|
17040
|
+
}
|
|
15861
17041
|
if (mc.appPermissions && typeof mc.appPermissions === "object" && !Array.isArray(mc.appPermissions)) {
|
|
15862
17042
|
const coerced = {};
|
|
15863
17043
|
for (const [srv, tools] of Object.entries(mc.appPermissions)) {
|
|
@@ -15956,14 +17136,15 @@ function getConfig(cwd) {
|
|
|
15956
17136
|
const envMode = process.env.NODE9_MODE;
|
|
15957
17137
|
if (envMode && !modeCloudControlled && // PR-2: one listening point includes the env var — a keyed machine's
|
|
15958
17138
|
// mode comes from the workspace (or the shipped default), never the shell.
|
|
15959
|
-
!keyed &&
|
|
17139
|
+
!keyed && isModeValue(envMode)) {
|
|
15960
17140
|
mergedSettings.mode = envMode;
|
|
15961
17141
|
}
|
|
15962
|
-
if ((cloudManagedShields.length > 0 || cloudMandatesEnforcement) && !modeCloudControlled && !modeCloudStaged && (mergedSettings.mode
|
|
17142
|
+
if ((cloudManagedShields.length > 0 || cloudMandatesEnforcement) && !modeCloudControlled && !modeCloudStaged && isRecordOnly(mergedSettings.mode)) {
|
|
15963
17143
|
mergedSettings.mode = "standard";
|
|
15964
17144
|
}
|
|
15965
|
-
const
|
|
15966
|
-
|
|
17145
|
+
const cloudStrictLevel = levelOf(mergedSettings.mode) === "strict" || levelOf(mergedSettings.mode) === "block-all";
|
|
17146
|
+
const managedFloorActive = cloudManagedShields.length > 0 || cloudMandatesEnforcement || modeCloudControlled && cloudStrictLevel;
|
|
17147
|
+
if (modeCloudControlled && cloudStrictLevel) {
|
|
15967
17148
|
for (const name of Object.keys(mergedEnvironments)) {
|
|
15968
17149
|
if (mergedEnvironments[name]?.requireApproval === false) {
|
|
15969
17150
|
const cleaned = { ...mergedEnvironments[name] };
|
|
@@ -15981,6 +17162,35 @@ function getConfig(cwd) {
|
|
|
15981
17162
|
mergedPolicy.ignoredTools = [...new Set(mergedPolicy.ignoredTools)];
|
|
15982
17163
|
mergedPolicy.skillPinning.roots = [...new Set(mergedPolicy.skillPinning.roots)];
|
|
15983
17164
|
const resolvedSsrfStrictSource = keyed && ssrfStrictSource === "local" ? "default" : ssrfStrictSource;
|
|
17165
|
+
const level = levelOf(mergedSettings.mode);
|
|
17166
|
+
const levelKnobChecks = Object.entries(levelChecks(level)).filter(([id]) => isKnobGoverned(id));
|
|
17167
|
+
const signals = levelPackageSignals(level);
|
|
17168
|
+
if (levelKnobChecks.length > 0 || signals) {
|
|
17169
|
+
const policyOf = (layer) => layer?.policy ?? {};
|
|
17170
|
+
const trusted = /* @__PURE__ */ new Set([...managedStatedChecks, ...Object.keys(workspaceChecks)]);
|
|
17171
|
+
const trustedSignals = new Set(managedStatedSignals);
|
|
17172
|
+
const project = /* @__PURE__ */ new Set();
|
|
17173
|
+
const projectSignals = /* @__PURE__ */ new Set();
|
|
17174
|
+
if (!keyed) {
|
|
17175
|
+
for (const id of knobStatedChecks(policyOf(globalConfig))) trusted.add(id);
|
|
17176
|
+
for (const k of statedSignalFields(policyOf(globalConfig))) trustedSignals.add(k);
|
|
17177
|
+
for (const id of knobStatedChecks(policyOf(projectConfig))) project.add(id);
|
|
17178
|
+
for (const k of statedSignalFields(policyOf(projectConfig))) projectSignals.add(k);
|
|
17179
|
+
}
|
|
17180
|
+
const before = resolveCheckMap(catalogSettingsFromConfig({}, mergedPolicy));
|
|
17181
|
+
for (const [id, v] of levelKnobChecks) {
|
|
17182
|
+
if (trusted.has(id)) continue;
|
|
17183
|
+
if (project.has(id) && CHECK_VERDICT_RANK[v] <= CHECK_VERDICT_RANK[before[id] ?? "off"])
|
|
17184
|
+
continue;
|
|
17185
|
+
checkToLegacyPolicy(mergedPolicy, id, v);
|
|
17186
|
+
}
|
|
17187
|
+
if (signals) {
|
|
17188
|
+
for (const key of ["newPackage", "installScript"]) {
|
|
17189
|
+
if (trustedSignals.has(key) || projectSignals.has(key)) continue;
|
|
17190
|
+
mergedPolicy.packageCheck[key] = signals[key];
|
|
17191
|
+
}
|
|
17192
|
+
}
|
|
17193
|
+
}
|
|
15984
17194
|
const explicit = {};
|
|
15985
17195
|
const checkSources = {};
|
|
15986
17196
|
if (keyed) {
|
|
@@ -16152,6 +17362,9 @@ function applyUntrustedContent(cur, raw, replaceLists = false, isProject = false
|
|
|
16152
17362
|
// src/utils/regex.ts
|
|
16153
17363
|
init_dist();
|
|
16154
17364
|
|
|
17365
|
+
// src/policy/index.ts
|
|
17366
|
+
init_dist();
|
|
17367
|
+
|
|
16155
17368
|
// src/dlp.ts
|
|
16156
17369
|
var import_fs5 = __toESM(require("fs"));
|
|
16157
17370
|
var import_path5 = __toESM(require("path"));
|
|
@@ -16403,7 +17616,8 @@ async function evaluatePolicy2(toolName, args, agent, cwd, opts) {
|
|
|
16403
17616
|
cwd: prepared.cwd,
|
|
16404
17617
|
activeEnvironment,
|
|
16405
17618
|
skipIgnoredFastPath: opts?.skipIgnoredFastPath,
|
|
16406
|
-
argumentScan: opts?.argumentScan
|
|
17619
|
+
argumentScan: opts?.argumentScan,
|
|
17620
|
+
mcpServer: opts?.mcpServer
|
|
16407
17621
|
},
|
|
16408
17622
|
{
|
|
16409
17623
|
checkProvenance,
|
|
@@ -17071,14 +18285,272 @@ async function resolveViaDaemon(id, decision, internalToken, source, record) {
|
|
|
17071
18285
|
|
|
17072
18286
|
// src/auth/orchestrator.ts
|
|
17073
18287
|
var import_crypto10 = require("crypto");
|
|
17074
|
-
|
|
18288
|
+
|
|
18289
|
+
// src/apps/state.ts
|
|
18290
|
+
var import_fs12 = __toESM(require("fs"));
|
|
18291
|
+
var import_os10 = __toESM(require("os"));
|
|
18292
|
+
var import_path12 = __toESM(require("path"));
|
|
18293
|
+
|
|
18294
|
+
// src/apps/catalog.ts
|
|
18295
|
+
var APP_CATALOG = [
|
|
18296
|
+
{
|
|
18297
|
+
slug: "redis",
|
|
18298
|
+
label: "Redis",
|
|
18299
|
+
shield: "redis",
|
|
18300
|
+
servers: ["redis", "valkey"],
|
|
18301
|
+
commands: ["redis-cli", "valkey-cli"],
|
|
18302
|
+
packages: ["redis", "ioredis", "valkey"],
|
|
18303
|
+
envPrefixes: ["REDIS_"]
|
|
18304
|
+
},
|
|
18305
|
+
{
|
|
18306
|
+
slug: "postgres",
|
|
18307
|
+
label: "PostgreSQL",
|
|
18308
|
+
shield: "postgres",
|
|
18309
|
+
servers: ["postgres", "pgsql"],
|
|
18310
|
+
commands: ["psql", "pg_dump", "pg_restore", "pgcli"],
|
|
18311
|
+
packages: ["pg", "postgres", "psycopg", "psycopg2", "psycopg2-binary", "asyncpg"],
|
|
18312
|
+
envPrefixes: ["PGHOST", "PGDATABASE", "POSTGRES_"]
|
|
18313
|
+
},
|
|
18314
|
+
{
|
|
18315
|
+
slug: "mongodb",
|
|
18316
|
+
label: "MongoDB",
|
|
18317
|
+
shield: "mongodb",
|
|
18318
|
+
servers: ["mongo"],
|
|
18319
|
+
commands: ["mongosh", "mongo", "mongodump", "mongorestore"],
|
|
18320
|
+
packages: ["mongodb", "mongoose", "pymongo", "motor"],
|
|
18321
|
+
envPrefixes: ["MONGO"]
|
|
18322
|
+
},
|
|
18323
|
+
{
|
|
18324
|
+
slug: "github",
|
|
18325
|
+
label: "GitHub",
|
|
18326
|
+
shield: "github",
|
|
18327
|
+
servers: ["github"],
|
|
18328
|
+
commands: ["gh"],
|
|
18329
|
+
packages: ["@octokit/", "octokit", "pygithub"],
|
|
18330
|
+
envPrefixes: ["GITHUB_TOKEN", "GH_TOKEN"]
|
|
18331
|
+
},
|
|
18332
|
+
{
|
|
18333
|
+
slug: "aws",
|
|
18334
|
+
label: "AWS",
|
|
18335
|
+
shield: "aws",
|
|
18336
|
+
servers: ["aws"],
|
|
18337
|
+
commands: ["aws", "sam", "cdk"],
|
|
18338
|
+
packages: ["aws-sdk", "@aws-sdk/", "boto3", "aws-cdk-lib"],
|
|
18339
|
+
envPrefixes: ["AWS_ACCESS_KEY_ID", "AWS_PROFILE"]
|
|
18340
|
+
},
|
|
18341
|
+
{
|
|
18342
|
+
slug: "k8s",
|
|
18343
|
+
label: "Kubernetes",
|
|
18344
|
+
shield: "k8s",
|
|
18345
|
+
servers: ["kubernetes", "k8s", "kubectl"],
|
|
18346
|
+
commands: ["kubectl", "helm", "k9s"],
|
|
18347
|
+
packages: ["@kubernetes/client-node", "kubernetes"],
|
|
18348
|
+
envPrefixes: ["KUBECONFIG"]
|
|
18349
|
+
},
|
|
18350
|
+
{
|
|
18351
|
+
slug: "docker",
|
|
18352
|
+
label: "Docker",
|
|
18353
|
+
shield: "docker",
|
|
18354
|
+
servers: ["docker"],
|
|
18355
|
+
commands: ["docker", "docker-compose", "podman"],
|
|
18356
|
+
packages: ["dockerode", "docker"],
|
|
18357
|
+
envPrefixes: ["DOCKER_HOST"]
|
|
18358
|
+
},
|
|
18359
|
+
{
|
|
18360
|
+
slug: "stripe",
|
|
18361
|
+
label: "Stripe",
|
|
18362
|
+
shield: "stripe",
|
|
18363
|
+
servers: ["stripe"],
|
|
18364
|
+
commands: ["stripe"],
|
|
18365
|
+
packages: ["stripe", "@stripe/"],
|
|
18366
|
+
envPrefixes: ["STRIPE_"]
|
|
18367
|
+
},
|
|
18368
|
+
{
|
|
18369
|
+
slug: "paypal",
|
|
18370
|
+
label: "PayPal",
|
|
18371
|
+
shield: "paypal",
|
|
18372
|
+
servers: ["paypal"],
|
|
18373
|
+
commands: [],
|
|
18374
|
+
packages: ["@paypal/", "paypalrestsdk", "paypal-checkout-serversdk", "paypal-server-sdk"],
|
|
18375
|
+
envPrefixes: ["PAYPAL_"]
|
|
18376
|
+
},
|
|
18377
|
+
{
|
|
18378
|
+
slug: "square",
|
|
18379
|
+
label: "Square",
|
|
18380
|
+
shield: "square",
|
|
18381
|
+
servers: ["square"],
|
|
18382
|
+
// Squarespace is a website builder. SQUARE_ already misses SQUARESPACE_.
|
|
18383
|
+
notServers: ["squarespace"],
|
|
18384
|
+
commands: [],
|
|
18385
|
+
packages: ["square", "squareup"],
|
|
18386
|
+
envPrefixes: ["SQUARE_"]
|
|
18387
|
+
},
|
|
18388
|
+
{
|
|
18389
|
+
slug: "plaid",
|
|
18390
|
+
label: "Plaid",
|
|
18391
|
+
shield: "plaid",
|
|
18392
|
+
servers: ["plaid"],
|
|
18393
|
+
commands: [],
|
|
18394
|
+
packages: ["plaid", "plaid-python"],
|
|
18395
|
+
envPrefixes: ["PLAID_"]
|
|
18396
|
+
},
|
|
18397
|
+
{
|
|
18398
|
+
slug: "okta",
|
|
18399
|
+
label: "Okta",
|
|
18400
|
+
shield: "okta",
|
|
18401
|
+
servers: ["okta"],
|
|
18402
|
+
commands: ["okta"],
|
|
18403
|
+
packages: ["@okta/", "okta"],
|
|
18404
|
+
envPrefixes: ["OKTA_"]
|
|
18405
|
+
},
|
|
18406
|
+
{
|
|
18407
|
+
slug: "auth0",
|
|
18408
|
+
label: "Auth0",
|
|
18409
|
+
shield: "auth0",
|
|
18410
|
+
servers: ["auth0"],
|
|
18411
|
+
commands: ["auth0"],
|
|
18412
|
+
packages: ["auth0", "@auth0/"],
|
|
18413
|
+
envPrefixes: ["AUTH0_"]
|
|
18414
|
+
}
|
|
18415
|
+
];
|
|
18416
|
+
var APP_BY_SLUG = new Map(
|
|
18417
|
+
APP_CATALOG.map((a) => [a.slug, a])
|
|
18418
|
+
);
|
|
18419
|
+
|
|
18420
|
+
// src/apps/state.ts
|
|
18421
|
+
init_audit();
|
|
18422
|
+
|
|
18423
|
+
// src/daemon/mcp-tools.ts
|
|
18424
|
+
var import_fs11 = __toESM(require("fs"));
|
|
18425
|
+
var import_path11 = __toESM(require("path"));
|
|
18426
|
+
var import_os9 = __toESM(require("os"));
|
|
18427
|
+
function getMcpToolsFile() {
|
|
18428
|
+
return import_path11.default.join(import_os9.default.homedir(), ".node9", "mcp-tools.json");
|
|
18429
|
+
}
|
|
18430
|
+
function readMcpToolsConfig() {
|
|
18431
|
+
try {
|
|
18432
|
+
const file2 = getMcpToolsFile();
|
|
18433
|
+
if (!import_fs11.default.existsSync(file2)) return {};
|
|
18434
|
+
const raw = import_fs11.default.readFileSync(file2, "utf-8");
|
|
18435
|
+
return JSON.parse(raw);
|
|
18436
|
+
} catch {
|
|
18437
|
+
return {};
|
|
18438
|
+
}
|
|
18439
|
+
}
|
|
18440
|
+
|
|
18441
|
+
// src/apps/state.ts
|
|
18442
|
+
function appsFilePath(home = import_os10.default.homedir()) {
|
|
18443
|
+
return import_path12.default.join(home, ".node9", "apps.json");
|
|
18444
|
+
}
|
|
18445
|
+
var SIGNAL_KINDS = /* @__PURE__ */ new Set(["mcp-server", "command", "package", "env-name"]);
|
|
18446
|
+
function emptyFile() {
|
|
18447
|
+
return { version: 1, apps: /* @__PURE__ */ Object.create(null), links: /* @__PURE__ */ Object.create(null) };
|
|
18448
|
+
}
|
|
18449
|
+
function ownString(map, key) {
|
|
18450
|
+
if (!Object.prototype.hasOwnProperty.call(map, key)) return void 0;
|
|
18451
|
+
const v = map[key];
|
|
18452
|
+
return typeof v === "string" ? v : void 0;
|
|
18453
|
+
}
|
|
18454
|
+
function readSignals(raw) {
|
|
18455
|
+
if (!Array.isArray(raw)) return [];
|
|
18456
|
+
const out = [];
|
|
18457
|
+
for (const x of raw) {
|
|
18458
|
+
if (!x || typeof x.kind !== "string" || typeof x.value !== "string") continue;
|
|
18459
|
+
if (!SIGNAL_KINDS.has(x.kind)) continue;
|
|
18460
|
+
out.push({
|
|
18461
|
+
kind: x.kind,
|
|
18462
|
+
value: x.value.slice(0, 256),
|
|
18463
|
+
...typeof x.project === "string" ? { project: x.project } : {}
|
|
18464
|
+
});
|
|
18465
|
+
}
|
|
18466
|
+
return out;
|
|
18467
|
+
}
|
|
18468
|
+
function parseAppsFile(text) {
|
|
18469
|
+
const raw = JSON.parse(text);
|
|
18470
|
+
const file2 = emptyFile();
|
|
18471
|
+
for (const [slug2, r] of Object.entries(raw.apps ?? {})) {
|
|
18472
|
+
if (!APP_BY_SLUG.has(slug2) || !r || r.state !== "on" && r.state !== "off") continue;
|
|
18473
|
+
file2.apps[slug2] = {
|
|
18474
|
+
state: r.state,
|
|
18475
|
+
by: r.by === "user" || r.by === "workspace" ? r.by : "detected",
|
|
18476
|
+
at: typeof r.at === "string" ? r.at : "",
|
|
18477
|
+
signals: readSignals(r.signals)
|
|
18478
|
+
};
|
|
18479
|
+
}
|
|
18480
|
+
for (const [server, slug2] of Object.entries(raw.links ?? {})) {
|
|
18481
|
+
if (typeof slug2 === "string" && APP_BY_SLUG.has(slug2)) file2.links[server.toLowerCase()] = slug2;
|
|
18482
|
+
}
|
|
18483
|
+
return file2;
|
|
18484
|
+
}
|
|
18485
|
+
function readAppsFile(home) {
|
|
18486
|
+
return readAppsFileStrict(home) ?? emptyFile();
|
|
18487
|
+
}
|
|
18488
|
+
function readAppsFileStrict(home) {
|
|
18489
|
+
let text;
|
|
18490
|
+
try {
|
|
18491
|
+
text = import_fs12.default.readFileSync(appsFilePath(home), "utf8");
|
|
18492
|
+
} catch (e) {
|
|
18493
|
+
return e.code === "ENOENT" ? emptyFile() : null;
|
|
18494
|
+
}
|
|
18495
|
+
try {
|
|
18496
|
+
return parseAppsFile(text);
|
|
18497
|
+
} catch {
|
|
18498
|
+
return null;
|
|
18499
|
+
}
|
|
18500
|
+
}
|
|
18501
|
+
var linkCache = null;
|
|
18502
|
+
var NO_LINKS = /* @__PURE__ */ Object.create(null);
|
|
18503
|
+
function localLinks() {
|
|
18504
|
+
const file2 = appsFilePath();
|
|
18505
|
+
try {
|
|
18506
|
+
const mtimeMs = import_fs12.default.statSync(file2).mtimeMs;
|
|
18507
|
+
if (!linkCache || linkCache.file !== file2 || linkCache.mtimeMs !== mtimeMs) {
|
|
18508
|
+
linkCache = { mtimeMs, file: file2, links: readAppsFile().links };
|
|
18509
|
+
}
|
|
18510
|
+
return linkCache.links;
|
|
18511
|
+
} catch {
|
|
18512
|
+
return NO_LINKS;
|
|
18513
|
+
}
|
|
18514
|
+
}
|
|
18515
|
+
function resolveLinkedServer(label2, opts = {}) {
|
|
18516
|
+
if (typeof label2 !== "string" || !label2) return label2;
|
|
18517
|
+
const applied = new Set(opts.appliedShields ?? []);
|
|
18518
|
+
const live = (slug2) => {
|
|
18519
|
+
const app = slug2 ? APP_BY_SLUG.get(slug2) : void 0;
|
|
18520
|
+
return app && applied.has(app.shield) ? slug2 : void 0;
|
|
18521
|
+
};
|
|
18522
|
+
const lower2 = label2.toLowerCase();
|
|
18523
|
+
if (!opts.workspaceGoverned) {
|
|
18524
|
+
const local = live(ownString(localLinks(), lower2));
|
|
18525
|
+
if (local) return local;
|
|
18526
|
+
}
|
|
18527
|
+
const managed = opts.managedLinks ?? {};
|
|
18528
|
+
if (opts.serverKey) {
|
|
18529
|
+
const byKey = live(ownString(managed, opts.serverKey));
|
|
18530
|
+
if (byKey) return byKey;
|
|
18531
|
+
}
|
|
18532
|
+
const keys = Object.keys(managed);
|
|
18533
|
+
if (keys.length === 0) return label2;
|
|
18534
|
+
const tools = readMcpToolsConfig();
|
|
18535
|
+
for (const key of keys) {
|
|
18536
|
+
const name = key.startsWith("cfg:") ? key.split(":").slice(2).join(":") : Object.prototype.hasOwnProperty.call(tools, key) ? tools[key]?.name : void 0;
|
|
18537
|
+
if (typeof name === "string" && name.toLowerCase() === lower2) {
|
|
18538
|
+
const linked = live(ownString(managed, key));
|
|
18539
|
+
if (linked) return linked;
|
|
18540
|
+
}
|
|
18541
|
+
}
|
|
18542
|
+
return label2;
|
|
18543
|
+
}
|
|
18544
|
+
|
|
18545
|
+
// src/auth/orchestrator.ts
|
|
18546
|
+
var import_os20 = __toESM(require("os"));
|
|
17075
18547
|
|
|
17076
18548
|
// src/ui/native.ts
|
|
17077
18549
|
var import_child_process = require("child_process");
|
|
17078
|
-
var
|
|
18550
|
+
var import_path14 = __toESM(require("path"));
|
|
17079
18551
|
|
|
17080
18552
|
// src/context-sniper.ts
|
|
17081
|
-
var
|
|
18553
|
+
var import_path13 = __toESM(require("path"));
|
|
17082
18554
|
function smartTruncate(str, maxLen = 500) {
|
|
17083
18555
|
if (str.length <= maxLen) return str;
|
|
17084
18556
|
const edge = Math.floor(maxLen / 2) - 3;
|
|
@@ -17146,7 +18618,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
|
|
|
17146
18618
|
intent = "EDIT";
|
|
17147
18619
|
if (obj.file_path) {
|
|
17148
18620
|
editFilePath = String(obj.file_path);
|
|
17149
|
-
editFileName =
|
|
18621
|
+
editFileName = import_path13.default.basename(editFilePath);
|
|
17150
18622
|
}
|
|
17151
18623
|
const result = extractContext(String(obj.new_string), matchedWord);
|
|
17152
18624
|
contextSnippet = result.snippet;
|
|
@@ -17214,7 +18686,7 @@ function formatArgs(args, matchedField, matchedWord) {
|
|
|
17214
18686
|
if (typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
17215
18687
|
const obj = parsed;
|
|
17216
18688
|
if (obj.old_string !== void 0 && obj.new_string !== void 0) {
|
|
17217
|
-
const file2 = obj.file_path ?
|
|
18689
|
+
const file2 = obj.file_path ? import_path14.default.basename(String(obj.file_path)) : "file";
|
|
17218
18690
|
const oldPreview = smartTruncate(String(obj.old_string), 120);
|
|
17219
18691
|
const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
|
|
17220
18692
|
return {
|
|
@@ -17417,24 +18889,24 @@ end run`;
|
|
|
17417
18889
|
}
|
|
17418
18890
|
|
|
17419
18891
|
// src/canary/registry.ts
|
|
17420
|
-
var
|
|
17421
|
-
var
|
|
17422
|
-
var
|
|
18892
|
+
var import_fs14 = __toESM(require("fs"));
|
|
18893
|
+
var import_os12 = __toESM(require("os"));
|
|
18894
|
+
var import_path16 = __toESM(require("path"));
|
|
17423
18895
|
init_dist();
|
|
17424
18896
|
|
|
17425
18897
|
// src/shields/jail.ts
|
|
17426
|
-
var
|
|
17427
|
-
var
|
|
17428
|
-
var
|
|
18898
|
+
var import_fs13 = __toESM(require("fs"));
|
|
18899
|
+
var import_os11 = __toESM(require("os"));
|
|
18900
|
+
var import_path15 = __toESM(require("path"));
|
|
17429
18901
|
init_dist();
|
|
17430
18902
|
var USER_JAIL_SHIELD = "user-jail";
|
|
17431
18903
|
function jailStorePath() {
|
|
17432
|
-
return
|
|
18904
|
+
return import_path15.default.join(import_os11.default.homedir(), ".node9", "jail-paths.json");
|
|
17433
18905
|
}
|
|
17434
18906
|
function readJailPaths() {
|
|
17435
18907
|
let text;
|
|
17436
18908
|
try {
|
|
17437
|
-
text =
|
|
18909
|
+
text = import_fs13.default.readFileSync(jailStorePath(), "utf8");
|
|
17438
18910
|
} catch (err) {
|
|
17439
18911
|
if (err.code === "ENOENT") return [];
|
|
17440
18912
|
throw err;
|
|
@@ -17463,7 +18935,7 @@ function findJailedPathIn(candidate, paths) {
|
|
|
17463
18935
|
|
|
17464
18936
|
// src/canary/registry.ts
|
|
17465
18937
|
function canaryStorePath() {
|
|
17466
|
-
return
|
|
18938
|
+
return import_path16.default.join(import_os12.default.homedir(), ".node9", "canaries.json");
|
|
17467
18939
|
}
|
|
17468
18940
|
function isRecord(x) {
|
|
17469
18941
|
if (!x || typeof x !== "object") return false;
|
|
@@ -17474,7 +18946,7 @@ function loadCanaries(opts) {
|
|
|
17474
18946
|
const p = canaryStorePath();
|
|
17475
18947
|
let raw;
|
|
17476
18948
|
try {
|
|
17477
|
-
raw =
|
|
18949
|
+
raw = import_fs14.default.readFileSync(p, "utf-8");
|
|
17478
18950
|
} catch (e) {
|
|
17479
18951
|
if (e.code === "ENOENT") return [];
|
|
17480
18952
|
throw e;
|
|
@@ -17500,22 +18972,22 @@ function canaryValues() {
|
|
|
17500
18972
|
init_dist();
|
|
17501
18973
|
|
|
17502
18974
|
// src/auth/session-script.ts
|
|
17503
|
-
var
|
|
17504
|
-
var
|
|
17505
|
-
var
|
|
18975
|
+
var import_fs15 = __toESM(require("fs"));
|
|
18976
|
+
var import_os13 = __toESM(require("os"));
|
|
18977
|
+
var import_path17 = __toESM(require("path"));
|
|
17506
18978
|
init_dist();
|
|
17507
18979
|
var MAX_SCRIPT_BYTES = 64 * 1024;
|
|
17508
18980
|
var MAX_RECORDED_WRITES = 32;
|
|
17509
18981
|
function readScriptFile(resolved) {
|
|
17510
18982
|
let fd;
|
|
17511
18983
|
try {
|
|
17512
|
-
fd =
|
|
17513
|
-
const st =
|
|
18984
|
+
fd = import_fs15.default.openSync(resolved, import_fs15.default.constants.O_RDONLY | (import_fs15.default.constants.O_NONBLOCK ?? 0));
|
|
18985
|
+
const st = import_fs15.default.fstatSync(fd);
|
|
17514
18986
|
if (!st.isFile() || st.size > MAX_SCRIPT_BYTES) return null;
|
|
17515
18987
|
const buf = Buffer.alloc(MAX_SCRIPT_BYTES + 1);
|
|
17516
18988
|
let n = 0;
|
|
17517
18989
|
while (n < buf.length) {
|
|
17518
|
-
const got =
|
|
18990
|
+
const got = import_fs15.default.readSync(fd, buf, n, buf.length - n, n);
|
|
17519
18991
|
if (got === 0) break;
|
|
17520
18992
|
n += got;
|
|
17521
18993
|
}
|
|
@@ -17526,7 +18998,7 @@ function readScriptFile(resolved) {
|
|
|
17526
18998
|
} finally {
|
|
17527
18999
|
if (fd !== void 0) {
|
|
17528
19000
|
try {
|
|
17529
|
-
|
|
19001
|
+
import_fs15.default.closeSync(fd);
|
|
17530
19002
|
} catch {
|
|
17531
19003
|
}
|
|
17532
19004
|
}
|
|
@@ -17534,9 +19006,9 @@ function readScriptFile(resolved) {
|
|
|
17534
19006
|
}
|
|
17535
19007
|
function absolute(p, cwd, home) {
|
|
17536
19008
|
const spelled = p === "~" || p.startsWith("~/") ? home + p.slice(1) : p;
|
|
17537
|
-
return
|
|
19009
|
+
return import_path17.default.resolve(cwd, spelled);
|
|
17538
19010
|
}
|
|
17539
|
-
async function resolveSessionScript(toolName, args, cwd, layers, deps, home =
|
|
19011
|
+
async function resolveSessionScript(toolName, args, cwd, layers, deps, home = import_os13.default.homedir()) {
|
|
17540
19012
|
try {
|
|
17541
19013
|
const d = deps ?? { checkWritten, readScript: readScriptFile };
|
|
17542
19014
|
const shell = resolveShellInput(toolName, args, layers);
|
|
@@ -17565,7 +19037,7 @@ async function resolveSessionScript(toolName, args, cwd, layers, deps, home = im
|
|
|
17565
19037
|
return null;
|
|
17566
19038
|
}
|
|
17567
19039
|
}
|
|
17568
|
-
function sessionWriteTargets(toolName, args, fileAccess, cwd, home =
|
|
19040
|
+
function sessionWriteTargets(toolName, args, fileAccess, cwd, home = import_os13.default.homedir()) {
|
|
17569
19041
|
const base2 = cwd ?? process.cwd();
|
|
17570
19042
|
const out = /* @__PURE__ */ new Set();
|
|
17571
19043
|
const argsObj = args && typeof args === "object" ? args : {};
|
|
@@ -17595,26 +19067,26 @@ init_audit();
|
|
|
17595
19067
|
init_dist();
|
|
17596
19068
|
|
|
17597
19069
|
// src/exposure/known-hosts.ts
|
|
17598
|
-
var
|
|
17599
|
-
var
|
|
19070
|
+
var import_fs17 = __toESM(require("fs"));
|
|
19071
|
+
var import_path19 = __toESM(require("path"));
|
|
17600
19072
|
|
|
17601
19073
|
// src/utils/git-remotes.ts
|
|
17602
|
-
var
|
|
17603
|
-
var
|
|
19074
|
+
var import_fs16 = __toESM(require("fs"));
|
|
19075
|
+
var import_path18 = __toESM(require("path"));
|
|
17604
19076
|
init_dist();
|
|
17605
19077
|
function readSmallFile(p, max) {
|
|
17606
19078
|
let fd;
|
|
17607
19079
|
try {
|
|
17608
|
-
fd =
|
|
17609
|
-
const st =
|
|
19080
|
+
fd = import_fs16.default.openSync(p, import_fs16.default.constants.O_RDONLY | (import_fs16.default.constants.O_NOFOLLOW ?? 0));
|
|
19081
|
+
const st = import_fs16.default.fstatSync(fd);
|
|
17610
19082
|
if (!st.isFile() || st.size > max) return null;
|
|
17611
19083
|
const buf = Buffer.alloc(st.size);
|
|
17612
|
-
const n =
|
|
19084
|
+
const n = import_fs16.default.readSync(fd, buf, 0, st.size, 0);
|
|
17613
19085
|
return buf.subarray(0, n).toString("utf8");
|
|
17614
19086
|
} catch {
|
|
17615
19087
|
return null;
|
|
17616
19088
|
} finally {
|
|
17617
|
-
if (fd !== void 0)
|
|
19089
|
+
if (fd !== void 0) import_fs16.default.closeSync(fd);
|
|
17618
19090
|
}
|
|
17619
19091
|
}
|
|
17620
19092
|
function gitRemoteHost(url) {
|
|
@@ -17633,20 +19105,20 @@ function gitRemoteHost(url) {
|
|
|
17633
19105
|
return null;
|
|
17634
19106
|
}
|
|
17635
19107
|
function findGitConfig(dir2, walkUp = true) {
|
|
17636
|
-
let cur =
|
|
19108
|
+
let cur = import_path18.default.resolve(dir2);
|
|
17637
19109
|
for (let i = 0; i < (walkUp ? 64 : 1); i++) {
|
|
17638
|
-
const dotGit =
|
|
17639
|
-
const direct = readSmallFile(
|
|
19110
|
+
const dotGit = import_path18.default.join(cur, ".git");
|
|
19111
|
+
const direct = readSmallFile(import_path18.default.join(dotGit, "config"), 1e6);
|
|
17640
19112
|
if (direct !== null) return direct;
|
|
17641
19113
|
const pointer = readSmallFile(dotGit, 4096);
|
|
17642
19114
|
const m = pointer && /^gitdir:\s*(.+?)\s*$/m.exec(pointer);
|
|
17643
19115
|
if (m) {
|
|
17644
|
-
let gitDir =
|
|
17645
|
-
const common = readSmallFile(
|
|
17646
|
-
if (common !== null) gitDir =
|
|
17647
|
-
return readSmallFile(
|
|
19116
|
+
let gitDir = import_path18.default.resolve(cur, m[1]);
|
|
19117
|
+
const common = readSmallFile(import_path18.default.join(gitDir, "commondir"), 4096);
|
|
19118
|
+
if (common !== null) gitDir = import_path18.default.resolve(gitDir, common.trim());
|
|
19119
|
+
return readSmallFile(import_path18.default.join(gitDir, "config"), 1e6);
|
|
17648
19120
|
}
|
|
17649
|
-
const parent =
|
|
19121
|
+
const parent = import_path18.default.dirname(cur);
|
|
17650
19122
|
if (parent === cur) return null;
|
|
17651
19123
|
cur = parent;
|
|
17652
19124
|
}
|
|
@@ -17656,12 +19128,12 @@ var cache = /* @__PURE__ */ new Map();
|
|
|
17656
19128
|
function readGitRemotes(dir2, opts = {}) {
|
|
17657
19129
|
if (!dir2) return [];
|
|
17658
19130
|
const walkUp = opts.walkUp !== false;
|
|
17659
|
-
const key = `${walkUp ? "up" : "at"}:${
|
|
19131
|
+
const key = `${walkUp ? "up" : "at"}:${import_path18.default.resolve(dir2)}`;
|
|
17660
19132
|
const hit = cache.get(key);
|
|
17661
19133
|
if (hit) return hit;
|
|
17662
19134
|
const out = [];
|
|
17663
19135
|
try {
|
|
17664
|
-
const cfg = findGitConfig(
|
|
19136
|
+
const cfg = findGitConfig(import_path18.default.resolve(dir2), walkUp);
|
|
17665
19137
|
if (cfg !== null) {
|
|
17666
19138
|
let current = null;
|
|
17667
19139
|
for (const line of cfg.split(/\r?\n/)) {
|
|
@@ -17697,15 +19169,15 @@ var ECOSYSTEMS = [
|
|
|
17697
19169
|
];
|
|
17698
19170
|
var cache2 = /* @__PURE__ */ new Map();
|
|
17699
19171
|
function manifestRoot(dir2) {
|
|
17700
|
-
let cur =
|
|
19172
|
+
let cur = import_path19.default.resolve(dir2);
|
|
17701
19173
|
for (let i = 0; i < 64; i++) {
|
|
17702
19174
|
for (const [m] of ECOSYSTEMS) {
|
|
17703
19175
|
try {
|
|
17704
|
-
if (
|
|
19176
|
+
if (import_fs17.default.existsSync(import_path19.default.join(cur, m))) return cur;
|
|
17705
19177
|
} catch {
|
|
17706
19178
|
}
|
|
17707
19179
|
}
|
|
17708
|
-
const parent =
|
|
19180
|
+
const parent = import_path19.default.dirname(cur);
|
|
17709
19181
|
if (parent === cur) return null;
|
|
17710
19182
|
cur = parent;
|
|
17711
19183
|
}
|
|
@@ -17714,7 +19186,7 @@ function manifestRoot(dir2) {
|
|
|
17714
19186
|
function knownProjectHosts(dir2) {
|
|
17715
19187
|
const empty = { hosts: /* @__PURE__ */ new Set(), repos: /* @__PURE__ */ new Set(), remotes: /* @__PURE__ */ new Map() };
|
|
17716
19188
|
if (!dir2) return empty;
|
|
17717
|
-
const key =
|
|
19189
|
+
const key = import_path19.default.resolve(dir2);
|
|
17718
19190
|
const hit = cache2.get(key);
|
|
17719
19191
|
if (hit) return hit;
|
|
17720
19192
|
const out = { hosts: /* @__PURE__ */ new Set(), repos: /* @__PURE__ */ new Set(), remotes: /* @__PURE__ */ new Map() };
|
|
@@ -17727,10 +19199,10 @@ function knownProjectHosts(dir2) {
|
|
|
17727
19199
|
const root = manifestRoot(key);
|
|
17728
19200
|
if (root !== null) {
|
|
17729
19201
|
for (const [m, registry] of ECOSYSTEMS) {
|
|
17730
|
-
if (
|
|
19202
|
+
if (import_fs17.default.existsSync(import_path19.default.join(root, m))) out.hosts.add(registry);
|
|
17731
19203
|
}
|
|
17732
19204
|
try {
|
|
17733
|
-
if (
|
|
19205
|
+
if (import_fs17.default.readdirSync(root).some((f) => f.endsWith(".gemspec")))
|
|
17734
19206
|
out.hosts.add("host:rubygems.org");
|
|
17735
19207
|
} catch {
|
|
17736
19208
|
}
|
|
@@ -17742,9 +19214,9 @@ function knownProjectHosts(dir2) {
|
|
|
17742
19214
|
}
|
|
17743
19215
|
|
|
17744
19216
|
// src/exposure/writes.ts
|
|
17745
|
-
var
|
|
17746
|
-
var
|
|
17747
|
-
var
|
|
19217
|
+
var import_fs18 = __toESM(require("fs"));
|
|
19218
|
+
var import_os14 = __toESM(require("os"));
|
|
19219
|
+
var import_path20 = __toESM(require("path"));
|
|
17748
19220
|
init_dist();
|
|
17749
19221
|
var WRITE_OPS = /* @__PURE__ */ new Set(["write", "create", "remove"]);
|
|
17750
19222
|
var LIFECYCLE_RE = /"(scripts|preinstall|install|postinstall|prepare|prepublish|prepublishOnly|prepack|postpack|publish|postpublish)"\s*:/;
|
|
@@ -17774,7 +19246,7 @@ function packageScriptsTouched(args, absPath, shellCommand) {
|
|
|
17774
19246
|
if (typeof a.content === "string") {
|
|
17775
19247
|
try {
|
|
17776
19248
|
const next = JSON.parse(a.content);
|
|
17777
|
-
const prev = JSON.parse(
|
|
19249
|
+
const prev = JSON.parse(import_fs18.default.readFileSync(absPath, "utf8"));
|
|
17778
19250
|
return JSON.stringify(next.scripts ?? null) !== JSON.stringify(prev.scripts ?? null);
|
|
17779
19251
|
} catch {
|
|
17780
19252
|
return LIFECYCLE_RE.test(a.content);
|
|
@@ -17784,8 +19256,8 @@ function packageScriptsTouched(args, absPath, shellCommand) {
|
|
|
17784
19256
|
}
|
|
17785
19257
|
function judgeWritesAfterExposure(opts) {
|
|
17786
19258
|
const holds = [];
|
|
17787
|
-
const home = opts.home ??
|
|
17788
|
-
const tmp = normalizeFilePath(
|
|
19259
|
+
const home = opts.home ?? import_os14.default.homedir();
|
|
19260
|
+
const tmp = normalizeFilePath(import_os14.default.tmpdir());
|
|
17789
19261
|
const root = opts.cwd ? normalizeFilePath(opts.cwd, "", home) : null;
|
|
17790
19262
|
const seen = /* @__PURE__ */ new Set();
|
|
17791
19263
|
for (const access of opts.accesses) {
|
|
@@ -17795,7 +19267,7 @@ function judgeWritesAfterExposure(opts) {
|
|
|
17795
19267
|
seen.add(p);
|
|
17796
19268
|
if (p === "/dev" || p.startsWith("/dev/")) continue;
|
|
17797
19269
|
if (root !== null && isInside(root, p)) {
|
|
17798
|
-
if (isAgentInstructionPath(p, root) && (
|
|
19270
|
+
if (isAgentInstructionPath(p, root) && (import_path20.default.basename(p) !== "package.json" || packageScriptsTouched(opts.args, p, opts.shellCommand)))
|
|
17799
19271
|
holds.push({ path: p, why: "agent-instruction" });
|
|
17800
19272
|
continue;
|
|
17801
19273
|
}
|
|
@@ -17817,25 +19289,25 @@ function judgeWritesAfterExposure(opts) {
|
|
|
17817
19289
|
|
|
17818
19290
|
// src/exposure/marker.ts
|
|
17819
19291
|
var import_crypto8 = __toESM(require("crypto"));
|
|
17820
|
-
var
|
|
17821
|
-
var
|
|
17822
|
-
var
|
|
19292
|
+
var import_fs19 = __toESM(require("fs"));
|
|
19293
|
+
var import_os15 = __toESM(require("os"));
|
|
19294
|
+
var import_path21 = __toESM(require("path"));
|
|
17823
19295
|
|
|
17824
19296
|
// src/daemon/exposure-store.ts
|
|
17825
19297
|
var SESSION_EXPOSURE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
17826
19298
|
|
|
17827
19299
|
// src/exposure/marker.ts
|
|
17828
19300
|
function dir() {
|
|
17829
|
-
return
|
|
19301
|
+
return import_path21.default.join(import_os15.default.homedir(), ".node9", "exposure-sessions");
|
|
17830
19302
|
}
|
|
17831
19303
|
function file(sessionId) {
|
|
17832
19304
|
const id = import_crypto8.default.createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
|
|
17833
|
-
return
|
|
19305
|
+
return import_path21.default.join(dir(), id);
|
|
17834
19306
|
}
|
|
17835
19307
|
function maybeSessionExposed(sessionId) {
|
|
17836
19308
|
if (!sessionId) return false;
|
|
17837
19309
|
try {
|
|
17838
|
-
const st =
|
|
19310
|
+
const st = import_fs19.default.statSync(file(sessionId));
|
|
17839
19311
|
return Date.now() - st.mtimeMs < SESSION_EXPOSURE_TTL_MS;
|
|
17840
19312
|
} catch (e) {
|
|
17841
19313
|
return e.code !== "ENOENT";
|
|
@@ -17843,27 +19315,7 @@ function maybeSessionExposed(sessionId) {
|
|
|
17843
19315
|
}
|
|
17844
19316
|
|
|
17845
19317
|
// src/exposure/roles.ts
|
|
17846
|
-
var
|
|
17847
|
-
|
|
17848
|
-
// src/daemon/mcp-tools.ts
|
|
17849
|
-
var import_fs18 = __toESM(require("fs"));
|
|
17850
|
-
var import_path20 = __toESM(require("path"));
|
|
17851
|
-
var import_os14 = __toESM(require("os"));
|
|
17852
|
-
function getMcpToolsFile() {
|
|
17853
|
-
return import_path20.default.join(import_os14.default.homedir(), ".node9", "mcp-tools.json");
|
|
17854
|
-
}
|
|
17855
|
-
function readMcpToolsConfig() {
|
|
17856
|
-
try {
|
|
17857
|
-
const file2 = getMcpToolsFile();
|
|
17858
|
-
if (!import_fs18.default.existsSync(file2)) return {};
|
|
17859
|
-
const raw = import_fs18.default.readFileSync(file2, "utf-8");
|
|
17860
|
-
return JSON.parse(raw);
|
|
17861
|
-
} catch {
|
|
17862
|
-
return {};
|
|
17863
|
-
}
|
|
17864
|
-
}
|
|
17865
|
-
|
|
17866
|
-
// src/exposure/roles.ts
|
|
19318
|
+
var import_fs20 = __toESM(require("fs"));
|
|
17867
19319
|
var EXPOSURE_LABEL = "\u{1F6E1}\uFE0F Node9 Exposure";
|
|
17868
19320
|
var AGENT_INTERNAL_PREFIXES = ["ccd_", "visualize"];
|
|
17869
19321
|
function foldServerName(name) {
|
|
@@ -17873,7 +19325,7 @@ var inventoryCache = null;
|
|
|
17873
19325
|
var inventoryStamp = null;
|
|
17874
19326
|
function inventoryFileStamp() {
|
|
17875
19327
|
try {
|
|
17876
|
-
const st =
|
|
19328
|
+
const st = import_fs20.default.statSync(getMcpToolsFile());
|
|
17877
19329
|
return `${st.mtimeMs}:${st.size}`;
|
|
17878
19330
|
} catch {
|
|
17879
19331
|
return "";
|
|
@@ -17957,16 +19409,16 @@ function sourceWord(toolName) {
|
|
|
17957
19409
|
}
|
|
17958
19410
|
|
|
17959
19411
|
// src/auth/trusted-recipients.ts
|
|
17960
|
-
var
|
|
17961
|
-
var
|
|
17962
|
-
var
|
|
19412
|
+
var import_fs21 = __toESM(require("fs"));
|
|
19413
|
+
var import_path22 = __toESM(require("path"));
|
|
19414
|
+
var import_os16 = __toESM(require("os"));
|
|
17963
19415
|
init_dist();
|
|
17964
19416
|
function getTrustedRecipientsPath() {
|
|
17965
|
-
return
|
|
19417
|
+
return import_path22.default.join(import_os16.default.homedir(), ".node9", "trusted-recipients.json");
|
|
17966
19418
|
}
|
|
17967
19419
|
function readTrustedRecipients() {
|
|
17968
19420
|
try {
|
|
17969
|
-
const raw =
|
|
19421
|
+
const raw = import_fs21.default.readFileSync(getTrustedRecipientsPath(), "utf8");
|
|
17970
19422
|
const parsed = JSON.parse(raw);
|
|
17971
19423
|
return Array.isArray(parsed.entries) ? parsed.entries.filter((e) => e && typeof e.value === "string") : [];
|
|
17972
19424
|
} catch {
|
|
@@ -17975,10 +19427,10 @@ function readTrustedRecipients() {
|
|
|
17975
19427
|
}
|
|
17976
19428
|
function writeTrustedRecipients(entries) {
|
|
17977
19429
|
const filePath = getTrustedRecipientsPath();
|
|
17978
|
-
|
|
19430
|
+
import_fs21.default.mkdirSync(import_path22.default.dirname(filePath), { recursive: true });
|
|
17979
19431
|
const tmp = filePath + ".node9-tmp";
|
|
17980
|
-
|
|
17981
|
-
|
|
19432
|
+
import_fs21.default.writeFileSync(tmp, JSON.stringify({ entries }, null, 2), { mode: 384 });
|
|
19433
|
+
import_fs21.default.renameSync(tmp, filePath);
|
|
17982
19434
|
}
|
|
17983
19435
|
function addTrustedRecipient(raw, addedBy = "user") {
|
|
17984
19436
|
const value = normalizeRecipient(raw);
|
|
@@ -17993,7 +19445,7 @@ function addTrustedRecipient(raw, addedBy = "user") {
|
|
|
17993
19445
|
var cache3 = null;
|
|
17994
19446
|
function mtimeOf() {
|
|
17995
19447
|
try {
|
|
17996
|
-
return
|
|
19448
|
+
return import_fs21.default.statSync(getTrustedRecipientsPath()).mtimeMs;
|
|
17997
19449
|
} catch {
|
|
17998
19450
|
return 0;
|
|
17999
19451
|
}
|
|
@@ -18007,41 +19459,41 @@ function isTrustedRecipient(value) {
|
|
|
18007
19459
|
}
|
|
18008
19460
|
|
|
18009
19461
|
// src/auth/cloud.ts
|
|
18010
|
-
var
|
|
18011
|
-
var
|
|
18012
|
-
var
|
|
19462
|
+
var import_fs23 = __toESM(require("fs"));
|
|
19463
|
+
var import_os17 = __toESM(require("os"));
|
|
19464
|
+
var import_path23 = __toESM(require("path"));
|
|
18013
19465
|
init_audit();
|
|
18014
19466
|
|
|
18015
19467
|
// src/utils/read-capped.ts
|
|
18016
|
-
var
|
|
19468
|
+
var import_fs22 = __toESM(require("fs"));
|
|
18017
19469
|
function readCapped(file2, maxBytes) {
|
|
18018
19470
|
if (maxBytes < 0) return null;
|
|
18019
19471
|
let fd;
|
|
18020
19472
|
try {
|
|
18021
|
-
fd =
|
|
19473
|
+
fd = import_fs22.default.openSync(file2, "r");
|
|
18022
19474
|
} catch {
|
|
18023
19475
|
return null;
|
|
18024
19476
|
}
|
|
18025
19477
|
try {
|
|
18026
|
-
const st =
|
|
19478
|
+
const st = import_fs22.default.fstatSync(fd);
|
|
18027
19479
|
if (!st.isFile()) return null;
|
|
18028
19480
|
const want = Math.min(st.size, maxBytes + 1);
|
|
18029
19481
|
const buf = Buffer.alloc(want);
|
|
18030
|
-
let
|
|
18031
|
-
while (
|
|
18032
|
-
const n =
|
|
19482
|
+
let read2 = 0;
|
|
19483
|
+
while (read2 < want) {
|
|
19484
|
+
const n = import_fs22.default.readSync(fd, buf, read2, want - read2, read2);
|
|
18033
19485
|
if (n <= 0) break;
|
|
18034
|
-
|
|
19486
|
+
read2 += n;
|
|
18035
19487
|
}
|
|
18036
19488
|
return {
|
|
18037
|
-
bytes: buf.subarray(0, Math.min(
|
|
18038
|
-
truncated:
|
|
19489
|
+
bytes: buf.subarray(0, Math.min(read2, maxBytes)),
|
|
19490
|
+
truncated: read2 > maxBytes
|
|
18039
19491
|
};
|
|
18040
19492
|
} catch {
|
|
18041
19493
|
return null;
|
|
18042
19494
|
} finally {
|
|
18043
19495
|
try {
|
|
18044
|
-
|
|
19496
|
+
import_fs22.default.closeSync(fd);
|
|
18045
19497
|
} catch {
|
|
18046
19498
|
}
|
|
18047
19499
|
}
|
|
@@ -18086,7 +19538,7 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPol
|
|
|
18086
19538
|
let ciContext;
|
|
18087
19539
|
if (process.env.CI) {
|
|
18088
19540
|
try {
|
|
18089
|
-
const ciContextPath =
|
|
19541
|
+
const ciContextPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "ci-context.json");
|
|
18090
19542
|
const ci = readCappedText(ciContextPath, 1e4);
|
|
18091
19543
|
if (!ci) throw new Error("ci-context.json unreadable");
|
|
18092
19544
|
if (ci.truncated) throw new Error("ci-context.json exceeds 10 KB");
|
|
@@ -18128,9 +19580,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPol
|
|
|
18128
19580
|
context: {
|
|
18129
19581
|
agent: meta?.agent,
|
|
18130
19582
|
mcpServer: meta?.mcpServer,
|
|
18131
|
-
hostname:
|
|
19583
|
+
hostname: import_os17.default.hostname(),
|
|
18132
19584
|
cwd: process.cwd(),
|
|
18133
|
-
platform:
|
|
19585
|
+
platform: import_os17.default.platform()
|
|
18134
19586
|
},
|
|
18135
19587
|
// The SaaS validates riskMetadata strictly and would drop it whole on
|
|
18136
19588
|
// an unknown key, so only fields its schema declares go on the wire.
|
|
@@ -18204,14 +19656,14 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
18204
19656
|
});
|
|
18205
19657
|
clearTimeout(timer);
|
|
18206
19658
|
if (!res.ok) {
|
|
18207
|
-
|
|
19659
|
+
import_fs23.default.appendFileSync(
|
|
18208
19660
|
HOOK_DEBUG_LOG,
|
|
18209
19661
|
`[resolve-cloud] PATCH ${safeMessage(resolveUrl, 200)} \u2192 HTTP ${res.status}
|
|
18210
19662
|
`
|
|
18211
19663
|
);
|
|
18212
19664
|
}
|
|
18213
19665
|
} catch (err) {
|
|
18214
|
-
|
|
19666
|
+
import_fs23.default.appendFileSync(
|
|
18215
19667
|
HOOK_DEBUG_LOG,
|
|
18216
19668
|
`[resolve-cloud] PATCH failed for ${safeMessage(requestId, 64)}: ${safeMessage(err)}
|
|
18217
19669
|
`
|
|
@@ -18220,18 +19672,18 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
18220
19672
|
}
|
|
18221
19673
|
|
|
18222
19674
|
// src/loop-detector.ts
|
|
18223
|
-
var
|
|
18224
|
-
var
|
|
18225
|
-
var
|
|
19675
|
+
var import_fs24 = __toESM(require("fs"));
|
|
19676
|
+
var import_path24 = __toESM(require("path"));
|
|
19677
|
+
var import_os18 = __toESM(require("os"));
|
|
18226
19678
|
init_dist();
|
|
18227
19679
|
init_dist();
|
|
18228
19680
|
function loopStateFile() {
|
|
18229
|
-
return
|
|
19681
|
+
return import_path24.default.join(import_os18.default.homedir(), ".node9", "loop-state.json");
|
|
18230
19682
|
}
|
|
18231
19683
|
function readState() {
|
|
18232
19684
|
try {
|
|
18233
|
-
if (!
|
|
18234
|
-
const raw =
|
|
19685
|
+
if (!import_fs24.default.existsSync(loopStateFile())) return [];
|
|
19686
|
+
const raw = import_fs24.default.readFileSync(loopStateFile(), "utf-8");
|
|
18235
19687
|
const parsed = JSON.parse(raw);
|
|
18236
19688
|
if (!Array.isArray(parsed)) return [];
|
|
18237
19689
|
return parsed;
|
|
@@ -18240,11 +19692,11 @@ function readState() {
|
|
|
18240
19692
|
}
|
|
18241
19693
|
}
|
|
18242
19694
|
function writeState(records) {
|
|
18243
|
-
const dir2 =
|
|
18244
|
-
if (!
|
|
18245
|
-
const tmpPath = `${loopStateFile()}.${
|
|
18246
|
-
|
|
18247
|
-
|
|
19695
|
+
const dir2 = import_path24.default.dirname(loopStateFile());
|
|
19696
|
+
if (!import_fs24.default.existsSync(dir2)) import_fs24.default.mkdirSync(dir2, { recursive: true });
|
|
19697
|
+
const tmpPath = `${loopStateFile()}.${import_os18.default.hostname()}.${process.pid}.tmp`;
|
|
19698
|
+
import_fs24.default.writeFileSync(tmpPath, JSON.stringify(records));
|
|
19699
|
+
import_fs24.default.renameSync(tmpPath, loopStateFile());
|
|
18248
19700
|
}
|
|
18249
19701
|
function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
|
|
18250
19702
|
try {
|
|
@@ -18258,21 +19710,21 @@ function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
|
|
|
18258
19710
|
|
|
18259
19711
|
// src/supply-chain/check.ts
|
|
18260
19712
|
var import_picomatch3 = __toESM(require("picomatch"));
|
|
18261
|
-
var
|
|
19713
|
+
var import_path27 = __toESM(require("path"));
|
|
18262
19714
|
init_dist();
|
|
18263
19715
|
|
|
18264
19716
|
// src/supply-chain/osv-index.ts
|
|
18265
|
-
var
|
|
18266
|
-
var
|
|
18267
|
-
var
|
|
19717
|
+
var import_fs25 = __toESM(require("fs"));
|
|
19718
|
+
var import_os19 = __toESM(require("os"));
|
|
19719
|
+
var import_path25 = __toESM(require("path"));
|
|
18268
19720
|
var import_crypto9 = require("crypto");
|
|
18269
19721
|
init_dist();
|
|
18270
19722
|
var INDEX_FRESH_MS = 72 * 60 * 60 * 1e3;
|
|
18271
19723
|
function osvRoot() {
|
|
18272
|
-
return
|
|
19724
|
+
return import_path25.default.join(import_os19.default.homedir(), ".node9", "osv");
|
|
18273
19725
|
}
|
|
18274
19726
|
function ecoDir(eco) {
|
|
18275
|
-
return
|
|
19727
|
+
return import_path25.default.join(osvRoot(), eco);
|
|
18276
19728
|
}
|
|
18277
19729
|
function indexKey(eco, name) {
|
|
18278
19730
|
switch (eco) {
|
|
@@ -18291,11 +19743,11 @@ function shardOf(key) {
|
|
|
18291
19743
|
return (0, import_crypto9.createHash)("sha1").update(key).digest("hex").slice(0, 2);
|
|
18292
19744
|
}
|
|
18293
19745
|
function shardPath(eco, shard) {
|
|
18294
|
-
return
|
|
19746
|
+
return import_path25.default.join(ecoDir(eco), "shards", `${shard}.json`);
|
|
18295
19747
|
}
|
|
18296
19748
|
function readMeta(eco) {
|
|
18297
19749
|
try {
|
|
18298
|
-
const m = JSON.parse(
|
|
19750
|
+
const m = JSON.parse(import_fs25.default.readFileSync(import_path25.default.join(ecoDir(eco), "meta.json"), "utf8"));
|
|
18299
19751
|
if (typeof m?.syncedAt !== "string" || typeof m?.lastModifiedMs !== "number") return null;
|
|
18300
19752
|
return m;
|
|
18301
19753
|
} catch {
|
|
@@ -18304,7 +19756,7 @@ function readMeta(eco) {
|
|
|
18304
19756
|
}
|
|
18305
19757
|
function readShard(eco, shard) {
|
|
18306
19758
|
try {
|
|
18307
|
-
const s = JSON.parse(
|
|
19759
|
+
const s = JSON.parse(import_fs25.default.readFileSync(shardPath(eco, shard), "utf8"));
|
|
18308
19760
|
return s && typeof s === "object" && !Array.isArray(s) ? s : {};
|
|
18309
19761
|
} catch {
|
|
18310
19762
|
return {};
|
|
@@ -18430,14 +19882,102 @@ async function queryOsvMalicious(queries) {
|
|
|
18430
19882
|
}
|
|
18431
19883
|
|
|
18432
19884
|
// src/supply-chain/registry.ts
|
|
19885
|
+
var import_semver2 = require("semver");
|
|
19886
|
+
|
|
19887
|
+
// src/supply-chain/provenance.ts
|
|
18433
19888
|
var import_semver = require("semver");
|
|
18434
19889
|
var TIMEOUT_MS2 = 1500;
|
|
18435
|
-
var MAX_BYTES =
|
|
19890
|
+
var MAX_BYTES = 256 * 1024;
|
|
19891
|
+
function normalizeRepo(raw, host2 = "github.com") {
|
|
19892
|
+
let s = raw.trim().toLowerCase();
|
|
19893
|
+
s = s.replace(/^git\+/, "").replace(/^[a-z]+:\/\//, "");
|
|
19894
|
+
s = s.replace(/@refs\/.*$/, "").replace(/\.git$/, "").replace(/\/+$/, "");
|
|
19895
|
+
if (!s.includes(".") || !s.split("/")[0].includes(".")) s = `${host2}/${s}`;
|
|
19896
|
+
return /^[a-z0-9.-]+\/[\w.-]+\/[\w.-]+/.test(s) ? s : null;
|
|
19897
|
+
}
|
|
19898
|
+
function npmRepoOf(doc) {
|
|
19899
|
+
const list = doc?.attestations;
|
|
19900
|
+
if (!Array.isArray(list)) return null;
|
|
19901
|
+
for (const a of list) {
|
|
19902
|
+
const att = a;
|
|
19903
|
+
if (!att.predicateType?.startsWith("https://slsa.dev/provenance/")) continue;
|
|
19904
|
+
const payload = att.bundle?.dsseEnvelope?.payload;
|
|
19905
|
+
if (typeof payload !== "string") continue;
|
|
19906
|
+
try {
|
|
19907
|
+
const statement = JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
|
|
19908
|
+
const p = statement.predicate;
|
|
19909
|
+
const repo = p?.buildDefinition?.externalParameters?.workflow?.repository ?? p?.invocation?.configSource?.uri;
|
|
19910
|
+
if (typeof repo === "string") return normalizeRepo(repo);
|
|
19911
|
+
} catch {
|
|
19912
|
+
}
|
|
19913
|
+
}
|
|
19914
|
+
return null;
|
|
19915
|
+
}
|
|
19916
|
+
function pypiRepoOf(doc) {
|
|
19917
|
+
const bundles = doc?.attestation_bundles;
|
|
19918
|
+
if (!Array.isArray(bundles)) return null;
|
|
19919
|
+
for (const b of bundles) {
|
|
19920
|
+
const pub = b.publisher;
|
|
19921
|
+
if (typeof pub?.repository === "string") {
|
|
19922
|
+
const host2 = /gitlab/i.test(pub.kind ?? "") ? "gitlab.com" : "github.com";
|
|
19923
|
+
return normalizeRepo(pub.repository, host2);
|
|
19924
|
+
}
|
|
19925
|
+
if (/^google$/i.test(pub?.kind ?? "") && typeof pub?.email === "string") {
|
|
19926
|
+
return `google:${pub.email.trim().toLowerCase()}`;
|
|
19927
|
+
}
|
|
19928
|
+
}
|
|
19929
|
+
return null;
|
|
19930
|
+
}
|
|
19931
|
+
async function npmSourceRepo(name, version) {
|
|
19932
|
+
const base2 = endpoints().npmRegistry;
|
|
19933
|
+
if (!base2) return "unknown";
|
|
19934
|
+
const url = `${base2}/-/npm/v1/attestations/${name.replaceAll("/", "%2F")}@${encodeURIComponent(version)}`;
|
|
19935
|
+
return read(url, npmRepoOf);
|
|
19936
|
+
}
|
|
19937
|
+
async function read(url, repoOf) {
|
|
19938
|
+
try {
|
|
19939
|
+
const doc = await fetchJson(url, { timeoutMs: TIMEOUT_MS2, maxBytes: MAX_BYTES });
|
|
19940
|
+
if (doc === null) return "none";
|
|
19941
|
+
return repoOf(doc) ?? "unknown";
|
|
19942
|
+
} catch {
|
|
19943
|
+
return "unknown";
|
|
19944
|
+
}
|
|
19945
|
+
}
|
|
19946
|
+
async function pypiSourceRepo(name, version, filename) {
|
|
19947
|
+
const base2 = endpoints().pypi;
|
|
19948
|
+
if (!base2) return "unknown";
|
|
19949
|
+
const url = `${base2}/integrity/${encodeURIComponent(name)}/${encodeURIComponent(version)}/${encodeURIComponent(filename)}/provenance`;
|
|
19950
|
+
return read(url, pypiRepoOf);
|
|
19951
|
+
}
|
|
19952
|
+
var PYPI_FINAL = /^\d+(?:\.\d+)*$/;
|
|
19953
|
+
function comparePypi(a, b) {
|
|
19954
|
+
const x = a.split(".").map(Number);
|
|
19955
|
+
const y = b.split(".").map(Number);
|
|
19956
|
+
for (let i = 0; i < Math.max(x.length, y.length); i++) {
|
|
19957
|
+
const d = (x[i] ?? 0) - (y[i] ?? 0);
|
|
19958
|
+
if (d !== 0) return d;
|
|
19959
|
+
}
|
|
19960
|
+
return 0;
|
|
19961
|
+
}
|
|
19962
|
+
function previousRelease(eco, versions, version) {
|
|
19963
|
+
if (eco === "npm") {
|
|
19964
|
+
if (!(0, import_semver.valid)(version)) return void 0;
|
|
19965
|
+
const below2 = versions.filter((v) => (0, import_semver.valid)(v) && !(0, import_semver.prerelease)(v) && (0, import_semver.rcompare)(v, version) > 0);
|
|
19966
|
+
return below2.sort(import_semver.rcompare)[0] ?? null;
|
|
19967
|
+
}
|
|
19968
|
+
if (!PYPI_FINAL.test(version)) return void 0;
|
|
19969
|
+
const below = versions.filter((v) => PYPI_FINAL.test(v) && comparePypi(v, version) < 0);
|
|
19970
|
+
return below.sort((a, b) => comparePypi(b, a))[0] ?? null;
|
|
19971
|
+
}
|
|
19972
|
+
|
|
19973
|
+
// src/supply-chain/registry.ts
|
|
19974
|
+
var TIMEOUT_MS3 = 1500;
|
|
19975
|
+
var MAX_BYTES2 = 2 * 1024 * 1024;
|
|
18436
19976
|
var VERSION_DOC_MAX_BYTES = 256 * 1024;
|
|
18437
19977
|
var INSTALL_SCRIPTS = ["preinstall", "install", "postinstall"];
|
|
18438
19978
|
async function npmVersionDocument(url, selector) {
|
|
18439
19979
|
const doc = await fetchJson(`${url}/${encodeURIComponent(selector)}`, {
|
|
18440
|
-
timeoutMs:
|
|
19980
|
+
timeoutMs: TIMEOUT_MS3,
|
|
18441
19981
|
maxBytes: VERSION_DOC_MAX_BYTES
|
|
18442
19982
|
});
|
|
18443
19983
|
if (!doc?.version) return null;
|
|
@@ -18461,16 +20001,16 @@ async function npmInfo(name, version, freshWindowMs, raw) {
|
|
|
18461
20001
|
let doc;
|
|
18462
20002
|
try {
|
|
18463
20003
|
doc = await fetchJson(url, {
|
|
18464
|
-
timeoutMs:
|
|
18465
|
-
maxBytes:
|
|
20004
|
+
timeoutMs: TIMEOUT_MS3,
|
|
20005
|
+
maxBytes: MAX_BYTES2,
|
|
18466
20006
|
headers: { Accept: "application/vnd.npm.install-v1+json" }
|
|
18467
20007
|
});
|
|
18468
20008
|
} catch (err) {
|
|
18469
20009
|
if (!/too large/.test(err.message)) throw err;
|
|
18470
|
-
const range = !version && spec && (0,
|
|
20010
|
+
const range = !version && spec && (0, import_semver2.validRange)(spec) ? spec : null;
|
|
18471
20011
|
const selector = version ?? (spec && !range ? spec : "latest");
|
|
18472
20012
|
const info2 = await npmVersionDocument(url, selector);
|
|
18473
|
-
if (info2 && range && !(0,
|
|
20013
|
+
if (info2 && range && !(0, import_semver2.satisfies)(info2.version, range)) return null;
|
|
18474
20014
|
return info2;
|
|
18475
20015
|
}
|
|
18476
20016
|
if (!doc) return null;
|
|
@@ -18479,8 +20019,8 @@ async function npmInfo(name, version, freshWindowMs, raw) {
|
|
|
18479
20019
|
if (!resolved) {
|
|
18480
20020
|
if (!spec) resolved = latest;
|
|
18481
20021
|
else if (doc["dist-tags"]?.[spec]) resolved = doc["dist-tags"][spec];
|
|
18482
|
-
else if ((0,
|
|
18483
|
-
resolved = latest && (0,
|
|
20022
|
+
else if ((0, import_semver2.validRange)(spec)) {
|
|
20023
|
+
resolved = latest && (0, import_semver2.satisfies)(latest, spec) ? latest : (0, import_semver2.maxSatisfying)(Object.keys(doc.versions ?? {}), spec) ?? void 0;
|
|
18484
20024
|
}
|
|
18485
20025
|
}
|
|
18486
20026
|
if (!resolved) return null;
|
|
@@ -18490,13 +20030,23 @@ async function npmInfo(name, version, freshWindowMs, raw) {
|
|
|
18490
20030
|
version: resolved,
|
|
18491
20031
|
hasInstallScript: v?.hasInstallScript === true
|
|
18492
20032
|
};
|
|
20033
|
+
const previous = previousRelease("npm", Object.keys(doc.versions ?? {}), resolved);
|
|
20034
|
+
if (v && previous !== void 0) {
|
|
20035
|
+
const pv = previous ? doc.versions?.[previous] : void 0;
|
|
20036
|
+
info.history = {
|
|
20037
|
+
previous,
|
|
20038
|
+
previousHasInstallScript: pv ? pv.hasInstallScript === true : void 0,
|
|
20039
|
+
provenance: !!v.dist?.attestations?.provenance,
|
|
20040
|
+
previousProvenance: pv ? !!pv.dist?.attestations?.provenance : void 0
|
|
20041
|
+
};
|
|
20042
|
+
}
|
|
18493
20043
|
if (!Number.isFinite(modifiedMs)) return info;
|
|
18494
20044
|
if (Date.now() - modifiedMs >= freshWindowMs) {
|
|
18495
20045
|
info.publishedAtMs = resolved === latest ? modifiedMs : void 0;
|
|
18496
20046
|
return info;
|
|
18497
20047
|
}
|
|
18498
20048
|
try {
|
|
18499
|
-
const full = await fetchJson(url, { timeoutMs:
|
|
20049
|
+
const full = await fetchJson(url, { timeoutMs: TIMEOUT_MS3, maxBytes: MAX_BYTES2 });
|
|
18500
20050
|
const t = full?.time?.[resolved];
|
|
18501
20051
|
const tMs = t ? Date.parse(t) : NaN;
|
|
18502
20052
|
if (Number.isFinite(tMs)) {
|
|
@@ -18514,15 +20064,37 @@ async function pypiInfo(name, version) {
|
|
|
18514
20064
|
const base2 = endpoints().pypi;
|
|
18515
20065
|
if (!base2) return null;
|
|
18516
20066
|
const url = version ? `${base2}/pypi/${encodeURIComponent(name)}/${encodeURIComponent(version)}/json` : `${base2}/pypi/${encodeURIComponent(name)}/json`;
|
|
18517
|
-
const doc = await fetchJson(url, { timeoutMs:
|
|
20067
|
+
const doc = await fetchJson(url, { timeoutMs: TIMEOUT_MS3, maxBytes: MAX_BYTES2 });
|
|
18518
20068
|
if (!doc?.info?.version) return null;
|
|
18519
20069
|
const times = (doc.urls ?? []).map((u) => u.upload_time_iso_8601 ? Date.parse(u.upload_time_iso_8601) : NaN).filter((t) => Number.isFinite(t));
|
|
18520
|
-
|
|
18521
|
-
|
|
20070
|
+
const resolved = version ?? doc.info.version;
|
|
20071
|
+
const info = {
|
|
20072
|
+
version: resolved,
|
|
18522
20073
|
// The earliest file of the release is when it became installable.
|
|
18523
|
-
publishedAtMs: times.length > 0 ? Math.min(...times) : void 0
|
|
20074
|
+
publishedAtMs: times.length > 0 ? Math.min(...times) : void 0,
|
|
20075
|
+
file: doc.urls?.find((u) => typeof u.filename === "string")?.filename
|
|
20076
|
+
};
|
|
20077
|
+
if (doc.releases) info.history = pypiHistoryOf(doc.releases, resolved) ?? void 0;
|
|
20078
|
+
return info;
|
|
20079
|
+
}
|
|
20080
|
+
function pypiHistoryOf(releases, version) {
|
|
20081
|
+
const versions = Object.keys(releases).filter((v) => (releases[v] ?? []).length > 0);
|
|
20082
|
+
const previous = previousRelease("PyPI", versions, version);
|
|
20083
|
+
if (previous === void 0) return null;
|
|
20084
|
+
return {
|
|
20085
|
+
previous,
|
|
20086
|
+
previousFile: previous ? releases[previous]?.find((f) => typeof f.filename === "string")?.filename : void 0
|
|
18524
20087
|
};
|
|
18525
20088
|
}
|
|
20089
|
+
async function pypiHistory(name, version) {
|
|
20090
|
+
const base2 = endpoints().pypi;
|
|
20091
|
+
if (!base2) return null;
|
|
20092
|
+
const doc = await fetchJson(`${base2}/pypi/${encodeURIComponent(name)}/json`, {
|
|
20093
|
+
timeoutMs: TIMEOUT_MS3,
|
|
20094
|
+
maxBytes: MAX_BYTES2
|
|
20095
|
+
});
|
|
20096
|
+
return doc?.releases ? pypiHistoryOf(doc.releases, version) : null;
|
|
20097
|
+
}
|
|
18526
20098
|
var HAS_REGISTRY_CLIENT = /* @__PURE__ */ new Set(["npm", "PyPI"]);
|
|
18527
20099
|
async function registryInfo(eco, name, version, freshWindowMs, raw) {
|
|
18528
20100
|
if (eco === "npm") return npmInfo(name, version, freshWindowMs, raw);
|
|
@@ -18531,19 +20103,19 @@ async function registryInfo(eco, name, version, freshWindowMs, raw) {
|
|
|
18531
20103
|
}
|
|
18532
20104
|
|
|
18533
20105
|
// src/supply-chain/local-resolve.ts
|
|
18534
|
-
var
|
|
18535
|
-
var
|
|
20106
|
+
var import_fs26 = __toESM(require("fs"));
|
|
20107
|
+
var import_path26 = __toESM(require("path"));
|
|
18536
20108
|
var MAX_LEVELS = 32;
|
|
18537
20109
|
var LOCAL_FIRST_MANAGERS = /* @__PURE__ */ new Set(["npx", "bunx", "npm exec"]);
|
|
18538
20110
|
function resolveInstalled(name, cwd) {
|
|
18539
|
-
if (!cwd || !
|
|
20111
|
+
if (!cwd || !import_path26.default.isAbsolute(cwd)) return null;
|
|
18540
20112
|
if (!/^(?:@[^/\\]+\/)?[^/\\]+$/.test(name)) return null;
|
|
18541
|
-
let dir2 =
|
|
20113
|
+
let dir2 = import_path26.default.resolve(cwd);
|
|
18542
20114
|
for (let i = 0; i < MAX_LEVELS; i++) {
|
|
18543
|
-
const pkgDir =
|
|
18544
|
-
const manifest =
|
|
20115
|
+
const pkgDir = import_path26.default.join(dir2, "node_modules", ...name.split("/"));
|
|
20116
|
+
const manifest = import_path26.default.join(pkgDir, "package.json");
|
|
18545
20117
|
try {
|
|
18546
|
-
const raw =
|
|
20118
|
+
const raw = import_fs26.default.readFileSync(manifest, "utf8");
|
|
18547
20119
|
const parsed = JSON.parse(raw);
|
|
18548
20120
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
18549
20121
|
return { version: parsed.version, dir: pkgDir };
|
|
@@ -18551,7 +20123,7 @@ function resolveInstalled(name, cwd) {
|
|
|
18551
20123
|
return null;
|
|
18552
20124
|
} catch {
|
|
18553
20125
|
}
|
|
18554
|
-
const parent =
|
|
20126
|
+
const parent = import_path26.default.dirname(dir2);
|
|
18555
20127
|
if (parent === dir2) break;
|
|
18556
20128
|
dir2 = parent;
|
|
18557
20129
|
}
|
|
@@ -18673,7 +20245,7 @@ function checkInstalled(p, version, misses) {
|
|
|
18673
20245
|
}
|
|
18674
20246
|
async function checkOne(p, cfg, misses, networkCapped, cwd) {
|
|
18675
20247
|
if (p.ecosystem === "npm" && LOCAL_FIRST_MANAGERS.has(p.manager) && p.localCwd !== null && (p.raw === p.name || p.version)) {
|
|
18676
|
-
const localCwd = cwd &&
|
|
20248
|
+
const localCwd = cwd && import_path27.default.isAbsolute(cwd) ? (p.localCwd ?? []).reduce((dir2, step) => import_path27.default.resolve(dir2, step), cwd) : void 0;
|
|
18677
20249
|
const installed = resolveInstalled(p.name, localCwd);
|
|
18678
20250
|
if (installed && (!p.version || p.version === installed.version))
|
|
18679
20251
|
return checkInstalled(p, installed.version, misses);
|
|
@@ -18716,8 +20288,61 @@ async function checkOne(p, cfg, misses, networkCapped, cwd) {
|
|
|
18716
20288
|
detail: `${label(p, version)} runs an install script`
|
|
18717
20289
|
});
|
|
18718
20290
|
}
|
|
20291
|
+
if (info && version && (cfg.newPackage !== "off" || cfg.installScript !== "off")) {
|
|
20292
|
+
return byHistory(p, version, info, findings, misses);
|
|
20293
|
+
}
|
|
18719
20294
|
return findings;
|
|
18720
20295
|
}
|
|
20296
|
+
var SOFT2 = /* @__PURE__ */ new Set(["new", "install-script"]);
|
|
20297
|
+
async function byHistory(p, version, info, findings, misses) {
|
|
20298
|
+
let out = findings;
|
|
20299
|
+
let h = info.history;
|
|
20300
|
+
if (!h && p.ecosystem === "PyPI" && out.some((f) => SOFT2.has(f.kind))) {
|
|
20301
|
+
try {
|
|
20302
|
+
h = await pypiHistory(p.name, version) ?? void 0;
|
|
20303
|
+
} catch (err) {
|
|
20304
|
+
misses.push(`${label(p, version)}: release history ${err.message}`);
|
|
20305
|
+
}
|
|
20306
|
+
}
|
|
20307
|
+
if (!h || !h.previous) return out;
|
|
20308
|
+
const previous = h.previous;
|
|
20309
|
+
if (h.previousHasInstallScript === true) {
|
|
20310
|
+
out = out.filter((f) => f.kind !== "install-script");
|
|
20311
|
+
}
|
|
20312
|
+
const drop = () => ({
|
|
20313
|
+
pkg: p,
|
|
20314
|
+
version,
|
|
20315
|
+
kind: "provenance-drop",
|
|
20316
|
+
detail: `${label(p, version)} was published without the provenance its previous release (${previous}) had`
|
|
20317
|
+
});
|
|
20318
|
+
if (p.ecosystem === "npm") {
|
|
20319
|
+
if (h.previousProvenance && h.provenance === false) return [...out, drop()];
|
|
20320
|
+
if (!out.some((f) => SOFT2.has(f.kind)) || !h.provenance || !h.previousProvenance) return out;
|
|
20321
|
+
const [now, before] = await Promise.all([
|
|
20322
|
+
npmSourceRepo(p.name, version),
|
|
20323
|
+
npmSourceRepo(p.name, previous)
|
|
20324
|
+
]);
|
|
20325
|
+
return vouched(p, version, now, before, out, misses);
|
|
20326
|
+
}
|
|
20327
|
+
if (p.ecosystem === "PyPI") {
|
|
20328
|
+
if (!out.some((f) => SOFT2.has(f.kind)) || !info.file || !h.previousFile) return out;
|
|
20329
|
+
const [now, before] = await Promise.all([
|
|
20330
|
+
pypiSourceRepo(p.name, version, info.file),
|
|
20331
|
+
pypiSourceRepo(p.name, previous, h.previousFile)
|
|
20332
|
+
]);
|
|
20333
|
+
if (now === "none" && before !== "none" && before !== "unknown") return [...out, drop()];
|
|
20334
|
+
return vouched(p, version, now, before, out, misses);
|
|
20335
|
+
}
|
|
20336
|
+
return out;
|
|
20337
|
+
}
|
|
20338
|
+
function vouched(p, version, now, before, findings, misses) {
|
|
20339
|
+
if (now === "unknown" || before === "unknown") {
|
|
20340
|
+
misses.push(`${label(p, version)}: provenance unreadable, findings kept`);
|
|
20341
|
+
return findings;
|
|
20342
|
+
}
|
|
20343
|
+
if (now === "none" || before === "none" || now !== before) return findings;
|
|
20344
|
+
return findings.filter((f) => !SOFT2.has(f.kind));
|
|
20345
|
+
}
|
|
18721
20346
|
async function runPackageCheck(toolName, args, cfg, cwd) {
|
|
18722
20347
|
if (!cfg.enabled) return ALLOW;
|
|
18723
20348
|
try {
|
|
@@ -19036,7 +20661,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
19036
20661
|
appendHookDebug(toolName, args, meta, hashAuditArgs);
|
|
19037
20662
|
}
|
|
19038
20663
|
const isManual = meta?.agent === "Terminal";
|
|
19039
|
-
const isObserveMode = config.settings.mode
|
|
20664
|
+
const isObserveMode = isRecordOnly(config.settings.mode);
|
|
19040
20665
|
let explainableLabel = "Local Config";
|
|
19041
20666
|
let dlpReviewFlagged = false;
|
|
19042
20667
|
let packageReview = null;
|
|
@@ -19094,13 +20719,20 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
19094
20719
|
explainableLabel = "Shell inspection incomplete";
|
|
19095
20720
|
policyRuleDescription = "The executable command could not be inspected. Approve this call explicitly.";
|
|
19096
20721
|
}
|
|
19097
|
-
const fileReview = !isObserveMode &&
|
|
20722
|
+
const fileReview = !isObserveMode && fileFinding?.decision === "review";
|
|
19098
20723
|
const evaluateCurrentPolicy = async (skipIgnoredFastPath = false) => {
|
|
19099
20724
|
const own = await evaluatePolicy2(toolName, args, meta?.agent, options?.cwd, {
|
|
19100
20725
|
fileAccess,
|
|
19101
20726
|
config,
|
|
19102
20727
|
skipIgnoredFastPath,
|
|
19103
|
-
argumentScan: argScan
|
|
20728
|
+
argumentScan: argScan,
|
|
20729
|
+
// A server a person linked to an app is judged as that app (fw#390).
|
|
20730
|
+
mcpServer: resolveLinkedServer(meta?.mcpServer, {
|
|
20731
|
+
serverKey: meta?.serverKey,
|
|
20732
|
+
managedLinks: config.policy.appLinks,
|
|
20733
|
+
appliedShields: config.policy.appliedShields,
|
|
20734
|
+
workspaceGoverned: config.policySource === "workspace"
|
|
20735
|
+
})
|
|
19104
20736
|
});
|
|
19105
20737
|
if (!sessionScript || own.decision === "block") return own;
|
|
19106
20738
|
const inner = await evaluatePolicy2("Bash", sessionScript.args, meta?.agent, options?.cwd, {
|
|
@@ -19114,7 +20746,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
19114
20746
|
if (fileFinding) {
|
|
19115
20747
|
meta = { ...meta, ruleName: fileFinding.ruleName };
|
|
19116
20748
|
hashAuditArgs = true;
|
|
19117
|
-
const observing = isObserveMode
|
|
20749
|
+
const observing = isObserveMode;
|
|
19118
20750
|
const blocked = !observing && (fileFinding.decision === "block" || fileReview && config.settings.panicMode);
|
|
19119
20751
|
appendLocalAudit(
|
|
19120
20752
|
toolName,
|
|
@@ -19316,7 +20948,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
19316
20948
|
const last = rec.sources[rec.sources.length - 1];
|
|
19317
20949
|
const readWord = last ? last.word ?? sourceWord(last.tool) : "content";
|
|
19318
20950
|
const when = last ? new Date(last.at).toTimeString().slice(0, 5) : "";
|
|
19319
|
-
const
|
|
20951
|
+
const read2 = last ? `${/^[aeiou]/i.test(readWord) ? "an" : "a"} ${readWord}` + (last.server ? ` through ${last.server}` : "") + (when ? ` at ${when}` : "") : "outside content";
|
|
19320
20952
|
if ((held.length > 0 || writeHolds.length > 0) && ucPolicy.afterExposure !== "log-only") {
|
|
19321
20953
|
const noun = (d) => {
|
|
19322
20954
|
const k = destinationKind(d);
|
|
@@ -19325,14 +20957,14 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
19325
20957
|
const parts = held.map(
|
|
19326
20958
|
(h) => h.dest ? `sends to ${destinationValue(h.dest)}, ${h.why === "appeared in what was read" ? `${noun(h.dest)} that appeared in that ${last ? readWord : "content"}` : `${noun(h.dest)} this session did not know`}` : h.why === "repository" ? h.repo.endsWith("/*") ? `sends to a repository on ${h.repo.slice(0, -2)} it does not name (a gist, a new repository or an API call)` : `sends to ${h.repo}, a repository that is not one of this project's remotes` : h.why === "unresolved" ? "sends to a destination this session cannot see (built at run time, or a remote of another repository)" : `is ${bare}, a destructive call, with no known destination`
|
|
19327
20959
|
);
|
|
19328
|
-
const home =
|
|
20960
|
+
const home = import_os20.default.homedir();
|
|
19329
20961
|
const short = (p) => p.startsWith(home + "/") ? "~" + p.slice(home.length) : p;
|
|
19330
20962
|
for (const w of writeHolds) {
|
|
19331
20963
|
parts.push(
|
|
19332
20964
|
w.why === "outside-project" ? `writes outside the project, to ${short(w.path)}` : w.why === "agent-instruction" ? `writes to ${short(w.path)}, a file that runs code or instructs the agent` : `writes content carrying ${destinationValue(w.destination ?? "")}, ${noun(w.destination ?? "host:x")} that appeared in that ${readWord}`
|
|
19333
20965
|
);
|
|
19334
20966
|
}
|
|
19335
|
-
exposureReview = `\u{1F6E1}\uFE0F Node9: this session read ${
|
|
20967
|
+
exposureReview = `\u{1F6E1}\uFE0F Node9: this session read ${read2}. This call ${parts.join("; and ")}.`;
|
|
19336
20968
|
exposureFromEid = last?.eid;
|
|
19337
20969
|
if (exposureFromEid) exposureMeta = { exposureFromEid };
|
|
19338
20970
|
exposureKeys = [
|
|
@@ -19596,15 +21228,6 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
19596
21228
|
}
|
|
19597
21229
|
return { approved: true, checkedBy: "audit" };
|
|
19598
21230
|
}
|
|
19599
|
-
if (config.settings.mode === "audit") {
|
|
19600
|
-
if (judge) {
|
|
19601
|
-
const policyResult = await evaluateCurrentPolicy();
|
|
19602
|
-
if (policyResult.decision === "review") {
|
|
19603
|
-
appendLocalAudit(toolName, args, "allow", "audit-mode", ownRowMeta(true), hashAuditArgs);
|
|
19604
|
-
}
|
|
19605
|
-
}
|
|
19606
|
-
return { approved: true, checkedBy: "audit" };
|
|
19607
|
-
}
|
|
19608
21231
|
let appPermReview = null;
|
|
19609
21232
|
let appPermReviewTool = null;
|
|
19610
21233
|
const callSpecificReview = () => fileReview || shellReview || dlpReviewFlagged || !!appPermReview || !!taintWarning || !!exposureReview || !!packageReview;
|