{
  "markdown": "# SSCS Bootstrapper (`sscsb`)\n\n[![CI](https://github.com/p4gs/sscs-bootstrapper/actions/workflows/ci.yml/badge.svg)](https://github.com/p4gs/sscs-bootstrapper/actions/workflows/ci.yml)\n[![CodeQL](https://github.com/p4gs/sscs-bootstrapper/actions/workflows/codeql.yml/badge.svg)](https://github.com/p4gs/sscs-bootstrapper/actions/workflows/codeql.yml)\n[![SAST (OpenGrep)](https://github.com/p4gs/sscs-bootstrapper/actions/workflows/sast-opengrep.yml/badge.svg)](https://github.com/p4gs/sscs-bootstrapper/actions/workflows/sast-opengrep.yml)\n[![Vulnerability Scan](https://github.com/p4gs/sscs-bootstrapper/actions/workflows/vuln-scan.yml/badge.svg)](https://github.com/p4gs/sscs-bootstrapper/actions/workflows/vuln-scan.yml)\n[![Secret Scan](https://github.com/p4gs/sscs-bootstrapper/actions/workflows/secrets-scan.yml/badge.svg)](https://github.com/p4gs/sscs-bootstrapper/actions/workflows/secrets-scan.yml)\n[![SBOM](https://github.com/p4gs/sscs-bootstrapper/actions/workflows/sbom.yml/badge.svg)](https://github.com/p4gs/sscs-bootstrapper/actions/workflows/sbom.yml)\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/p4gs/sscs-bootstrapper/badge)](https://securityscorecards.dev/viewer/?uri=github.com/p4gs/sscs-bootstrapper)\n[![SLSA Build Level 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev/spec/v1.0/levels#build-l3)\n[![Renovate enabled](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://docs.renovatebot.com/)\n\n> `sscsb` now dogfoods its own generated output: `.github/workflows/`,\n> `.sscsb/`, `.gitleaks.toml`, `.trufflehog.yaml`, and `renovate.json5` are\n> committed, not gitignored, so every badge above tracks a workflow that\n> actually runs in *this* repo's GitHub Actions on the default branch — not a\n> static claim. They read pending/blank until this change reaches `main`, then\n> self-populate from real runs. For the live local picture at any commit, run\n> `sscsb verify` / `sscsb report` directly.\n\nSoftware supply chain security for solo developers and small teams who write code\nwith AI — bootstrapped into a git repository in one command.\n\n`sscsb` **orchestrates** best-in-class tools. It does not reimplement them. It\ndetects what you have, configures it, invokes it, parses its output, and gates on\nthe result. TruffleHog and Gitleaks find the secrets. Syft builds the SBOM. Trivy\nand OSV-Scanner find the vulnerabilities. Cosign signs. slsa-verifier verifies.\n`sscsb` is the policy engine and the glue, and it is honest about which of those\ntools are actually present on your machine.\n\n```\nsscsb init      # config, hooks, policies, SHA-pinned CI templates\nsscsb status    # every control: enabled? tool installed?\nsscsb verify    # prove each enabled control actually works, here, now\nsscsb report    # control → SLSA / SSDF / CRA coverage\n```\n\n## Why this exists\n\nThe threat model changed. An AI agent can add a dependency you have never heard\nof, paste a credential into a config file, or write a `curl … | sh` install step —\nin a commit that looks exactly like every other commit. The controls that catch\nthis already exist and are excellent. Wiring them together correctly — pinned, least-privilege,\nfail-closed, verified — is the part nobody has time for.\n\nThat wiring is what this is.\n\nThree ideas run through the whole design:\n\n**Humans, CI, and AI never share a key.** Every signing identity is classified,\nand only `human`-class identities may sign a commit that lands on a protected\nbranch. The gate keys on the signer's **class**, not on presence in\n`allowed_signers`: with `agent-signing` off (the default) an AI-class key is not\nemitted into that file at all, and with it on the key *is* emitted — deliberately,\nso an agent's commit verifies as a genuine agent signature on a feature branch.\nEither way the protected-branch answer is the same. An AI can draft anything; it\ncannot land it.\n\n**Every control is toggleable, and off means off.** One `.sscsb/config.toml`,\ngenerated from the control registry itself, so the config and the code cannot\ndrift apart. Secure defaults on. If you disable a control, its code does not run.\n\n**A missing tool degrades loudly, never silently.** If Trivy isn't installed,\n`sscsb verify` says so, tells you the pinned version and how to install it, and\nreports `DEGRADED` — it does not quietly pass. Nothing here claims to protect you\nwith a tool that isn't there.\n\n## Threat & Control Model\n\nKept deliberately high level — SDLC-stage threats, not individual MITRE ATT&CK\ntechniques. Every arrow below maps to one of the five phases above; every\ncontrol listed is a real, `verify`-able check, not an aspiration.\n\n```mermaid\nflowchart LR\n    A[\"Attacker /<br/>compromised AI agent\"]\n    S[\"1 . Source<br/>(commit)\"]\n    D[\"2 . Dependencies<br/>(packages)\"]\n    B[\"3 . Build and<br/>Provenance\"]\n    C[\"4 . Code<br/>Analysis\"]\n    R[\"Release<br/>Artifact\"]\n    P[\"5 . Continuous<br/>Posture\"]\n    U[\"Consumer\"]\n\n    S --> D --> B --> C --> R --> P --> U\n\n    A -->|\"T1 unsigned/unreviewed commit\"| S\n    A -->|\"T2 leaked secret\"| S\n    A -->|\"T3 malicious/typosquat/vulnerable dependency\"| D\n    A -->|\"T4 tampered build,<br/>exfiltrated CI creds\"| B\n    A -->|\"T5 injected vuln,<br/>risky workflow pattern\"| C\n    A -->|\"T6 unverifiable provenance\"| R\n    A -->|\"T7 drift: new CVE<br/>after release, unnoticed\"| P\n\n    style A fill:#ff7676,stroke:#940000,color:#000\n    style R fill:#9bb8ff,stroke:#0035b3,color:#000\n    style U fill:#9bb8ff,stroke:#0035b3,color:#000\n    style S fill:#f9ebb9,stroke:#fb9400,color:#000\n    style D fill:#f9ebb9,stroke:#fb9400,color:#000\n    style B fill:#f9ebb9,stroke:#fb9400,color:#000\n    style C fill:#f9ebb9,stroke:#fb9400,color:#000\n    style P fill:#f9ebb9,stroke:#fb9400,color:#000\n```\n\n| ID | Threat | Stage | Example | `sscsb` control | Phase |\n|----|--------|-------|---------|------------------|-------|\n| T1 | Unsigned or unreviewed commit lands on a protected branch | Source | An attacker — or a compromised AI agent — pushes a commit no human ever reviewed or cryptographically attested | Hardware-backed, human-only commit signing (AI keys are refused, not just discouraged); branch protection audit; AI-provenance commit trailers | 1 |\n| T2 | Secret or credential committed to source | Source | An API key gets pasted into a config file and committed | Pre-commit + pre-push secret blocking (TruffleHog + Gitleaks) | 1 |\n| T3 | Malicious, typosquatted, or known-vulnerable dependency introduced | Dependencies | An AI agent (or a human) adds a package one edit away from a popular name, or one with an unpatched CVE | SBOM (Syft); vulnerability scanning (Trivy + OSV-Scanner V2); package-trust checks (does it exist, is it a look-alike, did a human approve it); Renovate with digest pinning | 2 |\n| T4 | Build process tampered with, or CI credentials exfiltrated | Build | A malicious build step alters what's actually compiled, or a workflow leaks a long-lived cloud credential | Harden-Runner egress control on every job; short-lived credentials (Octo STS); every CI template SHA-pinned to a 40-char commit digest | 1 & 3 |\n| T5 | Vulnerability injected into first-party code, or a risky Actions pattern shipped | Code | A command-injection bug lands in application code; a workflow misuses `pull_request_target` against untrusted input | OpenGrep SAST (Semgrep selectable) in pre-commit and CI; CodeQL on PRs and default branch; workflow auditing for `pull_request_target` misuse, credential persistence, secret echo | 4 |\n| T6 | Released artifact's provenance can't be verified — no proof of what built it, or from which commit | Build → Release | A binary is downloaded with nothing to confirm it came from the claimed pipeline and source | Keyless signing (Cosign/Fulcio/Rekor); SLSA Build L3 provenance via the official generator, checked with `slsa-verifier` before anything is promoted | 3 |\n| T7 | A shipped artifact becomes vulnerable after release and the drift goes unnoticed | Post-release | A CVE is disclosed in a dependency months after release; nobody re-scans what's already out | Dependency-Track continuous SBOM management; GUAC supply-chain graph; OpenVEX so \"not exploitable\" is an auditable, first-class answer | 5 |\n\n## Install\n\n```sh\nbrew install p4gs/p4gs/sscsb\n```\n\nOr download a release directly. Every file in an `sscsb` release carries a\nsignature — but not all of them from the same signer. A release publishes 17\nfiles. 16 of the 17 are signed at *our* tag by\n`.github/workflows/release.yml` — 8 keyless-signed into a `*.sigstore.json`\nbundle, plus those 8 bundles, each of which *is* such a signature. The 17th, the\n`*.intoto.jsonl` envelope, is signed by the SLSA generator's own workflow at the\ngenerator's own tag, not by ours — `slsa-verifier --builder-id` is what checks\nthat signature, and pinning our `release.yml` identity against it would be\npinning the wrong signer. That is the path on which the verification recipe in\n[docs/skill.md](docs/skill.md) is actually reachable:\n\n```sh\nTAG=v0.3.1                                  # the version you MEANT to install\ngh release download \"$TAG\" --repo p4gs/sscs-bootstrapper --dir sscsb-release\ncd sscsb-release\ncosign verify-blob sscsb-\"$TAG\"-x86_64-unknown-linux-gnu.tar.gz \\\n  --bundle sscsb-\"$TAG\"-x86_64-unknown-linux-gnu.tar.gz.sigstore.json \\\n  --certificate-identity \"https://github.com/p4gs/sscs-bootstrapper/.github/workflows/release.yml@refs/tags/${TAG}\" \\\n  --certificate-oidc-issuer https://token.actions.githubusercontent.com\ntar -xzf sscsb-\"$TAG\"-x86_64-unknown-linux-gnu.tar.gz\ninstall -m 0755 sscsb /usr/local/bin/sscsb\n```\n\nThat proves which pipeline produced those bytes. It is not a judgement of their\ncontent — see [docs/skill.md](docs/skill.md), which states both claims at their\nreal strength.\n\nOne gap, stated plainly: `SKILL.md` is not a release asset yet. `release.yml`\nstages and signs it, but the first release whose assets include it is the first\ntag cut after this change lands — the worked example above uses a platform\ntarball precisely because that is what every published tag actually carries.\nSteps in `docs/skill.md` that name `SKILL.md` become runnable from the next\nrelease onward; every other step works today.\n\nOr from source — note that a source build has no release asset, no Cosign bundle\nand no attestation, so none of the verification above applies to it:\n\n```sh\ncargo build --release\ninstall -m 0755 target/release/sscsb /usr/local/bin/sscsb\n```\n\nThen, in any git repository:\n\n```sh\nsscsb init\nsscsb deps baseline     # bless the dependencies you already have\nsscsb verify\n```\n\n`sscsb init` is idempotent: it writes what's missing and keeps what exists. Re-run\nit after an upgrade; it will not clobber your edits.\n\nExternal tools are **pinned** — `sscsb tools` prints the exact version `sscsb`\nexpects and where each one was found. Nothing installs `latest`, and nothing is\ninstalled behind your back.\n\n## The five phases\n\nEach phase is a coherent layer, and each is independently useful. Full detail —\nwhat each control does, which tool backs it, how it fails, how to turn it off — is\nin the per-phase docs.\n\n| Phase | What it gets you | Docs |\n|-------|------------------|------|\n| **1 — Commit integrity** | Secrets blocked pre-commit and pre-push. Hardware-backed, human-only signing enforced on protected branches. Branch protection checked. Actions audited for mutable refs and over-broad permissions. AI-provenance commit trailers, with extra gates when AI adds a dependency or a shell command. | [docs/phase-1.md](docs/phase-1.md) |\n| **2 — Know your dependencies** | CycloneDX SBOMs (Syft). Vulnerability scanning (Trivy + OSV-Scanner V2). Scorecard. Renovate with digest pinning. Package-trust: does this package *exist*, is it one edit away from a popular name, did a human approve it? Endpoint exposure (Bumblebee): is a known-compromised package, MCP server, editor extension or agent skill already installed on this machine? | [docs/phase-2.md](docs/phase-2.md) |\n| **3 — Provenance** | Keyless signing (Cosign/Fulcio/Rekor). SBOM and provenance attestations bound to artifact digests. SLSA Build L3 provenance via the official generator, verified with slsa-verifier before anything is promoted. GitHub-native build-provenance and SBOM attestations, verifiable with nothing but `gh`. Short-lived credentials (Octo STS). Harden-Runner on every job. | [docs/phase-3.md](docs/phase-3.md) |\n| **4 — Code analysis** | OpenGrep SAST by default (Semgrep selectable), in pre-commit and CI. CodeQL on PRs and the default branch. Extended workflow auditing: `pull_request_target` misuse, credential persistence, secret echo, known-risky actions. | [docs/phase-4.md](docs/phase-4.md) |\n| **5 — Continuous posture** | Dependency-Track for continuous SBOM management. GUAC for the supply-chain graph. OpenVEX so \"not exploitable\" is a first-class, auditable answer instead of a muted alert. A machine-readable control → SLSA/SSDF/CRA map behind `sscsb report`. | [docs/phase-5.md](docs/phase-5.md) |\n\nTwo more docs cover the parts people get wrong:\n\n- **[docs/signing.md](docs/signing.md)** — YubiKey / `ed25519-sk` setup, the\n  human/CI/AI key separation, and the WSL2 USB problem (and its fixes).\n- **[docs/ai-provenance.md](docs/ai-provenance.md)** — commit trailers, the AI\n  dependency and shell-command gates, and cryptographic receipts.\n- **[docs/skill.md](docs/skill.md)** — the bundled agent skill\n  (`sscsb skill install | print | check`), and how to verify a copy of it: what\n  the in-binary comparison does and does not prove, and the release-asset recipe\n  that does not depend on `sscsb` being honest.\n- **[docs/example-walkthrough.md](docs/example-walkthrough.md)** — a complete\n  bootstrap on a fresh repo, with the real terminal output, including the hooks\n  actually blocking a planted secret and an unsigned protected-branch commit.\n- **[docs/qa-corpus-2026-08.md](docs/qa-corpus-2026-08.md)** — what happened\n  when `sscsb` was run against twenty other repositories across two orgs, what\n  that surfaced, and what the fixes measurably changed.\n\n## Controls\n\n44 controls, each with an id you can `enable`, `disable`, and `verify`:\n\n```sh\nsscsb status                      # what's on, what's installed\nsscsb disable grype               # off means off — the code will not run\nsscsb enable dependency-track\nsscsb verify secrets commit-signing\nsscsb verify --strict             # DEGRADED also exits non-zero — the check could not be performed\n```\n\nSecure defaults are on. Off by default are the ones that need infrastructure you\nmay not have (Dependency-Track, GUAC, ORAS), a paid or unreleased tool\n(Sighthound, Socket), or that overlap something already on (Grype duplicates\nTrivy for most people; Witness overlaps the SLSA generator).\n\n## CI templates\n\n`sscsb init` installs workflow templates into `.github/workflows/`, one per\nenabled control that has a CI half. They are **SHA-pinned to 40-character commit\ndigests**, least-privilege (`permissions:` on every job, `contents: read` by\ndefault), and every job runs Harden-Runner.\n\nThere is exactly one action that is *not* SHA-pinned:\n`slsa-framework/slsa-github-generator`, which **must** be referenced by tag —\nthat is a requirement of its own trust model, and slsa-verifier validates the\nbuilder ref. The exception is called out in the template and encoded in the\nauditor as a single named exception rather than a general hole.\n\n`sscsb` audits its own templates: a test asserts that **every** shipped workflow\npasses `sscsb`'s own Actions audit. The tool that tells you to pin your actions\ncannot ship an unpinned one.\n\n## Verification, and what \"verified\" means here\n\n`sscsb verify` runs each enabled control against the actual repository and reports\none of:\n\n| Outcome | Meaning |\n|---------|---------|\n| `PASS` | The control is present and demonstrably working. |\n| `FAIL` | The control is on, the tooling is there, and the repository does not satisfy it. |\n| `DEGRADED` | The control is on, and the check **could not be performed** — so its posture is unknown, not fine. A missing tool is the common cause, and it tells you which one at which pinned version, but it is not the only one: no GitHub remote, an empty signer policy, or an incomplete setup all degrade with every tool present. Under `--strict` this exits non-zero. |\n| `disabled` | You turned it off. It did not run. (Rendered lowercase, unlike every other verdict — anything matching on these strings has to special-case it.) |\n| `INFO` | Reported for context; not a gate. |\n\nThere are no TODO stubs, no mock integrations, and no control that claims a tool\nworks without running it. Where a tool is absent, `sscsb` says so.\n\n### The local lane (`sscsb scan --local`)\n\nAbout a third of the controls are checks on a *development environment* —\nwhich key git will sign with, whether the installed hooks actually block, what\nis in the package-trust baseline, which scanners are on your `PATH`. Cloning a\nrepository tells you none of that, so the public directory scores them\n`unverified` and leaves them out of every denominator. That is why a repository\nwith a perfect posture can still read **provisional**.\n\n`sscsb scan --local` runs the full control set where those checks *are*\nobservable, writes a directory scan record to `.sscsb/scan-record.local.json`,\nand signs it with the key git already signs your commits with — `gpg.format`,\n`user.signingkey` and `gpg.ssh.program`, so a 1Password- or hardware-backed key\nworks untouched. Both files are **committed**: the submission is a pointer, and\nthe directory reads them — and the trust anchor — out of your public\nrepository. The detached signature verifies with `ssh-keygen -Y verify` against\n`.sscsb/policy/allowed_signers` **as committed at the recorded commit**.\n\n```sh\nsscsb scan --local                # scan, sign, write both files\ngit add .sscsb/scan-record.local.json .sscsb/scan-record.local.json.sig\ngit commit -m 'chore: publish a signed local scan record' && git push\nsscsb scan --local --submit       # …then point the directory at them\n```\n\nA verified local record proves that *a holder of a key this repository commits\nas an approved signer asserted this result at commit X*. It does **not** prove\nyour CI produced it — only the action lane does that. So where a repository\nscan could observe a control, the directory requires an independent record to\nagree with this one before the row counts at all; where it could not (the\nlocal-environment controls), this record stands on its own. Sources that\ndisagree score the control as a gap.\n\nOnly a `class = \"human\"` signer in `.sscsb/policy/signers.toml` is granted the\nscan namespace. A `ci`- or `ai`-class key cannot assert a local record: `sscsb\nscan --local` refuses to run under one, and a signature it produces in that\nnamespace verifies against no principal in the committed anchor. The full contract — the one normative\nstatement of the namespace, the paths, the record shape and the command —\nis in [docs/local-scan.md](docs/local-scan.md).\n\n## Platforms\n\nmacOS, Linux, and WSL. The hooks are POSIX shell shims that delegate to the Rust\nbinary, so they work under git's own shell everywhere, including Git for Windows.\nThe one genuine platform limitation is hardware-key signing under WSL2, which\ncannot reach USB FIDO2 devices directly — [docs/signing.md](docs/signing.md)\ncovers both workarounds.\n\n## Navigating the code\n\n`openwiki/` is a generated, evidence-grounded wiki over this repository — 41 pages\norganised by what each control does at runtime rather than by source directory. Every\nmaterial claim in it cites the narrowest line range that establishes it, so a page is\ncheckable against the code rather than merely readable.\n\nStart at [`openwiki/quickstart.md`](openwiki/quickstart.md). Three pages carry most of\nthe load:\n\n- [The control registry and the verdict contract](openwiki/control-model/registry-and-outcomes.md)\n  — the five verdicts, why `DEGRADED` is not `PASS`, and how verdicts become exit codes.\n- [Process execution and the tool exit-code contract](openwiki/runtime/process-execution.md)\n  — why a killed scanner must not read as a clean one, and the argument-injection guard on `git`.\n- [Signer policy](openwiki/commit-integrity/signer-policy.md) and\n  [the server-side policy gate](openwiki/commit-integrity/server-side-policy-gate.md)\n  — the two halves of the AI-cannot-sign invariant, and why only one of them holds\n  against a determined actor.\n\n## Development\n\n```sh\ncargo build --release\ncargo test               # unit + integration + library + tool-orchestration suites\ncargo clippy --all-targets -- -D warnings\ncargo fmt --check\ncargo llvm-cov --ignore-filename-regex '(main\\.rs|cli\\.rs)'   # gate: 95% lines / 94% functions (see ci.yml)\n```\n\nThe suites run the **real tools** where they are installed (a real `slsa-verifier`\nverification against a real signed release artifact, a real OpenGrep scan, real\nGitleaks and TruffleHog runs against a planted secret) and exercise the\ndegrade paths by masking `PATH` where they are not.\n\n`main.rs` and `cli.rs` are excluded from the coverage floor: they are argument\nparsing and printing over library functions that are themselves covered. Every\ncontrol's logic lives in the library, including `sscsb init` itself.\n\nNo secret-shaped string exists anywhere in this repository's history. The test\nthat proves the hooks block a planted credential constructs that credential at\nruntime, by concatenation. The hooks are run against this repository, by this\nrepository's CI.\n",
  "bytes": 21535,
  "sha": "1ef59782d9e921a389c689023d1deeaaab3f28697ad252e460e29f1f8ad3d0ed",
  "repo_slug": "p4gs/sscs-bootstrapper",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_p4gs_sscs_bootstrapper_openwiki_index_md_36d6567c/readme"
}