diff --git a/.github/workflows/leak-guard.yml b/.github/workflows/leak-guard.yml new file mode 100644 index 0000000..ed8efb4 --- /dev/null +++ b/.github/workflows/leak-guard.yml @@ -0,0 +1,35 @@ +name: leak-guard + +# Self-contained public-repo leak gate. Public CI cannot reach the private +# tooling, so this encodes only the generic net: internal-infra references, +# tracked CLAUDE.md/.claude paths, and AI-attribution in commit messages. +# No project-specific reverse-engineering vocabulary lives here. + +on: [push, pull_request] + +jobs: + leak-guard: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Compute commit range + id: range + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + base="${{ github.event.pull_request.base.sha }}" + head="${{ github.event.pull_request.head.sha }}" + echo "range=$base..$head" >> "$GITHUB_OUTPUT" + else + before="${{ github.event.before }}" + after="${{ github.sha }}" + # New branch / first push: github.event.before is all-zeros. + if [ -z "$before" ] || [ "$before" = "0000000000000000000000000000000000000000" ]; then + echo "range=$after" >> "$GITHUB_OUTPUT" + else + echo "range=$before..$after" >> "$GITHUB_OUTPUT" + fi + fi + - name: Run leak-guard + run: bash ci/leak-guard.sh "${{ steps.range.outputs.range }}" diff --git a/.gitignore b/.gitignore index 96ef6c0..563c7ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,9 @@ /target Cargo.lock + +# session scratch — never track (may contain RE breadcrumbs) +scratch/ + +# internal agent context — never publish (leak-guard blocks both) +CLAUDE.md +.claude/ diff --git a/Cargo.toml b/Cargo.toml index ff36fd5..775ec0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "freemkv-keysources" -version = "0.30.0" +version = "0.31.0" edition = "2024" license = "AGPL-3.0-only" description = "Pluggable AACS key sources (keydb, online key service, mapfile) for libfreemkv. Each source looks a disc up and hands libfreemkv a Key; the library does all derivation." @@ -10,8 +10,12 @@ categories = ["multimedia"] [dependencies] # The crate provides the `KeySource` trait + `Key`/`DiscInputs` types these impls fill. -libfreemkv = "0.30" +libfreemkv = "0.31" # OnlineSource: POST disc inputs + samples to a key service over HTTP. ureq = { version = "2", features = ["json"] } serde_json = "1" base64 = "0.22" + +[profile.release] +lto = "thin" +codegen-units = 1 diff --git a/ci/leak-guard.sh b/ci/leak-guard.sh new file mode 100755 index 0000000..b44cb91 --- /dev/null +++ b/ci/leak-guard.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +# +# leak-guard.sh — self-contained public-repo leak gate. +# +# This is the LAST line of defense in CI. It is intentionally self-contained: +# public CI cannot reach the private tooling, so this script encodes ONLY the +# generic net — internal infrastructure references, agent-context files, and +# AI-attribution in commit messages. It deliberately contains NO project- +# specific reverse-engineering vocabulary (those words would themselves be a +# leak). The richer private scanner stays private. +# +# Fails (exit 1) if any of the following appear in the repo: +# 1. a tracked CLAUDE.md or .claude/ path (agent context — never public), +# 2. tracked file content matching the internal-infra net, +# 3. a commit message (in the given range) with AI attribution. +# +# Usage: +# leak-guard.sh [] +# optional git rev-list range to scan commit messages +# (e.g. "abc..def"). If omitted, commit-message scan is +# skipped (path + content checks always run). + +set -euo pipefail + +# Absolute path to this script, resolved before any cd, so we can exclude it +# from the content scan (it necessarily contains the detection patterns). +SELF_ABS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" + +REPO="$(git rev-parse --show-toplevel)" +cd "$REPO" + +fail=0 +note() { printf ' ✗ %s\n' "$1"; fail=1; } + +# Internal-infra net — GENERIC ONLY. This script ships in the public repo, so +# the patterns themselves must not name any org-specific identifier (doing so +# would itself leak the infra they guard). We catch the leak *class*: +# - RFC1918 private IPv4 ranges (10/8, 172.16/12, 192.168/16), +# - private/internal/non-routable TLDs (.internal/.local/.lan/.corp/.invalid), +# - docker.internal. +# The full org-specific net (literal hostnames, service names, repo paths, +# vendor tooling, …) lives ONLY in the private scanner and never ships here. +INFRA_RE='\b10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|\b172\.(1[6-9]|2[0-9]|3[01])\.[0-9]{1,3}\.[0-9]{1,3}|\b192\.168\.[0-9]{1,3}\.[0-9]{1,3}|\.internal\b|\.local\b|\.lan\b|\.corp\b|\.invalid\b|docker\.internal' +# Home-path net — GENERIC ONLY. Catches an absolute developer home path +# committed into a tracked file (a macOS /Users//… or Linux /home//… +# path). This names NO specific user — it matches the leak *class* (any home +# path), so the pattern itself reveals nothing org- or person-specific. A real +# leak (e.g. /Users/alice/Developer/x slipping into a public RELEASE.md) trips +# this regardless of whose machine it came from. The username segment is a +# literal-username class ([A-Za-z0-9._-]) so dynamic/templated paths that build +# the user at runtime — shell `/home/$USER/`, doc `/home//`, Rust +# `/home/{user}/` — do NOT false-positive; only a baked-in literal home leaks. +HOMEPATH_RE='/Users/[A-Za-z0-9._-]+/|/home/[A-Za-z0-9._-]+/' +# AI-attribution net (case-insensitive). "claude" matches only as a standalone +# word — NOT preceded by a dot/slash/alnum and NOT followed by .md — so legit +# mentions of CLAUDE.md / .claude/ in a commit message don't false-positive. +ATTR_RE='co-authored-by|generated with|🤖|(?) { if ($l =~ /$rx/) { print "$.: $&\n"; } } + ' "$1" "$2" 2>/dev/null +} + +# This script's own source necessarily contains the detection patterns (e.g. +# the regex tokens in INFRA_RE), so scanning it would always self-flag. Skip it. +SELF="$(git ls-files --full-name -- "$SELF_ABS" 2>/dev/null | head -1)" + +echo "── leak-guard: internal-infra references in tracked files ──" +while IFS= read -r f; do + case "$f" in *.png|*.jpg|*.jpeg|*.ico|*.gif|*.bin|*.crate|*.gz|*.zip|*.pdf) continue ;; esac + [ -n "$SELF" ] && [ "$f" = "$SELF" ] && continue + [ -f "$f" ] || continue + while IFS= read -r hit; do + [ -z "$hit" ] && continue + note "internal-infra reference: $f:$hit" + done < <(pcre_matches "$f" "$INFRA_RE") + while IFS= read -r hit; do + [ -z "$hit" ] && continue + note "[HOME-PATH] absolute home path: $f:$hit (no local home path may be committed to a public repo)" + done < <(pcre_matches "$f" "$HOMEPATH_RE") +done < <(git ls-files) + +RANGE="${1:-}" +if [ -n "$RANGE" ]; then + echo "── leak-guard: AI-attribution in commit messages ($RANGE) ──" + while IFS= read -r sha; do + [ -z "$sha" ] && continue + msg="$(git log -1 --format='%B' "$sha" 2>/dev/null || true)" + # Pass the pattern as an argument (not interpolated into a //) so the + # lookbehind char class and "/" don't break the regex. + hit="$(printf '%s' "$msg" | perl -e ' + my $re = $ARGV[0]; my $rx = qr/$re/i; + while (my $l = ) { if ($l =~ /($rx)/) { print "$1\n"; last; } } + ' "$ATTR_RE" | head -1 || true)" + [ -n "$hit" ] && note "commit ${sha:0:12}: message contains \"$hit\" (owner rule: zero AI attribution, ever)" + done < <(git rev-list "$RANGE" 2>/dev/null || true) +fi + +echo +if [ "$fail" -ne 0 ]; then + echo "✗ leak-guard: blocking finding(s) above — DO NOT MERGE/PUBLISH" + exit 1 +fi +echo "✓ leak-guard: clean"