Secrets are sensitive values a pipeline needs — API keys, tokens, database passwords, signing credentials — stored encrypted in the CI system and injected into jobs as environment variables at run time, never committed to the repository.
Hard-coding credentials in code or YAML is a serious, common security failure; anyone with repo read access (and anyone who ever cloned it) gets the secret, and it lives forever in git history. CI systems provide an encrypted secret store (repository/organization/environment scoped) that injects values as masked environment variables — masked meaning the platform redacts them from logs if accidentally printed. Environments (dev/staging/prod) let you scope different secret values and add protection rules like required approvals before a job can access production secrets. Good practice: least privilege (a token scoped to only what the job needs), rotation, environment scoping, and never echoing secrets. For test environments specifically, prefer dedicated test credentials and test data, never production secrets, so a leak or a rogue test can't touch real systems.
An API key was pasted directly into a workflow file. A security scan flags it; the team rotates the key immediately (since it's now in git history forever), moves the new key into the encrypted secrets store as ${{ secrets.API_KEY }}, and scopes it to the staging environment so it can never touch production.
jobs:
api-tests:
runs-on: ubuntu-latest
environment: staging # scopes which secrets are available
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run test:api
env:
API_BASE_URL: https://staging.example.com
API_TOKEN: ${{ secrets.STAGING_API_TOKEN }} # injected, masked in logs
# NEVER: API_TOKEN: 'sk_live_9f8a...' hard-coded in YAML or sourceScenario: A code review turns up a database password committed in a test config file six months ago. The author says 'it's only the staging DB, and I'll just delete the line'. Why is deleting the line insufficient, and what's the correct remediation?