@node9/proxy 2.30.0 → 2.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +60 -13
- package/dist/cli.mjs +60 -13
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -66445,7 +66445,7 @@ function triggerReach(wf, raw) {
|
|
|
66445
66445
|
const on = onObject(wf, raw) ?? {};
|
|
66446
66446
|
const keys = triggerKeys(wf, raw);
|
|
66447
66447
|
const firable = keys.filter(
|
|
66448
|
-
(k) => keyStrangerFirable(on, k) || /^issues$/i.test(k) && testsAutoLabel(allIfText(wf))
|
|
66448
|
+
(k) => keyStrangerFirable(on, k) && !(/^workflow_run$/i.test(k) && upstreamRunTrusted(wf, raw)) || /^issues$/i.test(k) && testsAutoLabel(allIfText(wf))
|
|
66449
66449
|
);
|
|
66450
66450
|
const untrusted = firable.length > 0;
|
|
66451
66451
|
const reusable = !untrusted && keys.some((k) => /^workflow_call$/i.test(k));
|
|
@@ -66564,12 +66564,14 @@ function loadWorkflow(content) {
|
|
|
66564
66564
|
return LOADED;
|
|
66565
66565
|
}
|
|
66566
66566
|
var LOCAL_WORKFLOW_USES_RE = /^\s*\.\/\.github\/workflows\/([\w.-]{1,200}\.ya?ml)\s*$/i;
|
|
66567
|
-
function localCallsOf(path91, content, autoLabels) {
|
|
66567
|
+
function localCallsOf(path91, content, autoLabels, trustedRunSources) {
|
|
66568
66568
|
AUTO_LABELS = autoLabels ?? /* @__PURE__ */ new Set();
|
|
66569
|
+
TRUSTED_RUN_SOURCES = trustedRunSources ?? /* @__PURE__ */ new Set();
|
|
66569
66570
|
try {
|
|
66570
66571
|
return localCallsOfInner(path91, content);
|
|
66571
66572
|
} finally {
|
|
66572
66573
|
AUTO_LABELS = /* @__PURE__ */ new Set();
|
|
66574
|
+
TRUSTED_RUN_SOURCES = /* @__PURE__ */ new Set();
|
|
66573
66575
|
}
|
|
66574
66576
|
}
|
|
66575
66577
|
function localCallsOfInner(path91, content) {
|
|
@@ -67179,8 +67181,9 @@ function hasImplicitActorGate(agentSteps, bypassActive, workflowRun = false) {
|
|
|
67179
67181
|
return agentSteps.some((s) => {
|
|
67180
67182
|
const build = claudeActionBuild(s.uses);
|
|
67181
67183
|
if (!build) return false;
|
|
67184
|
+
if (!build.official) return false;
|
|
67182
67185
|
if (!workflowRun) return true;
|
|
67183
|
-
return
|
|
67186
|
+
return workflowRunChecked(build.ref);
|
|
67184
67187
|
});
|
|
67185
67188
|
}
|
|
67186
67189
|
function claudeActionBuild(uses) {
|
|
@@ -67419,13 +67422,13 @@ function analyzeWorkflowInner(path91, content, callers) {
|
|
|
67419
67422
|
"runs the checked-out head's own action (`uses: ./\u2026`): the PR author's code runs with the job's secrets"
|
|
67420
67423
|
);
|
|
67421
67424
|
if (head === "subdir") signals.push("checks out the untrusted PR head into an isolated subdir");
|
|
67425
|
+
const thirdParty = agentSteps.map((st) => claudeActionBuild(st.uses)).find((b) => b !== null && !b.official);
|
|
67426
|
+
if (thirdParty && !implicitGate)
|
|
67427
|
+
signals.push(
|
|
67428
|
+
`third-party build of claude-code-action (\`${safeText(thirdParty.owner, 40)}\`): not assumed to check the actor's write access like the official action`
|
|
67429
|
+
);
|
|
67422
67430
|
if (workflowRunJobs.length) {
|
|
67423
67431
|
const builds = workflowRunJobs.flatMap((j) => (j.steps ?? []).filter(isAgentStep)).map((st) => claudeActionBuild(st.uses)).filter((b) => b !== null);
|
|
67424
|
-
const thirdParty = builds.find((b) => !b.official);
|
|
67425
|
-
if (thirdParty)
|
|
67426
|
-
signals.push(
|
|
67427
|
-
`third-party build of claude-code-action (\`${safeText(thirdParty.owner, 40)}\`): on workflow_run it is not assumed to check the actor (the official action does from v1.0.185)`
|
|
67428
|
-
);
|
|
67429
67432
|
const old = builds.find((b) => b.official && !workflowRunChecked(b.ref));
|
|
67430
67433
|
if (old)
|
|
67431
67434
|
signals.push(
|
|
@@ -67722,12 +67725,14 @@ function readsItemList(text) {
|
|
|
67722
67725
|
if (text.length > 2e5) return ITEM_LIST_RE.test(text);
|
|
67723
67726
|
return text.replace(/\\\r?\n/g, " ").split("\n").some((l) => ITEM_LIST_RE.test(l) && !sanitizedListing(l));
|
|
67724
67727
|
}
|
|
67725
|
-
function analyzeTrustedReaders(path91, content, autoLabels, callers) {
|
|
67728
|
+
function analyzeTrustedReaders(path91, content, autoLabels, callers, trustedRunSources) {
|
|
67726
67729
|
AUTO_LABELS = autoLabels ?? /* @__PURE__ */ new Set();
|
|
67730
|
+
TRUSTED_RUN_SOURCES = trustedRunSources ?? /* @__PURE__ */ new Set();
|
|
67727
67731
|
try {
|
|
67728
67732
|
return analyzeTrustedReadersInner(path91, content, callers);
|
|
67729
67733
|
} finally {
|
|
67730
67734
|
AUTO_LABELS = /* @__PURE__ */ new Set();
|
|
67735
|
+
TRUSTED_RUN_SOURCES = /* @__PURE__ */ new Set();
|
|
67731
67736
|
}
|
|
67732
67737
|
}
|
|
67733
67738
|
function keysWithCallers(keys, callers) {
|
|
@@ -67882,12 +67887,14 @@ function requiresTrustedAuthor(src) {
|
|
|
67882
67887
|
};
|
|
67883
67888
|
return req(x);
|
|
67884
67889
|
}
|
|
67885
|
-
function analyzeForkCodeOnTrust(path91, content, callers, autoLabels) {
|
|
67890
|
+
function analyzeForkCodeOnTrust(path91, content, callers, autoLabels, trustedRunSources) {
|
|
67886
67891
|
AUTO_LABELS = autoLabels ?? /* @__PURE__ */ new Set();
|
|
67892
|
+
TRUSTED_RUN_SOURCES = trustedRunSources ?? /* @__PURE__ */ new Set();
|
|
67887
67893
|
try {
|
|
67888
67894
|
return analyzeForkCodeOnTrustInner(path91, content, callers);
|
|
67889
67895
|
} finally {
|
|
67890
67896
|
AUTO_LABELS = /* @__PURE__ */ new Set();
|
|
67897
|
+
TRUSTED_RUN_SOURCES = /* @__PURE__ */ new Set();
|
|
67891
67898
|
}
|
|
67892
67899
|
}
|
|
67893
67900
|
function analyzeForkCodeOnTrustInner(path91, content, callers) {
|
|
@@ -67972,6 +67979,16 @@ var EXTRA_RUNNER_RE = new RegExp(
|
|
|
67972
67979
|
String.raw`^Bash\((?:[A-Z_][A-Z0-9_]{0,40}=\S{0,200} )*(?:/[\w./-]{0,80}/)?` + String.raw`(awk|gawk|mawk|sed|tee|tar|make(?! -n\b)|vim|vi|nvim|less|man|timeout|dd|install|` + String.raw`dotnet (?:run|script)|cargo run|go run|mvn exec|gradle run|npm start|tmux|` + String.raw`rm|git (?:push|config|submodule|filter-branch)|gh (?:repo|secret|auth|release))` + String.raw`(?: --?[A-Za-z][\w-]{0,30}){0,4} ?['"]?(?::\*| \*|\*)\)$`,
|
|
67973
67980
|
"i"
|
|
67974
67981
|
);
|
|
67982
|
+
var EXEC_ESCAPE_RE = new RegExp(
|
|
67983
|
+
String.raw`^Bash\((?:[A-Z_][A-Z0-9_]{0,40}=\S{0,200} )*(?:/[\w./-]{0,80}/)?` + String.raw`(find|make -n)` + String.raw`(?: --?[A-Za-z][\w-]{0,30}){0,4} ?['"]?(?::\*| \*|\*)\)$`,
|
|
67984
|
+
"i"
|
|
67985
|
+
);
|
|
67986
|
+
function execEscape(grant) {
|
|
67987
|
+
if (grant.length > OPEN_RUNNER_MAX) return false;
|
|
67988
|
+
return EXEC_ESCAPE_RE.test(
|
|
67989
|
+
grant.replace(/^PowerShell(?=$|\()/, "Bash").replace(/\s+/g, " ").replace(/\(\s+/, "(")
|
|
67990
|
+
);
|
|
67991
|
+
}
|
|
67975
67992
|
var MCP_EXEC_RE = /^mcp__ide__executeCode$|^mcp__[\w-]{1,80}__(?:execute|exec|run)_(?:code|command|shell|sql|query)$|^mcp__[\w-]{1,80}__(?:ctx_)?(?:batch_)?execute$|^mcp__[\w-]{1,80}__(?:bash|shell|terminal)$/;
|
|
67976
67993
|
function extraRunner(grant) {
|
|
67977
67994
|
if (grant.length > OPEN_RUNNER_MAX || INFO_FLAG_RE.test(grant)) return false;
|
|
@@ -68152,6 +68169,24 @@ function analyzeAgentConfig(path91, content, tree) {
|
|
|
68152
68169
|
})
|
|
68153
68170
|
});
|
|
68154
68171
|
}
|
|
68172
|
+
const escapes = allow.filter(execEscape);
|
|
68173
|
+
if (escapes.length) {
|
|
68174
|
+
const at = jsonValueIndex(content, escapes[0]);
|
|
68175
|
+
findings.push({
|
|
68176
|
+
...at >= 0 ? { line: lineAtIndex(content, at) } : {},
|
|
68177
|
+
check: "CI-1",
|
|
68178
|
+
rule: "CI-1.exec-escape-grant",
|
|
68179
|
+
dimension: "toolRules",
|
|
68180
|
+
severity: "medium",
|
|
68181
|
+
title: "Committed agent config pre-approves a command that can run other programs",
|
|
68182
|
+
file: path91,
|
|
68183
|
+
signals: [
|
|
68184
|
+
`pre-approved: ${escapes.slice(0, 5).map((e) => `\`${safeText(e, 80)}\``).join(", ")}`,
|
|
68185
|
+
"`find -exec` and `make -n --eval` run other programs without a prompt"
|
|
68186
|
+
],
|
|
68187
|
+
fix: "Pre-approve the exact command you need (for example `Bash(find . -name *.ts)` or `Bash(make test)`), not every argument."
|
|
68188
|
+
});
|
|
68189
|
+
}
|
|
68155
68190
|
return findings;
|
|
68156
68191
|
}
|
|
68157
68192
|
var GRANT_CARRIER_RE = /(^|\/)[Ss][Kk][Ii][Ll][Ll]\.md$|(^|\/)\.claude\/commands\/.+\.md$/;
|
|
@@ -68490,6 +68525,7 @@ var CONCEALMENT = /* @__PURE__ */ new Set(["CI-6.unicode-tag-chars", "CI-6.bidi-
|
|
|
68490
68525
|
function tierOf(f) {
|
|
68491
68526
|
const { rule, severity } = f;
|
|
68492
68527
|
if (rule === "CI-1.skill-allowed-tools") return "review";
|
|
68528
|
+
if (rule === "CI-1.exec-escape-grant") return "review";
|
|
68493
68529
|
if (rule === "CI-2.trusted-trigger-reader" || rule === "CI-2.fork-code-on-trust" || rule === "CI-1.skill-untrusted-input")
|
|
68494
68530
|
return "alert";
|
|
68495
68531
|
if (UNREAD_RULES.has(rule)) return "review";
|
|
@@ -68517,6 +68553,8 @@ function reviewQuestion(rule) {
|
|
|
68517
68553
|
return "Does this hook exist on every machine that runs it?";
|
|
68518
68554
|
if (/hidden-chars/.test(rule))
|
|
68519
68555
|
return "Why does this script hold invisible characters? A list of them to detect is fine.";
|
|
68556
|
+
if (/exec-escape/.test(rule))
|
|
68557
|
+
return "Does the agent need this command with any arguments, or only one exact form?";
|
|
68520
68558
|
if (/unscanned/.test(rule))
|
|
68521
68559
|
return "node9 did not read this script (too large, or outside the folders it reads). What does it run?";
|
|
68522
68560
|
return "Is this an instruction the agent follows, or text for a person?";
|
|
@@ -68889,7 +68927,12 @@ function scanTree(tree) {
|
|
|
68889
68927
|
if (routeOfFile(file) !== "workflow") continue;
|
|
68890
68928
|
try {
|
|
68891
68929
|
const content = file.content;
|
|
68892
|
-
for (const { target, caller } of localCallsOf(
|
|
68930
|
+
for (const { target, caller } of localCallsOf(
|
|
68931
|
+
file.path,
|
|
68932
|
+
content,
|
|
68933
|
+
autoLabels,
|
|
68934
|
+
trustedRunSources
|
|
68935
|
+
)) {
|
|
68893
68936
|
const list = callersOf.get(target) ?? [];
|
|
68894
68937
|
list.push(caller);
|
|
68895
68938
|
callersOf.set(target, list);
|
|
@@ -68925,8 +68968,12 @@ function scanTree(tree) {
|
|
|
68925
68968
|
);
|
|
68926
68969
|
if (s) findings.push(s);
|
|
68927
68970
|
findings.push(...analyzeWorkflowPlugins(file.path, content, tree.owner));
|
|
68928
|
-
findings.push(
|
|
68929
|
-
|
|
68971
|
+
findings.push(
|
|
68972
|
+
...analyzeTrustedReaders(file.path, content, autoLabels, callers, trustedRunSources)
|
|
68973
|
+
);
|
|
68974
|
+
findings.push(
|
|
68975
|
+
...analyzeForkCodeOnTrust(file.path, content, callers, autoLabels, trustedRunSources)
|
|
68976
|
+
);
|
|
68930
68977
|
break;
|
|
68931
68978
|
}
|
|
68932
68979
|
case "agent-config":
|
package/dist/cli.mjs
CHANGED
|
@@ -66427,7 +66427,7 @@ function triggerReach(wf, raw) {
|
|
|
66427
66427
|
const on = onObject(wf, raw) ?? {};
|
|
66428
66428
|
const keys = triggerKeys(wf, raw);
|
|
66429
66429
|
const firable = keys.filter(
|
|
66430
|
-
(k) => keyStrangerFirable(on, k) || /^issues$/i.test(k) && testsAutoLabel(allIfText(wf))
|
|
66430
|
+
(k) => keyStrangerFirable(on, k) && !(/^workflow_run$/i.test(k) && upstreamRunTrusted(wf, raw)) || /^issues$/i.test(k) && testsAutoLabel(allIfText(wf))
|
|
66431
66431
|
);
|
|
66432
66432
|
const untrusted = firable.length > 0;
|
|
66433
66433
|
const reusable = !untrusted && keys.some((k) => /^workflow_call$/i.test(k));
|
|
@@ -66546,12 +66546,14 @@ function loadWorkflow(content) {
|
|
|
66546
66546
|
return LOADED;
|
|
66547
66547
|
}
|
|
66548
66548
|
var LOCAL_WORKFLOW_USES_RE = /^\s*\.\/\.github\/workflows\/([\w.-]{1,200}\.ya?ml)\s*$/i;
|
|
66549
|
-
function localCallsOf(path91, content, autoLabels) {
|
|
66549
|
+
function localCallsOf(path91, content, autoLabels, trustedRunSources) {
|
|
66550
66550
|
AUTO_LABELS = autoLabels ?? /* @__PURE__ */ new Set();
|
|
66551
|
+
TRUSTED_RUN_SOURCES = trustedRunSources ?? /* @__PURE__ */ new Set();
|
|
66551
66552
|
try {
|
|
66552
66553
|
return localCallsOfInner(path91, content);
|
|
66553
66554
|
} finally {
|
|
66554
66555
|
AUTO_LABELS = /* @__PURE__ */ new Set();
|
|
66556
|
+
TRUSTED_RUN_SOURCES = /* @__PURE__ */ new Set();
|
|
66555
66557
|
}
|
|
66556
66558
|
}
|
|
66557
66559
|
function localCallsOfInner(path91, content) {
|
|
@@ -67161,8 +67163,9 @@ function hasImplicitActorGate(agentSteps, bypassActive, workflowRun = false) {
|
|
|
67161
67163
|
return agentSteps.some((s) => {
|
|
67162
67164
|
const build = claudeActionBuild(s.uses);
|
|
67163
67165
|
if (!build) return false;
|
|
67166
|
+
if (!build.official) return false;
|
|
67164
67167
|
if (!workflowRun) return true;
|
|
67165
|
-
return
|
|
67168
|
+
return workflowRunChecked(build.ref);
|
|
67166
67169
|
});
|
|
67167
67170
|
}
|
|
67168
67171
|
function claudeActionBuild(uses) {
|
|
@@ -67401,13 +67404,13 @@ function analyzeWorkflowInner(path91, content, callers) {
|
|
|
67401
67404
|
"runs the checked-out head's own action (`uses: ./\u2026`): the PR author's code runs with the job's secrets"
|
|
67402
67405
|
);
|
|
67403
67406
|
if (head === "subdir") signals.push("checks out the untrusted PR head into an isolated subdir");
|
|
67407
|
+
const thirdParty = agentSteps.map((st) => claudeActionBuild(st.uses)).find((b) => b !== null && !b.official);
|
|
67408
|
+
if (thirdParty && !implicitGate)
|
|
67409
|
+
signals.push(
|
|
67410
|
+
`third-party build of claude-code-action (\`${safeText(thirdParty.owner, 40)}\`): not assumed to check the actor's write access like the official action`
|
|
67411
|
+
);
|
|
67404
67412
|
if (workflowRunJobs.length) {
|
|
67405
67413
|
const builds = workflowRunJobs.flatMap((j) => (j.steps ?? []).filter(isAgentStep)).map((st) => claudeActionBuild(st.uses)).filter((b) => b !== null);
|
|
67406
|
-
const thirdParty = builds.find((b) => !b.official);
|
|
67407
|
-
if (thirdParty)
|
|
67408
|
-
signals.push(
|
|
67409
|
-
`third-party build of claude-code-action (\`${safeText(thirdParty.owner, 40)}\`): on workflow_run it is not assumed to check the actor (the official action does from v1.0.185)`
|
|
67410
|
-
);
|
|
67411
67414
|
const old = builds.find((b) => b.official && !workflowRunChecked(b.ref));
|
|
67412
67415
|
if (old)
|
|
67413
67416
|
signals.push(
|
|
@@ -67704,12 +67707,14 @@ function readsItemList(text) {
|
|
|
67704
67707
|
if (text.length > 2e5) return ITEM_LIST_RE.test(text);
|
|
67705
67708
|
return text.replace(/\\\r?\n/g, " ").split("\n").some((l) => ITEM_LIST_RE.test(l) && !sanitizedListing(l));
|
|
67706
67709
|
}
|
|
67707
|
-
function analyzeTrustedReaders(path91, content, autoLabels, callers) {
|
|
67710
|
+
function analyzeTrustedReaders(path91, content, autoLabels, callers, trustedRunSources) {
|
|
67708
67711
|
AUTO_LABELS = autoLabels ?? /* @__PURE__ */ new Set();
|
|
67712
|
+
TRUSTED_RUN_SOURCES = trustedRunSources ?? /* @__PURE__ */ new Set();
|
|
67709
67713
|
try {
|
|
67710
67714
|
return analyzeTrustedReadersInner(path91, content, callers);
|
|
67711
67715
|
} finally {
|
|
67712
67716
|
AUTO_LABELS = /* @__PURE__ */ new Set();
|
|
67717
|
+
TRUSTED_RUN_SOURCES = /* @__PURE__ */ new Set();
|
|
67713
67718
|
}
|
|
67714
67719
|
}
|
|
67715
67720
|
function keysWithCallers(keys, callers) {
|
|
@@ -67864,12 +67869,14 @@ function requiresTrustedAuthor(src) {
|
|
|
67864
67869
|
};
|
|
67865
67870
|
return req(x);
|
|
67866
67871
|
}
|
|
67867
|
-
function analyzeForkCodeOnTrust(path91, content, callers, autoLabels) {
|
|
67872
|
+
function analyzeForkCodeOnTrust(path91, content, callers, autoLabels, trustedRunSources) {
|
|
67868
67873
|
AUTO_LABELS = autoLabels ?? /* @__PURE__ */ new Set();
|
|
67874
|
+
TRUSTED_RUN_SOURCES = trustedRunSources ?? /* @__PURE__ */ new Set();
|
|
67869
67875
|
try {
|
|
67870
67876
|
return analyzeForkCodeOnTrustInner(path91, content, callers);
|
|
67871
67877
|
} finally {
|
|
67872
67878
|
AUTO_LABELS = /* @__PURE__ */ new Set();
|
|
67879
|
+
TRUSTED_RUN_SOURCES = /* @__PURE__ */ new Set();
|
|
67873
67880
|
}
|
|
67874
67881
|
}
|
|
67875
67882
|
function analyzeForkCodeOnTrustInner(path91, content, callers) {
|
|
@@ -67954,6 +67961,16 @@ var EXTRA_RUNNER_RE = new RegExp(
|
|
|
67954
67961
|
String.raw`^Bash\((?:[A-Z_][A-Z0-9_]{0,40}=\S{0,200} )*(?:/[\w./-]{0,80}/)?` + String.raw`(awk|gawk|mawk|sed|tee|tar|make(?! -n\b)|vim|vi|nvim|less|man|timeout|dd|install|` + String.raw`dotnet (?:run|script)|cargo run|go run|mvn exec|gradle run|npm start|tmux|` + String.raw`rm|git (?:push|config|submodule|filter-branch)|gh (?:repo|secret|auth|release))` + String.raw`(?: --?[A-Za-z][\w-]{0,30}){0,4} ?['"]?(?::\*| \*|\*)\)$`,
|
|
67955
67962
|
"i"
|
|
67956
67963
|
);
|
|
67964
|
+
var EXEC_ESCAPE_RE = new RegExp(
|
|
67965
|
+
String.raw`^Bash\((?:[A-Z_][A-Z0-9_]{0,40}=\S{0,200} )*(?:/[\w./-]{0,80}/)?` + String.raw`(find|make -n)` + String.raw`(?: --?[A-Za-z][\w-]{0,30}){0,4} ?['"]?(?::\*| \*|\*)\)$`,
|
|
67966
|
+
"i"
|
|
67967
|
+
);
|
|
67968
|
+
function execEscape(grant) {
|
|
67969
|
+
if (grant.length > OPEN_RUNNER_MAX) return false;
|
|
67970
|
+
return EXEC_ESCAPE_RE.test(
|
|
67971
|
+
grant.replace(/^PowerShell(?=$|\()/, "Bash").replace(/\s+/g, " ").replace(/\(\s+/, "(")
|
|
67972
|
+
);
|
|
67973
|
+
}
|
|
67957
67974
|
var MCP_EXEC_RE = /^mcp__ide__executeCode$|^mcp__[\w-]{1,80}__(?:execute|exec|run)_(?:code|command|shell|sql|query)$|^mcp__[\w-]{1,80}__(?:ctx_)?(?:batch_)?execute$|^mcp__[\w-]{1,80}__(?:bash|shell|terminal)$/;
|
|
67958
67975
|
function extraRunner(grant) {
|
|
67959
67976
|
if (grant.length > OPEN_RUNNER_MAX || INFO_FLAG_RE.test(grant)) return false;
|
|
@@ -68134,6 +68151,24 @@ function analyzeAgentConfig(path91, content, tree) {
|
|
|
68134
68151
|
})
|
|
68135
68152
|
});
|
|
68136
68153
|
}
|
|
68154
|
+
const escapes = allow.filter(execEscape);
|
|
68155
|
+
if (escapes.length) {
|
|
68156
|
+
const at = jsonValueIndex(content, escapes[0]);
|
|
68157
|
+
findings.push({
|
|
68158
|
+
...at >= 0 ? { line: lineAtIndex(content, at) } : {},
|
|
68159
|
+
check: "CI-1",
|
|
68160
|
+
rule: "CI-1.exec-escape-grant",
|
|
68161
|
+
dimension: "toolRules",
|
|
68162
|
+
severity: "medium",
|
|
68163
|
+
title: "Committed agent config pre-approves a command that can run other programs",
|
|
68164
|
+
file: path91,
|
|
68165
|
+
signals: [
|
|
68166
|
+
`pre-approved: ${escapes.slice(0, 5).map((e) => `\`${safeText(e, 80)}\``).join(", ")}`,
|
|
68167
|
+
"`find -exec` and `make -n --eval` run other programs without a prompt"
|
|
68168
|
+
],
|
|
68169
|
+
fix: "Pre-approve the exact command you need (for example `Bash(find . -name *.ts)` or `Bash(make test)`), not every argument."
|
|
68170
|
+
});
|
|
68171
|
+
}
|
|
68137
68172
|
return findings;
|
|
68138
68173
|
}
|
|
68139
68174
|
var GRANT_CARRIER_RE = /(^|\/)[Ss][Kk][Ii][Ll][Ll]\.md$|(^|\/)\.claude\/commands\/.+\.md$/;
|
|
@@ -68472,6 +68507,7 @@ var CONCEALMENT = /* @__PURE__ */ new Set(["CI-6.unicode-tag-chars", "CI-6.bidi-
|
|
|
68472
68507
|
function tierOf(f) {
|
|
68473
68508
|
const { rule, severity } = f;
|
|
68474
68509
|
if (rule === "CI-1.skill-allowed-tools") return "review";
|
|
68510
|
+
if (rule === "CI-1.exec-escape-grant") return "review";
|
|
68475
68511
|
if (rule === "CI-2.trusted-trigger-reader" || rule === "CI-2.fork-code-on-trust" || rule === "CI-1.skill-untrusted-input")
|
|
68476
68512
|
return "alert";
|
|
68477
68513
|
if (UNREAD_RULES.has(rule)) return "review";
|
|
@@ -68499,6 +68535,8 @@ function reviewQuestion(rule) {
|
|
|
68499
68535
|
return "Does this hook exist on every machine that runs it?";
|
|
68500
68536
|
if (/hidden-chars/.test(rule))
|
|
68501
68537
|
return "Why does this script hold invisible characters? A list of them to detect is fine.";
|
|
68538
|
+
if (/exec-escape/.test(rule))
|
|
68539
|
+
return "Does the agent need this command with any arguments, or only one exact form?";
|
|
68502
68540
|
if (/unscanned/.test(rule))
|
|
68503
68541
|
return "node9 did not read this script (too large, or outside the folders it reads). What does it run?";
|
|
68504
68542
|
return "Is this an instruction the agent follows, or text for a person?";
|
|
@@ -68871,7 +68909,12 @@ function scanTree(tree) {
|
|
|
68871
68909
|
if (routeOfFile(file) !== "workflow") continue;
|
|
68872
68910
|
try {
|
|
68873
68911
|
const content = file.content;
|
|
68874
|
-
for (const { target, caller } of localCallsOf(
|
|
68912
|
+
for (const { target, caller } of localCallsOf(
|
|
68913
|
+
file.path,
|
|
68914
|
+
content,
|
|
68915
|
+
autoLabels,
|
|
68916
|
+
trustedRunSources
|
|
68917
|
+
)) {
|
|
68875
68918
|
const list = callersOf.get(target) ?? [];
|
|
68876
68919
|
list.push(caller);
|
|
68877
68920
|
callersOf.set(target, list);
|
|
@@ -68907,8 +68950,12 @@ function scanTree(tree) {
|
|
|
68907
68950
|
);
|
|
68908
68951
|
if (s) findings.push(s);
|
|
68909
68952
|
findings.push(...analyzeWorkflowPlugins(file.path, content, tree.owner));
|
|
68910
|
-
findings.push(
|
|
68911
|
-
|
|
68953
|
+
findings.push(
|
|
68954
|
+
...analyzeTrustedReaders(file.path, content, autoLabels, callers, trustedRunSources)
|
|
68955
|
+
);
|
|
68956
|
+
findings.push(
|
|
68957
|
+
...analyzeForkCodeOnTrust(file.path, content, callers, autoLabels, trustedRunSources)
|
|
68958
|
+
);
|
|
68912
68959
|
break;
|
|
68913
68960
|
}
|
|
68914
68961
|
case "agent-config":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/proxy",
|
|
3
|
-
"version": "2.30.
|
|
3
|
+
"version": "2.30.1",
|
|
4
4
|
"description": "Access control for AI agents. Set what Claude Code, Codex, Gemini, Cursor and any MCP server are allowed to do, review risky actions before they run, and keep every action on the record.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|