You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
sec(ci): sibling deploy and sanity workflows grant id-token write to ungated jobs #140
A structural sweep (parsing each workflow's YAML for id-token inheritance versus environment: bindings, rather than grepping) found jobs that hold id-token: write with noenvironment: binding:
All five declare permissions: id-token: write at workflow level, which hands it to every job whether or not that job authenticates.
Note deploy-gcp.yml and deploy-azure.yml are worse than deploy-aws-lambda.yml was: their build-and-deploy jobs actually assume cloud credentials and have no environment binding, so their OIDC subject is repo:<org/repo>:ref:refs/heads/main — the subject terraform/environments/aws/ci-cd-permissions/role.tf:29 accepts unconditionally.
Why this is not p0
The only untrusted value reaching a run: block in any of these is inputs.environment, and it is not injectable:
on workflow_dispatch it is type: choice with options [dev, staging, prod], which GitHub validates server-side;
the workflow_call input is type: string (unvalidated), but the only caller is deploy-all.yml, whose own environment input is also type: choice.
So the escalation path exists structurally but has no injection to trigger it. deploy-aws-lambda.yml was the only one of the four with an attacker-settable value (github.event.release.tag_name) — that is LeanerCloud/cloud-commitments-cli#1649.
github.actor also reaches run: blocks in all four deploy workflows. GitHub usernames are [A-Za-z0-9-], so those are not injectable; listed here only so a reader does not re-flag them.
Remove id-token: write from workflow level; grant it per job, only to jobs that actually call configure-aws-credentials / azure/login / google-github-actions/auth.
Give those jobs an environment: binding so their OIDC subject is environment:<name> rather than ref:refs/heads/main.
Route inputs.environment through env: with a dev|staging|prod allowlist, since workflow_call types it as a free-form string.
Once all of these are gated, the repo:<org/repo>:ref:refs/heads/main subject can finally be dropped from the trust policy — currently it cannot, because these jobs plus cleanup-staging.yml and destroy-fargate-dev.yml (LeanerCloud/cloud-commitments-cli#1591) depend on it.
Caveat: per LeanerCloud/cloud-commitments-cli#1648, no Environment in this repo currently has protection rules, so an environment: binding buys the correct OIDC subject but not yet an approval gate. Steps 1-4 are still worth doing — they are the precondition for the gate to mean anything.
Follow-up from LeanerCloud/cloud-commitments-cli#1649 / PR LeanerCloud/cloud-commitments-cli#1657. Same structural shape, but — unlike LeanerCloud/cloud-commitments-cli#1649 — no attacker-settable value reaches a
run:block today, so this is hardening rather than a live vulnerability. Labelled accordingly.What
A structural sweep (parsing each workflow's YAML for
id-tokeninheritance versusenvironment:bindings, rather than grepping) found jobs that holdid-token: writewith noenvironment:binding:id-token: writedeploy-aws-fargate.ymlprepare,test-deployment,summarydeploy-gcp.ymlprepare,build-and-deploy,test-deployment,summarydeploy-azure.ymlprepare,build-and-deploy,test-deployment,summaryaws_sanity.ymlsanityazure_sanity.ymlsanityAll five declare
permissions: id-token: writeat workflow level, which hands it to every job whether or not that job authenticates.Note
deploy-gcp.ymlanddeploy-azure.ymlare worse thandeploy-aws-lambda.ymlwas: theirbuild-and-deployjobs actually assume cloud credentials and have no environment binding, so their OIDC subject isrepo:<org/repo>:ref:refs/heads/main— the subjectterraform/environments/aws/ci-cd-permissions/role.tf:29accepts unconditionally.Why this is not p0
The only untrusted value reaching a
run:block in any of these isinputs.environment, and it is not injectable:workflow_dispatchit istype: choicewith options[dev, staging, prod], which GitHub validates server-side;workflow_callinput istype: string(unvalidated), but the only caller isdeploy-all.yml, whose ownenvironmentinput is alsotype: choice.So the escalation path exists structurally but has no injection to trigger it.
deploy-aws-lambda.ymlwas the only one of the four with an attacker-settable value (github.event.release.tag_name) — that is LeanerCloud/cloud-commitments-cli#1649.github.actoralso reachesrun:blocks in all four deploy workflows. GitHub usernames are[A-Za-z0-9-], so those are not injectable; listed here only so a reader does not re-flag them.Fix
Apply the same split PR LeanerCloud/cloud-commitments-cli#1657 applied to
deploy-aws-lambda.yml:id-token: writefrom workflow level; grant it per job, only to jobs that actually callconfigure-aws-credentials/azure/login/google-github-actions/auth.environment:binding so their OIDC subject isenvironment:<name>rather thanref:refs/heads/main.inputs.environmentthroughenv:with adev|staging|prodallowlist, sinceworkflow_calltypes it as a free-form string.deploy-aws-fargate.yml,deploy-gcp.ymlanddeploy-azure.ymlall declareoutputs: environment: ..., an output named environment that reads like a binding and gates nothing. PR fix(ci): stop interpolating the release tag into a run block cloud-commitments-cli#1657 renamed it totarget_environment; do the same here.Once all of these are gated, the
repo:<org/repo>:ref:refs/heads/mainsubject can finally be dropped from the trust policy — currently it cannot, because these jobs pluscleanup-staging.ymlanddestroy-fargate-dev.yml(LeanerCloud/cloud-commitments-cli#1591) depend on it.Caveat: per LeanerCloud/cloud-commitments-cli#1648, no Environment in this repo currently has protection rules, so an
environment:binding buys the correct OIDC subject but not yet an approval gate. Steps 1-4 are still worth doing — they are the precondition for the gate to mean anything.Related: LeanerCloud/cloud-commitments-cli#1649 / PR LeanerCloud/cloud-commitments-cli#1657, LeanerCloud/cloud-commitments-cli#1542 / PR LeanerCloud/cloud-commitments-cli#1641, LeanerCloud/cloud-commitments-cli#1591, LeanerCloud/cloud-commitments-cli#1648, LeanerCloud/cloud-commitments-cli#1646.