1. Why Secrets in Code and Config Are a Recurring Disaster
A hardcoded API key or database password feels harmless in a private repository — it stops feeling harmless the moment that repo is made public, forked, or has its history scraped by one of the many automated bots that specifically hunt GitHub for exactly this pattern.
# A secret committed directly into source
DATABASE_URL = "postgres://admin:SuperSecret123@prod-db.internal:5432/app"
# Deleting this line in a LATER commit does NOT remove it from git history --
# it's still fully retrievable:
$ git log -p -- config.py | grep SuperSecret123
# found it, in the old commit, forever, unless history is rewritten
# And by the time anyone notices, automated scanners have often already
# found and exploited it -- these bots scan public commits within minutes
The fix isn't "be more careful" — it's structural: secrets should never enter version control at all, verified automatically, not by hoping every commit gets manually reviewed for exactly this.
# A pre-commit hook (or a CI step) running a secret scanner
$ pip install detect-secrets
$ detect-secrets scan --all-files
# gitleaks is another common, free choice, often run as a GitHub Action
# on every pull request -- catching a leaked secret in the PR review
# stage, before it ever reaches the default branch's history at all
Once a credential has been committed, even briefly, treat it as compromised: rotate it (issue a new one, revoke the old) immediately, regardless of whether you believe anyone actually saw it. Deleting the line from the current code leaves the value fully intact in git history — "removing" it from the repo does nothing to the credential itself still being valid.
2. Vaults & Runtime Secret Injection
A secrets vault (HashiCorp Vault, AWS Secrets Manager, and similar tools) stores credentials centrally, encrypted at rest, and hands them to applications at runtime — never checked into config files, never sitting in plaintext on disk longer than necessary.
# Before: a secret baked into a config file, deployed alongside the app
DATABASE_PASSWORD=SuperSecret123 # sitting in a file, on disk, forever
# After: the app fetches it at startup, using its OWN identity (an IAM
# role, a Kubernetes service account) to authenticate to the vault --
# no long-lived credential to leak in the first place
$ vault kv get -field=password secret/prod/database
# The app calls this equivalent at startup; the actual password never
# touches a config file, a repo, or a deploy artifact
Vaults typically add dynamic secrets too — instead of one static database password shared forever, the vault can generate a unique, short-lived credential per application instance, automatically expiring it. This is Week 8's "prefer temporary IAM roles over long-lived keys" principle, generalized beyond just cloud IAM to any credential.
A full vault is the right answer for a production system, but even a smaller project's .env file deserves the basics: it must be in .gitignore (never committed), it should exist only in the deploy environment (not synced to a shared drive or Slack), and access to the machine holding it should follow Week 3's least-privilege discipline — an .env file readable by every user on a shared server is barely better than committing the secret directly.
3. Dependency Scanning & the Software Bill of Materials (SBOM)
Modern applications are built from dozens or hundreds of open-source dependencies — most teams have a rough idea of their direct dependencies, and almost no idea of the full transitive tree those dependencies pull in beneath them.
$ npm audit
found 3 vulnerabilities (1 moderate, 2 high) in 842 scanned packages
lodash <4.17.21 High Prototype Pollution fix available via npm audit fix
# This is Week 9's image-scanning idea, applied to your application's
# actual dependency tree instead of a container's OS packages
An SBOM (Software Bill of Materials) takes this further: a complete, machine-readable inventory of every component in a piece of software — direct and transitive dependencies, their versions, their licenses. It exists for exactly the question that matters the moment a new CVE is disclosed: "do we use this anywhere, and where?"
$ syft packages dir:. -o cyclonedx-json > sbom.json
# Now, when a new critical CVE drops in some deeply transitive package,
# a single grep against every project's SBOM answers "are we exposed?"
# in minutes -- instead of manually auditing every service's dependency tree
Beyond known CVEs in legitimate packages, attackers have repeatedly published deliberately malicious packages with names similar to popular ones (typosquatting a package name, mirroring Week 2's domain typosquatting), or exploited misconfigured private-registry resolution to have a build pull a malicious public package instead of the intended internal one. An SBOM and dependency scanning are the primary defenses that catch both classes.
4. Signing Artifacts & Verifying Provenance
Week 4 established that a digital signature proves a file genuinely came from a specific source, unmodified since. Applied to software distribution, this is how a consumer of a package or container image can verify it hasn't been tampered with somewhere in the pipeline between the original publisher and their own machine.
# Sign an image as part of the build/release process
$ cosign sign --key cosign.key myregistry/myapp:v1.2.0
# A consumer (or, critically, an automated deployment gate) verifies
# the signature before trusting the image
$ cosign verify --key cosign.pub myregistry/myapp:v1.2.0
Verification succeeded
# A tampered or unsigned image fails this check -- and a deployment
# pipeline can be configured to REFUSE to deploy anything that fails it
This is the direct, practical answer to the supply-chain question "how do I know this artifact is genuinely what my own CI pipeline built, and not something swapped in along the way (a compromised registry, a man-in-the-middle during a pull)?" — signing plus verification closes exactly that gap.
Verifying a signature confirms the artifact matches what the private key holder signed — it says nothing about whether the signer's build process itself was compromised before signing. Signing protects the link between "what my pipeline built" and "what got deployed" — it doesn't replace Section 3's scanning of what actually went into the build in the first place.
5. Locking Down CI/CD Pipeline Write Access
A CI/CD pipeline is itself a high-value target: it typically holds the credentials to deploy to production, publish packages, and push container images — compromising the pipeline can be more valuable to an attacker than compromising any single application it deploys.
1. Overly broad secrets scope -- every job in every pipeline can read
the production deploy credential, even jobs that never need to deploy
2. Untrusted pull requests triggering workflows with full secret access --
a malicious PR from an external contributor running CI code that
can read your deploy keys, before any human review
3. Unpinned third-party CI actions -- referencing an action by a
mutable tag (@v1) instead of a pinned commit SHA means the action's
maintainer (or an attacker who compromises their account) can change
what that action does, retroactively, for every pipeline using it
# Risky: @v4 can be repointed to a different, possibly malicious commit later
- uses: some-org/some-action@v4
# Safer: pinned to an exact, immutable commit -- what runs today is
# exactly what will run in a year, regardless of what the tag points to later
- uses: some-org/some-action@a1b2c3d4e5f6...
Most CI systems let you scope a secret to a specific workflow, environment, or even require manual approval before a job with production access runs. Treat CI secrets with the exact same least-privilege scrutiny as any cloud IAM policy — a lint job or a test job almost never needs the production deploy key, even if it's technically convenient to give every job blanket access to everything.
6. Hands-on Exercise
Catch a leaked secret, generate an SBOM, and lock down a CI pipeline
Work through the full lifecycle: a secret that leaked, a dependency tree you didn't fully know, and a pipeline that trusted too much by default.
Part 1 — Catch and rotate a leaked secret:
- In a disposable local git repo, commit a file containing a fake but realistic-looking secret (e.g.
API_KEY=sk_test_51H...), then delete the line in a second commit. - Confirm the "deleted" secret is still fully retrievable from git history using
git log -p— this is the concrete proof behind Section 1's warning. - Install
gitleaks(ordetect-secrets) and run it against your repo's full history — confirm it flags the leaked secret, including in the deleted commit. - Write the "incident response" you'd actually perform for a real leaked production credential: rotate it, and describe (in 2-3 sentences) whether rewriting git history is worth doing given the credential is already rotated and thus no longer valid.
Use a clearly fake value (a real-looking prefix like sk_test_ but obviously invalid) — the point of this exercise is proving the git-history mechanics and the scanner's detection, never generating or exposing a real, valid credential anywhere, even in a disposable local repo.
Part 2 — Generate an SBOM and scan dependencies:
- Take any real project with a dependency manifest (a Node
package.json, a Pythonrequirements.txt) and run its ecosystem's audit tool (npm audit,pip-audit) — record the findings. - Generate an SBOM for the same project using a tool like
syft, and open the output — count how many total packages appear (direct + transitive) versus how many are listed in the project's own manifest file directly. - Pick one transitive dependency you'd never heard of before this exercise, and note what package pulled it in (most SBOM/lock-file formats show the dependency chain).
Part 3 — Lock down a CI workflow:
- Find (or write) a GitHub Actions workflow file with at least one third-party action referenced by a mutable tag (
@v1,@main). - Look up that action's exact commit SHA for the version you're using, and pin the workflow to it instead of the mutable tag.
- If the workflow has a deploy or publish step, rewrite its secrets access so only that specific job (not every job in the workflow) has access to the deploy credential — using your CI platform's job-scoped secrets or environment-protection features.
GitHub itself lists the exact commit SHA for any tagged release on the action's repository page — look for "commits" or the tag's own page, not just the version number, to find the SHA to pin against.
7. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
A team commits a secret, then deletes the line in the next commit. Is the secret safe now?
A team commits a secret, then deletes the line in the next commit. Is the secret safe now?
No. Git history retains the original commit, and the secret is fully retrievable from it (git log -p) regardless of what later commits do. The only real fix is treating the credential as compromised and rotating it — deleting the line changes the current file, not the historical record.
Q2
What specific question is a Software Bill of Materials (SBOM) designed to answer quickly?
What specific question is a Software Bill of Materials (SBOM) designed to answer quickly?
"Do we use this specific component (direct or transitive), and where?" — the question every team faces the moment a new CVE is disclosed in some widely-used package. A complete, up-to-date SBOM lets that be answered in minutes via a search, instead of a manual audit across every project's dependency tree.
Q3
Why does pinning a third-party CI action to a commit SHA matter more than pinning it to a version tag like @v4?
Why does pinning a third-party CI action to a commit SHA matter more than pinning it to a version tag like @v4?
A version tag can be moved to point at a different commit later — by the maintainer, or by an attacker who compromises the maintainer's account — silently changing what code your pipeline runs the next time it executes, with no change on your end. A commit SHA is immutable; what runs today is guaranteed to be exactly what runs a year from now, regardless of what happens to the tag.
Q4
Does verifying a container image's digital signature guarantee the image itself is free of vulnerabilities?
Does verifying a container image's digital signature guarantee the image itself is free of vulnerabilities?
No. A valid signature proves the image is genuinely what the signer built and hasn't been tampered with since signing — it says nothing about whether the build itself contained vulnerable dependencies or a misconfiguration. Signing and image/dependency scanning (Section 3) address different, complementary parts of supply-chain trust, not the same problem.