Chapter 20Lesson 04~175 minutes

Secrets, Configuration Variables, GITHUB_TOKEN, Fine-Grained Permissions, and OIDC: Diagnostics, Failure Modes, Security, and Performance

Credential failures are often misdiagnosed as syntax errors. This lesson uses an evidence-first sequence to distinguish missing secrets, restricted events, insufficient permissions, over-broad authority, redaction failures, unsafe checkout persistence, and overly permissive OIDC trust.

DiagnosticsFork safetyMaskingOIDC policyCredential response

Learning objectives

  • Diagnose unavailable secrets separately from insufficient GITHUB_TOKEN permissions.
  • Preserve the original 401/403/log/policy evidence before changing credentials or retrying.
  • Recognize transformed-secret leakage and credential persistence as security incidents rather than cosmetic logging problems.
  • Find over-broad OIDC trust conditions that authorize unintended repositories, refs, environments, or workflows.
  • Apply revoke/rotate-first incident response and least-destructive remediation.

Safety: The broken examples use synthetic values and local policy fixtures. Do not intentionally leak a real secret, broaden a production cloud trust policy, enable privileged fork execution, create a broad PAT, or persist a credential merely to reproduce a failure.

1. Diagnostic sequence: preserve first, mutate last

  1. Preserve evidence: run ID/attempt, event, actor, head SHA/ref, job name, HTTP status, selected log lines, environment name, and relevant policy snapshot.
  2. Identify scope: repository/org/account, event trust class, job, token type, secret/variable scope, environment, provider role.
  3. Inspect controls: workflow/job permissions, repository Actions defaults, fork/Dependabot restrictions, environment gates, provider trust conditions.
  4. Classify failure: missing value, authenticated-but-forbidden, expired/revoked credential, wrong audience/subject, or code-level bug.
  5. Choose least destructive correction: add one missing read permission, split privileged work, narrow/fix trust policy, or move data to correct scope.
  6. Verify independently: repeat the exact operation and inspect hosted state; do not treat “workflow became green” as sufficient evidence.

2. Secret unavailable on fork or Dependabot event

Symptom: a command reports a missing API key only for external pull requests or Dependabot. Inspect github.event_name and github.actor; do not print the secret context.

- name: Require credential only in trusted deployment path
  if: github.event_name == 'workflow_dispatch'
  env:
    SERVICE_TOKEN: ${{ secrets.SERVICE_TOKEN }}
  run: |
    set -euo pipefail
    test -n "$SERVICE_TOKEN"
    ./deploy-safe-wrapper

The fix is architectural: untrusted validation should not require production authority. If Dependabot legitimately needs a dependency-registry credential, configure the correct Dependabot secret class rather than assuming Actions secrets are available.

3. Authenticated but denied: interpret 403 before expanding permissions

A 403 can mean the token is valid but lacks a required permission, repository/org policy denies the operation, or the event reduced effective privileges. Preserve the endpoint and response headers/body. GitHub APIs may expose accepted-permission hints for some credential types.

# Read-only metadata request: expected 200 in the lab.
gh api -H "X-GitHub-Api-Version: 2026-03-10"   "repos/$GITHUB_REPOSITORY"

# A mutation under issues: read should remain denied.
# Do not repair this by granting unrelated write permissions.

Add the narrow permission only if the operation is an intentional responsibility of that job. If the job should not mutate state, the 403 is a successful security control, not an error to eliminate.

4. Excessive permissions are a latent failure

A workflow can be green while dangerously overprivileged. Review authority as part of diagnostics, especially after someone “fixed” a 403. A build job that merely compiles code should not gain issue, package, deployment, or repository-content write access because a later reporting job needs it.

Use explicit empty/default permissions and opt in job by job. Separate the code that processes untrusted input from the code that receives write authority.

5. Transformed secret bypasses masking

GitHub documents that automatic redaction is not guaranteed because a secret may be URL-encoded, base64-encoded, sliced, JSON-embedded, or otherwise transformed. Therefore a log line containing a transformed credential is still a credential leak even if the original plaintext never appears.

Incident response: revoke/rotate the credential first. Then remove or restrict access to logs/artifacts where possible, investigate downstream use, and correct the workflow. Editing source or history does not make a previously exposed credential safe.

6. Intentionally broken OIDC trust policy

This local policy evaluator is deliberately too broad: it accepts any branch in the expected repository. The failure is a policy decision, not a JWT parser bug.

cat > claims.json <<'JSON'
{
  "iss": "https://token.actions.githubusercontent.com",
  "aud": "https://cloud.example.invalid",
  "repository_id": "456789",
  "repository_owner_id": "123456",
  "ref": "refs/heads/feature/untrusted",
  "environment": "production"
}
JSON

# BROKEN: repository identity matches, but ref is not constrained.
jq -e '
  .iss == "https://token.actions.githubusercontent.com" and
  .aud == "https://cloud.example.invalid" and
  .repository_id == "456789" and
  .repository_owner_id == "123456" and
  .environment == "production"
' claims.json

# REPAIRED: exact approved ref is part of the policy intent.
jq -e '
  .iss == "https://token.actions.githubusercontent.com" and
  .aud == "https://cloud.example.invalid" and
  .repository_id == "456789" and
  .repository_owner_id == "123456" and
  .ref == "refs/heads/main" and
  .environment == "production"
' claims.json

The first expression exits 0 even though the feature branch should not deploy; that is the broken-policy evidence. The repaired expression exits non-zero for the same claims. A real provider must additionally verify signature, token lifetime, issuer, and its exact supported claim syntax.

7. Tokens in artifacts, caches, logs, and checkout credentials

Artifacts and caches extend data lifetime beyond a shell process; logs can be downloaded later; checkout may configure Git credentials for subsequent steps. Treat every output path as a potential credential exfiltration route.

  • Never include .env, credential stores, token response files, or whole workspaces in artifact/cache globs.
  • Prefer allowlisted artifact paths, as Chapter 17 practiced.
  • Do not cache authentication directories.
  • If authenticated Git is unnecessary, set checkout credential persistence off.
  • Do not serialize a live OIDC JWT or provider credential into an artifact “for debugging.”

8. Why blind retry is wrong for 401/403

Retry helps transient failures such as a rate-limited or temporarily unavailable service. Authentication/authorization failures are usually deterministic. Blindly retrying a mutation with the same invalid or over-scoped credential adds noise and can complicate incident evidence. First inspect status, endpoint, token type, event, and permission policy.

9. Credential incident runbook

Observation Immediate action Correction Verification
Real secret appears in log/artifact Revoke/rotate immediately. Remove logging path; narrow permission/lifetime; purge exposed evidence where supported. Old credential fails; replacement works only in intended path.
Fork/Dependabot secret missing Do not expose base secret. Separate trusted/untrusted jobs or use correct secret class. Untrusted job passes without privileged secret.
403 on intended GitHub write Preserve response. Add only required job permission after confirming responsibility. Mutation succeeds; unrelated jobs still lack write.
OIDC wrong branch accepted Disable/stop role use if real. Tighten subject/claim/provider policy. Synthetic wrong-ref case fails; approved case succeeds.
Credential persisted unnecessarily Rotate if exposure plausible. Disable persistence / isolate step / shorten lifetime. Later untrusted step cannot access credential material.

Knowledge checks

A job is green but has repository-content write permission it never uses. Is there a failure?

A secret is unavailable on a Dependabot PR. What should you inspect before changing YAML?

A 403 appears after a valid API authentication. What does it prove?

Why must a transformed leaked secret be rotated even if GitHub masked the original form elsewhere?

What is wrong with an OIDC policy that checks repository ID and environment but accepts every ref when only main should deploy?

Lesson summary

Credential diagnostics is evidence-driven authorization analysis. Preserve the run and response, identify the identity and event boundary, inspect the smallest policy that should authorize the operation, and repair that policy without widening unrelated jobs. For leaks, rotate first. For OIDC, distrust broad matches. Lesson 5 integrates these controls into one checkpoint.

Next lesson

Checkpoint Lab — Secrets, Configuration Variables, GITHUB_TOKEN, Fine-Grained Permissions, and OIDC

Further reading

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.