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
52 changes: 52 additions & 0 deletions .github/rulesets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Repository rulesets

`main-branch-protection.json` is the ruleset this repository's supply-chain policy
expects on the default branch. It is kept here because a ruleset lives in GitHub's
settings, not in the tree — so without a checked-in copy there is nothing to review,
diff, or restore.

## Current state vs. this file

`sscsb verify branch-protection` reports what is actually live. As of 2026-09-12 the
live ruleset (id `14413947`, "Branch protection", active, targeting `~DEFAULT_BRANCH`)
carried only `deletion` and `non_fast_forward`, and `sscsb verify` failed with
`MISSING required pull requests`, `MISSING required signed commits`, and
`MISSING required status checks`. This file adds exactly those three rules.

Note that `sscsb harden branch-protection` cannot write them here: it looks for a
ruleset whose `ref_name.include` names the branch literally, and this one targets the
`~DEFAULT_BRANCH` alias, so the planner reports "no ruleset targets this branch —
skipped" and changes nothing.

| Rule | Why |
|------|-----|
| `pull_request` (0 approvals required) | Nothing reaches `main` without a pull request, so every change has a reviewable record and CI runs against it before merge. 0 approvals is the solo-safe setting — a maintainer cannot approve their own PR, so requiring 1 would deadlock a single-maintainer repo. Raise it to 1 as soon as there is a second maintainer. |
| `required_signatures` | Every commit that lands on `main` is cryptographically attributable. |
| `required_status_checks` | The 13 checks listed must pass before merge. They were taken from checks that actually ran on a real pull request (`34117a7`), not guessed. `Behavioral Analysis (Socket.dev)` is deliberately excluded because it is conditional and reports `skipped`; `Test (nightly)` because a nightly-toolchain regression is not a reason to block a merge. |

`allowed_merge_methods` is `squash` and `rebase`, and that is not cosmetic. With
`required_signatures` active, GitHub signs the squashed or rebased commit with its
web-flow key. A merge-commit merge instead lands the PR's own commits on `main`
unchanged, so an unsigned commit from a contributor would be rejected at merge time.
Every commit currently on `main` is single-parent, so squash is already the norm here.

## Applying it

Either import it in the UI — **Settings → Rules → Rulesets → New ruleset → Import a
ruleset** — or update the existing ruleset in place:

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

Then confirm against the tool rather than the UI:

```sh
sscsb verify branch-protection
```

## Rolling it back

Re-import, or send a body whose `rules` array holds only `deletion` and
`non_fast_forward`. Rulesets are versioned by GitHub and nothing here is destructive.
50 changes: 50 additions & 0 deletions .github/rulesets/main-branch-protection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "Branch protection",
"target": "branch",
"enforcement": "active",
"bypass_actors": [],
"conditions": {
"ref_name": {
"include": ["~DEFAULT_BRANCH"],
"exclude": []
}
},
"rules": [
{ "type": "deletion" },
{ "type": "non_fast_forward" },
{ "type": "required_signatures" },
{
"type": "pull_request",
"parameters": {
"required_approving_review_count": 0,
"dismiss_stale_reviews_on_push": true,
"require_code_owner_review": false,
"require_last_push_approval": false,
"required_review_thread_resolution": false,
"allowed_merge_methods": ["squash", "rebase"]
}
},
{
"type": "required_status_checks",
"parameters": {
"strict_required_status_checks_policy": false,
"do_not_enforce_on_create": false,
"required_status_checks": [
{ "context": "Test (stable)" },
{ "context": "Lint" },
{ "context": "Security Audit" },
{ "context": "Supply Chain (cargo-vet)" },
{ "context": "Supply Chain (cargo-deny)" },
{ "context": "Build (ubuntu-latest)" },
{ "context": "Coverage (70% gate)" },
{ "context": "HTH Parity Validate" },
{ "context": "SAST (Semgrep)" },
{ "context": "opengrep" },
{ "context": "trufflehog" },
{ "context": "trivy" },
{ "context": "osv-scanner / osv-scan" }
]
}
}
]
}
27 changes: 10 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,23 +167,14 @@ jobs:
- run: cargo fmt --check

# -------------------------------------------------------------------------
# Secrets detection (TruffleHog)
# Secrets detection lives in secrets-scan.yml, not here.
# That workflow runs the same tool (TruffleHog) on a SUPERSET of these triggers
# (push to main + every pull_request, vs. pull_request targeting main here) with
# a strictly stronger configuration: a newer pinned release and
# `--results=verified,unknown` instead of `--only-verified`, so unvalidatable
# candidates are reported rather than dropped. The weaker duplicate that used to
# sit here was removed on 2026-09-12 — one control, one scanner, the strong one.
# -------------------------------------------------------------------------
secrets:
name: Secret Scanning (TruffleHog)
runs-on: ubuntu-latest
steps:
- uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
persist-credentials: false
- name: TruffleHog scan
uses: trufflesecurity/trufflehog@17456f8c7d042d8c82c9a8ca9e937231f9f42e26 # v3.95.2
with:
extra_args: --only-verified

# -------------------------------------------------------------------------
# SAST (Semgrep)
Expand Down Expand Up @@ -334,5 +325,7 @@ jobs:
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.12'
- run: pip install pyyaml
# Hash-locked install: pip rejects any artifact whose SHA-256 is not listed
# in the requirements file (sscsb dependency-pinning).
- run: pip install --require-hashes -r scripts/requirements-parity.txt
- run: python3 scripts/hth_parity.py --validate
54 changes: 54 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# sscsb: CodeQL — deep interprocedural analysis on PRs and the default branch.
#
# Two languages are analysed, and both are real source in this repo:
# rust — the OCEAN crate itself (build-mode: none, CodeQL's buildless Rust extractor)
# actions — the workflows under .github/workflows (every sscsb-bootstrapped repo has these)
#
# CodeQL is kept ALONGSIDE OpenGrep (sast-opengrep.yml), not instead of it: they
# model different vulnerability classes. OpenGrep is pattern/dataflow matching over
# a rule corpus; CodeQL does interprocedural taint tracking over a compiled database
# and finds classes an OpenGrep default ruleset does not express at all.
name: CodeQL
on:
push:
branches: ["main"]
pull_request:
schedule:
- cron: "45 3 * * 2"

permissions:
contents: read

jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
include:
- language: rust
build-mode: none
- language: actions
build-mode: none
steps:
- name: Harden runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
- name: Analyze
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
with:
category: "/language:${{ matrix.language }}"
13 changes: 9 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ on:
tags:
- 'v*'

# Least privilege at the top: every write grant is declared by the ONE job that
# needs it, so `build` and `sbom` no longer inherit contents/packages/id-token
# write. `attestations: write` was dropped outright — no job in this workflow
# uses actions/attest* (that lives in release-attest*.yml).
permissions:
contents: write
id-token: write
packages: write
attestations: write
contents: read

env:
CARGO_TERM_COLOR: always
Expand All @@ -22,6 +23,8 @@ jobs:
build:
name: Build (${{ matrix.name }})
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -159,6 +162,8 @@ jobs:
name: Generate SBOM
needs: [build]
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
Expand Down
100 changes: 6 additions & 94 deletions .github/workflows/secrets-scan.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# sscsb: CI secret-scanning redundancy (TruffleHog + Gitleaks).
# sscsb: CI secret scanning (TruffleHog).
# Local hooks block first; this catches anything that slips past (e.g. web UI commits).
#
# TruffleHog is the ONLY credential scanner wired here. It is the strongest option
# for this control because it VALIDATES a candidate against the issuing provider
# rather than only matching entropy/regex, so `verified` means a live credential.
# The former gitleaks job was removed on 2026-09-12 (see .sscsb/config.toml).
# All actions pinned to full commit SHAs (sscsb actions-audit enforces this).
name: Secret Scan
on:
Expand Down Expand Up @@ -28,96 +33,3 @@ jobs:
uses: trufflesecurity/trufflehog@27b0417c16317ca9a472a9a8092acce143b49c55 # v3.95.9
with:
extra_args: --results=verified,unknown

gitleaks:
runs-on: ubuntu-latest
steps:
- name: Harden runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false

# 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: |
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

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}" \
.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,6 @@ id_ed25519
.serena/memories/
# CocoIndex Code (ccc)
/.cocoindex_code/

# sscsb: generated output (SBOMs, scan records, receipts, VEX), not policy
.sscsb/out/
23 changes: 0 additions & 23 deletions .gitleaks.toml

This file was deleted.

Loading
Loading