Skip to content

sec(sscsb): close every verify gap but branch protection and signing-model; keep TruffleHog + Gitleaks for secrets - #22

Open
p4gs wants to merge 7 commits into
mainfrom
sec/sscsb-hardening
Open

p4gs wants to merge 7 commits into
mainfrom
sec/sscsb-hardening

Conversation

@p4gs

@p4gs p4gs commented Sep 12, 2026 •

Copy link
Copy Markdown
Collaborator

Update (2026-10-06): Gitleaks is back, by owner decision — it runs alongside TruffleHog rather than being replaced by it. TruffleHog only finds credentials for providers it has a detector for, and with --results=verified,unknown it reports a match only when the provider confirms it live or the verification attempt errors; it drops matches it could not verify, including those from detectors that have no verifier. Gitleaks matches on shape, so it also catches generic secrets such as passwords, internal tokens and keys for services TruffleHog has no detector for. c29097a restores the Gitleaks CI job, the .sscsb/config.toml toggle, and .gitleaks.toml, and updates SECURITY.md, .sscsb/POSTURE.md, and the best-practices badge line; 8daf695 updates .sscsb/POSTURE.md again, the ci.yml comment, and the checked-in branch ruleset — adding gitleaks as a required status check next to trufflehog; 06295a4 then corrects the ruleset's README, which still said 13 required checks, to 14. The Secret scanning section below reflects all of that. The CodeQL section also reflects 074a85b (python added to the matrix after GitHub default setup was turned off), and 3fe5566 patches rustls for RUSTSEC-2026-0285 (see Also).

What this does

sscsb verify on this repo went from 19 PASS / 8 FAIL / 1 DEGRADED to 27 PASS / 1 FAIL / 1 DEGRADED, measured on b50b020 (2026-09-12). That count was not re-run on the current head (aef7fc3), which also carries 074a85b (CodeQL python), 3fe5566 (rustls RUSTSEC-2026-0285 patch), and the Gitleaks restore and follow-up docs (c29097a, 8daf695, 06295a4, aef7fc3).

Secret scanning: TruffleHog + Gitleaks, same as main

This branch briefly made TruffleHog the only secret scanner (2026-09-12): gitleaks off in .sscsb/config.toml, its CI job gone from secrets-scan.yml, .gitleaks.toml deleted. The owner reversed that call on 2026-10-06. TruffleHog only finds credentials for providers it has a detector for, and with --results=verified,unknown it reports a match only when the provider confirms it live or the verification attempt errors; it drops matches it could not verify, including those from detectors that have no verifier. Gitleaks matches on shape, so it also catches generic secrets such as passwords, internal tokens and keys for services TruffleHog has no detector for — which is why the two run together instead of one replacing the other. Net against main, the scanners themselves are unchanged: secrets-scan.yml, .gitleaks.toml, and the [controls.secrets] block in .sscsb/config.toml are byte-identical to main. The secret-scanning changes are the ci.yml duplicate-job removal below, a wording update to the best-practices-badge line (TruffleHog + Gitleaks → TruffleHog, verified+unknown, + Gitleaks), the ruleset entries, a new Secret scanning bullet in SECURITY.md, and the secret-scanning content in .sscsb/POSTURE.md (the "Known sscsb defect" section and the "Deliberate tool choices" bullet).

What does change, and stays changed, is removing the duplicate TruffleHog job in ci.yml: secrets-scan.yml runs the same tool on a superset of triggers (push to main + every PR, vs push to main + only PRs targeting main for the ci.yml job) and with --results=verified,unknown instead of --only-verified, so a candidate whose verification attempt errored (status unknown) is also reported — the one extra class this picks up over --only-verified. The action wrapper is also pinned to a newer release (v3.95.9 vs v3.95.2), though both default to pulling the latest TruffleHog Docker image — so the scanner binary itself isn't actually older on either side, only the action commit is. Nothing is lost by deleting the duplicate.

The checked-in ruleset (.github/rulesets/main-branch-protection.json) lists both secrets-scan.yml jobs, trufflehog and gitleaks, as required status checks, so once it is applied a finding from either blocks a merge.

SAST keeps three engines on purpose

codeql.yml is added (rust + actions + python, buildless) alongside the existing OpenGrep and Semgrep workflows. CodeQL does interprocedural taint tracking over a compiled database and surfaces classes an OpenGrep default ruleset does not express at all — complementary coverage, the same shape as running TruffleHog and Gitleaks together for secrets rather than picking one.

Resolved. GitHub code-scanning default setup has been turned off (gh api repos/grcengineering/OCEAN/code-scanning/default-setup → not-configured) because it cannot coexist with an advanced-setup workflow. codeql.yml analyses the same three languages default setup covered (rust, actions, python; python added in 074a85b), and Analyze (actions) / Analyze (python) / Analyze (rust) all passed on 8daf695.

Five controls were failing for one shared reason

secrets, commit-signing, ai-trailers, ai-dep-gate and package-trust all failed because core.hooksPath pointed at .git/hooks, which holds only git's samples — so no sscsb hook had ever run. The shims were also tracked at mode 0644, and git silently skips a non-executable hook, so a fresh clone would have hit the same silent no-op even with the path set. Both fixed; the shims are 0755 in the tree now.

dependency-pinning

  • pip install pyyaml in the parity job → hash-locked pip install --require-hashes -r scripts/requirements-parity.txt.
  • Both Dockerfile stages digest-pinned.

Pinning stage 2 surfaced a live break: cgr.dev/chainguard/cc-dynamic is no longer anonymously pullable (403 FORBIDDEN on the anonymous pull token), so that build could not have succeeded on a runner without Chainguard credentials. Swapped to glibc-dynamic, which is anonymously pullable and carries everything this stage needs — verified by listing the image layers: libc.so.6, libgcc_s.so.1, ld-linux-*.so, ca-certificates.crt.

Also

  • release.yml: top-level write grants no longer leak into build and sbom; attestations: write dropped outright (no job in that workflow uses actions/attest*).
  • LICENSE (Apache-2.0) added — Cargo.toml and README.md both declared it and the README linked to a file that did not exist.
  • SECURITY.md added — security-insights.yml already pointed at it.
  • Stale "REPLACE-ME / TODO" note removed from security-insights.yml (there were no placeholders left), flipping it INFO → PASS.
  • .sscsb/out/ gitignored (generated SBOM/scan output).
  • rustls 0.23.37 → 0.23.45 and rustls-webpki 0.103.13 → 0.103.15 for RUSTSEC-2026-0285 (3fe5566). cargo-vet imports.lock refreshed from audit sources already imported; no exemptions added.

What is NOT closed

Written down in .sscsb/POSTURE.md rather than left to be scrolled past.

branch-protection FAILs. A ruleset lives in GitHub's settings, not the tree, and sscsb harden branch-protection cannot write this one — it looks for a ruleset naming the branch literally, and ours targets the ~DEFAULT_BRANCH alias, so the planner reports "no ruleset targets this branch" and changes nothing. The three missing rules (required PRs, required signed commits, required status checks) are checked in as an importable ruleset:

gh api -X PUT repos/grcengineering/OCEAN/rulesets/14413947 \
  --input .github/rulesets/main-branch-protection.json
sscsb verify branch-protection

signing-model stays DEGRADED. Three lanes need an owner attestation of GitHub web settings that have no read API. The agent-claude-code lane is deliberately not converged: it asks for a distinct agent identity, and this owner runs one-signer-everywhere with AI involvement recorded through commit trailers instead.

Gitleaks can false-positive on a brand-new branch's first push. Under sscsb 0.4.0, the pre-push range scan treats a branch with no remote counterpart as having no lower bound, so it scans the tip's entire history instead of just the commits being pushed. On this repository that reported 16 findings, every one a false positive in code that had already been on main for months, while the actual outgoing range was clean under both scanners. Fixed upstream in p4gs/sscs-bootstrapper#77, not yet merged.

Verified, not assumed

Measured on b50b020 (2026-09-12); not re-run on the current head (aef7fc3), which also carries 074a85b (CodeQL python), 3fe5566 (rustls RUSTSEC-2026-0285 patch), and the Gitleaks restore and follow-up docs (c29097a, 8daf695, 06295a4, aef7fc3):

  • sscsb verify 27 PASS / 1 FAIL / 1 DEGRADED / 3 INFO / 15 disabled
  • sscsb sast — 0 findings
  • sscsb scan — 1 low (DS-0026, Dockerfile HEALTHCHECK), below the high fail threshold
  • the hash-locked pip install resolved and installed in a clean venv
  • both image digests resolved from the live registries
  • the glibc-dynamic layer contents were listed, not assumed

On the current head (aef7fc3): the Secret Scan run passed both trufflehog and gitleaks, and the CodeQL run passed Analyze (actions) / Analyze (python) / Analyze (rust).

🤖 Generated with Claude Code

https://claude.ai/code/session_012hwWMsTiv9Uf2gNbMh75uk

…is the only secret scanner

`sscsb verify` went 19 PASS / 8 FAIL / 1 DEGRADED -> 27 PASS / 1 FAIL / 1 DEGRADED.

Secret scanning is now TruffleHog ONLY. gitleaks is off in .sscsb/config.toml, its
CI job is gone from secrets-scan.yml, and .gitleaks.toml is deleted. TruffleHog
validates a candidate against the issuing provider, so `verified` means a live
credential; gitleaks matches entropy and regex and cannot make that call. Running
both bought a second scan and a second allowlist file to keep honest, for no
detection the stronger tool did not already make. The same reasoning retires the
weaker duplicate TruffleHog job in ci.yml: secrets-scan.yml runs the same tool on a
superset of triggers with a newer pinned release and --results=verified,unknown
instead of --only-verified, so nothing is lost by deleting the duplicate.

SAST keeps THREE engines on purpose. codeql.yml is added (rust + actions, buildless)
alongside the existing OpenGrep and Semgrep workflows: CodeQL does interprocedural
taint tracking over a compiled database and surfaces classes an OpenGrep default
ruleset does not express at all. That is not the kind of redundancy gitleaks was.

Five controls (secrets, commit-signing, ai-trailers, ai-dep-gate, package-trust) were
failing for one shared reason: core.hooksPath pointed at .git/hooks, which holds only
git's samples, so NO sscsb hook had been running. The shims were also tracked mode
0644 — git silently skips a non-executable hook, so a fresh clone would have had the
same silent no-op even with the path set. Both fixed; the shims are 0755 now.

dependency-pinning: the parity job's `pip install pyyaml` becomes a hash-locked
`--require-hashes` install, and both Dockerfile stages are digest-pinned. Pinning
stage 2 surfaced a live break — cgr.dev/chainguard/cc-dynamic is no longer
anonymously pullable (403 FORBIDDEN on the anonymous pull token), so that build could
not have succeeded on a runner without Chainguard credentials. Swapped to
glibc-dynamic, which is anonymously pullable and carries everything this stage needs
(verified in the image: libc.so.6, libgcc_s.so.1, ld-linux, ca-certificates.crt).

Also: release.yml's top-level write grants no longer leak into `build` and `sbom`,
and `attestations: write` is dropped outright because no job in that workflow uses
actions/attest*. LICENSE (Apache-2.0) is added — Cargo.toml and README both declared
it and README linked to a file that did not exist. SECURITY.md is added, which
security-insights.yml already pointed at. The stale "REPLACE-ME / TODO" note is
removed from security-insights.yml (there were no placeholders left), flipping it
INFO -> PASS.

What is NOT closed, with reasons, is written down in .sscsb/POSTURE.md rather than
left to be scrolled past:

- branch-protection FAILs because a ruleset lives in GitHub's settings, not the tree,
  and `sscsb harden` cannot write this one (it looks for a ruleset naming the branch
  literally; ours targets ~DEFAULT_BRANCH). The three missing rules are checked in as
  an importable ruleset under .github/rulesets/, with the gh command beside it.
- signing-model stays DEGRADED. Three lanes need an owner attestation of GitHub web
  settings that have no read API. The agent-claude-code lane is deliberately NOT
  converged: it asks for a distinct agent identity, and this owner runs one-signer,
  everywhere, with AI involvement recorded through commit trailers instead.

Verified, not assumed: sscsb verify 27/1/1; sscsb sast 0 findings; sscsb scan 1 low
(Dockerfile HEALTHCHECK, below the `high` fail threshold); the hash-locked pip
install resolved and installed in a clean venv; both image digests resolved from the
live registries; the glibc-dynamic layer contents were listed, not assumed.

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
@socket-security

socket-security Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedpypi/​pyyaml@​6.0.3100100100100100

View full report

p4gs and others added 6 commits October 5, 2026 21:28
…coverage

GitHub's default CodeQL setup scanned actions, python and rust. This advanced
config replaces it (default setup is now off, since the two cannot coexist), so
scripts/hth_parity.py would otherwise drop out of CodeQL coverage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hwWMsTiv9Uf2gNbMh75uk
rustls 0.23.37 -> 0.23.45 and rustls-webpki 0.103.13 -> 0.103.15. The advisory was published after this branch's last CI run, so cargo-deny, cargo-audit and osv-scanner went red on the next one.

cargo-vet: imports.lock refreshed from audit sources this repo already imports, which now carry a Bytecode Alliance delta audit for rustls 0.23.37 -> 0.23.45. No exemptions added. audits.toml loses one comment line via cargo vet fmt, because the refreshed imports no longer carry that publisher's display name.

AI-Assisted: true
AI-Tool: Claude Code
AI-Model: Opus 5.5
AI-Role: draft
AI-Dependency-Review: approved
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hwWMsTiv9Uf2gNbMh75uk
This PR had switched secret scanning to TruffleHog-only. Per owner decision,
Gitleaks is restored to run alongside TruffleHog: Gitleaks catches generic
secrets and unverifiable credentials that TruffleHog's --results=verified,unknown
filter drops, so the two scanners are complementary rather than redundant.

AI-Assisted: true
AI-Tool: Claude Code
AI-Model: Sonnet 5
AI-Role: draft
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hwWMsTiv9Uf2gNbMh75uk
…n the ruleset

Follows the restore of Gitleaks alongside TruffleHog. POSTURE.md and the ci.yml
comment no longer describe TruffleHog-only as the goal, and the checked-in
main-branch ruleset now lists `gitleaks` as a required check next to `trufflehog`,
so a Gitleaks finding blocks a merge once the ruleset is applied.

AI-Assisted: true
AI-Tool: Claude Code
AI-Model: Opus 5.5
AI-Role: draft
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hwWMsTiv9Uf2gNbMh75uk
8daf695 added `gitleaks` to main-branch-protection.json, but the README next to
it still said 13 checks, all taken from the 34117a7 pull request. Say 14, and
where the added one came from.

AI-Assisted: true
AI-Tool: Claude Code
AI-Model: Opus 5.5
AI-Role: draft
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hwWMsTiv9Uf2gNbMh75uk
The comments said a TruffleHog finding means a live credential, or called the
filter verified-only. With --results=verified,unknown it also reports results
whose verification errored, which nobody has confirmed live. Separately, it
never reports generic secrets, since it has no detector for them, or keys it
cannot verify. Wording only; no scanner, rule or job changes.

AI-Assisted: true
AI-Tool: Claude Code
AI-Model: Opus 5.5
AI-Role: draft
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hwWMsTiv9Uf2gNbMh75uk
@p4gs p4gs changed the title sec(sscsb): close every verify gap but branch protection; TruffleHog is the only secret scanner sec(sscsb): close every verify gap but branch protection and signing-model; keep TruffleHog + Gitleaks for secrets Oct 6, 2026

This branch has not been deployed

No deployments
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.

1 participant