Skip to content

Detect workflows using long-lived cloud keys instead of OIDC #399

Description

@thomasboni

Problem

A workflow that authenticates to a cloud provider with static credentials stored as secrets (AWS access keys, GCP service account JSON, Azure client secrets) carries a long-lived credential into CI. If any step of the job is compromised (malicious action, script injection, leaked log), the attacker gets keys that keep working after the run ends. CI-native OIDC removes that class entirely: the workflow mints a short-lived token per run, nothing to steal that outlives the job.

Suggested by @samrocketman in the CDF Slack: "Credentials must be ephemeral", with STS at deploy time as the example. The static-key pattern is detectable from the workflow YAML.

Proposal

New control: flag jobs that pass long-lived cloud credentials where an OIDC path exists.

Detectable patterns, first pass:

  • aws-actions/configure-aws-credentials called with aws-access-key-id/aws-secret-access-key inputs instead of role-to-assume (the action supports both, the inputs difference is the signal)
  • google-github-actions/auth with credentials_json instead of workload_identity_provider
  • azure/login with creds (secret JSON) instead of federated client-id/tenant-id/subscription-id
  • generic env pattern: AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY (and equivalents) bound from secrets.* in env: of a job or step

Remediation text: point to the OIDC setup of the matched provider, mention permissions: id-token: write. The fix is well documented by every provider, this control mostly has to name the right doc.

Notes

  • severity should reflect what the job does: static keys on a deploy/publish job is worse than on a read-only job. Candidate input for the attack-pattern work (Correlate findings into contextual attack patterns #377), a long-lived key inside a job reachable through a weak trigger is exactly a toxic combination
  • keep an allowlist escape hatch: some providers/on-prem targets have no OIDC support, users need a way to accept a finding permanently

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions