GitHub Security
Sign commits when the project requires it, keep tokens out of history, and rotate a leaked credential as a documented incident.
Four security boundaries
GitHub Authentication proved who may talk to a host. Git .gitignore kept expected local files outside ordinary staging. Security adds signatures, credential lifecycle, automated least privilege, and incident response without pretending any one control solves all four.
Authentication
An SSH key, helper, or token proves an account may perform a network operation. It does not sign each commit automatically.
Signing
A private key signs exact commit or tag bytes. It does not grant GitHub repository access and does not encrypt content.
Secret storage
Credential helpers, agents, and approved secret stores keep values outside source history and routine logs.
Authorization
Token scopes, repository grants, workflow permissions, and branch rules limit what an authenticated identity may do.
What commit signatures prove
A Git commit id covers its tree, parents, author and committer metadata, message, and optional signature. Signing uses a private key to create evidence tied to those exact bytes. Change the parent, message, timestamp, or tree and the commit id—and signature target—changes.
# Inspect without changing configuration.
git config --show-origin --get gpg.format
git config --show-origin --get user.signingkey
git config --show-origin --get commit.gpgsign
# Inspect the latest commit's signature, if it has one.
git show --show-signature --no-patch HEADPossession and integrity
- The matching private key signed this object.
- The signed bytes have not changed.
- A verifier can map the key to an allowed identity.
- The evidence travels with the commit.
Safety or intent
- The code is correct.
- The author account was uncompromised.
- The change was reviewed.
- The key should still be trusted today.
Git supports OpenPGP, SSH, and X.509 signing formats. Use the format your project documents. This lab shows SSH signing because GitHub Authentication already introduced SSH keys; an organization may require a separate signing key, hardware-backed key, or OpenPGP instead.
Configure signing deliberately
# In a disposable repository, after your SSH signing key exists:
git config --local gpg.format ssh
git config --local user.signingkey ~/.ssh/id_ed25519.pub
git config --local commit.gpgsign true
git commit --allow-empty -m "Record signed release evidence"
git show --show-signature --no-patch HEADTest what you learned
Type the command that enables automatic signing only in the current repository.
--local keeps this practice policy in the disposable repository. Do not turn on global signing until your key, agent, email, and project expectations are known. If the private key is passphrase-protected, the SSH agent should request or cache access; do not remove the passphrase to silence a prompt.
For GitHub to show an SSH-signed commit as Verified, add the public key to your account as a signing key and use an email identity GitHub can associate with the commit. If the same public key is also used for authentication, GitHub requires it to be registered for each purpose. The private key never leaves your machine or approved hardware.
Verify locally and on GitHub
# Sign only this commit, regardless of commit.gpgsign.
git commit -S -m "Document security boundary"
# Verify the object rather than trusting a badge screenshot.
git show --show-signature --no-patch HEADTest what you learned
Type the command that shows the latest commit and verifies its signature without printing a patch.
Local SSH verification needs an allowed-signers file mapping identities to public keys. Configure that file according to your team policy with gpg.ssh.allowedSignersFile; do not commit a private key. Without a trusted mapping, Git can still show that a signature exists but may not label the signer trusted.
GitHub's Verified badge is platform evidence, not the object itself. GitHub checks the signature against keys and identity it knows. A local verifier may trust a different allowed-signers policy. Record the commit SHA and verification output; do not use a cropped badge screenshot as the only evidence.
Unsigned
No cryptographic signature is attached. That may be allowed unless branch rules or release policy require one.
Good signature
The bytes validate against the public key. Trust still depends on how that key was bound to an identity.
Unknown signer
The signature may be mathematically valid while the verifier lacks an approved identity mapping.
Bad signature
Do not merge or publish based on it. Preserve the object and investigate key, content, and tooling.
Treat tokens as expiring credentials
# These commands report account/session metadata, not the token value.
gh auth status
git remote -v
# Do NOT run this for screenshots, logs, or lesson evidence:
# gh auth token
# Do not put a token in a remote URL, command argument, file, or commit message.Prefer GitHub CLI, Git Credential Manager, an SSH agent, or an organization-approved secret store. If a personal access token is required, prefer a fine-grained token when the operation supports it: select only required repositories, grant only required permissions, set a short expiration, and write a note naming the device or automation.
Scope
Read-only when possible. One repository instead of every repository. No administration grant for a clone.
Lifetime
Set an expiry and rotate before it. Delete credentials for retired machines and completed automation.
Storage
Use a helper or secret store. Never a remote URL, source file, shell script, issue, chat, or commit message.
Review
Periodically inspect active tokens, SSH keys, OAuth apps, deploy keys, and organization grants. Remove what you cannot explain.
Constrain Actions credentials
GitHub Actions runs code on GitHub infrastructure. Every workflow should declare the smallest GITHUB_TOKEN permissions it needs. A read-only check usually does not need write access.
name: Read-only check
on: pull_request
permissions:
contents: read
jobs:
inspect:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: git diff --checkStore required values in GitHub Actions secrets or an approved external secret manager, then reference them through the workflow expression syntax. Do not place literal values in YAML. Do not echo secrets. Masking is defense in depth, not permission to transform, archive, or upload a credential.
Untrusted pull requests
Do not expose repository secrets to code from a fork. Treat checkout plus execution as running someone else's program.
Third-party actions
Review source and pin according to policy. An action receives the workspace and any credentials you grant it.
Environment gates
Production credentials may require environment approval. A passing test job should not automatically receive deployment power.
Logs and artifacts
Both can outlive a run and reach more readers. Never use them as secret transport.
Prevent secrets from entering Git
# Use a fake marker only. Never paste a real credential into a search command.
git grep -n 'EXAMPLE_TOKEN_DO_NOT_USE'
git log -S'EXAMPLE_TOKEN_DO_NOT_USE' --all --oneline
# Inspect the exact staged snapshot before committing.
git diff --cached --name-status
git diff --cachedTest what you learned
Using the safe marker only, type the command that lists commits where EXAMPLE_TOKEN_DO_NOT_USE was added or removed.
Practice with the literal fake marker EXAMPLE_TOKEN_DO_NOT_USE. It grants nothing. Never paste a real credential into git grep, git log -S, terminal history, screenshots, or lesson answers. Provider secret scanning and approved security tools can identify token patterns without teaching contributors to repeat the value.
- 01Keep local values outside Git
Ignore
.env; commit only a value-free.env.example. - 02Inspect the index
Read every staged path and patch. A broad
git add .is not a review. - 03Scan before merge
Use provider scanning and a repository-approved pre-commit or CI scanner.
- 04Minimize the credential
A short-lived read token limits damage if prevention fails.
Respond to a leaked credential
If a real token, private key, password, or production connection string reaches Git history or logs, treat it as used by an attacker even if the repository was private and the commit existed briefly.
- 01Revoke or rotate
Invalidate the credential at its provider first. Create a replacement only through the approved store and grant the minimum access.
- 02Record safe facts
Time, repository, path, commit SHA, visibility, branches, forks, logs, and systems reached—never copy the secret into the incident report.
- 03Assess use
Review provider audit logs and affected resources. Rotation stops future use; it does not explain what already happened.
- 04Remove current exposure
Stop tracking the file, add a precise ignore rule, remove log artifacts, and replace real values with documented placeholders.
- 05Coordinate cleanup
Decide whether history rewriting is required, identify every ref and clone, communicate new commit ids, and require fresh clones when policy says so.
- 06Prevent recurrence
Add scanning, narrow permissions, improve setup docs, and close the incident with evidence.
Coordinate history cleanup
History cleanup changes commit ids for every rewritten descendant. Branches, tags, pull requests, forks, caches, release artifacts, and old clones may retain the original objects. Use the hosting provider's current sensitive-data-removal process and an approved rewriting tool. Do not improvise a force-push from a tutorial.
Plan the boundary
- Credential already revoked.
- Affected paths and refs identified.
- Owners and collaborators notified.
- Protected evidence stored safely.
Close every copy path
- Rewritten refs verified.
- Hosted caches/support process handled.
- Collaborators re-clone instead of merging old history.
- Forks, artifacts, and logs reviewed.
Git Recovery teaches that old objects can remain locally even after refs move. That is exactly why history rewriting is coordinated cleanup, not a guarantee that every copy vanished.
Guided practice: signed, secret-free evidence
- 01Use a disposable repository
Inspect signing config. If you already control an approved signing key, configure it locally; otherwise document the required policy without generating a throwaway identity.
- 02Create one signed commit
Commit a value-free
SECURITY.md. Record the SHA and local--show-signatureoutput. Do not claim a badge proves review. - 03Audit a fake marker
Commit and remove
EXAMPLE_TOKEN_DO_NOT_USEin a practice file. Use grep and pickaxe history to prove a later deletion leaves the earlier snapshot. - 04Constrain a workflow
Review a workflow with explicit
permissions: contents: read. Confirm it prints no secret and runs no untrusted third-party action. - 05Write the response order
Revoke, record safe facts, assess, remove current exposure, coordinate cleanup, prevent recurrence.
Independent lab: security runbook
- In a disposable repository, add a value-free
SECURITY.mddescribing supported reporting channels, signing policy, and what must never be committed. Do not include a real email, token, private key, customer record, or production URL. - Inspect signing configuration. If an approved key already exists, configure signing locally and create one signed practice commit. Otherwise, write the exact setup your organization requires and state that no signature was fabricated.
- Add then remove the fake marker
EXAMPLE_TOKEN_DO_NOT_USEacross two commits. Prove the current tree is clean whilegit log -Sstill finds the earlier history. - Review a small Actions workflow with explicit read-only permissions. Identify its event, code it executes, credentials available, and whether untrusted pull-request code can reach them.
- Write a seven-line incident card: credential type, revoke owner, safe evidence to record, audit-log location, current-tree containment, coordinated history-cleanup owner, and recurrence control. Use placeholders only.
- Finish with clean status. Your evidence may include key fingerprints and commit SHAs, never private key material or credential values. Do not create or revoke a real token solely for this lab.
Common security mistakes
Calling signed code safe
A signature proves key possession and object integrity, not correctness or review.
Turning on global signing blindly
Confirm key, agent, identity, verifier, and repository policy first.
Putting tokens in remote URLs
URLs leak through config, logs, process lists, and screenshots. Use a helper.
Trusting log masking
Encoding or transforming a value can bypass masks. Do not print secrets at all.
Deleting before revoking
History cleanup does not stop a copied credential from working.
Force-pushing without coordination
Old clones and refs can reintroduce exposed history. Follow an incident plan.
Lesson review
You can sign and verify commits under an explicit key policy, keep tokens narrow and outside Git, constrain workflow permissions, prevent secret commits, and respond to exposure with revocation before coordinated cleanup. The Team Repository CI project remains planned.
- I can explain exactly what a valid signature proves—and what it does not prove.
- I keep tokens in helpers or secret stores with narrow scope, expiry, and periodic review.
- I inspect staged content and grant Actions only the permissions each job needs.
- I revoke exposed credentials before repository cleanup and never repeat the value in incident evidence.