@node9/proxy 1.58.1 → 1.58.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.
Files changed (3) hide show
  1. package/dist/cli.js +52 -19
  2. package/dist/cli.mjs +52 -19
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -53109,8 +53109,20 @@ async function fetchTree(input, onProgress) {
53109
53109
 
53110
53110
  // src/ci-check/workflows.ts
53111
53111
  var import_yaml = require("yaml");
53112
+
53113
+ // src/ci-check/types.ts
53114
+ var SEVERITY_RANK2 = {
53115
+ critical: 4,
53116
+ high: 3,
53117
+ medium: 2,
53118
+ advisory: 1
53119
+ };
53120
+
53121
+ // src/ci-check/workflows.ts
53112
53122
  var AGENT_ACTION_RE = /(anthropics\/claude-code(-base)?-action|anthropics\/claude-code|openai\/codex|codex-action|run-?aider|aider-?action|google-github-actions\/run-gemini)/i;
53113
53123
  var BROAD_TOOL_RE = /(^|["\s,])(Bash|Write|Edit)(\s|,|"|$)|Bash\(\s*\*|Bash\((curl|wget|git push|git commit|git config|git:|rm|sh|bash|eval|npx|pip)/i;
53124
+ var EXFIL_RCE_RE = /(^|["\s,])Bash(\s|,|"|$)|Bash\(\s*\*|Bash\((curl|wget|sh[\s):]|eval|rm[\s):]|git push)/i;
53125
+ var GH_WRITE_TOOL_RE = /Bash\(\s*(gh api|gh:|gh (pr|issue) (comment|edit|merge|close|review|create|ready|lock|reopen)|git push|git:)/i;
53114
53126
  function str(v) {
53115
53127
  return typeof v === "string" ? v : v == null ? "" : JSON.stringify(v);
53116
53128
  }
@@ -53135,11 +53147,12 @@ function isAgentStep(step) {
53135
53147
  return false;
53136
53148
  }
53137
53149
  function collectTools(steps) {
53150
+ const stripDeny = (x) => x.replace(/--disallowed[-_]?tools\s+("[^"]*"|'[^']*'|\S+)/gi, " ").replace(/["']disallowed[_]?[tT]ools["']\s*:\s*\[[^\]]*\]/g, " ");
53138
53151
  let s = "";
53139
53152
  for (const st of steps) {
53140
53153
  const w = st.with ?? {};
53141
- s += " " + str(w["claude_args"]) + " " + str(w["allowed_tools"]) + " " + str(w["allowedTools"]);
53142
- s += " " + str(w["settings"]);
53154
+ s += " " + stripDeny(str(w["claude_args"])) + " " + str(w["allowed_tools"]) + " " + str(w["allowedTools"]);
53155
+ s += " " + stripDeny(str(w["settings"]));
53143
53156
  }
53144
53157
  return s;
53145
53158
  }
@@ -53177,12 +53190,22 @@ function hasActorGate(wf, raw) {
53177
53190
  const labelGated = !!labeled && /event\.label|label\.name/i.test(ifs);
53178
53191
  return gated || labelGated;
53179
53192
  }
53180
- function permsElevated(wf) {
53193
+ function hasImplicitActorGate(agentSteps, bypassActive) {
53194
+ if (bypassActive) return false;
53195
+ return agentSteps.some((s) => /anthropics\/claude-code-action@/i.test(s.uses ?? ""));
53196
+ }
53197
+ function permsElevated(wf, agentJobs) {
53181
53198
  const check = (p) => {
53182
53199
  const s = str(p);
53183
53200
  return /["']?(contents|id-token|packages)["']?\s*:\s*["']?write/i.test(s);
53184
53201
  };
53185
- return check(wf.permissions) || Object.values(wf.jobs ?? {}).some((j) => check(j.permissions));
53202
+ return check(wf.permissions) || agentJobs.some((j) => check(j.permissions));
53203
+ }
53204
+ function hasGithubWritePerm(wf, agentJobs) {
53205
+ const check = (p) => /["']?(contents|pull-requests|issues|packages|actions|deployments)["']?\s*:\s*["']?write/i.test(
53206
+ str(p)
53207
+ );
53208
+ return check(wf.permissions) || agentJobs.some((j) => check(j.permissions));
53186
53209
  }
53187
53210
  function usesPat(wf) {
53188
53211
  for (const { step } of allSteps(wf)) {
@@ -53216,6 +53239,11 @@ function analyzeWorkflow(path70, content) {
53216
53239
  const wf = raw;
53217
53240
  const steps = allSteps(wf).map((s) => s.step);
53218
53241
  const agentSteps = steps.filter(isAgentStep);
53242
+ const agentJobs = [
53243
+ ...new Set(
53244
+ allSteps(wf).filter((s) => isAgentStep(s.step)).map((s) => s.job)
53245
+ )
53246
+ ];
53219
53247
  if (agentSteps.length === 0) return null;
53220
53248
  const triggers = triggerKeys(wf, raw);
53221
53249
  const secretExposed = triggers.some((t) => /pull_request_target|workflow_run/i.test(t));
@@ -53223,19 +53251,23 @@ function analyzeWorkflow(path70, content) {
53223
53251
  const nonWrite = str(agentSteps.map((s) => s.with?.["allowed_non_write_users"]).find(Boolean));
53224
53252
  const nonWriteStar = nonWrite === "*";
53225
53253
  const nonWriteList = !!nonWrite && nonWrite !== "*";
53254
+ const untrustedTrigger = secretExposed || forkInput;
53255
+ const bypassActive = nonWriteStar && untrustedTrigger;
53226
53256
  const head = untrustedHeadCheckout(wf);
53227
53257
  const promptUntrusted = promptTakesUntrusted(agentSteps);
53228
53258
  const reach = Math.max(
53229
53259
  head === "root" ? 3 : head === "subdir" ? 1 : 0,
53230
53260
  promptUntrusted ? 2 : 0,
53231
- nonWriteStar ? 2 : 0
53261
+ bypassActive ? 2 : 0
53232
53262
  );
53233
53263
  const toolsBlob = collectTools(agentSteps);
53234
53264
  const broadTools = BROAD_TOOL_RE.test(toolsBlob);
53235
- const elevated = permsElevated(wf);
53265
+ const elevated = permsElevated(wf, agentJobs);
53236
53266
  const pat = usesPat(wf);
53237
- const power = (broadTools ? 2 : 0) + (nonWriteStar ? 1 : 0) + (elevated ? 1 : 0) + (pat ? 1 : 0);
53238
- const gate = hasActorGate(wf, raw);
53267
+ const power = (broadTools ? 2 : 0) + (bypassActive ? 1 : 0) + (elevated ? 1 : 0) + (pat ? 1 : 0);
53268
+ const explicitGate = hasActorGate(wf, raw);
53269
+ const implicitGate = hasImplicitActorGate(agentSteps, bypassActive);
53270
+ const gate = explicitGate || implicitGate;
53239
53271
  const envDeny = hasEnvDeny(agentSteps);
53240
53272
  const pinned = agentActionsPinned(agentSteps);
53241
53273
  let score = reach + power;
@@ -53245,7 +53277,14 @@ function analyzeWorkflow(path70, content) {
53245
53277
  score = Math.max(0, score);
53246
53278
  if (score === 0 && !secretExposed) return null;
53247
53279
  let severity = severityFromScore(score);
53248
- if (gate && reach > 0 && severity && severity !== "advisory") severity = "advisory";
53280
+ if (gate && severity && severity !== "advisory") severity = "advisory";
53281
+ if (reach === 0 && severity && severity !== "advisory") severity = "advisory";
53282
+ if (!broadTools && severity && SEVERITY_RANK2[severity] > SEVERITY_RANK2.medium)
53283
+ severity = "medium";
53284
+ const exfilOrRce = EXFIL_RCE_RE.test(toolsBlob);
53285
+ const githubWriteTool = GH_WRITE_TOOL_RE.test(toolsBlob);
53286
+ const canDamage = exfilOrRce || (hasGithubWritePerm(wf, agentJobs) || pat) && githubWriteTool;
53287
+ if (!canDamage && severity && SEVERITY_RANK2[severity] > SEVERITY_RANK2.medium) severity = "medium";
53249
53288
  if (!severity) severity = "advisory";
53250
53289
  const signals = [];
53251
53290
  if (secretExposed)
@@ -53257,12 +53296,14 @@ function analyzeWorkflow(path70, content) {
53257
53296
  if (head === "subdir") signals.push("checks out the untrusted PR head into an isolated subdir");
53258
53297
  if (promptUntrusted) signals.push("feeds untrusted PR/issue text to the agent");
53259
53298
  if (broadTools) signals.push("agent has broad/write-capable tools (Bash/Write/curl/git push)");
53260
- if (nonWriteStar) signals.push('allowed_non_write_users: "*" \u2014 any user can trigger the agent');
53299
+ if (bypassActive) signals.push('allowed_non_write_users: "*" \u2014 any user can trigger the agent');
53261
53300
  if (elevated) signals.push("elevated permissions (contents/id-token: write)");
53262
53301
  if (pat) signals.push("a static PAT is exposed to the agent (recoverable via injection)");
53263
53302
  if (!gate && reach > 0) signals.push("no effective actor gate");
53264
53303
  const mitigations = [];
53265
- if (gate) mitigations.push("actor-gated (maintainer/label/write-user required)");
53304
+ if (explicitGate) mitigations.push("actor-gated (maintainer/label/write-user required)");
53305
+ else if (implicitGate)
53306
+ mitigations.push("claude-code-action gates the agent to write-access users by default");
53266
53307
  if (head === "subdir") mitigations.push("untrusted head isolated in a subdir, not root");
53267
53308
  if (envDeny) mitigations.push("secrets env-denied from the agent subprocess");
53268
53309
  if (pinned) mitigations.push("agent action pinned to a commit SHA");
@@ -53389,14 +53430,6 @@ function analyzeMcp(path70, content) {
53389
53430
  return findings;
53390
53431
  }
53391
53432
 
53392
- // src/ci-check/types.ts
53393
- var SEVERITY_RANK2 = {
53394
- critical: 4,
53395
- high: 3,
53396
- medium: 2,
53397
- advisory: 1
53398
- };
53399
-
53400
53433
  // src/ci-check/index.ts
53401
53434
  function worstOf(findings) {
53402
53435
  let worst = null;
package/dist/cli.mjs CHANGED
@@ -53102,8 +53102,20 @@ async function fetchTree(input, onProgress) {
53102
53102
 
53103
53103
  // src/ci-check/workflows.ts
53104
53104
  import { parse as parseYaml } from "yaml";
53105
+
53106
+ // src/ci-check/types.ts
53107
+ var SEVERITY_RANK2 = {
53108
+ critical: 4,
53109
+ high: 3,
53110
+ medium: 2,
53111
+ advisory: 1
53112
+ };
53113
+
53114
+ // src/ci-check/workflows.ts
53105
53115
  var AGENT_ACTION_RE = /(anthropics\/claude-code(-base)?-action|anthropics\/claude-code|openai\/codex|codex-action|run-?aider|aider-?action|google-github-actions\/run-gemini)/i;
53106
53116
  var BROAD_TOOL_RE = /(^|["\s,])(Bash|Write|Edit)(\s|,|"|$)|Bash\(\s*\*|Bash\((curl|wget|git push|git commit|git config|git:|rm|sh|bash|eval|npx|pip)/i;
53117
+ var EXFIL_RCE_RE = /(^|["\s,])Bash(\s|,|"|$)|Bash\(\s*\*|Bash\((curl|wget|sh[\s):]|eval|rm[\s):]|git push)/i;
53118
+ var GH_WRITE_TOOL_RE = /Bash\(\s*(gh api|gh:|gh (pr|issue) (comment|edit|merge|close|review|create|ready|lock|reopen)|git push|git:)/i;
53107
53119
  function str(v) {
53108
53120
  return typeof v === "string" ? v : v == null ? "" : JSON.stringify(v);
53109
53121
  }
@@ -53128,11 +53140,12 @@ function isAgentStep(step) {
53128
53140
  return false;
53129
53141
  }
53130
53142
  function collectTools(steps) {
53143
+ const stripDeny = (x) => x.replace(/--disallowed[-_]?tools\s+("[^"]*"|'[^']*'|\S+)/gi, " ").replace(/["']disallowed[_]?[tT]ools["']\s*:\s*\[[^\]]*\]/g, " ");
53131
53144
  let s = "";
53132
53145
  for (const st of steps) {
53133
53146
  const w = st.with ?? {};
53134
- s += " " + str(w["claude_args"]) + " " + str(w["allowed_tools"]) + " " + str(w["allowedTools"]);
53135
- s += " " + str(w["settings"]);
53147
+ s += " " + stripDeny(str(w["claude_args"])) + " " + str(w["allowed_tools"]) + " " + str(w["allowedTools"]);
53148
+ s += " " + stripDeny(str(w["settings"]));
53136
53149
  }
53137
53150
  return s;
53138
53151
  }
@@ -53170,12 +53183,22 @@ function hasActorGate(wf, raw) {
53170
53183
  const labelGated = !!labeled && /event\.label|label\.name/i.test(ifs);
53171
53184
  return gated || labelGated;
53172
53185
  }
53173
- function permsElevated(wf) {
53186
+ function hasImplicitActorGate(agentSteps, bypassActive) {
53187
+ if (bypassActive) return false;
53188
+ return agentSteps.some((s) => /anthropics\/claude-code-action@/i.test(s.uses ?? ""));
53189
+ }
53190
+ function permsElevated(wf, agentJobs) {
53174
53191
  const check = (p) => {
53175
53192
  const s = str(p);
53176
53193
  return /["']?(contents|id-token|packages)["']?\s*:\s*["']?write/i.test(s);
53177
53194
  };
53178
- return check(wf.permissions) || Object.values(wf.jobs ?? {}).some((j) => check(j.permissions));
53195
+ return check(wf.permissions) || agentJobs.some((j) => check(j.permissions));
53196
+ }
53197
+ function hasGithubWritePerm(wf, agentJobs) {
53198
+ const check = (p) => /["']?(contents|pull-requests|issues|packages|actions|deployments)["']?\s*:\s*["']?write/i.test(
53199
+ str(p)
53200
+ );
53201
+ return check(wf.permissions) || agentJobs.some((j) => check(j.permissions));
53179
53202
  }
53180
53203
  function usesPat(wf) {
53181
53204
  for (const { step } of allSteps(wf)) {
@@ -53209,6 +53232,11 @@ function analyzeWorkflow(path70, content) {
53209
53232
  const wf = raw;
53210
53233
  const steps = allSteps(wf).map((s) => s.step);
53211
53234
  const agentSteps = steps.filter(isAgentStep);
53235
+ const agentJobs = [
53236
+ ...new Set(
53237
+ allSteps(wf).filter((s) => isAgentStep(s.step)).map((s) => s.job)
53238
+ )
53239
+ ];
53212
53240
  if (agentSteps.length === 0) return null;
53213
53241
  const triggers = triggerKeys(wf, raw);
53214
53242
  const secretExposed = triggers.some((t) => /pull_request_target|workflow_run/i.test(t));
@@ -53216,19 +53244,23 @@ function analyzeWorkflow(path70, content) {
53216
53244
  const nonWrite = str(agentSteps.map((s) => s.with?.["allowed_non_write_users"]).find(Boolean));
53217
53245
  const nonWriteStar = nonWrite === "*";
53218
53246
  const nonWriteList = !!nonWrite && nonWrite !== "*";
53247
+ const untrustedTrigger = secretExposed || forkInput;
53248
+ const bypassActive = nonWriteStar && untrustedTrigger;
53219
53249
  const head = untrustedHeadCheckout(wf);
53220
53250
  const promptUntrusted = promptTakesUntrusted(agentSteps);
53221
53251
  const reach = Math.max(
53222
53252
  head === "root" ? 3 : head === "subdir" ? 1 : 0,
53223
53253
  promptUntrusted ? 2 : 0,
53224
- nonWriteStar ? 2 : 0
53254
+ bypassActive ? 2 : 0
53225
53255
  );
53226
53256
  const toolsBlob = collectTools(agentSteps);
53227
53257
  const broadTools = BROAD_TOOL_RE.test(toolsBlob);
53228
- const elevated = permsElevated(wf);
53258
+ const elevated = permsElevated(wf, agentJobs);
53229
53259
  const pat = usesPat(wf);
53230
- const power = (broadTools ? 2 : 0) + (nonWriteStar ? 1 : 0) + (elevated ? 1 : 0) + (pat ? 1 : 0);
53231
- const gate = hasActorGate(wf, raw);
53260
+ const power = (broadTools ? 2 : 0) + (bypassActive ? 1 : 0) + (elevated ? 1 : 0) + (pat ? 1 : 0);
53261
+ const explicitGate = hasActorGate(wf, raw);
53262
+ const implicitGate = hasImplicitActorGate(agentSteps, bypassActive);
53263
+ const gate = explicitGate || implicitGate;
53232
53264
  const envDeny = hasEnvDeny(agentSteps);
53233
53265
  const pinned = agentActionsPinned(agentSteps);
53234
53266
  let score = reach + power;
@@ -53238,7 +53270,14 @@ function analyzeWorkflow(path70, content) {
53238
53270
  score = Math.max(0, score);
53239
53271
  if (score === 0 && !secretExposed) return null;
53240
53272
  let severity = severityFromScore(score);
53241
- if (gate && reach > 0 && severity && severity !== "advisory") severity = "advisory";
53273
+ if (gate && severity && severity !== "advisory") severity = "advisory";
53274
+ if (reach === 0 && severity && severity !== "advisory") severity = "advisory";
53275
+ if (!broadTools && severity && SEVERITY_RANK2[severity] > SEVERITY_RANK2.medium)
53276
+ severity = "medium";
53277
+ const exfilOrRce = EXFIL_RCE_RE.test(toolsBlob);
53278
+ const githubWriteTool = GH_WRITE_TOOL_RE.test(toolsBlob);
53279
+ const canDamage = exfilOrRce || (hasGithubWritePerm(wf, agentJobs) || pat) && githubWriteTool;
53280
+ if (!canDamage && severity && SEVERITY_RANK2[severity] > SEVERITY_RANK2.medium) severity = "medium";
53242
53281
  if (!severity) severity = "advisory";
53243
53282
  const signals = [];
53244
53283
  if (secretExposed)
@@ -53250,12 +53289,14 @@ function analyzeWorkflow(path70, content) {
53250
53289
  if (head === "subdir") signals.push("checks out the untrusted PR head into an isolated subdir");
53251
53290
  if (promptUntrusted) signals.push("feeds untrusted PR/issue text to the agent");
53252
53291
  if (broadTools) signals.push("agent has broad/write-capable tools (Bash/Write/curl/git push)");
53253
- if (nonWriteStar) signals.push('allowed_non_write_users: "*" \u2014 any user can trigger the agent');
53292
+ if (bypassActive) signals.push('allowed_non_write_users: "*" \u2014 any user can trigger the agent');
53254
53293
  if (elevated) signals.push("elevated permissions (contents/id-token: write)");
53255
53294
  if (pat) signals.push("a static PAT is exposed to the agent (recoverable via injection)");
53256
53295
  if (!gate && reach > 0) signals.push("no effective actor gate");
53257
53296
  const mitigations = [];
53258
- if (gate) mitigations.push("actor-gated (maintainer/label/write-user required)");
53297
+ if (explicitGate) mitigations.push("actor-gated (maintainer/label/write-user required)");
53298
+ else if (implicitGate)
53299
+ mitigations.push("claude-code-action gates the agent to write-access users by default");
53259
53300
  if (head === "subdir") mitigations.push("untrusted head isolated in a subdir, not root");
53260
53301
  if (envDeny) mitigations.push("secrets env-denied from the agent subprocess");
53261
53302
  if (pinned) mitigations.push("agent action pinned to a commit SHA");
@@ -53382,14 +53423,6 @@ function analyzeMcp(path70, content) {
53382
53423
  return findings;
53383
53424
  }
53384
53425
 
53385
- // src/ci-check/types.ts
53386
- var SEVERITY_RANK2 = {
53387
- critical: 4,
53388
- high: 3,
53389
- medium: 2,
53390
- advisory: 1
53391
- };
53392
-
53393
53426
  // src/ci-check/index.ts
53394
53427
  function worstOf(findings) {
53395
53428
  let worst = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.58.1",
3
+ "version": "1.58.2",
4
4
  "description": "The Sudo Command for AI Agents. Execution Security for Claude Code, Codex, Gemini, Cursor, Opencode, Pi, and any MCP server.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",