Skip to main content

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

StageWhat happensControlsEnforced?
0. FoundationS.E.T team builds the secure skeleton ("the cage") before contractors are addedRepo + branch protection + CODEOWNERS + pre-built CI workflows + AWS OIDC + org policies
1. LaptopContractor writes code, pulls npm packagesUntrusted — rely on nothing. Contract requires laptop hygiene + 2FA. Optional dev-container sandboxNo (untrusted zone)
2. CommitLocal snapshotlefthook → Gitleaks + ESLint (fast feedback only; bypassable)No (convenience)
3. PushUpload to a draft branchGitHub Push Protection (secrets); branch protection blocks direct push to main/developYes
4. Pull RequestValidation job runs — holds zero credentialsHarden-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 scanYes
5. Human reviewS.E.T team reviews + approvesRequired reviewer (contractor can't self-approve); CODEOWNERS gates sensitive filesYes
6. Merge → nonprodDeploy job (separate from validation) gets short-lived OIDC creds, deploys to nonprodJob separation · short-lived OIDC · least-privilege deploy role · SHA-pinned steps onlyYes
7. Test nonprodQA + demoSeparate AWS account; no real customer dataYes
8. Promote → mainDeliberate second approvalGitHub Environment "production" required-reviewer gate (recommend two — four-eyes)Yes
9. Deploy prodSame tested image deploysRolling deploy + circuit breaker + auto-rollback; S.E.T creds onlyYes
10. RuntimeLive + monitoredVPC Flow Logs + GuardDuty → Logz.io; alerts → PagerDutyYes

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.254 returns 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)

CategoryToolLicenseRole
Git hook managerlefthookMITRuns local pre-commit checks (replaces Husky — Husky stale since Nov 2024)
Secret scan (pre-commit)GitleaksMITFast local secret scan
LintESLint + security pluginsMITCode quality + dangerous-pattern lint
Secret scan (CI)TruffleHog --only-verifiedAGPL-3.0Verified secret scan on PR (blocks merge)
Dependency vuln scanOSV-ScannerApache-2.0npm vuln + malware advisories (caught Shai-Hulud cleanly)
Backup vuln + image scanTrivyApache-2.0Container image CVE scan + secondary dep scan
SASTSemgrep OSSLGPL-2.1Dangerous-code analysis on every PR
Workflow analysiszizmor + actionlintMIT / Apache-2.0zizmor = security; actionlint = syntax/shell (run both)
Runner hardeningHarden-Runner ≥ v2.19.4Apache-2.0Egress monitoring/block (detection + defense-in-depth)
SBOMSyft + cyclonedx-npmApache-2.0Build SBOMs (container + npm) for SOC 2 evidence
Pin Actions by SHApinactMITAuto-pins Actions (ratchet rejected — stale since Jun 2025)
Posture scoringOpenSSF ScorecardApache-2.0Weekly scheduled posture score (non-blocking)
Artifact signingcosign (Sigstore)Apache-2.0Keyless OIDC signing of images
Install-time blockSocket Firewall FreeFree tierBlocks confirmed-malicious packages at install (early-warning)
Lockfile integritynpm ci --ignore-scriptsbuilt-inLockfile match + no postinstall execution
Auto dependency updatesDependabotGitHubPatch/minor auto-PRs; major requires manual review
Secret push blockGitHub Push ProtectionGitHubBlocks secrets at push (Stage 3)
DAST (passive + active)OWASP ZAPApache-2.0Per-PR baseline + nightly full scan against nonprod; findings flow into Vulnerability Management per the DAST & Penetration Testing Policy
DAST (templated CVE / vuln)NucleiMITWeekly 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.config bypass and only blocks confirmed malware (unconfirmed = warn-only). Strong early-warning layer, not a guarantee; paired with npm 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