Skip to main content

What the check does

Flags a pipeline that authenticates to AWS, GCP, or Azure with a long-lived static credential. Three signals:
  1. A cloud login action configured with static inputsaws-actions/configure-aws-credentials with aws-access-key-id:, google-github-actions/auth with credentials_json:, azure/login with creds: or client-secret:.
  2. A static-credential environment variable or CI variable — a closed set of names (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, GCP_SA_KEY, GOOGLE_CREDENTIALS, AZURE_CREDENTIALS, AZURE_CLIENT_SECRET, ARM_CLIENT_SECRET, and a few siblings) — or a literal AKIA… access-key id in a value.
  3. A cloud CLI invoked in its static-credential formaws configure set aws_access_key_id, gcloud auth activate-service-account, az login --service-principal … -p ….
Signals 1 and 2 are name-exact and carry HIGH confidence. Signal 3 matches a command line and carries MEDIUM: a script can name a command it does not run.

Why it matters

This is not the “you leaked a key” rule — that is CICD-SEC-6 hardcoded credentials, and it fires on the literal value. This rule fires on a credential stored correctly in secrets, because that is still a credential:
  • it outlives the job that used it,
  • every workflow in the repository can read it,
  • and rotating it is a manual task somebody has to remember.
All three major clouds will instead trade the runner’s OIDC token for a short-lived credential scoped to the repository and the ref. That removes the stored credential rather than protecting it — a different answer to the same finding.

Safe alternative

Grant the job an OIDC token and let the provider mint short-lived credentials.
Then delete the stored credential — leaving it in place keeps the blast radius the migration was meant to remove.

GitLab CI

The same rule ID fires on .gitlab-ci.yml, driven by variables: blocks and script: lines rather than actions. The token comes from an id_tokens: block instead of a permissions: grant:

What does not fire

  • A federated login step (role-to-assume, workload_identity_provider, client-id + tenant-id), on either platform.
  • AWS_SESSION_TOKEN — that is the short-lived half of the pair, and flagging it would advise migrating away from the thing you migrated to.
  • AWS_REGION and other non-credential configuration.
  • Commands that merely use an existing identity (aws s3 sync, gcloud storage cp).
A job that federates to one cloud and keeps a stored key for another is still flagged for the second — the credential still on the shelf is the one worth naming.

Auto-fix

Partial, and comment-only. The replacement needs a role ARN, a workload-identity provider, or a federated app registration — none of which exist in the file and none of which can be guessed. A placeholder would parse and then fail at deploy time, which is worse than the credential it replaced. So the fix annotates the exact line with the provider’s replacement and leaves the pipeline working:
Running the fix twice does not stack a second comment.