For AI agents: A markdown version of this page is available at https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/cicd/github/overprovisioned_secrets.md. A documentation index is available at /llms.txt.
This product is not supported for your selected Datadog site. ().

Metadata

Id: b2c3d4e5-f6a7-48b9-c0d1-e2f3a4b5c6d7

Cloud Provider: GitHub

Platform: CICD

Severity: Medium

Category: Access Control

Learn More

Description

Referencing the entire GitHub Actions secrets context or using dynamic/non-literal secret indexing exposes all repository secrets to the workflow runner. If a workflow or runner is compromised, an attacker could read every secret instead of only the ones required by the job.

This rule flags expression patterns that serialize or expand the secrets object—specifically calls to toJSON(secrets) and context accesses like secrets[<non-literal>] where the index is not a literal string or number. Reference required secrets explicitly by literal property names, such as secrets.MY_SECRET. Expressions that call toJSON(secrets) or use non-literal secret indices will be flagged.

Secure usage example:

env:
  MY_TOKEN: ${{ secrets.MY_TOKEN }}
steps:
  - name: Use token
    run: echo "${{ secrets.MY_TOKEN }}"

Compliant Code Examples

name: Proper Secret Usage
on: push

jobs:
  deploy:
    runs-on: ubuntu-latest
    # Proper: individual secret in job-level env
    env:
      DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
      API_KEY: ${{ secrets.API_KEY }}
    # Proper: individual secret check in job-level if
    if: ${{ secrets.DEPLOY_TOKEN != '' }}
    steps:
      # Proper: individual secrets in step run block
      - name: Deploy with specific secret
        run: |
          curl -H "Authorization: Bearer ${{ secrets.DEPLOY_TOKEN }}" \
               -H "API-Key: ${{ secrets.API_KEY }}" \
               https://api.example.com/deploy

      # Proper: individual secret in step-level if
      - name: Conditional deployment
        if: ${{ secrets.PRODUCTION_KEY != '' }}
        run: echo "Deploying to production"

      # Proper: individual secrets in step env block
      - name: Step with environment variables
        env:
          DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
          SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
        run: echo "Configured environment"

      # Proper: individual secret in step with block
      - name: Use action with secret
        uses: some/action@v1
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          api_key: ${{ secrets.API_KEY }}

      # Proper: matrix with literal values (not dynamic secret indexing)
      - name: Deploy to environment
        run: echo "Deploying with ${{ secrets.DEPLOY_TOKEN }}"

      # Proper: toJSON on non-secret contexts
      - name: Use toJSON on env
        run: echo '${{ toJSON(env) }}'

      # Proper: toJSON on github context
      - name: Use toJSON on github context
        run: echo '${{ toJSON(github.event) }}'

Non-Compliant Code Examples

name: Overprovisioned Secrets
on: push

jobs:
  deploy:
    runs-on: ubuntu-latest
    # toJSON(secrets) in job-level env block
    env:
      ALL_SECRETS: ${{ toJSON(secrets) }}
    # toJSON(secrets) in job-level if condition
    if: ${{ toJSON(secrets) != '{}' }}
    steps:
      # toJSON(secrets) in step run block
      - name: Export all secrets
        run: echo '${{ toJSON(secrets) }}'

      # Dynamic secret indexing in step run block
      - name: Dynamic secret access
        run: |
          SECRET_NAME="DEPLOY_TOKEN_${{ matrix.env }}"
          echo ${{ secrets[format('DEPLOY_TOKEN_{0}', matrix.env)] }}

      # toJSON(secrets) in step-level if condition
      - name: Check secrets with toJSON
        if: ${{ toJSON(secrets) != '{}' }}
        run: echo "Has secrets"

      # Dynamic secret indexing in step-level if condition
      - name: Check dynamic secret
        if: ${{ secrets[format('KEY_{0}', github.event.inputs.env)] != '' }}
        run: echo "Secret exists"

      # toJSON(secrets) in step env block
      - name: Step with env secrets
        env:
          SECRETS_JSON: ${{ toJSON(secrets) }}
        run: echo "Processing secrets"

      # Dynamic indexing in step env block
      - name: Step with dynamic env
        env:
          DYNAMIC_SECRET: ${{ secrets[github.event.inputs.secret_name] }}
        run: echo "Using dynamic secret"

      # toJSON(secrets) in step with block
      - name: Action with toJSON secrets
        uses: some/action@v1
        with:
          config: ${{ toJSON(secrets) }}

      # Dynamic indexing in step with block
      - name: Action with dynamic secret
        uses: another/action@v1
        with:
          token: ${{ secrets[matrix.secret_key] }}

  another-job:
    runs-on: ubuntu-latest
    # Dynamic indexing in job-level env block
    env:
      TOKEN: ${{ secrets[format('TOKEN_{0}', github.ref_name)] }}
    # Dynamic indexing in job-level if condition
    if: ${{ secrets[github.event.inputs.key] != '' }}
    steps:
      - run: echo "Running"
name: dogfood-overprovisioned-secrets-noncompliant-workflow-env
on:
  workflow_dispatch: {}

env:
  ALL_SECRETS: ${{ toJSON(secrets) }}
  DYNAMIC_SECRET: ${{ secrets[matrix.secret_key] }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: echo "inherits env; entire secrets JSON is available to the job"