@node9/proxy 2.23.2 → 2.23.3
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 +31 -10
- package/dist/cli.mjs +31 -10
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -60496,11 +60496,15 @@ function injectableJobs(wf, raw, untrustedTrigger) {
|
|
|
60496
60496
|
}
|
|
60497
60497
|
return out;
|
|
60498
60498
|
}
|
|
60499
|
+
var DEAD_SECRET_TAIL_RE = /(?<=(?:\$\{\{|\(|,)\s*)(secrets\.GITHUB_TOKEN|github\.token)(?:\s*\|\|\s*(?:'[^']*'|[A-Za-z_][A-Za-z0-9_.]*))+(?=\s*(?:\}\}|\)|,))/gi;
|
|
60500
|
+
function dropDeadSecretRefs(text) {
|
|
60501
|
+
return text.replace(DEAD_SECRET_TAIL_RE, "$1");
|
|
60502
|
+
}
|
|
60499
60503
|
function usesPatIn(jobs) {
|
|
60500
60504
|
for (const j of jobs)
|
|
60501
60505
|
for (const step of j.steps ?? []) {
|
|
60502
|
-
const gt = str(step.with?.["github_token"]);
|
|
60503
|
-
if (/secrets
|
|
60506
|
+
const gt = dropDeadSecretRefs(str(step.with?.["github_token"]));
|
|
60507
|
+
if (/secrets\.(?!GITHUB_TOKEN\b)[A-Za-z_]/i.test(gt)) return true;
|
|
60504
60508
|
}
|
|
60505
60509
|
return false;
|
|
60506
60510
|
}
|
|
@@ -60690,7 +60694,8 @@ var FUEL_INPUT_KEYS = /^(anthropic_api_key|anthropic_auth_token|anthropic_base_u
|
|
|
60690
60694
|
function fuelSecretNames(agentSteps) {
|
|
60691
60695
|
const out = /* @__PURE__ */ new Set();
|
|
60692
60696
|
const add = (v) => {
|
|
60693
|
-
for (const m of str(v).matchAll(/secrets\.([A-Za-z_][A-Za-z0-9_]*)/gi))
|
|
60697
|
+
for (const m of dropDeadSecretRefs(str(v)).matchAll(/secrets\.([A-Za-z_][A-Za-z0-9_]*)/gi))
|
|
60698
|
+
out.add(m[1]);
|
|
60694
60699
|
};
|
|
60695
60700
|
for (const st of agentSteps) {
|
|
60696
60701
|
for (const [k, v] of Object.entries(st.with ?? {})) if (FUEL_INPUT_KEYS.test(k)) add(v);
|
|
@@ -60712,7 +60717,7 @@ function agentReachableSecrets(wf, agentSteps, agentJobs) {
|
|
|
60712
60717
|
blobs.push(str(wf.env));
|
|
60713
60718
|
const fuel = fuelSecretNames(agentSteps);
|
|
60714
60719
|
const found = /* @__PURE__ */ new Map();
|
|
60715
|
-
for (const b of blobs) {
|
|
60720
|
+
for (const b of blobs.map(dropDeadSecretRefs)) {
|
|
60716
60721
|
for (const m of b.matchAll(/secrets\.([A-Za-z_][A-Za-z0-9_]*)/gi)) {
|
|
60717
60722
|
const name = m[1];
|
|
60718
60723
|
if (AGENT_FUEL_RE.test(name) || fuel.has(name)) continue;
|
|
@@ -60840,19 +60845,35 @@ function analyzeAgentConfig(path80, content) {
|
|
|
60840
60845
|
);
|
|
60841
60846
|
if (broad.length > 0) {
|
|
60842
60847
|
const hasBackstop = deny.some((d) => /Bash|Write|Edit/.test(d));
|
|
60848
|
+
const bashBackstop = deny.some((d) => /^Bash\b/.test(d));
|
|
60849
|
+
const bareShell = broad.some((a) => /^Bash$|^Bash\(\s*\*/.test(a));
|
|
60850
|
+
const high = bareShell && !bashBackstop;
|
|
60851
|
+
const signals = [`broad allow(s): ${broad.slice(0, 5).join(", ")}`];
|
|
60852
|
+
if (high)
|
|
60853
|
+
signals.push(
|
|
60854
|
+
"unrestricted `Bash` with no `deny` backstop \u2014 any command an injected instruction names runs without a prompt, for everyone who opens this repo with the agent"
|
|
60855
|
+
);
|
|
60856
|
+
else if (bareShell)
|
|
60857
|
+
signals.push(
|
|
60858
|
+
"a `Bash` deny list narrows the unrestricted `Bash` allow; it blocks only the commands it names"
|
|
60859
|
+
);
|
|
60860
|
+
if (broad.some((a) => /^Bash\(git:/.test(a)))
|
|
60861
|
+
signals.push(
|
|
60862
|
+
"`Bash(git:*)` pre-approves every git command; git can run other programs (`-c core.pager=\u2026`, `!` aliases), so this is broader than it looks"
|
|
60863
|
+
);
|
|
60864
|
+
if (broad.some((a) => /^Write|^Edit$/.test(a)))
|
|
60865
|
+
signals.push("`Write`/`Edit` pre-approve file changes without a prompt");
|
|
60866
|
+
if (!high && !bareShell && !hasBackstop) signals.push("no `deny` entry narrows these grants");
|
|
60843
60867
|
findings.push({
|
|
60844
60868
|
check: "CI-1",
|
|
60845
60869
|
rule: "CI-1.broad-allow",
|
|
60846
60870
|
// File-level: one finding per config file. Adding a SECOND broad allow makes the
|
|
60847
60871
|
// same statement about the same file, so it must not read as a new finding.
|
|
60848
60872
|
dimension: "toolRules",
|
|
60849
|
-
severity:
|
|
60850
|
-
title:
|
|
60873
|
+
severity: high ? "high" : "medium",
|
|
60874
|
+
title: high ? "Committed agent config pre-authorizes broad tools with no deny backstop" : "Committed agent config pre-authorizes broad tools",
|
|
60851
60875
|
file: path80,
|
|
60852
|
-
signals
|
|
60853
|
-
`broad allow(s): ${broad.slice(0, 5).join(", ")}`,
|
|
60854
|
-
hasBackstop ? "a `deny` list backstops the broad allow" : "no `deny` entry covers Bash/Write/Edit \u2014 every contributor is pre-authorized for catastrophic tools"
|
|
60855
|
-
],
|
|
60876
|
+
signals,
|
|
60856
60877
|
fix: "Scope the allow-list to specific read-only subcommands (e.g. `Bash(gh pr view:*)`); avoid bare `Bash`/`git:`/`Write`, or add a `deny` backstop."
|
|
60857
60878
|
});
|
|
60858
60879
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -60485,11 +60485,15 @@ function injectableJobs(wf, raw, untrustedTrigger) {
|
|
|
60485
60485
|
}
|
|
60486
60486
|
return out;
|
|
60487
60487
|
}
|
|
60488
|
+
var DEAD_SECRET_TAIL_RE = /(?<=(?:\$\{\{|\(|,)\s*)(secrets\.GITHUB_TOKEN|github\.token)(?:\s*\|\|\s*(?:'[^']*'|[A-Za-z_][A-Za-z0-9_.]*))+(?=\s*(?:\}\}|\)|,))/gi;
|
|
60489
|
+
function dropDeadSecretRefs(text) {
|
|
60490
|
+
return text.replace(DEAD_SECRET_TAIL_RE, "$1");
|
|
60491
|
+
}
|
|
60488
60492
|
function usesPatIn(jobs) {
|
|
60489
60493
|
for (const j of jobs)
|
|
60490
60494
|
for (const step of j.steps ?? []) {
|
|
60491
|
-
const gt = str(step.with?.["github_token"]);
|
|
60492
|
-
if (/secrets
|
|
60495
|
+
const gt = dropDeadSecretRefs(str(step.with?.["github_token"]));
|
|
60496
|
+
if (/secrets\.(?!GITHUB_TOKEN\b)[A-Za-z_]/i.test(gt)) return true;
|
|
60493
60497
|
}
|
|
60494
60498
|
return false;
|
|
60495
60499
|
}
|
|
@@ -60679,7 +60683,8 @@ var FUEL_INPUT_KEYS = /^(anthropic_api_key|anthropic_auth_token|anthropic_base_u
|
|
|
60679
60683
|
function fuelSecretNames(agentSteps) {
|
|
60680
60684
|
const out = /* @__PURE__ */ new Set();
|
|
60681
60685
|
const add = (v) => {
|
|
60682
|
-
for (const m of str(v).matchAll(/secrets\.([A-Za-z_][A-Za-z0-9_]*)/gi))
|
|
60686
|
+
for (const m of dropDeadSecretRefs(str(v)).matchAll(/secrets\.([A-Za-z_][A-Za-z0-9_]*)/gi))
|
|
60687
|
+
out.add(m[1]);
|
|
60683
60688
|
};
|
|
60684
60689
|
for (const st of agentSteps) {
|
|
60685
60690
|
for (const [k, v] of Object.entries(st.with ?? {})) if (FUEL_INPUT_KEYS.test(k)) add(v);
|
|
@@ -60701,7 +60706,7 @@ function agentReachableSecrets(wf, agentSteps, agentJobs) {
|
|
|
60701
60706
|
blobs.push(str(wf.env));
|
|
60702
60707
|
const fuel = fuelSecretNames(agentSteps);
|
|
60703
60708
|
const found = /* @__PURE__ */ new Map();
|
|
60704
|
-
for (const b of blobs) {
|
|
60709
|
+
for (const b of blobs.map(dropDeadSecretRefs)) {
|
|
60705
60710
|
for (const m of b.matchAll(/secrets\.([A-Za-z_][A-Za-z0-9_]*)/gi)) {
|
|
60706
60711
|
const name = m[1];
|
|
60707
60712
|
if (AGENT_FUEL_RE.test(name) || fuel.has(name)) continue;
|
|
@@ -60829,19 +60834,35 @@ function analyzeAgentConfig(path80, content) {
|
|
|
60829
60834
|
);
|
|
60830
60835
|
if (broad.length > 0) {
|
|
60831
60836
|
const hasBackstop = deny.some((d) => /Bash|Write|Edit/.test(d));
|
|
60837
|
+
const bashBackstop = deny.some((d) => /^Bash\b/.test(d));
|
|
60838
|
+
const bareShell = broad.some((a) => /^Bash$|^Bash\(\s*\*/.test(a));
|
|
60839
|
+
const high = bareShell && !bashBackstop;
|
|
60840
|
+
const signals = [`broad allow(s): ${broad.slice(0, 5).join(", ")}`];
|
|
60841
|
+
if (high)
|
|
60842
|
+
signals.push(
|
|
60843
|
+
"unrestricted `Bash` with no `deny` backstop \u2014 any command an injected instruction names runs without a prompt, for everyone who opens this repo with the agent"
|
|
60844
|
+
);
|
|
60845
|
+
else if (bareShell)
|
|
60846
|
+
signals.push(
|
|
60847
|
+
"a `Bash` deny list narrows the unrestricted `Bash` allow; it blocks only the commands it names"
|
|
60848
|
+
);
|
|
60849
|
+
if (broad.some((a) => /^Bash\(git:/.test(a)))
|
|
60850
|
+
signals.push(
|
|
60851
|
+
"`Bash(git:*)` pre-approves every git command; git can run other programs (`-c core.pager=\u2026`, `!` aliases), so this is broader than it looks"
|
|
60852
|
+
);
|
|
60853
|
+
if (broad.some((a) => /^Write|^Edit$/.test(a)))
|
|
60854
|
+
signals.push("`Write`/`Edit` pre-approve file changes without a prompt");
|
|
60855
|
+
if (!high && !bareShell && !hasBackstop) signals.push("no `deny` entry narrows these grants");
|
|
60832
60856
|
findings.push({
|
|
60833
60857
|
check: "CI-1",
|
|
60834
60858
|
rule: "CI-1.broad-allow",
|
|
60835
60859
|
// File-level: one finding per config file. Adding a SECOND broad allow makes the
|
|
60836
60860
|
// same statement about the same file, so it must not read as a new finding.
|
|
60837
60861
|
dimension: "toolRules",
|
|
60838
|
-
severity:
|
|
60839
|
-
title:
|
|
60862
|
+
severity: high ? "high" : "medium",
|
|
60863
|
+
title: high ? "Committed agent config pre-authorizes broad tools with no deny backstop" : "Committed agent config pre-authorizes broad tools",
|
|
60840
60864
|
file: path80,
|
|
60841
|
-
signals
|
|
60842
|
-
`broad allow(s): ${broad.slice(0, 5).join(", ")}`,
|
|
60843
|
-
hasBackstop ? "a `deny` list backstops the broad allow" : "no `deny` entry covers Bash/Write/Edit \u2014 every contributor is pre-authorized for catastrophic tools"
|
|
60844
|
-
],
|
|
60865
|
+
signals,
|
|
60845
60866
|
fix: "Scope the allow-list to specific read-only subcommands (e.g. `Bash(gh pr view:*)`); avoid bare `Bash`/`git:`/`Write`, or add a `deny` backstop."
|
|
60846
60867
|
});
|
|
60847
60868
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/proxy",
|
|
3
|
-
"version": "2.23.
|
|
3
|
+
"version": "2.23.3",
|
|
4
4
|
"description": "IAM for your 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",
|