flybrain/infra/docs/secrets-history-scan.md
acamilo 660c3cf00d
Some checks failed
ci / node 22 (test + typecheck) (push) Has been cancelled
ci / rust stable (cargo test --workspace --release) (push) Has been cancelled
ci / infra/tests/lint.sh (push) Has been cancelled
ci / playwright apps/stage (allowed to fail) (push) Has been cancelled
flybrain v0.4.0: public tree (history retained privately)
2026-09-21 15:09:46 +00:00

6.5 KiB

Secrets scan over the whole git history

Run 2026-09-17 against the local checkout at main = c9858cc, before any public push. Scope: every commit on every ref, not just the working tree.

No secret value appears anywhere in this document. Findings are described by shape and location only.

What was run

tool version command coverage
gitleaks 8.30.0 gitleaks git . --log-opts="--all" --redact 460 non-merge commits, ~8.27 MB of diffs
gitleaks 8.30.0 gitleaks dir . the checked-out tree, 2.02 GB incl. target/ and node_modules/
trufflehog 3.90.10 trufflehog git file:///<checkout> 322,604 chunks, 4.23 GB, all branches
grep (own pattern set) — every blob in the object DB, see below 5,791 objects incl. dangling blobs

Both binaries were installed as prebuilt releases under ~/.local/bin (no root, no package manager). Repository size for reference: 563 commits, 87 refs, one author.

The grep pass exists because gitleaks and trufflehog know generic provider shapes but not this project's own ones. It enumerates git rev-list --objects --all plus the dangling blobs from git fsck --lost-found, pipes each blob through git cat-file blob, and matches:

  • -----BEGIN [A-Z ]*PRIVATE KEY-----
  • Twitch IRC tokens: oauth:[A-Za-z0-9]{20,}
  • Twitch/RTMP stream keys: live_[0-9]{6,}_[A-Za-z0-9]{20,}
  • Authorization: (Bearer|Basic|OAuth) <12+ chars>
  • client_secret / clientSecret / CLIENT_SECRET assigned a 20+ char value
  • stream_key / STREAM_KEY / streamKey assigned a 12+ char value
  • access_token / refresh_token / accessToken / refreshToken assigned a 16+ char value
  • api_key / API_KEY / apiKey assigned a 16+ char value
  • password / passwd / PASSWORD assigned an 8+ char literal
  • pass show / pass ls (this repo's credential store is pass, so captured output would look like a shell pipeline in a script or a log)
  • AWS AKIA…, GitHub ghp_/gho_/ghu_/ghs_/ghr_, Slack xox[baprs]-, OpenAI sk-
  • a separate broad sweep for any 32+ char high-entropy token not already explained by a checksum, integrity field or base64 blob

Findings

gitleaks: no leaks found, on history and on the tree. trufflehog: 0 verified, 0 unverified secrets (one non-critical decode error on data/fafb-v783/weights.binz, which is a gzip stream trufflehog tried to read as brotli — not a finding).

The grep pass matched 14 blobs across 5 paths. Every one is a reference to a credential, never a credential. Full list:

# shape matched path commits (first → last touching) verdict
1 pass show "$PASS_KEY" shell pipelines, 4 blobs infra/06-secrets.sh 0d1a762 → 69816e6 False positive. The script's whole job is to pipe pass show into systemd-creds on the container; the value never lands in a file and never in git. PASS_KEY holds a pass entry name (twitch/fly-pokemon-key), not a secret.
2 pass show … | head -c8 in a verify helper, 6 blobs infra/verify.sh 0d1a762 → 57276d4 False positive. Reads the first 8 bytes of the stored key to compare a prefix against what the container holds. The prefix is computed at run time and printed nowhere that is committed.
3 accessToken: / refreshToken: struct fields, 3 blobs services/bridge/src/auth.ts d9f00b6 → 48cd40a False positive. Twurple AccessToken field names in code (accessToken: token.accessToken). No literal values.
4 accessToken: / refreshToken: struct fields, 1 blob services/bridge/tools/authorize.mts 77f5892 False positive. Same: the interactive authorize tool writes the token it just fetched to a path outside the repo.
5 the words pass show | systemd-creds in prose infra/docs/release-ct-provision.md 6598ad0 False positive. A provisioning log describing the mechanism.

Broad high-entropy sweep: 1,101 blobs carried a 32+ char token line, all accounted for by package-lock.json integrity hashes, the FlyWire meta.json / tools/artifact-checksums.txt sha256 digests, and vendored binjgb tables. Nothing credential-shaped.

Negative results worth recording

  • No .env was ever committed. The only *.env paths in history are infra/env/fly-pokemon.env, infra/env/fly-platformer.env and infra/env/spike.env, which are deployment profiles, not credential files. Dumping every KEY=value line those three files have ever carried, across every commit, yields only CT sizing (CORES, CPUSET, MEMORY_MB), network (IP, HOSTNAME), stream geometry (STREAM_WIDTH, STREAM_KBPS), feature flags, PASS_KEY (an entry name), TWITCH_CHANNEL/TWITCH_BOT_USER (public handles) and PUSH_TARGET. .gitignore covers .env and .env.* with a !.env.example exception.
  • No key material of any kind. Zero matches for BEGIN … PRIVATE KEY, id_rsa, id_ed25519, *.pem, *.key, *.p12, *.pfx as paths or as content.
  • No stream key, ever. Zero matches for the live_… RTMP shape or for a STREAM_KEY= assignment. Stream keys reach the containers only through pass → systemd-creds.
  • The remote carries no credential. the operator's git remote — SSH, no token in the URL.
  • One advisory, not a secret: ROM_SHA256=0e85…219f is committed in infra/env/spike.env and infra/env/fly-pokemon.env. It is a cartridge fingerprint, so it is not a credential and needs no rotation, but it does identify a specific commercial ROM dump. Flagging it for whoever is handling the licence/redaction pass, not for this scan.

Verdict

Clean. Nothing to rotate, nothing to rewrite history for. Three independent engines (gitleaks, trufflehog, a project-specific grep set over every blob including dangling objects) found zero real credentials across all 563 commits. All 14 grep hits are code and prose that name credentials handled by pass at run time. The hard rule in CLAUDE.md — "Stream keys and tokens live in pass, never in git" — holds in the history as well as in the tree.

Re-running this

gitleaks git . --log-opts="--all" --redact --no-banner
trufflehog git "file://$PWD" --no-update

Neither is wired into .forgejo/workflows/ci.yml or .github/workflows/ci.yml: CI runs no secret-bearing step, and a full-history scan is a pre-publish gate rather than a per-push one. Run it again before any new public push, and after any commit that touches infra/06-secrets.sh, infra/env/*.env or services/bridge/src/auth.ts.