Supply Chain & CI/CD Security
Development is performed by a third-party firm: their developers are GitHub collaborators with no AWS access. They can only propose changes; nothing reaches production without S.E.T-team approval. This section defines how a code change travels safely from a developer's laptop to production, and the controls at each stage.
Threat model
Defending against the 2025–2026 supply-chain attack wave: Shai-Hulud (self-replicating npm worm via postinstall, weaponized TruffleHog to steal creds), tj-actions/changed-files + reviewdog (poisoned Action tags dumping runner secrets), TanStack (OIDC token extraction + cache poisoning), TeamPCP (Trivy/Checkmarx Actions compromise that ran curl 169.254.169.254 for IMDS creds), and assorted hijacked packages (Axios, node-ipc, Bitwarden CLI). Also: a compromised contractor laptop, a rogue/compromised contractor, and the contractor firm itself being breached.
The two zones
- Untrusted zone — the contractor's laptop. We cannot control it; we assume it could be compromised and rely on nothing here.
- Our turf — everything from GitHub onward. Runs on systems we control; gates cannot be bypassed.
The border is the push to GitHub. Real enforcement begins there.
Third-party developer access model
- Contractors are Write-only GitHub collaborators (never Admin/Maintain); only the S.E.T team can change settings, branch protection, or add collaborators
- Mandatory hardware 2FA (FIDO2) enforced at org level; no shared accounts (each developer uses their own named identity)
- CODEOWNERS names the S.E.T team as required approver for sensitive files:
package-lock.json,.github/workflows/**,infra/**(Terraform), production config, schema migrations - No AWS access — contractors never hold cloud credentials; deploys run via OIDC under S.E.T's control
- Monthly GitHub audit-log review; quarterly collaborator-list reconciliation against the firm's current roster
- Contract requirements: DPA (Amendment 13), NDA, security clauses (hardware 2FA, no shared accounts, no committed secrets, breach notification), 24–48h offboarding SLA, right-to-audit, IP assignment (work-for-hire), cyber-insurance
Stage-by-stage controls
| Stage | What happens | Controls | Enforced? |
|---|---|---|---|
| 0. Foundation | S.E.T team builds the secure skeleton ("the cage") before contractors are added | Repo + branch protection + CODEOWNERS + pre-built CI workflows + AWS OIDC + org policies | — |
| 1. Laptop | Contractor writes code, pulls npm packages | Untrusted — rely on nothing. Contract requires laptop hygiene + 2FA. Optional dev-container sandbox | No (untrusted zone) |
| 2. Commit | Local snapshot | lefthook → Gitleaks + ESLint (fast feedback only; bypassable) | No (convenience) |
| 3. Push | Upload to a draft branch | GitHub Push Protection (secrets); branch protection blocks direct push to main/develop | Yes |
| 4. Pull Request | Validation job runs — holds zero credentials | Harden-Runner egress block · npm ci --ignore-scripts · TruffleHog (--only-verified) · OSV-Scanner · Trivy · Socket Firewall · Semgrep · zizmor + actionlint · tests + cross-tenant isolation tests · container build + Trivy image scan | Yes |
| 5. Human review | S.E.T team reviews + approves | Required reviewer (contractor can't self-approve); CODEOWNERS gates sensitive files | Yes |
| 6. Merge → nonprod | Deploy job (separate from validation) gets short-lived OIDC creds, deploys to nonprod | Job separation · short-lived OIDC · least-privilege deploy role · SHA-pinned steps only | Yes |
| 7. Test nonprod | QA + demo | Separate AWS account; no real customer data | Yes |
| 8. Promote → main | Deliberate second approval | GitHub Environment "production" required-reviewer gate (recommend two — four-eyes) | Yes |
| 9. Deploy prod | Same tested image deploys | Rolling deploy + circuit breaker + auto-rollback; S.E.T creds only | Yes |
| 10. Runtime | Live + monitored | VPC Flow Logs + GuardDuty → Logz.io; alerts → PagerDuty | Yes |
Core CI/CD security principles (locked)
- OIDC short-lived credentials only — no long-lived AWS keys stored anywhere. A leaked token dies in ~1 hour.
- Job separation — untrusted contractor code (install/build/test) never runs in the same job that holds AWS credentials. The validation job has zero secrets; the deploy job runs only trusted, SHA-pinned, post-review steps.
- Least-privilege deploy role — even a stolen/used credential cannot create IAM backdoors or copy data cross-account; the role can only perform narrow deploy actions.
- GitHub-hosted runners only — they run on Azure, so
curl 169.254.169.254returns no AWS credentials. No self-hosted runners on AWS without IMDSv2 + hop-limit-1 + network-namespace IMDS blocking + ephemeral runners. - All third-party Actions pinned by 40-char commit SHA (via
pinact); a CI check blocks unpinned actions. Org policy: Allowed Actions = selected + verified creators only. - Layered exfiltration defense — egress allowlist (Harden-Runner) + DNS controls + least-privilege + short-lived creds + GuardDuty detection. No single control is relied on (egress filtering has known bypasses — see caveats).
Locked tool stack (all free; validated for stars / maintenance / reliability 2026-05-24)
| Category | Tool | License | Role |
|---|---|---|---|
| Git hook manager | lefthook | MIT | Runs local pre-commit checks (replaces Husky — Husky stale since Nov 2024) |
| Secret scan (pre-commit) | Gitleaks | MIT | Fast local secret scan |
| Lint | ESLint + security plugins | MIT | Code quality + dangerous-pattern lint |
| Secret scan (CI) | TruffleHog --only-verified | AGPL-3.0 | Verified secret scan on PR (blocks merge) |
| Dependency vuln scan | OSV-Scanner | Apache-2.0 | npm vuln + malware advisories (caught Shai-Hulud cleanly) |
| Backup vuln + image scan | Trivy | Apache-2.0 | Container image CVE scan + secondary dep scan |
| SAST | Semgrep OSS | LGPL-2.1 | Dangerous-code analysis on every PR |
| Workflow analysis | zizmor + actionlint | MIT / Apache-2.0 | zizmor = security; actionlint = syntax/shell (run both) |
| Runner hardening | Harden-Runner ≥ v2.19.4 | Apache-2.0 | Egress monitoring/block (detection + defense-in-depth) |
| SBOM | Syft + cyclonedx-npm | Apache-2.0 | Build SBOMs (container + npm) for SOC 2 evidence |
| Pin Actions by SHA | pinact | MIT | Auto-pins Actions (ratchet rejected — stale since Jun 2025) |
| Posture scoring | OpenSSF Scorecard | Apache-2.0 | Weekly scheduled posture score (non-blocking) |
| Artifact signing | cosign (Sigstore) | Apache-2.0 | Keyless OIDC signing of images |
| Install-time block | Socket Firewall Free | Free tier | Blocks confirmed-malicious packages at install (early-warning) |
| Lockfile integrity | npm ci --ignore-scripts | built-in | Lockfile match + no postinstall execution |
| Auto dependency updates | Dependabot | GitHub | Patch/minor auto-PRs; major requires manual review |
| Secret push block | GitHub Push Protection | GitHub | Blocks secrets at push (Stage 3) |
| DAST (passive + active) | OWASP ZAP | Apache-2.0 | Per-PR baseline + nightly full scan against nonprod; findings flow into Vulnerability Management per the DAST & Penetration Testing Policy |
| DAST (templated CVE / vuln) | Nuclei | MIT | Weekly templated vulnerability coverage against nonprod; complements ZAP |
Deliberately avoided paid traps: CodeQL (needs paid GitHub Advanced Security on private repos) → use Semgrep instead; GitHub native secret scanning (paid for private) → covered by TruffleHog + Gitleaks. Grype rejected (false positives on npm devDependencies in Shai-Hulud post-mortems). Docker Scout rejected (free tier caps at 1 repo).
Reliability caveats (known, accepted)
- Harden-Runner free-tier egress has 2026 bypass CVEs (CVE-2026-32946/32947 — DNS-over-TCP / DNS-over-HTTPS). Fixed in v2.16.0+; pin ≥ v2.19.4. Treat free-tier egress filtering as detection + defense-in-depth, NOT a hard boundary — which is why it's layered with job separation, least-privilege, and short-lived creds.
- Socket Firewall Free has a
.swf.configbypass and only blocks confirmed malware (unconfirmed = warn-only). Strong early-warning layer, not a guarantee; paired withnpm ci --ignore-scripts. - Semgrep OSS has a large open-issue backlog (mostly rule requests, not bugs) — still the best free SAST.
SOC 2 / Amendment 13 evidence retention
- SBOMs (every build) — retained ≥ 12 months
- Scan reports (TruffleHog, OSV-Scanner, Trivy, Semgrep) — retained as PR check evidence
- Signed artifacts (cosign) — provenance trail
- OpenSSF Scorecard history — posture over time
- GitHub deployment + approval logs — automatic change-management evidence (who approved which production deploy)
- All retained in an immutable store for auditor access