OpenLab Apprenticeship 2026

JOSA OpenLab

Week 07: Security & the Software Supply Chain

Week 07 of 10
Status Completed
Apprentice
Qutibah Ananzeh
Cohort
OpenLab 2026
Deadline
Jun 27, 2026
// 00

The Standard

The unifying principle: most code in a modern app is not mine, an average npm app pulls in 1,000+ transitive dependencies, a Python web app 200+. Every one of those is an attack surface, and the goal this week is to use the free tooling that makes that surface visible instead of trusting it by default.
The supply chain in three layers
  • Dependency-on-itself. A package I depend on turns malicious: typosquatting, a maintainer going rogue, or a takeover like the xz/liblzma backdoor.
  • Build-process. CI itself is compromised, e.g. a GitHub Action imported as @main that silently changes underneath a project (the 2024 tj-actions/changed-files incident).
  • Distribution. A published artifact is tampered with after build. The defense is signing, Sigstore.
Tooling map
  • OpenSSF Scorecard. 18+ automated checks on any GitHub repo, 0-10 score: branch protection, code review, pinned dependencies, signed releases, SAST, token permissions.
  • Dependabot / Renovate. Bot-opened PRs for outdated deps. Default to Dependabot (zero-config); switch to Renovate for grouping or a polyglot repo.
  • Sigstore (cosign). Keyless signing tied to an existing GitHub/Google identity instead of managed GPG keys, logged to the public Rekor transparency log.
  • SBOM (Syft + Grype). A CycloneDX/SPDX inventory of every dependency, version, and license; Grype cross-references it against osv.dev for known CVEs.
Case study: the xz backdoor (CVE-2024-3094)
A contributor spent two years quietly building maintainer trust on the xz compression library, then landed a backdoor in liblzma that compromised SSH on most Linux distros. It was caught by accident, a Microsoft engineer noticed slow SSH logins. The lesson: a single-maintainer project is a single point of failure, tarball releases can differ from what is visible in git tags, and long-game social engineering is a real threat model. Scorecard, Sigstore, and SBOMs are built against exactly this failure mode.
// 01

Add Dependabot

Done
Repo: Ti-03/MacDirStat  ·  PR: #12  ·  both ecosystems the repo actually has: swift (SwiftPM) and github-actions, weekly.
What I did
The pinning half was the real lesson. Every uses: line in ci.yml and pages.yml moved from a mutable tag (@v4) to the tag's resolved commit SHA with the version kept as a trailing # v4 comment, which Dependabot understands and keeps updated. Resolving a SHA takes two API hops, because a tag ref can point at an annotated tag object rather than the commit itself. One honest mistake worth recording: my first commit staged .github/ wholesale and swept 41 vale-sync style files into the PR; a follow-up commit dropped them and the diff settled at exactly 3 files.
resolve tag -> commit sha
$ gh api repos/actions/checkout/git/ref/tags/v4   # -> object sha (may be a tag object)
$ gh api repos/actions/checkout/git/tags/<sha>    # -> the actual commit
actions/checkout@v4          11d5960a326750d5838078e36cf38b85af677262
actions/cache@v4             0057852bfaa89a56745cba8c7296529d2fc39830
actions/deploy-pages@v4      d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e
Definition of done
  • .github/dependabot.yml covers swift and github-actions. ✓
  • All 6 uses: lines across both workflows pinned by commit SHA. ✓
  • Submitted as PR #12. ✓
// 02

Sign a Release with Sigstore

Done
Repo: Ti-03/MacDirStat  ·  PR: #14 merged  ·  v1.2.0 released and signed; clean-download verification: Verified OK.
What I did
PR #14 adds release.yml: on any v* tag it builds the release binary, zips it, signs it with cosign keylessly (the job's OIDC token becomes a short-lived Fulcio certificate, logged in Rekor), and attaches the artifact plus its .cosign.bundle to the GitHub release. The README now documents the exact verify-blob command, pinned to this repo's release.yml identity. Exercising the mechanics locally against the existing v1.1 DMG with a throwaway key caught a real bug before it shipped: cosign v3 removed --output-signature/--output-certificate and requires --bundle, so the workflow as first written would have failed on its very first tag. The local demo also proved the point of the exercise: the intact DMG verifies OK, and the same DMG with one byte flipped is rejected.
local sign / verify / tamper demo
$ cosign sign-blob --key cosign.key --yes --bundle MacDirStat-1.1.dmg.cosign.bundle MacDirStat-1.1.dmg
Wrote bundle to file MacDirStat-1.1.dmg.cosign.bundle
$ cosign verify-blob --key cosign.pub --bundle ... MacDirStat-1.1.dmg
Verified OK
$ cosign verify-blob --key cosign.pub --bundle ... tampered.dmg   # one byte flipped
Error: failed to verify signature
Definition of done
  • Release workflow signs keylessly with cosign, bundle logged to Rekor. ✓ (PR #14, merged)
  • Verify command documented in the README. ✓
  • v1.2.0 tagged; workflow built, signed, and published the release. Verified from a clean download with the README command: Verified OK. ✓
// 03

Generate and Read an SBOM

Done
Subject: this progress site itself. A "tiny static site" whose CycloneDX SBOM lists 482 components; Grype found 16 known vulnerabilities, two High. After targeted updates: 1.
What I did
The finding I investigated in depth: GHSA-52cp-r559-cp3m in js-yaml 4.1.1, published two days before the scan. It is a quadratic-CPU parsing bug: a YAML document chaining merge keys (<<: *anchor) forces O(N²) work for O(N) input, so a sub-100KB document can hang the parser for seconds. The interesting part is who is affected: npm ls shows js-yaml reaches this repo only through eslint and the shadcn CLI, both dev-time tools, and the site is a static export that parses no YAML at runtime. High severity, near-zero exposure here; that judgment is the whole point. I fixed what was fixable anyway: npm update bumped js-yaml to 4.3.0 plus five other flagged packages, and the re-scan dropped from 16 findings to 1. The leftover is postcss 8.4.31, which Next.js itself pins: Medium, build-time only, tracked rather than fought.
syft + grype, before / after
$ syft . -o cyclonedx-json=sbom.json   # 482 components
$ grype sbom:sbom.json
js-yaml          4.1.1   4.3.0   GHSA-52cp-r559-cp3m  High
brace-expansion  5.0.6   5.0.7   GHSA-3jxr-9vmj-r5cp  High
... 14 more (hono, qs, postcss, body-parser, @babel/core)
$ npm ls js-yaml   # eslint + shadcn -> js-yaml (dev-time only)
$ npm update js-yaml brace-expansion hono qs body-parser @babel/core
$ grype sbom:sbom-after.json
postcss  8.4.31  8.5.10  GHSA-qx2v-qp2m-jg93  Medium   # pinned by Next itself
Definition of done
  • sbom.json generated for a real project. ✓ (482 components)
  • Grype run against it, output captured. ✓ (16 findings → 1 after fixes)
  • One CVE investigated in depth. ✓ (GHSA-52cp-r559-cp3m: mechanism, fix, who is affected)
// 04

Run OpenSSF Scorecard

Done
Repo: Ti-03/MacDirStat  ·  Score: 3.0/10, twelve checks at zero  ·  PR: #13
What I did
Reading the full report was the calibration exercise. Most of the twelve zeros need process or settings, not code: branch protection, code review on a solo repo, fuzzing, a CII badge. Chasing all of them would be checkbox security. Two zeros were fixable from inside the repo and actually change the threat model, so they became PR #13: Token-Permissions (ci.yml had no permissions block, so every job got a default token with write access to repo contents; build-and-test needs read-only) and Security-Policy (no SECURITY.md, so the only way to report a vulnerability was a public issue). Two more zeros, Dependency-Update-Tool and Pinned-Dependencies, are already covered by the Dependabot PR #12 from task 1, so the two PRs together should move four checks off zero once merged.
scorecard
$ export GITHUB_AUTH_TOKEN=$(gh auth token)
$ scorecard --repo=github.com/Ti-03/MacDirStat
AGGREGATE: 3.0 / 10
  0  Token-Permissions        default token can write contents
  0  Security-Policy          no SECURITY.md
  0  Dependency-Update-Tool   (fixed by PR #12)
  0  Pinned-Dependencies      (fixed by PR #12)
  0  Branch-Protection, Code-Review, Fuzzing, ...   settings/process
 10  License, CI-Tests, Dangerous-Workflow, Binary-Artifacts, Vulnerabilities
Definition of done
  • Scorecard run, full report read, lowest-scoring checks identified. ✓
  • PR opened addressing the PR-fixable zeros: #13 (least-privilege CI token + SECURITY.md). ✓
// 05

Soft Skill, Conscientious Skepticism

Done
The principle: security work needs someone who asks "but what if this fails?" without becoming the person nobody invites back to the kickoff meeting.
How the week's work exercised it
  • Bring data, not alarm. The SBOM scan surfaced a High in js-yaml. The alarmed move is "drop everything"; the data-first move was npm ls showing it only enters via eslint and shadcn at dev time in a static-export site. Severity is not exposure. Fixed with a one-line update anyway.
  • Pick battles. Scorecard handed me twelve zeros. Fighting all of them is checkbox security; the PR fixed the two that change the threat model (CI token write access, no vulnerability reporting path) and skipped fuzzing badges for a solo desktop app.
  • Phrase concerns as questions, with evidence attached. The week's PRs lead with the incident that motivates them (tj-actions for SHA pinning) rather than "this is insecure".
  • Default to charity. My own first Dependabot commit accidentally shipped 41 generated files, exactly the class of oversight I would be tempted to flag in someone else's PR while they were just trying to ship.