Chapter 24Lesson 02~205 minutes

Secret Scanning, Push Protection, Custom Patterns, Bypass Controls, and Incident Response: Guided Hands-On Workflow and Core Operations

You will create a disposable public repository, prove current Secret Protection state before changing it, enable repository push protection where available, and trigger a real GitHub push-protection block with GitHub’s published dummy token. The token cannot authenticate, the blocked change is discarded, and all later alert/custom-pattern work uses redacted or synthetic fixtures.

Public labDummy secretREST inspectionCustom-pattern fixtureRevocation first

Learning objectives

  • Create and inspect a disposable public repository without placing any real credential into Git, logs, prompts, or GitHub content.
  • Enable/verify public Secret Protection and repository push protection, while distinguishing that state from account-level user push protection.
  • Trigger and interpret a real push-protection block using GitHub’s published non-authenticating dummy secret, then discard the write rather than bypass it.
  • Inspect secret-scanning alert metadata through REST with literal values hidden and interpret an empty result safely.
  • Model custom-pattern dry-run and incident triage with synthetic fixtures when organization-only features are unavailable.
Safety boundary: this lab uses only GitHub’s documented training token and synthetic strings. Never paste a real API key, PAT, private key, cloud credential, registry password, recovery code, or organization secret into this lab. The training token is intended to trigger push protection but cannot authenticate.

1. Preflight: create an intentionally disposable public repository

Use a personal public repository so the mandatory path remains GitHub Free compatible. You need permission to administer this disposable repository because enabling repository security settings is an administrative change. The repository contains no production code, no collaborators, no secrets, and no automation that can reach external systems.

gh --version
gh auth status

OWNER=$(gh api -H "X-GitHub-Api-Version: 2026-03-10" user --jq .login)
REPO="$OWNER/gh-secret-scanning-lab"

gh repo create "$REPO" --public --add-readme --description "Disposable Chapter 24 secret-scanning lab"
gh repo view "$REPO" --json nameWithOwner,visibility,defaultBranchRef,url

gh api -H "X-GitHub-Api-Version: 2026-03-10" "repos/$REPO" \
  --jq '{id,full_name,visibility,security_and_analysis}'

Record the repository ID, default branch, visibility, and the security_and_analysis object. This is your before-state. If Secret Protection fields are absent or different, do not guess; use the current repository settings UI and official docs to distinguish a product/permission difference from a failed command.

2. Inspect account protection, then enable repository protection

First inspect your personal GitHub settings under account code-security settings. Push protection for yourself should be enabled by default for public repositories. This account-level protection can block your own public pushes even if repository-level protection is not enabled.

Next, in the disposable repository’s current Settings → Advanced Security area, enable Secret Protection and repository Push protection if they are not already enabled. Public repositories can use these features without purchasing Secret Protection. This changes a GitHub repository security setting; it does not change Git history or create a credential.

# Re-read state after the UI change.
gh api -H "X-GitHub-Api-Version: 2026-03-10" "repos/$REPO" \
  --jq '.security_and_analysis'

# Read alerts without returning literal secret values.
gh api --paginate -H "X-GitHub-Api-Version: 2026-03-10" \
  "repos/$REPO/secret-scanning/alerts?hide_secret=true&per_page=100" \
  --jq '.[] | {number,state,secret_type,validity,resolution}'

Expected observation: the public repository exposes Secret Protection/secret-scanning state and an empty or previously known alert list. Enabling push protection does not retroactively create a fake alert; it establishes the write-path control for future pushes.

3. Trigger a real block with GitHub’s official dummy token

GitHub publishes a dedicated dummy token for learning how push protection works. It is deliberately shaped so secret scanning recognizes it but it cannot authenticate. Use the GitHub web editor so the candidate value never becomes a local Git commit and never reaches repository history.

GitHub Docs dummy token (training only; cannot authenticate):
secret_scanning_EXAMPLE_DUMMY_TOKEN_NOT_REAL_12345
  • Open the disposable repository and choose to create or edit a harmless file such as training/push-protection-demo.txt.
  • Paste the exact dummy token shown above.
  • Choose the current UI control to commit/save the change. GitHub should stop the operation and identify a GitHub Secret Scanning secret.
  • Do not choose a bypass reason. Select Cancel, discard the unsaved change, and confirm the file was never committed.
  • Refresh repository history and verify the default branch SHA did not change because of the canceled attempt.
# Independent proof that the blocked experiment created no commit/file.
DEFAULT_BRANCH=$(gh repo view "$REPO" --json defaultBranchRef --jq .defaultBranchRef.name)
BEFORE_SHA=$(gh api -H "X-GitHub-Api-Version: 2026-03-10" "repos/$REPO/commits/$DEFAULT_BRANCH" --jq .sha)
printf 'Current default-branch SHA: %s\n' "$BEFORE_SHA"

gh api -H "X-GitHub-Api-Version: 2026-03-10" "repos/$REPO/contents/training/push-protection-demo.txt" >/dev/null 2>&1
STATUS=$?
if [ "$STATUS" -ne 0 ]; then
  echo "Expected: training file is absent because the blocked commit was canceled."
fi

The important state transition is a non-transition: GitHub evaluated candidate content and refused to create repository state. This is precisely why prevention is cheaper than incident response.

4. Inspect alert metadata without turning the alert API into a leak

Because you canceled the blocked write, the training token should not appear in Git history. Repository push protection also did not need a bypass, so there should be no bypass alert for this exercise. Re-read the alert list with hide_secret=true and record the HTTP/JSON state.

gh api --paginate \
  -H "Accept: application/vnd.github+json" \
  -H "X-GitHub-Api-Version: 2026-03-10" \
  "repos/$REPO/secret-scanning/alerts?hide_secret=true&per_page=100" \
  --jq '.[] | {number,state,secret_type,secret_type_display_name,validity,resolution,created_at}'
Interpretation: zero alerts after a canceled push is expected. It means there is no persisted alert in this repository for that blocked/canceled exercise. It does not mean secret scanning is unnecessary or that no secret exists outside GitHub.

5. Custom-pattern exercise: simulate first; mutate only when entitled

Current custom-pattern creation is an organization-owned Team/Enterprise + Secret Protection feature. It is therefore not mandatory. The free path tests the pattern contract locally using values that cannot authenticate to anything.

import re
pattern = re.compile(r"\bACME_TEST_[A-Z0-9]{12}\b")
samples = {
    "expected_match": "ACME_TEST_ABC123DEF456",
    "too_short": "ACME_TEST_ABC123",
    "ordinary_code": "ACME_TEST_MODE = true",
}
for name, value in samples.items():
    print(name, bool(pattern.search(value)))

If you already administer an eligible disposable organization repository, the optional live extension is: define a custom pattern with that expression, Save and dry run, inspect false positives, refine it, then publish only when the result set is acceptable. Current GitHub supports dry runs before publishing and warns that broad custom patterns can disrupt contributors when push protection is enabled.

Do not create an organization or buy a plan for this chapter. The local fixture teaches the engineering contract: expected positives, expected negatives, false-positive review, and staged publication.

6. Triage a simulated alert: revoke first even when the value is redacted

The following JSON is a training incident record, not a GitHub API response and not a real secret. It contains enough metadata to practice ownership and sequencing without exposing credential material.

{
  "incident": "INC-CH24-001",
  "secret_type": "fictional_acme_api_key",
  "secret_value": "<redacted-never-recorded>",
  "owner": "payments-platform",
  "locations": [
    "commit:abc123:config/example.txt:7",
    "issue_comment:42"
  ],
  "issuer_status": "active-simulated",
  "known_consumers": ["staging-deployer", "nightly-job"],
  "evidence": []
}

Process it in this order: page the credential owner; mark the fictional issuer action as revoked-simulated; check simulated access logs for unexpected use; migrate both consumers to a replacement identifier; remove the exposed current content/issue comment; then decide whether old Git history needs rewriting. The location list deliberately includes an issue comment because current secret scanning covers non-Git surfaces too.

Evidence to record Good training observation
Revocation Timestamp + owner + issuer confirmation, never the old secret value
Use assessment Time window and whether suspicious authentication was found
Consumer migration Each legitimate consumer confirmed on replacement credential
Location remediation Commit/current file + issue/comment location handled
History decision Written rationale: rewrite required or not required
Prevention Push protection/pattern/runbook improvement with owner

7. Challenge: choose the correct GitHub or provider surface

Choose the smallest correct control for each situation before looking at the answer: (A) a public contributor is about to push a recognized provider token; (B) an old credential is found in an issue comment; (C) the company has an internal token format GitHub does not recognize; (D) a detected value is a legitimate non-secret test marker; (E) a real production token was committed three minutes ago.

Situation Primary surface/control
A Push protection; remove the value and retry
B Secret-scanning location + issue/comment remediation + provider revocation if real
C Custom-pattern design/dry run in an eligible organization, or external/pre-commit detection until available
D Documented bypass/false-positive process; delegated review where available
E Credential issuer revocation/rotation first, then scope/use/content/history decisions

8. Verification and cleanup

Confirm the training token was never committed, no real secret was ever used, the alert API was queried with literal-value hiding, and any optional custom-pattern work occurred only in an eligible disposable organization. If you enabled repository Secret Protection specifically for this lab, you may leave it enabled; it is a protective control. Archive the disposable repository after reviewing the evidence rather than deleting it impulsively.

gh api -H "X-GitHub-Api-Version: 2026-03-10" "repos/$REPO" \
  --jq '{full_name,visibility,archived,security_and_analysis}'

gh api --paginate -H "X-GitHub-Api-Version: 2026-03-10" \
  "repos/$REPO/secret-scanning/alerts?hide_secret=true&per_page=100" \
  --jq '.[] | {number,state,secret_type,validity,resolution}'

# Reversible cleanup after reviewing the repository.
gh repo archive "$REPO" --yes
Do not disable protection just to make the lab “clean,” and do not create/delete a real credential as a demonstration. Cleanup should reduce risk, not erase the evidence that prevention worked.

Knowledge check

Why is the GitHub-documented dummy token appropriate for the live block test?

Why cancel the blocked commit instead of choosing “used in tests”?

Why use hide_secret=true when listing alerts?

Why is the custom-pattern live lab optional?

What is the first step for the simulated active credential?

Summary

You proved the prevention boundary with a real GitHub block but no real credential. You also separated repository security state, account protection, alert metadata, organization-only custom detection, and provider-side incident containment.

Next you will choose policy: when to block, when a bypass is defensible, how to scale protection across repositories, and when rewriting history produces enough security benefit to justify its operational cost.

Next lesson

Secret Scanning, Push Protection, Custom Patterns, Bypass Controls, and Incident Response: Configuration, Design Choices, and Tradeoffs

Official references

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.