Skip to content

sec(ci): sibling deploy and sanity workflows grant id-token write to ungated jobs #140

Description

@cristim

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-token inheritance versus environment: bindings, rather than grepping) found jobs that hold id-token: write with no environment: binding:

workflow ungated jobs holding id-token: write
deploy-aws-fargate.yml prepare, test-deployment, summary
deploy-gcp.yml prepare, build-and-deploy, test-deployment, summary
deploy-azure.yml prepare, build-and-deploy, test-deployment, summary
aws_sanity.yml sanity
azure_sanity.yml sanity

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.

Fix

Apply the same split PR LeanerCloud/cloud-commitments-cli#1657 applied to deploy-aws-lambda.yml:

  1. 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.
  2. Give those jobs an environment: binding so their OIDC subject is environment:<name> rather than ref:refs/heads/main.
  3. Route inputs.environment through env: with a dev|staging|prod allowlist, since workflow_call types it as a free-form string.
  4. Watch for the same reviewer trap sec(ci): deploy-aws-lambda.yml interpolates a release tag name into a run block in an ungated job holding id-token write cloud-commitments-cli#1649 had: deploy-aws-fargate.yml, deploy-gcp.yml and deploy-azure.yml all declare outputs: 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 to target_environment; do the same here.

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.

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions