This is the canonical definition of application-security and software-supply-chain rigor for every repo in this portfolio. It owns the machinery: ASVS level, SAST/SCA/secret-scanning configuration, Action SHA-pinning, SBOM/signing/provenance, and the token-permission model. Repos record only project-specific values and findings — the threat model narrative, the residual-risk register, the per-repo ASVS level declaration — in docs/RESPONSIBLE-TECH-AUDITS.md (methodology: RESPONSIBLE-TECH-FRAMEWORK.md §F) and their ROADMAP.md Metrics table. Reference, don't repeat.
Enforcement is binary. A control is AUTO-GATE (mechanically checkable, merge-blocking in CI, no
|| true, nocontinue-on-error) or REVIEW-GATE (human judgment, paired with a checklist line and a committed, dated artifact regenerated on release). There is no aspirational third category. SHA pinning, SBOM generation, keyless signing, provenance, and OIDC are established mechanisms; each repository records its own adoption evidence privately.
CI/CD-pipeline hardening (token permissions, OIDC, branch rulesets, workflow SAST) lives in CI-CD-STANDARD.md; this document covers it only where it is load-bearing for supply-chain integrity and cross-references the rest. Toolchain floors (ruff/mypy/coverage) live in CODE-QUALITY-STANDARD.md.
Target framework is OWASP ASVS 5.0.0 (May 2025). Every repo declares its level in docs/RESPONSIBLE-TECH-AUDITS.md §F. There is no silent default — a repo with no declaration fails review.
| Repo class | ASVS target | Rationale | Examples |
|---|---|---|---|
| Default floor | L1 | ASVS 5.0 states L1 is achievable with automated tooling alone; that maps exactly to our AUTO-GATE set. | every repo, minimum |
| Touches PII / identity / location | L2 | Field-level authz (BOPLA V8.2.1), cross-tenant isolation, breached-password checks, OIDC acr/amr validation. |
public-service applications, privacy-sensitive tools, identity-aware frontends, or local data stores |
| Catastrophic-breach surface | L3 | Hardware phishing-resistant factor, adaptive authz, annual threat-model + leadership justification. Any repository at this blast radius declares L3 and adopts the V6/V8/V10 L3 review-gates. | high-impact identity or authorization systems |
L1 is satisfied entirely by §3–§4 AUTO-GATEs (parameterized queries, output encoding, TLS 1.2+, server-side function- and object-level authz). L2 adds the authz integration tests in §5 and the OAuth/OIDC review-gate. No-outing sentinel tests and AST-level no-identity-inference checks are reference ASVS-V8 abuse-case controls; repositories keep equivalent project-specific guarantees.
A repo with no authentication, no authorization surface, and no network ingress (for example, a pure offline CLI or library) declares ASVS: N/A (no auth/authz/ingress surface) with that exact reason. It still inherits all of §3, §4, §6, §7 — supply-chain and scanning are never N/A for a repo that ships code.
Tools that log sensitive activity, monitor people or identity signals, or help people navigate sensitive resources carry a high individual-harm blast radius. They adopt every AUTO-GATE below as merge-blocking with zero waivers, plus:
| Control | Target | Measured by | Gate |
|---|---|---|---|
| No-secret/no-PII in logs [SEC-02] | zero matches for password/token/email/Authorization field values |
Semgrep custom rule + log-assertion integration test (jq over emitted JSON) |
AUTO-GATE |
| Consent gate before feature code [SEC-03] | sequenced before any feature that processes sensitive data; CI asserts gate module imported on every entrypoint | static import test | AUTO-GATE |
| No third-party exfiltration [SEC-04] | StepSecurity Harden-Runner egress allowlist in every job; deny-by-default | Harden-Runner block mode + audit |
AUTO-GATE |
| Sentinel-identity tripwire [SEC-05] | injected sentinel survives full pipeline without leaking | isolated no-outing CI job | AUTO-GATE |
| ASVS L2 [SEC-06] | full §5 authz tests | integration suite | AUTO-GATE |
A privacy-sensitive tool with no implementation yet scaffolds this standard
before its first feature code: Harden-Runner, gitleaks pre-commit + CI,
SHA-pinned actions, top-level permissions: contents: read, and the consent gate
land first.
Semgrep for fast diff-aware PR coverage (~10 s, SARIF to Code Scanning); CodeQL for deep cross-file taint/dataflow on a nightly schedule + required check on main. Rejected: Semgrep alone (misses multi-file auth bypasses); CodeQL alone (too slow for every PR, no IaC breadth).
| Metric | Target | Measured by | Gate |
|---|---|---|---|
| Semgrep findings [SEC-07] | zero unwaived HIGH/CRITICAL at merge | semgrep ci --sarif |
AUTO-GATE |
| CodeQL findings [SEC-08] | zero unwaived HIGH/CRITICAL on main |
github/codeql-action, nightly + required on push to main |
AUTO-GATE |
| Workflow SAST [SEC-09] | zero high/critical | CodeQL language: actions + zizmor (see §7) |
AUTO-GATE |
| Waiver hygiene [SEC-10] | every waiver has expiry + reason | committed .semgrep-waivers.yml, reviewed quarterly |
REVIEW-GATE |
# .github/workflows/sast.yml — Semgrep PR gate
permissions:
contents: read
jobs:
semgrep:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # upload SARIF only
steps:
- uses: actions/checkout@<40-char-sha> # v4.2.2
with: { persist-credentials: false }
- uses: semgrep/semgrep-action@<40-char-sha> # v1.x
- run: semgrep ci --sarif --output=semgrep.sarif --severity=ERROR --severity=WARNING
- uses: github/codeql-action/upload-sarif@<40-char-sha> # v3.x
with: { sarif_file: semgrep.sarif }make verify runs semgrep ci locally so the gate is byte-for-byte reproducible (matches the portfolio's make verify == CI discipline).
OSV-Scanner is the portfolio baseline (queries the same OSV DB as Scorecard's Vulnerabilities check, covers 20+ ecosystems incl. transitive lockfile deps). pip-audit / npm audit run additionally per ecosystem. A scanner followed by || true does not constitute a gate and is forbidden.
| Metric | Target | Measured by | Gate |
|---|---|---|---|
| Python vulns [SEC-11] | zero HIGH+CRITICAL with a fix available | pip-audit (no || true) + osv-scanner -r . over uv.lock |
AUTO-GATE |
| Node vulns [SEC-12] | zero HIGH+CRITICAL | npm audit --audit-level=high |
AUTO-GATE (frontends and Node Lambda handlers) |
| Transitive coverage [SEC-13] | lockfile present & scanned | osv-scanner fails if no uv.lock/package-lock.json |
AUTO-GATE |
| Update automation [SEC-14] | Dependabot or Renovate config present (Scorecard Dependency-Update-Tool) |
committed dependabot.yml/renovate.json |
AUTO-GATE |
| Open critical alerts [SEC-15] | none merge-able | branch ruleset blocks merge on open Dependabot alert CVSS ≥ 7.0 | AUTO-GATE |
| Unfixable HIGH/CRITICAL waiver [SEC-16] | tracked + VEX-justified | committed vex.json (CycloneDX 1.7 VEX), quarterly review |
REVIEW-GATE |
Repositories that fetch untrusted archives, spawn subprocesses, or process sensitive transit or location data adopt the full SCA and secret-scan set before lower-risk repositories. Current project prioritization lives in the private remediation registry.
Two-gate gitleaks: pre-commit (Gate 1) and CI diff (Gate 2). TruffleHog runs a scheduled full-history scan (Gate 3) with live-credential verification.
| Gate | Tool | Target | Gate |
|---|---|---|---|
| 1 pre-commit [SEC-17] | gitleaks v8.30.1 |
zero unredacted matches | AUTO-GATE |
| 2 CI diff [SEC-18] | gitleaks --exit-code 1 --redact (no || true) |
zero matches | AUTO-GATE |
| 3 scheduled [SEC-19] | trufflehog git --object-discovery --results=verified,unknown --fail |
zero verified live creds | AUTO-GATE (page on hit) |
# .pre-commit-config.yaml — Gate 1
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1
hooks: [{ id: gitleaks }]This pre-commit configuration is mandatory in every repository; the gitleaks + ruff + mypy hooks are the required floor.
L1 floor (AUTO-GATE, all repos with ingress): parameterized queries only (no string-built SQL — Semgrep rule), output encoding on all user-controlled HTML sinks (XSS), TLS 1.2+ asserted, server-side function-level authz (V8.1.1) and object-level authz (V8.1.2).
L2 (AUTO-GATE, PII repos): an integration suite asserting every protected endpoint returns 403 to an unauthorized principal and that object-level (BOLA/IDOR) and field-level (BOPLA) access is denied cross-tenant. Block deploy if any protected route lacks a negative-path test.
# tests/test_authz.py — the negative-path assertion, every protected route
@pytest.mark.parametrize("route", PROTECTED_ROUTES)
def test_unauthorized_principal_gets_403(client, route):
assert client.get(route, headers=other_tenant_token()).status_code == 403| Metric | Target | Measured by | Gate |
|---|---|---|---|
| Injection / XSS [SEC-20] | zero | Semgrep taint rules + DAST in staging | AUTO-GATE |
| Function-level authz [SEC-21] | every protected route 403s unauth | parametrized integration test | AUTO-GATE (L2) |
| Object-level authz (BOLA) [SEC-22] | cross-tenant access denied | integration test w/ second principal | AUTO-GATE (L2) |
| OAuth/OIDC (V10) [SEC-23] | PKCE enforced; acr/amr validated; sender-constrained tokens |
security architecture review | REVIEW-GATE (identity-critical services) |
| Annual pentest (BOLA/BOPLA/tenant) [SEC-24] | report attached to release | manual + human triage | REVIEW-GATE (L2 PII repos) |
Mutable action references are an active supply-chain threat: the March 2026 trivy-action force-push and tj-actions compromises were real exfiltration events, not hypotheticals. Adoption status stays in the private remediation registry.
This section owns the supply-chain machinery (SBOM, signing, provenance) that a release invokes. The release process and policy — SemVer, signed tags, CHANGELOG, the trusted-main signed-tag pipeline, and Trusted Publishing — lives in
RELEASE-AND-VERSIONING-STANDARD.md. When that standard says "sign + attest → SECURITY §6," this is the section it means.
Every uses: — third-party actions, actions/*, reusable workflows, and the deploy path — is pinned to a full 40-char commit SHA with a trailing # vX.Y.Z comment. Tags and branches are immutable-reference failures. Partial adoption does not pass: one moving reference in a preview or deploy workflow fails the gate.
# correct — immutable, human-readable
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: astral-sh/setup-uv@<40-char-sha> # v5.x
# WRONG — mutable, exploitable
- uses: actions/checkout@v4
- uses: gitleaks/gitleaks-action@v2Renovate keeps SHAs current and survivable:
// renovate.json
{
"extends": ["config:recommended", "helpers:pinGitHubActionDigestsToSemver"],
"minimumReleaseAge": "72 hours"
}Migration: run StepSecurity Action-Advisor (app.stepsecurity.io) or pin-github-action over each workflow to auto-generate SHA-pinned stubs.
npx pin-github-action .github/workflows/*.yml # rewrites tags -> SHA + comment| Metric | Target | Measured by | Gate |
|---|---|---|---|
Every uses: SHA-pinned [SEC-25] |
100% incl. reusable + deploy | Scorecard Pinned-Dependencies ≥ 9/10 on default branch |
AUTO-GATE |
| SHAs kept current [SEC-26] | Renovate active, 72h cooldown | committed renovate.json |
AUTO-GATE |
Generate a CycloneDX 1.7 (ECMA-424) SBOM for every release artifact and every container image; validate against schema before signing; fail the build on schema failure. Rejected SPDX-only: CycloneDX 1.7's attestation + VEX + ML-BOM modelCard shapes fit our SLSA and AI-repo needs better; both are NTIA/CISA/EU-CRA accepted.
syft . -o cyclonedx-json=sbom.cdx.json # or: cdxgen -t python -o sbom.cdx.json
cyclonedx-cli validate --input-file sbom.cdx.json --input-version v1_7
grype sbom:sbom.cdx.json --fail-on high # gate the SBOM itselfRequired component fields: name, version, purl, hashes (SHA-256). AI/RAG repos additionally emit an ML-BOM modelCard component for each model dependency (ties to the model-card lint in AI-EVALUATION-STANDARD.md).
Trivy/Grype image scan, threshold standardized to CRITICAL,HIGH portfolio-wide. A CRITICAL-only threshold is raised; any repository that builds an image must run the scan.
trivy image --severity CRITICAL,HIGH --ignore-unfixed --exit-code 1 $IMAGEKeyless cosign (OIDC via the workflow identity; Fulcio cert + Rekor log; no long-lived keys). Target SLSA Build L2 minimum (signed provenance, hosted build, consumer-verifiable); L3 for public/critical packages via slsa-framework/slsa-github-generator (signing key inaccessible to build steps, isolated ephemeral build). Provenance predicate https://slsa.dev/provenance/v1.
# release signing — keyless, OIDC identity
permissions:
id-token: write # OIDC
contents: write # attach release assets
attestations: write
steps:
- uses: sigstore/cosign-installer@<40-char-sha> # v3.x
- run: cosign sign --yes $IMAGE
- run: cosign attest --yes --predicate sbom.cdx.json --type cyclonedx $IMAGE
- uses: actions/attest-build-provenance@<40-char-sha> # v2.x (SLSA L2 + Source Track)
with: { subject-path: 'dist/*' }Caching is disabled in any job that signs/publishes/generates provenance (cache poisoning violates SLSA isolation). Concurrency group on release jobs. Both rules cross-reference CI-CD-STANDARD.md.
Deployment-side: consumers verify before install.
cosign verify --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
--certificate-identity-regexp='^https://github.com/<org>/<repo>/' $IMAGE
slsa-verifier verify-artifact --source-uri github.com/<org>/<repo> dist/<artifact>ossf/scorecard-action nightly + on PRs to default branch; SARIF to Code Scanning.
| Scorecard check | Target | Gate |
|---|---|---|
Pinned-Dependencies [SEC-31] |
≥ 9/10 | AUTO-GATE |
Token-Permissions [SEC-32] |
10/10 | AUTO-GATE |
Dangerous-Workflow [SEC-33] |
10/10 | AUTO-GATE |
Branch-Protection [SEC-34] |
≥ 8/10 | AUTO-GATE |
Signed-Releases [SEC-35] |
10/10 (.intoto.jsonl on last 5) |
AUTO-GATE (release repos) |
Vulnerabilities [SEC-36] |
10/10 | AUTO-GATE |
| Aggregate [SEC-37] | ≥ 8/10 | AUTO-GATE |
| Monthly Scorecard report committed [SEC-38] | present, dated | REVIEW-GATE |
Owned in detail by CI-CD-STANDARD.md; the supply-chain-load-bearing minimums repeated here so a repo author can't miss them:
| Control | Target | Measured by | Gate |
|---|---|---|---|
Top-level permissions [CICD-02] |
contents: read, per-job escalation only |
present in every workflow | AUTO-GATE |
| Cloud creds [CICD-05, CICD-06, CICD-07] | OIDC only, no long-lived secrets, sub scoped to repo:…:environment:… |
audit-log alert on new long-lived secret | AUTO-GATE |
| Workflow SAST [CICD-19] | zizmor required check on any PR touching .github/workflows/ |
merge-blocked on high/critical | AUTO-GATE |
persist-credentials: false [SEC-39] |
on every actions/checkout |
zizmor / grep | AUTO-GATE |
No direct push to main [CQ-37, CICD-15] |
branch ruleset blocks direct admin pushes; designated maintainer bypass is PR-only and break-glass; normal/solo review disposition follows CQ §7.1 | committed ruleset export + bypassed PR attestation | AUTO+REVIEW-GATE |
CODEOWNERS routes .github/workflows/ + security-critical files [CICD-17] |
routing present in both profiles; approval mechanics are owned by CICD-18 / CQ §7.1 | committed CODEOWNERS |
AUTO-GATE |
| Branch ruleset + CODEOWNERS as committed artifacts [CICD-12] | present | repo files | REVIEW-GATE |
# zizmor as a required check
permissions: { contents: read }
jobs:
zizmor:
if: contains(toJSON(github.event.pull_request.changed_files), '.github/workflows/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<40-char-sha> # v4.2.2
with: { persist-credentials: false }
- uses: zizmorcore/zizmor-action@<40-char-sha> # v0.xEvery repo's docs/RESPONSIBLE-TECH-AUDITS.md §F records, with no blanks:
- ASVS level (L1 / L2 / L3 /
N/A (reason)). - Container scanning: enabled, or
N/A (no Dockerfile). - SBOM + signing: enabled on release, or
N/A (not a release-producing repo)— note this is rare; libraries published to PyPI do produce releases. - Secret-management policy (OSPS-BR-07.02): storage, rotation, revocation — committed once, reviewed annually.
- VEX for any unfixable HIGH/CRITICAL dependency CVE.
An N/A is a declared decision with a one-line reason, reviewed like any other. A blank is a defect.
- A duplicated or renamed package records one canonical repository before controls are applied, preventing drift and double-counting. REVIEW-GATE.
- A nested project declares its package path or exposes repository-root verification so portfolio tooling cannot silently skip it. AUTO-GATE.
Ordered, all blocking; make verify runs the local-runnable subset byte-for-byte:
1. secret scan → gitleaks pre-commit (Gate 1) + CI diff (Gate 2)
2. SAST → semgrep ci (HIGH/CRITICAL) ; CodeQL nightly + required on main
3. SCA → pip-audit / npm audit --audit-level=high / osv-scanner (no || true)
4. authz tests → 403-on-unauth + BOLA/BOPLA (L2 repos)
5. container scan → trivy image --severity CRITICAL,HIGH (Dockerfile repos)
6. workflow SAST → zizmor (PRs touching .github/workflows/)
7. supply chain (rel) → SBOM (CycloneDX 1.7) -> validate -> cosign sign + attest -> SLSA provenance
8. scorecard → ossf/scorecard-action, aggregate >= 8, criticals at target
9. (human) review → threat-model sign-off, VEX, secrets policy, pentest (L2)
Last verified: 2026-06-21 · Recheck cadence: per OWASP ASVS, SLSA, CycloneDX, OpenSSF Scorecard/Baseline, and Sigstore release; and immediately on any disclosed GitHub Actions supply-chain compromise. Confirm current tool versions (gitleaks, TruffleHog, Scorecard, cosign, CycloneDX spec) at build time.