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.
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.
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}'
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.
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
Knowledge check
Why is the GitHub-documented dummy token appropriate for the live block test?
GitHub publishes it specifically as a non-authenticating training value that secret scanning recognizes. It exercises the control without creating a usable credential.
Why cancel the blocked commit instead of choosing “used in tests”?
The learning objective is prevention. Bypass would weaken the simplest safe outcome and create exception semantics that are unnecessary for this test.
Why use hide_secret=true when listing
alerts?
It lets automation inspect state/type/validity/resolution without copying a detected credential into terminals, logs, transcripts, or CI artifacts.
Why is the custom-pattern live lab optional?
Current custom-pattern creation requires an eligible organization-owned repository with Team/Enterprise and Secret Protection; the course cannot require that paid/admin context.
What is the first step for the simulated active credential?
Revoke/rotate at the issuer, before repository cleanup or history rewrite.
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.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.