Skip to content

chore(security): bootstrap SSCS Bootstrapper baseline and harden CI - #42

Open
p4gs wants to merge 3 commits into
mainfrom
chore/sscsb-baseline
Open

p4gs wants to merge 3 commits into
mainfrom
chore/sscsb-baseline

Conversation

@p4gs

@p4gs p4gs commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Summary

First-time SSCS Bootstrapper bootstrap for the repository behind grc.engineering, then every gap the first sscsb verify reported, fixed.

sscsb verify went 5 FAIL / 2 DEGRADED → 1 FAIL / 1 DEGRADED (22 → 28 PASS of 47 controls). The two that remain are not code in this repo, and are described at the bottom.

Policy set before the first verify

Credential detection is TruffleHog only. TruffleHog verifies a candidate against the issuing provider, so a finding is a live credential rather than a regex match. Gitleaks is regex-only, and gitleaks-action additionally requires a paid GITLEAKS_LICENSE for organisation-owned repositories — this one is owned by grcengineering — so the second job would have been licence-gated noise, not added detection. GitHub secret scanning with push protection is separately enabled on the repo.

.sscsb/config.toml sets [controls.secrets] gitleaks = false and sscsb verify honours it (gitleaks: disabled in config), but the secrets-scan.yml template does not render from that keysscsb init writes the gitleaks job and .gitleaks.toml regardless. Both are removed here by hand so config and CI tell the same story. Worth noting upstream: a future sscsb init in this repo will re-create .gitleaks.toml.

SAST is CodeQL + OpenGrep, both enabled, alongside the pre-existing Semgrep Cloud job (a third, genuinely different rule set — the org's own semgrep.dev policy).

Gaps found and fixed

Control Before Fix
actions-audit FAIL actions/checkout, actions/setup-python, actions/cache in ci.yml and semgrep.yml were on mutable tags — all pinned to full commit SHAs (staying within their current major; Renovate now proposes the bumps)
workflow-audit-extended FAIL Same pins, plus permissions narrowed and persist-credentials: false on semgrep.yml
harden-runner FAIL Added to ci.yml and semgrep.yml. semgrep.yml had to come off its container: semgrep/semgrep (a mutable image) and onto the runner host, because Harden-Runner cannot monitor a container job; Semgrep is now pip-installed at a pinned version
dependency-pinning FAIL The four pip install steps in ci.yml took whatever PyPI served at build time — now version-pinned
codeql PASS, but hollow The matrix analysed actions only and had never read this site's JavaScript. Now actions + javascript-typescript with security-extended
security-insights INFO REPLACE-ME administrator placeholder filled; SECURITY.md written — the generated file pointed at a policy file that did not exist anywhere in the repo or the org .github repo
commit-signing DEGRADED Owner's signing key recorded in .sscsb/policy/signers.toml
branch-protection FAIL master removed from protected_branches (this repo has no such branch, so it was reporting on nothing); the remote ruleset is handled separately, below

ci.yml keeps its checkout credential deliberately, and says so in a comment: mkdocs gh-deploy pushes the built site to gh-pages over the origin remote and needs it. It is the only job in the repo that does; every other workflow sets persist-credentials: false. The contents: write grant moved from the top level (where all jobs inherited it) down to that one job.

CodeQL default setup is being switched off

GitHub's code-scanning default setup was configured for actions only, on a weekly schedule. Default setup and an advanced configuration cannot both be enabled, so it was set to not-configured immediately before this branch was pushed and .github/workflows/codeql.yml takes over: both languages, security-extended, on every push and PR, with pinned actions and Harden-Runner.

Honest values, not green checkmarks

hardware_backed = false is recorded for the owner's signing key. It is an ed25519 key held in a 1Password vault and used through the 1Password SSH agent with device authentication on every signature — strong custody, but not a secure-element ssh-ed25519-sk credential. require_hardware_backed is left on rather than lowered to force a pass, so sscsb verify keeps reporting the advisory note. Closing it means provisioning a YubiKey-resident signing key, which is an owner decision.

Dormant by design: the release-* workflows and deploy-gate.yml trigger on release, and this repo cuts none. They are inert until it does.

Observation, not changed here

docs/projects/nthpartyfinder/index.html loads a stylesheet from fonts.googleapis.com at page-render time. That is a live third-party dependency on a public site with no SRI possible (the response varies by user agent). It is left alone on purpose — that page is in-flight design work and this is a security-baseline PR, not a design change.

AI Provenance Declaration

  • AI generated or assisted with code in this PR
  • AI generated or assisted with tests in this PR
  • AI introduced or suggested new dependencies in this PR
  • AI generated or assisted with documentation in this PR

AI tool(s)/model(s) used (if any): Claude Code (claude-opus-5)

Human review performed on AI-generated parts (what/how): every workflow change verified by running the tools that gate them — actionlint clean across all 14 workflows, sscsb verify before and after, sscsb sast (0 findings), sscsb scan (0 findings), and opengrep against the p/javascript, p/secrets, p/github-actions, p/ci and p/xss registry packs. Action SHAs resolved from the GitHub API against their tags, not written from memory. Pending owner review of the two open items below.

Dependency Changes

  • No new dependencies

No package-manager dependencies are added. The pinned pip install versions in ci.yml are the same packages that were already being installed unpinned; pinning them is the fix.

Merge Policy Reminder

Merges to protected branches must be signed by an approved human key, and AI involvement is declared above, so the merge wants a Reviewed-by: trailer.


Still open — deliberately

  1. signing-model stays DEGRADED. Its four open lanes are account- and machine-level, not repository-level, and two of them would contradict a standing owner directive: agent-claude-code wants a distinct AI signing identity, which this account retired on purpose in favour of one signer everywhere, and cloud-claude wants a repo-level .claude/settings.json carrying that same separate identity. github-web (vigilant mode, phishing-resistant MFA) and codespaces (GPG verification) have no read APIsscsb signing setup … --confirm only records a dated human attestation, and attesting facts about the owner's account that cannot be verified is exactly the shortcut this baseline exists to prevent. These are the owner's to close.

  2. sscsb harden cannot see this repo's ruleset. The existing "Protect" ruleset targets ~DEFAULT_BRANCH; sscsb harden looks for a literal branch name and reports no ruleset targets this branch — skipped. The ruleset is updated directly through the API instead, as a separate step on this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm

`sscsb init` on the repo behind grc.engineering, then fix every gap the first
`sscsb verify` reported. Verify went 5 FAIL / 2 DEGRADED -> 1 FAIL / 1 DEGRADED
(the remaining two are remote-GitHub and account-level, handled separately).

Policy set before the first verify:
  - Credential detection is TruffleHog ONLY. It verifies a candidate against the
    issuing provider, so a finding is a live credential rather than a regex
    match; gitleaks-action also needs a paid licence for org-owned repos.
    [controls.secrets] gitleaks = false, and the gitleaks job and .gitleaks.toml
    are removed by hand because the init template does not render from that key.
  - SAST is CodeQL + OpenGrep, both enabled, plus the pre-existing Semgrep Cloud.

Existing workflows hardened (actions-audit, workflow-audit-extended,
harden-runner, dependency-pinning all FAIL -> PASS):
  - ci.yml: top-level `contents: write` -> `contents: read` with a job-scoped
    write grant; harden-runner added; checkout/setup-python/cache pinned to
    full commit SHAs; the four `pip install` steps version-pinned. checkout
    keeps its credential deliberately and says why -- `mkdocs gh-deploy` pushes
    to gh-pages over the origin remote.
  - semgrep.yml: moved off the mutable `semgrep/semgrep` container onto the
    runner host so harden-runner can attach (it cannot monitor a container job),
    Semgrep version pinned, actions SHA-pinned, persist-credentials: false.

CodeQL was analysing `actions` only -- it had never read this site's
JavaScript. The matrix now covers actions + javascript-typescript with
security-extended. That requires GitHub's code-scanning *default setup* to be
turned off, which is done on the repo alongside this change.

Also: SECURITY.md written (security-insights.yml referenced a file that did not
exist); security-insights placeholders filled; the owner's signing key recorded
in .sscsb/policy/signers.toml with hardware_backed = false, which is the honest
value and leaves commit-signing's advisory note standing rather than asserting
a secure-element key that does not exist; protected_branches narrowed to the
one branch this repo actually has.

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.

Comment thread .github/workflows/release-slsa.yml
p4gs and others added 2 commits September 12, 2026 12:01
`sscsb scan --local` at 361c9d3, after the branch ruleset on `main` was brought
up to standard (required PRs, required signed commits, required status checks,
strict up-to-date, on top of the deletion + force-push rules that were already
there). Result: 0 failed, 1 degraded, 29 of 47 controls passing.

The one degraded control is `signing-model`, whose remaining lanes are
account- and machine-level rather than repository-level; see PR description.

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