Skip to content

SEC002 — hardcoded-secret

Severity: blocker · Category: Security

What it checks

Flags a with: or env: value on any step where the key looks like a credential (matches password, secret, token, api[_-]?key, or access[_-]?key, case-insensitive) and the value is a literal string rather than a ${{ ... }} expression referencing secrets.* or vars.*.

Why it matters

Anything committed to a workflow file is visible to anyone with read access to the repository — including its full git history, long after the line is deleted. GitHub's encrypted secrets exist specifically so credentials never have to appear as plaintext in a file at all.

Examples

Flagged — a literal string sitting where a secret reference belongs:

steps:
  - name: Publish package
    uses: some-registry/publish-action@f1a2b3c4d5e6f7890123456789abcdef0123456
    with:
      api_key: sk_live_51H8x9K2mN7qR3vW

Fixed — referencing an encrypted repository secret instead:

steps:
  - name: Publish package
    uses: some-registry/publish-action@f1a2b3c4d5e6f7890123456789abcdef0123456
    with:
      api_key: ${{ secrets.REGISTRY_API_KEY }}

If the credential was ever committed for real (not just in a test fixture), rotating it is the only real fix — deleting the line doesn't remove it from history.

Suppressing

with:
  api_key: sk_test_not_a_real_key # vlotpipe: ignore[SEC002]

Reasonable for genuinely fake test/example credentials (as long as they're not accidentally real) — otherwise this should never be suppressed without rotating the leaked value first.