@node9/proxy 2.23.3 → 2.24.0
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 +50 -1
- package/dist/cli.mjs +50 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -60537,6 +60537,36 @@ function hasMetaWritePerm(wf, agentJobs) {
|
|
|
60537
60537
|
function hasEnvDeny(steps) {
|
|
60538
60538
|
return steps.some((s) => /"?mode"?\s*:\s*"?deny/i.test(str(s.with?.["settings"])));
|
|
60539
60539
|
}
|
|
60540
|
+
var CLAUDE_CODE_ACTION_RE = /anthropics\/claude-code-action@/i;
|
|
60541
|
+
var FULL_OUTPUT_ACTION_RE = /anthropics\/claude-code(-base)?-action@/i;
|
|
60542
|
+
function pinnedBelow(uses, min) {
|
|
60543
|
+
const m = /@v?(\d+)\.(\d+)\.(\d+)$/.exec(uses.trim());
|
|
60544
|
+
if (!m) return false;
|
|
60545
|
+
const v = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
60546
|
+
for (let i = 0; i < 3; i++) if (v[i] !== min[i]) return v[i] < min[i];
|
|
60547
|
+
return false;
|
|
60548
|
+
}
|
|
60549
|
+
var FULL_OUTPUT_MIN = [1, 0, 16];
|
|
60550
|
+
var ENV_SCRUB_MIN = [1, 0, 77];
|
|
60551
|
+
function showsFullOutput(steps) {
|
|
60552
|
+
return steps.some(
|
|
60553
|
+
(s) => FULL_OUTPUT_ACTION_RE.test(s.uses ?? "") && !pinnedBelow(s.uses ?? "", FULL_OUTPUT_MIN) && str(s.with?.["show_full_output"]) === "true"
|
|
60554
|
+
);
|
|
60555
|
+
}
|
|
60556
|
+
function envScrubOptedOut(pairs, wf) {
|
|
60557
|
+
const wfEnv = wf.env;
|
|
60558
|
+
const KEY = "CLAUDE_CODE_SUBPROCESS_ENV_SCRUB";
|
|
60559
|
+
return pairs.some(({ job, step }) => {
|
|
60560
|
+
const uses = step.uses ?? "";
|
|
60561
|
+
if (!CLAUDE_CODE_ACTION_RE.test(uses) || pinnedBelow(uses, ENV_SCRUB_MIN)) return false;
|
|
60562
|
+
if (!str(step.with?.["allowed_non_write_users"]).trim()) return false;
|
|
60563
|
+
const holder = [step.env, job.env, wfEnv].find((e) => e != null && KEY in e);
|
|
60564
|
+
if (!holder) return false;
|
|
60565
|
+
const v = str(holder[KEY]).trim();
|
|
60566
|
+
if (!v || /\$\{\{/.test(v)) return false;
|
|
60567
|
+
return !/^(1|true|yes|on)$/i.test(v);
|
|
60568
|
+
});
|
|
60569
|
+
}
|
|
60540
60570
|
function agentActionsPinned(steps) {
|
|
60541
60571
|
const agent = steps.filter(isAgentStep).filter((s) => s.uses);
|
|
60542
60572
|
if (agent.length === 0) return false;
|
|
@@ -60656,6 +60686,19 @@ function analyzeWorkflow(path80, content) {
|
|
|
60656
60686
|
signals.push('allowed_non_write_users: "*" with github_token \u2014 any user can trigger the agent');
|
|
60657
60687
|
if (elevated) signals.push("elevated permissions (contents/id-token: write)");
|
|
60658
60688
|
if (pat) signals.push("a static PAT is exposed to the agent (recoverable via injection)");
|
|
60689
|
+
const scopedPairs = allSteps(wf).filter(
|
|
60690
|
+
(p) => isAgentStep(p.step) && (injJobs.length === 0 || injJobs.includes(p.job))
|
|
60691
|
+
);
|
|
60692
|
+
const fullOutput = showsFullOutput(scopedPairs.map((p) => p.step));
|
|
60693
|
+
const scrubOff = envScrubOptedOut(scopedPairs, wf);
|
|
60694
|
+
if (fullOutput)
|
|
60695
|
+
signals.push(
|
|
60696
|
+
"`show_full_output: true` writes every agent command and its output to the Actions log, public on a public repo; GitHub masks known secret values there, but an agent told to encode one first can publish it without any network tool"
|
|
60697
|
+
);
|
|
60698
|
+
if (scrubOff)
|
|
60699
|
+
signals.push(
|
|
60700
|
+
"`CLAUDE_CODE_SUBPROCESS_ENV_SCRUB` is switched off, so the action no longer scrubs the Anthropic key, cloud credentials and Actions runtime tokens from the agent's shell"
|
|
60701
|
+
);
|
|
60659
60702
|
if (!gate && reach > 0) signals.push("no effective actor gate");
|
|
60660
60703
|
const noExplicitPerms = wf.permissions == null && jobList(wf).every((j) => j.permissions == null);
|
|
60661
60704
|
if (noExplicitPerms && reach > 0 && (broadTools || githubWriteTool))
|
|
@@ -60676,7 +60719,7 @@ function analyzeWorkflow(path80, content) {
|
|
|
60676
60719
|
'allowed_non_write_users: "*" is set without github_token, so the action keeps its default write gate'
|
|
60677
60720
|
);
|
|
60678
60721
|
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";
|
|
60679
|
-
|
|
60722
|
+
const finding = {
|
|
60680
60723
|
check: "CI-2",
|
|
60681
60724
|
// One verdict per workflow file — the finding IS the file's reachability score.
|
|
60682
60725
|
rule: "CI-2.injectable-workflow",
|
|
@@ -60688,6 +60731,12 @@ function analyzeWorkflow(path80, content) {
|
|
|
60688
60731
|
mitigations: mitigations.length ? mitigations : void 0,
|
|
60689
60732
|
fix: head === "root" && privileged ? "Do not check out the untrusted PR head into the workspace root under a privileged trigger (pull_request_target/workflow_run) \u2014 check out the base ref, or isolate the head in a subdir (--add-dir). Add an actor gate and scope the agent tools." : head === "root" ? "This runs under `pull_request` (fork PRs get a read-only token), so the head checkout is low-risk today \u2014 keep it on `pull_request` (not `pull_request_target`) and keep the actor gate + scoped tools." : "Add/verify an actor gate, scope the agent tools to read-only, and env-deny secrets. See Anthropic\u2019s claude-code-action security doc."
|
|
60690
60733
|
};
|
|
60734
|
+
const extraFix = [
|
|
60735
|
+
fullOutput ? "Remove `show_full_output: true` (the action documents it for debugging only)." : "",
|
|
60736
|
+
scrubOff ? "Remove the `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB` opt-out." : ""
|
|
60737
|
+
].filter(Boolean).join(" ");
|
|
60738
|
+
if (extraFix) finding.fix = `${finding.fix} ${extraFix}`;
|
|
60739
|
+
return finding;
|
|
60691
60740
|
}
|
|
60692
60741
|
var AGENT_FUEL_RE = /^(ANTHROPIC_API_KEY|ANTHROPIC_AUTH_TOKEN|ANTHROPIC_BASE_URL|CLAUDE_CODE_OAUTH_TOKEN|OPENAI_API_KEY|OPENAI_BASE_URL|GEMINI_API_KEY|GOOGLE_API_KEY|GITHUB_TOKEN)$/i;
|
|
60693
60742
|
var FUEL_INPUT_KEYS = /^(anthropic_api_key|anthropic_auth_token|anthropic_base_url|claude_code_oauth_token|openai_api_key|openai_base_url|gemini_api_key|google_api_key)$/i;
|
package/dist/cli.mjs
CHANGED
|
@@ -60526,6 +60526,36 @@ function hasMetaWritePerm(wf, agentJobs) {
|
|
|
60526
60526
|
function hasEnvDeny(steps) {
|
|
60527
60527
|
return steps.some((s) => /"?mode"?\s*:\s*"?deny/i.test(str(s.with?.["settings"])));
|
|
60528
60528
|
}
|
|
60529
|
+
var CLAUDE_CODE_ACTION_RE = /anthropics\/claude-code-action@/i;
|
|
60530
|
+
var FULL_OUTPUT_ACTION_RE = /anthropics\/claude-code(-base)?-action@/i;
|
|
60531
|
+
function pinnedBelow(uses, min) {
|
|
60532
|
+
const m = /@v?(\d+)\.(\d+)\.(\d+)$/.exec(uses.trim());
|
|
60533
|
+
if (!m) return false;
|
|
60534
|
+
const v = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
60535
|
+
for (let i = 0; i < 3; i++) if (v[i] !== min[i]) return v[i] < min[i];
|
|
60536
|
+
return false;
|
|
60537
|
+
}
|
|
60538
|
+
var FULL_OUTPUT_MIN = [1, 0, 16];
|
|
60539
|
+
var ENV_SCRUB_MIN = [1, 0, 77];
|
|
60540
|
+
function showsFullOutput(steps) {
|
|
60541
|
+
return steps.some(
|
|
60542
|
+
(s) => FULL_OUTPUT_ACTION_RE.test(s.uses ?? "") && !pinnedBelow(s.uses ?? "", FULL_OUTPUT_MIN) && str(s.with?.["show_full_output"]) === "true"
|
|
60543
|
+
);
|
|
60544
|
+
}
|
|
60545
|
+
function envScrubOptedOut(pairs, wf) {
|
|
60546
|
+
const wfEnv = wf.env;
|
|
60547
|
+
const KEY = "CLAUDE_CODE_SUBPROCESS_ENV_SCRUB";
|
|
60548
|
+
return pairs.some(({ job, step }) => {
|
|
60549
|
+
const uses = step.uses ?? "";
|
|
60550
|
+
if (!CLAUDE_CODE_ACTION_RE.test(uses) || pinnedBelow(uses, ENV_SCRUB_MIN)) return false;
|
|
60551
|
+
if (!str(step.with?.["allowed_non_write_users"]).trim()) return false;
|
|
60552
|
+
const holder = [step.env, job.env, wfEnv].find((e) => e != null && KEY in e);
|
|
60553
|
+
if (!holder) return false;
|
|
60554
|
+
const v = str(holder[KEY]).trim();
|
|
60555
|
+
if (!v || /\$\{\{/.test(v)) return false;
|
|
60556
|
+
return !/^(1|true|yes|on)$/i.test(v);
|
|
60557
|
+
});
|
|
60558
|
+
}
|
|
60529
60559
|
function agentActionsPinned(steps) {
|
|
60530
60560
|
const agent = steps.filter(isAgentStep).filter((s) => s.uses);
|
|
60531
60561
|
if (agent.length === 0) return false;
|
|
@@ -60645,6 +60675,19 @@ function analyzeWorkflow(path80, content) {
|
|
|
60645
60675
|
signals.push('allowed_non_write_users: "*" with github_token \u2014 any user can trigger the agent');
|
|
60646
60676
|
if (elevated) signals.push("elevated permissions (contents/id-token: write)");
|
|
60647
60677
|
if (pat) signals.push("a static PAT is exposed to the agent (recoverable via injection)");
|
|
60678
|
+
const scopedPairs = allSteps(wf).filter(
|
|
60679
|
+
(p) => isAgentStep(p.step) && (injJobs.length === 0 || injJobs.includes(p.job))
|
|
60680
|
+
);
|
|
60681
|
+
const fullOutput = showsFullOutput(scopedPairs.map((p) => p.step));
|
|
60682
|
+
const scrubOff = envScrubOptedOut(scopedPairs, wf);
|
|
60683
|
+
if (fullOutput)
|
|
60684
|
+
signals.push(
|
|
60685
|
+
"`show_full_output: true` writes every agent command and its output to the Actions log, public on a public repo; GitHub masks known secret values there, but an agent told to encode one first can publish it without any network tool"
|
|
60686
|
+
);
|
|
60687
|
+
if (scrubOff)
|
|
60688
|
+
signals.push(
|
|
60689
|
+
"`CLAUDE_CODE_SUBPROCESS_ENV_SCRUB` is switched off, so the action no longer scrubs the Anthropic key, cloud credentials and Actions runtime tokens from the agent's shell"
|
|
60690
|
+
);
|
|
60648
60691
|
if (!gate && reach > 0) signals.push("no effective actor gate");
|
|
60649
60692
|
const noExplicitPerms = wf.permissions == null && jobList(wf).every((j) => j.permissions == null);
|
|
60650
60693
|
if (noExplicitPerms && reach > 0 && (broadTools || githubWriteTool))
|
|
@@ -60665,7 +60708,7 @@ function analyzeWorkflow(path80, content) {
|
|
|
60665
60708
|
'allowed_non_write_users: "*" is set without github_token, so the action keeps its default write gate'
|
|
60666
60709
|
);
|
|
60667
60710
|
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";
|
|
60668
|
-
|
|
60711
|
+
const finding = {
|
|
60669
60712
|
check: "CI-2",
|
|
60670
60713
|
// One verdict per workflow file — the finding IS the file's reachability score.
|
|
60671
60714
|
rule: "CI-2.injectable-workflow",
|
|
@@ -60677,6 +60720,12 @@ function analyzeWorkflow(path80, content) {
|
|
|
60677
60720
|
mitigations: mitigations.length ? mitigations : void 0,
|
|
60678
60721
|
fix: head === "root" && privileged ? "Do not check out the untrusted PR head into the workspace root under a privileged trigger (pull_request_target/workflow_run) \u2014 check out the base ref, or isolate the head in a subdir (--add-dir). Add an actor gate and scope the agent tools." : head === "root" ? "This runs under `pull_request` (fork PRs get a read-only token), so the head checkout is low-risk today \u2014 keep it on `pull_request` (not `pull_request_target`) and keep the actor gate + scoped tools." : "Add/verify an actor gate, scope the agent tools to read-only, and env-deny secrets. See Anthropic\u2019s claude-code-action security doc."
|
|
60679
60722
|
};
|
|
60723
|
+
const extraFix = [
|
|
60724
|
+
fullOutput ? "Remove `show_full_output: true` (the action documents it for debugging only)." : "",
|
|
60725
|
+
scrubOff ? "Remove the `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB` opt-out." : ""
|
|
60726
|
+
].filter(Boolean).join(" ");
|
|
60727
|
+
if (extraFix) finding.fix = `${finding.fix} ${extraFix}`;
|
|
60728
|
+
return finding;
|
|
60680
60729
|
}
|
|
60681
60730
|
var AGENT_FUEL_RE = /^(ANTHROPIC_API_KEY|ANTHROPIC_AUTH_TOKEN|ANTHROPIC_BASE_URL|CLAUDE_CODE_OAUTH_TOKEN|OPENAI_API_KEY|OPENAI_BASE_URL|GEMINI_API_KEY|GOOGLE_API_KEY|GITHUB_TOKEN)$/i;
|
|
60682
60731
|
var FUEL_INPUT_KEYS = /^(anthropic_api_key|anthropic_auth_token|anthropic_base_url|claude_code_oauth_token|openai_api_key|openai_base_url|gemini_api_key|google_api_key)$/i;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/proxy",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.24.0",
|
|
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",
|