@node9/proxy 2.23.1 → 2.23.2

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 CHANGED
@@ -60475,6 +60475,11 @@ function jobActorGate(job, wf, raw) {
60475
60475
  return ifsAreGated(ifs, labelTypeConfigured(wf, raw)) || /assignee\.login\s*==|event\.assignee\b/i.test(ifs) || PERMISSION_OUTPUT_GATE_RE.test(ifs) || // [2] job-scoped permission-check-output gate
60476
60476
  hasStepMembershipGate(job);
60477
60477
  }
60478
+ function bypassArmed(steps) {
60479
+ return steps.some(
60480
+ (s) => str(s.with?.["allowed_non_write_users"]) === "*" && (!!str(s.with?.["github_token"]) || !!str(s.env?.["OVERRIDE_GITHUB_TOKEN"]))
60481
+ );
60482
+ }
60478
60483
  function hasImplicitActorGate(agentSteps, bypassActive) {
60479
60484
  if (bypassActive) return false;
60480
60485
  return agentSteps.some((s) => /anthropics\/claude-code-action@/i.test(s.uses ?? ""));
@@ -60484,7 +60489,7 @@ function injectableJobs(wf, raw, untrustedTrigger) {
60484
60489
  for (const job of jobList(wf)) {
60485
60490
  const a = (job.steps ?? []).filter(isAgentStep);
60486
60491
  if (!a.length) continue;
60487
- const jobStar = str(a.map((s) => s.with?.["allowed_non_write_users"]).find(Boolean)) === "*";
60492
+ const jobStar = bypassArmed(a);
60488
60493
  if (jobActorGate(job, wf, raw) || hasImplicitActorGate(a, jobStar && untrustedTrigger))
60489
60494
  continue;
60490
60495
  out.push(job);
@@ -60564,7 +60569,8 @@ function analyzeWorkflow(path80, content) {
60564
60569
  privileged
60565
60570
  } = triggerReach(wf, raw);
60566
60571
  const nonWrite = str(agentSteps.map((s) => s.with?.["allowed_non_write_users"]).find(Boolean));
60567
- const nonWriteStar = nonWrite === "*";
60572
+ const nonWriteStar = bypassArmed(agentSteps);
60573
+ const starWithoutToken = nonWrite === "*" && !nonWriteStar;
60568
60574
  const nonWriteList = !!nonWrite && nonWrite !== "*";
60569
60575
  const untrustedTrigger = forkInput || reusable;
60570
60576
  const bypassActive = nonWriteStar && untrustedTrigger;
@@ -60642,7 +60648,8 @@ function analyzeWorkflow(path80, content) {
60642
60648
  names.length ? `agent has broad/write-capable tools: ${names.map((n) => `\`${n}\``).join(", ")}` : "agent has broad/write-capable tool grants"
60643
60649
  );
60644
60650
  }
60645
- if (bypassActive) signals.push('allowed_non_write_users: "*" \u2014 any user can trigger the agent');
60651
+ if (bypassActive)
60652
+ signals.push('allowed_non_write_users: "*" with github_token \u2014 any user can trigger the agent');
60646
60653
  if (elevated) signals.push("elevated permissions (contents/id-token: write)");
60647
60654
  if (pat) signals.push("a static PAT is exposed to the agent (recoverable via injection)");
60648
60655
  if (!gate && reach > 0) signals.push("no effective actor gate");
@@ -60660,6 +60667,10 @@ function analyzeWorkflow(path80, content) {
60660
60667
  if (envDeny) mitigations.push("secrets env-denied from the agent subprocess");
60661
60668
  if (pinned) mitigations.push("agent action pinned to a commit SHA");
60662
60669
  if (nonWriteList) mitigations.push('non-write users scoped to a list, not "*"');
60670
+ if (starWithoutToken)
60671
+ mitigations.push(
60672
+ 'allowed_non_write_users: "*" is set without github_token, so the action keeps its default write gate'
60673
+ );
60663
60674
  const title = severity === "critical" || severity === "high" ? "Injectable agent workflow \u2014 untrusted input reaches a tool-using agent with secrets" : severity === "medium" ? "Agent workflow with a risky pattern (partially mitigated)" : "Agent workflow on a privileged trigger \u2014 review the actor gate";
60664
60675
  return {
60665
60676
  check: "CI-2",
@@ -60719,8 +60730,7 @@ function evalAgentJob(job, wf, raw, untrustedTrigger, reusable) {
60719
60730
  if (jobAgentSteps.length === 0) return null;
60720
60731
  const secrets = agentReachableSecrets(wf, jobAgentSteps, [job]);
60721
60732
  if (secrets.length === 0) return null;
60722
- const nonWriteStar = str(jobAgentSteps.map((s) => s.with?.["allowed_non_write_users"]).find(Boolean)) === "*";
60723
- const bypassActive = nonWriteStar && untrustedTrigger;
60733
+ const bypassActive = bypassArmed(jobAgentSteps) && untrustedTrigger;
60724
60734
  const head = untrustedHeadCheckout(jobSteps);
60725
60735
  const reach = Math.max(
60726
60736
  head === "root" ? 3 : head === "subdir" ? 1 : 0,
package/dist/cli.mjs CHANGED
@@ -60464,6 +60464,11 @@ function jobActorGate(job, wf, raw) {
60464
60464
  return ifsAreGated(ifs, labelTypeConfigured(wf, raw)) || /assignee\.login\s*==|event\.assignee\b/i.test(ifs) || PERMISSION_OUTPUT_GATE_RE.test(ifs) || // [2] job-scoped permission-check-output gate
60465
60465
  hasStepMembershipGate(job);
60466
60466
  }
60467
+ function bypassArmed(steps) {
60468
+ return steps.some(
60469
+ (s) => str(s.with?.["allowed_non_write_users"]) === "*" && (!!str(s.with?.["github_token"]) || !!str(s.env?.["OVERRIDE_GITHUB_TOKEN"]))
60470
+ );
60471
+ }
60467
60472
  function hasImplicitActorGate(agentSteps, bypassActive) {
60468
60473
  if (bypassActive) return false;
60469
60474
  return agentSteps.some((s) => /anthropics\/claude-code-action@/i.test(s.uses ?? ""));
@@ -60473,7 +60478,7 @@ function injectableJobs(wf, raw, untrustedTrigger) {
60473
60478
  for (const job of jobList(wf)) {
60474
60479
  const a = (job.steps ?? []).filter(isAgentStep);
60475
60480
  if (!a.length) continue;
60476
- const jobStar = str(a.map((s) => s.with?.["allowed_non_write_users"]).find(Boolean)) === "*";
60481
+ const jobStar = bypassArmed(a);
60477
60482
  if (jobActorGate(job, wf, raw) || hasImplicitActorGate(a, jobStar && untrustedTrigger))
60478
60483
  continue;
60479
60484
  out.push(job);
@@ -60553,7 +60558,8 @@ function analyzeWorkflow(path80, content) {
60553
60558
  privileged
60554
60559
  } = triggerReach(wf, raw);
60555
60560
  const nonWrite = str(agentSteps.map((s) => s.with?.["allowed_non_write_users"]).find(Boolean));
60556
- const nonWriteStar = nonWrite === "*";
60561
+ const nonWriteStar = bypassArmed(agentSteps);
60562
+ const starWithoutToken = nonWrite === "*" && !nonWriteStar;
60557
60563
  const nonWriteList = !!nonWrite && nonWrite !== "*";
60558
60564
  const untrustedTrigger = forkInput || reusable;
60559
60565
  const bypassActive = nonWriteStar && untrustedTrigger;
@@ -60631,7 +60637,8 @@ function analyzeWorkflow(path80, content) {
60631
60637
  names.length ? `agent has broad/write-capable tools: ${names.map((n) => `\`${n}\``).join(", ")}` : "agent has broad/write-capable tool grants"
60632
60638
  );
60633
60639
  }
60634
- if (bypassActive) signals.push('allowed_non_write_users: "*" \u2014 any user can trigger the agent');
60640
+ if (bypassActive)
60641
+ signals.push('allowed_non_write_users: "*" with github_token \u2014 any user can trigger the agent');
60635
60642
  if (elevated) signals.push("elevated permissions (contents/id-token: write)");
60636
60643
  if (pat) signals.push("a static PAT is exposed to the agent (recoverable via injection)");
60637
60644
  if (!gate && reach > 0) signals.push("no effective actor gate");
@@ -60649,6 +60656,10 @@ function analyzeWorkflow(path80, content) {
60649
60656
  if (envDeny) mitigations.push("secrets env-denied from the agent subprocess");
60650
60657
  if (pinned) mitigations.push("agent action pinned to a commit SHA");
60651
60658
  if (nonWriteList) mitigations.push('non-write users scoped to a list, not "*"');
60659
+ if (starWithoutToken)
60660
+ mitigations.push(
60661
+ 'allowed_non_write_users: "*" is set without github_token, so the action keeps its default write gate'
60662
+ );
60652
60663
  const title = severity === "critical" || severity === "high" ? "Injectable agent workflow \u2014 untrusted input reaches a tool-using agent with secrets" : severity === "medium" ? "Agent workflow with a risky pattern (partially mitigated)" : "Agent workflow on a privileged trigger \u2014 review the actor gate";
60653
60664
  return {
60654
60665
  check: "CI-2",
@@ -60708,8 +60719,7 @@ function evalAgentJob(job, wf, raw, untrustedTrigger, reusable) {
60708
60719
  if (jobAgentSteps.length === 0) return null;
60709
60720
  const secrets = agentReachableSecrets(wf, jobAgentSteps, [job]);
60710
60721
  if (secrets.length === 0) return null;
60711
- const nonWriteStar = str(jobAgentSteps.map((s) => s.with?.["allowed_non_write_users"]).find(Boolean)) === "*";
60712
- const bypassActive = nonWriteStar && untrustedTrigger;
60722
+ const bypassActive = bypassArmed(jobAgentSteps) && untrustedTrigger;
60713
60723
  const head = untrustedHeadCheckout(jobSteps);
60714
60724
  const reach = Math.max(
60715
60725
  head === "root" ? 3 : head === "subdir" ? 1 : 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "2.23.1",
3
+ "version": "2.23.2",
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",