Skip to content

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

Closed

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.

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