@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.
- package/dist/cli.js +52 -19
- package/dist/cli.mjs +52 -19
- 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
|
|
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) ||
|
|
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
|
-
|
|
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) + (
|
|
53238
|
-
const
|
|
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 &&
|
|
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 (
|
|
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 (
|
|
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
|
|
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) ||
|
|
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
|
-
|
|
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) + (
|
|
53231
|
-
const
|
|
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 &&
|
|
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 (
|
|
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 (
|
|
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.
|
|
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",
|