Skip to content

chore(security): bootstrap sscs-bootstrapper and close the gaps it found - #6

Open
p4gs wants to merge 2 commits into
mainfrom
chore/sscsb-bootstrap
Open

p4gs wants to merge 2 commits into
mainfrom
chore/sscsb-bootstrap

Conversation

@p4gs

@p4gs p4gs commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

What

First-time sscsb init bootstrap of this repository (47 controls, 32 enabled), plus a fix for every real finding the first sscsb verify produced.

sscsb verify before → after: 23 pass / 3 fail / 3 degraded / 3 info27 pass / 1 fail / 2 degraded / 2 info (15 controls deliberately disabled in both).

Strongest-tool-only policy

Surface Tool Why
Credentials TruffleHog only (controls.secrets.gitleaks = false) TruffleHog verifies a candidate against the issuing provider, so a finding is a live credential, not a regex hit. gitleaks-action also needs a paid license for org-owned repos.
SAST CodeQL + OpenGrep, both on Two engines with genuinely different reach: OpenGrep is rule-driven and fast, CodeQL is interprocedural. Both genuinely support this repo's languages (Rust + Actions).

sscsb's secrets-scan.yml template emits the Gitleaks job regardless of the gitleaks config key, so that job was removed by hand — it must be removed again after any sscsb init re-run (noted in the file header).

The archived semgrep/semgrep-action@v1 job in security.yml is retired. OpenGrep is Semgrep's open fork and resolves the same registry rulesets, so p/rust and p/security-audit were added to the OpenGrep run: same rule coverage, now on a pinned cosign-verified binary with SARIF uploaded to code scanning. Verified locally — Ran 19 rules on 66 files: 0 findings, exit 0.

Findings fixed

  1. CODEOWNERS was completely inert. All ten rules named @grcengineering/security, a team that does not exist. GET /repos/grcengineering/cvm/codeowners/errors returned Unknown owner for every line, so no rule bound to anyone — including the branch-protection "require review from Code Owners" path. Rules now name @p4gs and additionally cover the new .sscsb/ policy surface. Swap back if a real, publicly-visible @grcengineering/security team with write access is ever created.
  2. deny.toml could not be parsed at all. cargo-deny 0.19.4: error[unexpected-value] ... unmaintained = "warn". The v2 schema turned unmaintained into a scope, and removed vulnerability, unlicensed, copyleft. The deny job — a required status check on main — could therefore never have run. Rewritten to the v2 schema, same intent, and tightened (unmaintained = "all"). cargo deny check now exits 0.
  3. Two Renovate config files. sscsb init wrote renovate.json5 next to the pre-existing renovate.json; Renovate aborts with "Found multiple config file names" when both exist. Merged into renovate.json5 (old cool-down rules preserved, isVulnerabilityAlert migrated to the modern vulnerabilityAlerts block); renovate.json deleted.
  4. Dockerfile base images were tag-pinned, not digest-pinned. Both cgr.dev/chainguard/rust:latest-dev and cgr.dev/chainguard/static:latest now carry sha256 digests (resolved 2026-09-12).
  5. CodeQL analysed actions only — not this workspace's Rust. rust added to the matrix with build-mode: none.
  6. release.yml and security.yml were unhardened. harden-runner added to all ten jobs, persist-credentials: false on every checkout, --locked on cargo install.
  7. security-insights.yml REPLACE-ME administrator placeholder filled in; reporting channel aligned with SECURITY.md.

Left open, with reasons

  • branch-protection — FAIL. main is missing Require signed commits; everything else already passes (required PRs, force-push block, required checks, deletion protection, stale-review dismissal, 1 approving review, strict up-to-date). This is a repo-admin remote write, deliberately not made by an agent. sscsb harden branch-protection --apply is not the fix here — it only edits rulesets, and this repo uses classic branch protection, so its plan output is no ruleset targets this branch — skipped. The one-line fix is:
    gh api -X POST repos/grcengineering/cvm/branches/main/protection/required_signatures
    Two Scorecard sub-gaps (code-owner review, last-push approval) need a second reviewer and cannot be satisfied by a solo maintainer.
  • signing-model — DEGRADED. Remaining items are account-level attestations only the maintainer can truthfully assert (github-web vigilant mode + phishing-resistant MFA, Codespaces GPG verification, Claude GitHub App authorization). The agent-claude-code lane wants a distinct agent commit identity, which conflicts with this operator's standing one-signer directive — a policy call, not a config gap.
  • scorecard — DEGRADED. No published Scorecard results yet; scorecard.yml runs on push to the default branch, so this resolves once this PR lands.

Merge note

main requires a PR and 1 approving review with enforce_admins: true. GitHub does not let an author approve their own PR, so this needs a second reviewer or a temporary protection adjustment by an admin.

AI-Assisted: true
AI-Tool: Claude Code
AI-Model: claude-opus-5
AI-Role: draft

🤖 Generated with Claude Code

https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm

Runs `sscsb init` on this repo for the first time (47 controls, 32 enabled)
and fixes every real finding the first verify produced.

Policy: strongest-tool-only.
* Credential scanning is TruffleHog alone — `controls.secrets.gitleaks = false`.
  TruffleHog verifies a candidate against the issuing provider, so a finding is
  a live credential rather than a regex hit; gitleaks-action additionally needs
  a paid license for org-owned repositories. sscsb's secrets-scan.yml template
  emits the gitleaks job regardless of that key, so the job is removed by hand
  and must be removed again after any `sscsb init` re-run.
* SAST is CodeQL + OpenGrep, both enabled. The archived
  semgrep/semgrep-action@v1 job in security.yml is retired; OpenGrep is
  Semgrep's open fork and resolves the same rulesets, so `p/rust` and
  `p/security-audit` were added to the OpenGrep run — same coverage, on a
  pinned cosign-verified binary, with SARIF uploaded to code scanning.

Findings fixed:
* CODEOWNERS was entirely inert. All ten rules named @grcengineering/security,
  a team that does not exist — GitHub's codeowners/errors endpoint returned
  "Unknown owner" for every line, so no rule bound to anyone and the
  code-owner-review path had nothing behind it. Rules now name @p4gs and cover
  the new .sscsb/ policy surface.
* deny.toml could not be parsed by cargo-deny 0.19.4 at all: `unmaintained`
  became a scope rather than a severity in the v2 schema, and `vulnerability`,
  `unlicensed` and `copyleft` were removed. The `deny` job in security.yml —
  a required status check on main — could therefore never have run. Rewritten
  to the v2 schema with the same intent, and tightened: unmaintained = "all".
* Two Renovate config files (pre-existing renovate.json plus the renovate.json5
  sscsb wrote) make Renovate abort with "Found multiple config file names".
  Merged into renovate.json5; renovate.json deleted.
* Dockerfile base images were tag-pinned, not digest-pinned. Both now carry
  sha256 digests (resolved 2026-09-12).
* CodeQL analysed `actions` only, not this workspace's Rust. Added `rust` to
  the matrix with build-mode none.
* release.yml and security.yml: harden-runner added to all ten jobs,
  persist-credentials: false on every checkout, --locked on cargo install.
* security-insights.yml REPLACE-ME administrator filled in.

Verify: 23 pass / 3 fail / 3 degraded before, 27 pass / 1 fail / 2 degraded
after. Remaining: branch-protection (required signed commits is a repo-admin
remote write, left for a human), signing-model (account-level attestations
only the maintainer can truthfully make), scorecard (no published results until
this lands on the default branch).

AI-Assisted: true
AI-Tool: Claude Code
AI-Model: claude-opus-5
AI-Role: draft

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

`sscsb scan --local` writes the checks that are only observable on a developer
machine — which key git signs with, whether the installed hooks actually block,
what is in the package-trust baseline, which scanners are on PATH — and signs
the result in the `sscsb-scan-record` namespace with the repository's own
committed trust anchor (.sscsb/policy/allowed_signers). A clone cannot observe
any of it, so without this record the public directory scores those controls
`unverified` and the repository reads provisional however good its posture is.

Signed by the human-class signer this repo commits as approved
(SHA256:prXatGO56nl8Or4JdDSzIIcj8hZE1jBxnFaXZOnAPDQ); ssh-keygen -Y verify
returns Good for the namespace. Record asserts 28 pass / 1 fail / 1 degraded at
12094c5 — the residual FAIL is branch-protection's missing 'Require signed
commits', a repo-admin remote write left for a human.

Not submitted to the public directory: `sscsb scan --local --submit` is a
separate, deliberate publication step.

AI-Assisted: true
AI-Tool: Claude Code
AI-Model: claude-opus-5
AI-Role: draft

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants