@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 +15 -5
- package/dist/cli.mjs +15 -5
- package/package.json +1 -1
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 =
|
|
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 =
|
|
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)
|
|
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
|
|
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 =
|
|
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 =
|
|
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)
|
|
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
|
|
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.
|
|
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",
|