Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 91 additions & 14 deletions .github/workflows/secrets-scan.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# sscsb: CI secret-scanning redundancy (TruffleHog + Gitleaks).
# Local hooks block first; this catches anything that slips past (e.g. web UI commits).
# Both jobs scan the commits under review (the PR or push range), not the full history.
#
# Two independent engines, deliberately. TruffleHog VERIFIES a candidate credential
# against the issuing provider, so it distinguishes "this looks like a key" from
# "this key is live right now" (`--results=verified,unknown` keeps the unverifiable
# candidates in scope). Gitleaks runs alongside it: independent probes showed it
# catches generic secrets (e.g. `api_key = "<random>"`) and unverifiable keys that
# TruffleHog's verified/unknown filter still drops — a detection class TruffleHog
# alone does not cover.
#
# All actions pinned to full commit SHAs (sscsb actions-audit enforces this).
name: Secret Scan
on:
Expand All @@ -23,7 +33,8 @@ jobs:
with:
fetch-depth: 0
persist-credentials: false
# Upstream README suggests @main; sscsb pins the release SHA instead (v3.95.9).
# Upstream README suggests @main; sscsb pins the release SHA instead (see the
# version comment on the `uses:` line, kept current by Dependabot).
- name: TruffleHog scan
uses: trufflesecurity/trufflehog@6f3c981e7b77f235fd2702dd74af25fc4b72bf11 # v3.96.0
with:
Expand All @@ -33,26 +44,92 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
# The gitleaks *CLI* (MIT) is used directly rather than
# gitleaks/gitleaks-action, which requires a paid GITLEAKS_LICENSE for
# organization repositories. This runs the same engine the local sscsb
# pre-commit/pre-push hooks use, against the tap's own .gitleaks.toml.
- name: Install gitleaks

# gitleaks/gitleaks-action is commercially licensed: for organization-owned
# repos it hard-fails before scanning unless a GITLEAKS_LICENSE secret exists
# ("[org] is an organization. License key is required." -- src/index.js).
# This org has no license, so that action never actually scanned anything.
# The gitleaks BINARY is MIT-licensed OSS, so we run it directly.
# actions-audit mandates full-SHA pinning for ACTIONS; the equivalent for a
# downloaded binary is a pinned version plus a checksum verified BEFORE exec.
- name: Install gitleaks (pinned release, checksum-verified before execution)
env:
GITLEAKS_VERSION: "8.30.1"
GITLEAKS_SHA256: "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb"
run: |
curl -fsSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
-o /tmp/gitleaks.tgz
tar -xzf /tmp/gitleaks.tgz -C /tmp gitleaks
sudo install -m 0755 /tmp/gitleaks /usr/local/bin/gitleaks
set -euo pipefail
tarball="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
base_url="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}"
workdir="$(mktemp -d)"

curl --fail --silent --show-error --location --retry 3 --retry-delay 2 \
-o "${workdir}/${tarball}" "${base_url}/${tarball}"
curl --fail --silent --show-error --location --retry 3 --retry-delay 2 \
-o "${workdir}/checksums.txt" \
"${base_url}/gitleaks_${GITLEAKS_VERSION}_checksums.txt"

# 1. the digest pinned here must be the digest this release publishes
grep -Fxq "${GITLEAKS_SHA256} ${tarball}" "${workdir}/checksums.txt"
# 2. the bytes we downloaded must match that digest -- checked before exec
( cd "${workdir}" \
&& printf '%s %s\n' "${GITLEAKS_SHA256}" "${tarball}" | sha256sum -c - )

install_dir="${RUNNER_TEMP}/gitleaks-bin"
mkdir -p "${install_dir}"
tar -xzf "${workdir}/${tarball}" -C "${install_dir}" gitleaks
chmod +x "${install_dir}/gitleaks"
printf '%s\n' "${install_dir}" >> "${GITHUB_PATH}"

- name: Gitleaks scan
env:
# Context values are passed as env vars, never interpolated into the
# script body (GitHub Actions expression-injection hardening).
EVENT_NAME: ${{ github.event_name }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PUSH_BEFORE_SHA: ${{ github.event.before }}
PUSH_HEAD_SHA: ${{ github.sha }}
run: |
set -euo pipefail
gitleaks version
- name: Gitleaks scan (full history)
run: gitleaks git --config .gitleaks.toml --redact --no-banner .

null_sha="0000000000000000000000000000000000000000"
case "${EVENT_NAME}" in
pull_request) base_sha="${PR_BASE_SHA}"; head_sha="${PR_HEAD_SHA}" ;;
push) base_sha="${PUSH_BEFORE_SHA}"; head_sha="${PUSH_HEAD_SHA}" ;;
*) base_sha=""; head_sha="${PUSH_HEAD_SHA}" ;;
esac

# Scope = the commits under review. This is exactly the scope the
# licensed action scanned on push/pull_request events
# (src/gitleaks.js: --log-opts=--no-merges --first-parent BASE..HEAD);
# it is a scan-scope choice, not a rule/finding suppression.
if [ -n "${base_sha}" ] && [ "${base_sha}" != "${null_sha}" ] \
&& git cat-file -e "${base_sha}^{commit}" 2>/dev/null; then
log_opts="--no-merges --first-parent ${base_sha}..${head_sha}"
else
# No resolvable base (new branch / force push): scan the head commit.
# No --no-merges here: it could filter out a merge head and leave us
# silently scanning zero commits, which would pass without checking.
log_opts="-1 ${head_sha}"
fi
echo "Scanning commit range: ${log_opts}"

# --redact keeps any match out of the job log. Exit code is left at the
# gitleaks default (1 when leaks are found), so a leak fails this check.
gitleaks git \
--config .gitleaks.toml \
--redact \
--no-banner \
--verbose \
--log-opts="${log_opts}" \
.

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# sscsb: generated output (SBOMs, receipts, VEX), not policy
.sscsb/out/
52 changes: 52 additions & 0 deletions .sscsb/best-practices-badge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# OpenSSF Best Practices Badge — worksheet for grcengineering/homebrew-grcengineering

`sscsb` can't register the badge for you (it needs your GitHub OAuth), but it
CAN make earning the **passing** badge a copy-paste exercise: most passing
criteria are already satisfied by the controls sscsb installs. Register, then
paste the evidence below.

**Register:** https://www.bestpractices.dev/ → "Get Your Project Badge" →
sign in with GitHub → add `https://github.com/grcengineering/homebrew-grcengineering`.

**Scorecard link:** OpenSSF Scorecard's `CII-Best-Practices` check reads the
badge API for this repo, so earning the badge also lifts your Scorecard score.

## Passing criteria → evidence in THIS repository

sscsb generates this table from its stock control list. It has been corrected to
what this repository actually enforces: a tap is a distribution channel with no
build, no releases and no package dependencies of its own, so several stock rows
are genuinely not applicable here rather than pending. A worksheet that claims a
control the repo does not run is worse than no worksheet — it is the kind of
unearned assurance this whole tap exists to prevent.

| Criterion (passing level) | Evidence here | Status |
|---|---|---|
| Project has an OSS license | `LICENSE` (MIT) | ☑ |
| Public version-controlled source repo | GitHub `grcengineering/homebrew-grcengineering` | ☑ |
| Automated test suite runs in CI | `.github/workflows/tests.yml` (`brew test-bot`: `brew style`, `brew audit`, formula install/test) | ☑ |
| Static analysis (SAST) applied | `sast` (OpenGrep, `.github/workflows/sast-opengrep.yml`) + `codeql` (`actions` and `ruby` extractors) | ☑ |
| Vulnerability scanning of dependencies | n/a — the only dependency surface this repo owns is its GitHub Actions, which are SHA-pinned (`actions-audit`) and updated by Dependabot. There are no lockfiles or package manifests to scan; `vuln-scan` is off for that reason. | ☑ (n/a) |
| No unpatched medium+/high vulns | n/a, same reason as the row above | ☑ (n/a) |
| Secrets not committed; scanned | `secrets` — TruffleHog + Gitleaks at pre-commit, pre-push and in CI on the commits under review (`.github/workflows/secrets-scan.yml`) | ☑ |
| Vulnerability reporting process documented | `SECURITY.md` + private GitHub Security Advisories | ☑ |
| Cryptographic signing of releases | n/a — this tap publishes no releases. What it does instead is the harder half: `formula-integrity.yml` verifies UPSTREAM's Sigstore/SLSA signatures for every artifact a formula points at, pinned to the expected source repo and tag, on every PR, every push to `main`, and weekly. | ☑ (n/a) |
| Delivery over TLS / pinned deps | all actions SHA-pinned (`actions-audit`, `workflow-audit-extended`); all formula URLs are HTTPS with pinned `sha256` | ☑ |
| Change control (PRs, protected branch) | `branch-protection` verifies `main`: required PRs, signed commits, required status checks, no force-push, no deletion | ☑ |
| SBOM published | n/a — no build and no released artifact to describe. Upstream `nthpartyfinder` publishes the SBOM for the bytes this tap installs. | ☑ (n/a) |

### Known gaps (open, not waved through)

- **Approving reviews are not required on `main`.** Scorecard scores `Code-Review`
0 for this repo. The cause is structural, not neglect: a solo maintainer cannot
self-approve, so turning the requirement on would block all work. Remediation is
a second maintainer, then `sscsb harden branch-protection --require-reviews`.
- **The human signing key is device-auth-gated, not hardware-resident.** See
`.sscsb/policy/signers.toml`; `require_hardware_backed = true` stays set in
`.sscsb/config.toml` as the standing target, and the remediation is a YubiKey
`-sk` key.
- **The badge itself is not registered.** Registration needs the maintainer's
GitHub OAuth and cannot be automated from here.

_Generated by sscs-bootstrapper. Re-run `sscsb init` after deleting this file to
regenerate._
69 changes: 64 additions & 5 deletions .sscsb/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,25 @@ github_repo = "grcengineering/homebrew-grcengineering"

# ── Phase 1 — Local source integrity ──

# Secret scanning hooks: TruffleHog + Gitleaks block secrets at pre-commit and pre-push
# Secret scanning hooks: TruffleHog and Gitleaks both block secrets at pre-commit
# and pre-push.
#
# Both run, deliberately. TruffleHog VERIFIES a candidate credential against the
# issuing provider, so it can tell "this looks like a key" apart from "this key is
# live right now" (`--results=verified,unknown` keeps unverifiable candidates in
# scope). Gitleaks runs alongside it: independent probes showed it catches generic
# secrets (e.g. `api_key = "<random>"`) and unverifiable keys that TruffleHog's
# verified/unknown filter still drops — a detection class TruffleHog alone does
# not cover.
[controls.secrets]
enabled = true
trufflehog = true
gitleaks = true
pre_push_range_scan = true
# sscsb 0.4.0 writes the sample `.gitleaks.toml` whenever the `secrets` control is
# enabled. With `gitleaks = true` that file is live config for the engine actually
# running here, not an orphan — review and maintain it like any other scanner
# config; don't delete it.

# CommitSigningGuard: Hardware-backed, human-only signing enforced on protected branches at pre-push
[controls.commit-signing]
Expand All @@ -36,10 +49,12 @@ allowed_backends = ["github-app", "tpm", "fido2", "kms", "piv", "software"]
max_key_age_days = 90

# Five-environment signing model: Machine-wide signing posture: human enclave lane, distinct agent identity, cloud/web/Codespaces guidance
# Lane identities are not config keys in sscsb 0.4.0 — the human key lives in
# .sscsb/policy/signers.toml and the un-probeable lanes (cloud / web / Codespaces)
# are dated attestations in .sscsb/policy/signing-model.toml. Setting `agent` /
# `human_backend` here was silently ignored on every run.
[controls.signing-model]
enabled = true
agent = "claude-code"
human_backend = "secretive"

# Branch protection verification: Verify GitHub protected-branch rules (PRs, no force-push, signatures, checks)
[controls.branch-protection]
Expand Down Expand Up @@ -110,18 +125,62 @@ enabled = false
[controls.slsa-provenance]
enabled = false

# Provenance verification gates: slsa-verifier + cosign verification required before promote/deploy/publish
# GitHub artifact attestations / GitHub SBOM attestation: DISABLED, and the reason is
# structural rather than a deferral.
#
# Both controls install an `on: release published` workflow that builds something,
# attests the bytes, and verifies the attestation. This repository is a Homebrew TAP:
# it has no build, no tags and no releases (`gh release list` is empty, `git tag` is
# empty), and it never will — it ships Ruby metadata describing artifacts that are
# built, signed and attested in grcengineering/nthpartyfinder. Enabling them here
# would add two workflows that either never fire, or, if a tag were ever cut, attest
# a `git archive` tarball of the tap's own formula files. Attesting a stub is not
# provenance; it is a green check over a claim nobody makes.
#
# The provenance question a tap actually has to answer — "are the bytes this formula
# points at the ones upstream built?" — is answered by formula-integrity.yml, which
# downloads every (url, sha256) pair and verifies it against upstream's keyless
# signing identity with cosign and slsa-verifier, pinned to the expected source repo
# and tag, on every PR, every push to main, and weekly against already-merged
# formulae. The `provenance-verify` control below is the sscsb control that gates it.
#
# Re-enable both the day this repo builds and publishes an artifact of its own.
[controls.github-attestations]
enabled = false

[controls.sbom-attestation]
enabled = false

# Provenance verification gates: slsa-verifier + cosign verification required before
# promote/deploy/publish. Enabled — but note that `sscsb init` writes a
# `.github/workflows/deploy-gate.yml` for this control, and that workflow is
# unreachable in a tap: its `workflow_call` entry is only invoked by a `release.yml`
# this repo does not have, and its `workflow_dispatch` entry needs a published
# release tag (this repo has none). It is deliberately not committed; delete it if a
# future `sscsb init` puts it back. The verification that matters here runs in
# formula-integrity.yml against UPSTREAM's artifacts, which is what a distribution
# channel actually has to verify.
[controls.provenance-verify]
enabled = true
# Without this, `sscsb provenance verify` accepts provenance from ANY builder
# slsa-verifier trusts for the source repo — "something built this" rather than
# "the builder we expect built this". Pinned to the builder that actually produced
# the provenance the tap's formulae point at: read out of
# grcengineering/nthpartyfinder v1.8.3's multiple.intoto.jsonl
# (predicate https://slsa.dev/provenance/v0.2), not assumed.
builder_id = "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v2.1.0"

# Octo STS federation: Short-lived repo-scoped GitHub credentials replacing long-lived PATs
[controls.octo-sts]
enabled = false

# Harden-Runner: StepSecurity Harden-Runner egress/tamper monitoring in every workflow
# The egress policy itself is a per-workflow input (`with: egress-policy:`), not a
# config key sscsb 0.4.0 reads; setting `egress_policy` here was silently ignored.
# Every workflow in this repo runs harden-runner in `audit` mode — the audit output
# is the input to a future `block` allowlist.
[controls.harden-runner]
enabled = true
egress_policy = "audit"

# Witness (optional): Richer in-toto attestation capture and policy around build steps
[controls.witness]
Expand Down
48 changes: 48 additions & 0 deletions .sscsb/osps-baseline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# OSPS Baseline assessment — grcengineering/homebrew-grcengineering

The [OpenSSF Project Security Baseline](https://baseline.openssf.org/) is a set of
minimum, maturity-tiered security requirements. sscsb maps the controls it installs
to the Baseline's requirement families so you can see, and attest to, which the repo
already satisfies. Run `sscsb report` for the live, machine-readable version (the
OSPS column) and `sscsb report --format json` for evidence.

## Coverage by requirement family

| OSPS family | Intent | Covered by sscsb controls |
|---|---|---|
| **OSPS-AC** Access Control | protect the primary branch; least-privilege access | `branch-protection`, `commit-signing`, `gittuf` |
| **OSPS-BR** Build & Release | integrity/provenance of source and released assets | `sigstore-signing`, `slsa-provenance`, `github-attestations`, `sbom-attestation`, `provenance-verify`, `model-signing`, `actions-audit` |
| **OSPS-QA** Quality | dependency transparency, review, well-documented code | `sbom`, `renovate`, `package-trust`, `sast` |
| **OSPS-SA** Security Assessment | architecture/security documentation | `security-insights`, `scorecard` |
| **OSPS-VM** Vulnerability Management | reporting process + finding/fixing defects | `vuln-scan`, `codeql`, `fuzzing`, `openvex`, `security-insights` |
| **OSPS-DO** Documentation | user & security docs | `SECURITY.md` (installed), `security-insights` |
| **OSPS-LE** Legal | license present, DCO/CLA | `LICENSE` (confirm) |
| **OSPS-GV** Governance | roles, decision-making | maintainer-authored (`security-insights` roles) |

## What this table is, and is not, for this repository

The table above is sscsb's generic control-to-family mapping: it says which sscsb
control WOULD cover each family, not which are enabled here. Several named controls
are deliberately off in this tap because it has no build, no releases and no package
dependencies of its own — `sbom`, `vuln-scan`, `renovate`, `package-trust`,
`sigstore-signing`, `slsa-provenance`, `github-attestations`, `sbom-attestation`,
`fuzzing`, `openvex`, `model-signing`, `gittuf`. `sscsb status` is the live answer;
`.sscsb/config.toml` carries the reason beside each toggle.

What covers **OSPS-BR** here is not an attestation this repo produces but one it
CHECKS: `formula-integrity.yml` verifies upstream's Sigstore bundle or SLSA
provenance for every artifact a formula points at, pinned to the expected source
repository and tag, on every pull request, every push to `main`, and weekly against
already-merged formulae. For a distribution channel that is the load-bearing
Build & Release control; signing the tap's own Ruby files would attest nothing a
user consumes.

## Maturity note

sscsb's controls target the Baseline's **Level 1–2** technical requirements (the
machine-enforceable ones). Level-3 and the Documentation/Legal/Governance families
include human attestations sscsb cannot auto-satisfy — fill those in your
`security-insights.yml` and project docs.

_Generated by sscs-bootstrapper. Mappings use real OSPS family IDs from
github.com/ossf/security-baseline. Delete + re-run `sscsb init` to regenerate._
Loading
Loading