@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.
Files changed (3) hide show
  1. package/dist/cli.js +31 -10
  2. package/dist/cli.mjs +31 -10
  3. 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\./i.test(gt) && !/secrets\.GITHUB_TOKEN/i.test(gt)) return true;
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)) out.add(m[1]);
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: hasBackstop ? "medium" : "high",
60850
- title: hasBackstop ? "Committed agent config pre-authorizes broad tools" : "Committed agent config pre-authorizes broad tools with no deny backstop",
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\./i.test(gt) && !/secrets\.GITHUB_TOKEN/i.test(gt)) return true;
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)) out.add(m[1]);
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: hasBackstop ? "medium" : "high",
60839
- title: hasBackstop ? "Committed agent config pre-authorizes broad tools" : "Committed agent config pre-authorizes broad tools with no deny backstop",
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.2",
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",