Problem
When no explicit repos / allowed-repos is configured in the workflow frontmatter, the CLI proxy falls back to defaultCliProxyPolicyJSON:
const defaultCliProxyPolicyJSON = `{"allow-only":{"repos":"all","min-integrity":"none"}}`
This means the gh CLI inside AWF can read any repo the token can access — including private repos — even when the workflow runs in a public repository.
The MCP Gateway path does not have this problem because it references $GITHUB_MCP_GUARD_REPOS (an env var set by the determine-automatic-lockdown step at runtime), which correctly restricts access based on repo visibility.
Evidence
In github/gh-aw-firewall smoke-sink-visibility-blocked, the CLI proxy allowed gh repo view github/agentic-workflows (a private repo) despite the workflow running in a public repo with no private-to-public-flows: allow opt-out. The CLI proxy logs confirm:
{"event":"exec_done","args":["repo","view","github/agentic-workflows","--json","name"],"exitCode":0}
The MCPG proxy logs show:
[difc:evaluator] Read access allowed: resource=file:github/agentic-workflows
Because agentSecrecy=[] and agentIntegrity=[none:all] — no repo-visibility constraint was applied.
Desired Behavior
| Repo visibility |
Explicit repos in frontmatter |
CLI proxy repos value |
| Public |
(none) |
"public" — block private repos |
| Public |
"all" |
"all" — user explicitly opted in |
| Public |
"public" |
"public" |
| Public |
["org/*", "org/repo"] |
["org/*", "org/repo"] — pattern limits scope |
| Private |
(none) |
"all" — private repos can read own + other private repos |
| Private |
any explicit value |
whatever user specified |
| Any |
private-to-public-flows: allow |
whatever repos resolves to (forcePublicRepos=false disables extra checks) |
Key principle: When no guard policy is explicitly configured, the CLI proxy should match the MCP Gateway's behavior — use the repo visibility as determined at runtime by determine-automatic-lockdown.
Step Ordering (Critical Constraint)
The agent job steps execute in this order:
1. determine-automatic-lockdown ← outputs: visibility, (proposed) repos
2. parse-guard-vars ← outputs: blocked_users, trusted_users, approval_labels
3. Start MCP Gateway ← references steps.determine-automatic-lockdown.outputs.*
4. Mount MCP servers as CLIs
5. Start CLI Proxy ← currently uses compile-time static CLI_PROXY_POLICY
6. Execute agent (AWF)
The CLI proxy starts after determine-automatic-lockdown, so it can reference its step outputs. The code comment about "proxy starts before parse-guard-vars" refers to the fact that blocked-users/trusted-users/approval-labels are not available — but repos and visibility ARE available.
Implementation Plan
1. actions/setup/js/determine_automatic_lockdown.cjs
Add a new step output repos that reflects the default repos scope based on repo visibility:
- Public repo + no explicit
GH_AW_GITHUB_REPOS env → output repos = "public"
- Private repo + no explicit
GH_AW_GITHUB_REPOS env → output repos = "all"
- Explicit
GH_AW_GITHUB_REPOS env set → output repos = <that value> (passthrough)
2. pkg/workflow/compiler_difc_proxy.go — buildStartCliProxyStepYAML()
When no explicit guard policy is configured (the policyJSON == "" branch that falls back to defaultCliProxyPolicyJSON), instead of using the hardcoded default, emit a policy that references the step output:
CLI_PROXY_POLICY: '{"allow-only":{"repos":"${{ steps.determine-automatic-lockdown.outputs.repos }}","min-integrity":"${{ steps.determine-automatic-lockdown.outputs.min_integrity || ''none'' }}"}}'
This mirrors how the MCP Gateway already references $GITHUB_MCP_GUARD_REPOS / $GITHUB_MCP_GUARD_MIN_INTEGRITY.
3. When explicit repos IS configured
No change needed — getDIFCProxyPolicyJSON() already emits the user's explicit value. The fix only applies to the fallback/default case.
4. private-to-public-flows: allow interaction
When private-to-public-flows: allow is set, the compiler already emits forcePublicRepos: false in the gateway config. For the CLI proxy, this means the repos value should remain whatever the user configured (or "all" if not configured). The determine-automatic-lockdown step should detect this opt-out and output repos = "all" accordingly.
Open question: Does determine_automatic_lockdown.cjs receive enough context to know about private-to-public-flows? If not, the compiler could pass it as an env var (e.g., GH_AW_PRIVATE_TO_PUBLIC_FLOWS: "allow").
5. Tests
- Update
compiler_difc_proxy_test.go — verify that when no guard policy is configured, the emitted CLI_PROXY_POLICY references the step output instead of the hardcoded default
- Update
determine_automatic_lockdown.test.cjs — verify the new repos output for public/private repos
- The existing
smoke-sink-visibility-blocked workflow in gh-aw-firewall will serve as the integration test
Files to Change
| File |
Change |
actions/setup/js/determine_automatic_lockdown.cjs |
Add repos output based on visibility |
actions/setup/js/determine_automatic_lockdown.test.cjs |
Test new output |
pkg/workflow/compiler_difc_proxy.go |
Reference step output in default CLI proxy policy |
pkg/workflow/compiler_difc_proxy_test.go |
Update expectations |
Related
Problem
When no explicit
repos/allowed-reposis configured in the workflow frontmatter, the CLI proxy falls back todefaultCliProxyPolicyJSON:This means the
ghCLI inside AWF can read any repo the token can access — including private repos — even when the workflow runs in a public repository.The MCP Gateway path does not have this problem because it references
$GITHUB_MCP_GUARD_REPOS(an env var set by thedetermine-automatic-lockdownstep at runtime), which correctly restricts access based on repo visibility.Evidence
In
github/gh-aw-firewallsmoke-sink-visibility-blocked, the CLI proxy allowedgh repo view github/agentic-workflows(a private repo) despite the workflow running in a public repo with noprivate-to-public-flows: allowopt-out. The CLI proxy logs confirm:The MCPG proxy logs show:
Because
agentSecrecy=[]andagentIntegrity=[none:all]— no repo-visibility constraint was applied.Desired Behavior
reposin frontmatterreposvalue"public"— block private repos"all""all"— user explicitly opted in"public""public"["org/*", "org/repo"]["org/*", "org/repo"]— pattern limits scope"all"— private repos can read own + other private reposprivate-to-public-flows: allowreposresolves to (forcePublicRepos=false disables extra checks)Key principle: When no guard policy is explicitly configured, the CLI proxy should match the MCP Gateway's behavior — use the repo visibility as determined at runtime by
determine-automatic-lockdown.Step Ordering (Critical Constraint)
The agent job steps execute in this order:
The CLI proxy starts after
determine-automatic-lockdown, so it can reference its step outputs. The code comment about "proxy starts before parse-guard-vars" refers to the fact thatblocked-users/trusted-users/approval-labelsare not available — butreposandvisibilityARE available.Implementation Plan
1.
actions/setup/js/determine_automatic_lockdown.cjsAdd a new step output
reposthat reflects the default repos scope based on repo visibility:GH_AW_GITHUB_REPOSenv → outputrepos = "public"GH_AW_GITHUB_REPOSenv → outputrepos = "all"GH_AW_GITHUB_REPOSenv set → outputrepos = <that value>(passthrough)2.
pkg/workflow/compiler_difc_proxy.go—buildStartCliProxyStepYAML()When no explicit guard policy is configured (the
policyJSON == ""branch that falls back todefaultCliProxyPolicyJSON), instead of using the hardcoded default, emit a policy that references the step output:This mirrors how the MCP Gateway already references
$GITHUB_MCP_GUARD_REPOS/$GITHUB_MCP_GUARD_MIN_INTEGRITY.3. When explicit
reposIS configuredNo change needed —
getDIFCProxyPolicyJSON()already emits the user's explicit value. The fix only applies to the fallback/default case.4.
private-to-public-flows: allowinteractionWhen
private-to-public-flows: allowis set, the compiler already emitsforcePublicRepos: falsein the gateway config. For the CLI proxy, this means thereposvalue should remain whatever the user configured (or"all"if not configured). Thedetermine-automatic-lockdownstep should detect this opt-out and outputrepos = "all"accordingly.Open question: Does
determine_automatic_lockdown.cjsreceive enough context to know aboutprivate-to-public-flows? If not, the compiler could pass it as an env var (e.g.,GH_AW_PRIVATE_TO_PUBLIC_FLOWS: "allow").5. Tests
compiler_difc_proxy_test.go— verify that when no guard policy is configured, the emittedCLI_PROXY_POLICYreferences the step output instead of the hardcoded defaultdetermine_automatic_lockdown.test.cjs— verify the newreposoutput for public/private repossmoke-sink-visibility-blockedworkflow ingh-aw-firewallwill serve as the integration testFiles to Change
actions/setup/js/determine_automatic_lockdown.cjsreposoutput based on visibilityactions/setup/js/determine_automatic_lockdown.test.cjspkg/workflow/compiler_difc_proxy.gopkg/workflow/compiler_difc_proxy_test.goRelated
repos: "public"edit: https://github.com/github/gh-aw-firewall/actions/runs/29219603161 (after gh-aw-firewall PR #6164)