Skip to content

test(privacy): stop fixtures shipping real host recon, and scan for infrastructure disclosure - #88

Merged
Cipher208 merged 4 commits into
masterfrom
fix/fixtures-must-not-ship-house-recon
Oct 7, 2026
Merged

Cipher208 merged 4 commits into
masterfrom
fix/fixtures-must-not-ship-house-recon

Conversation

@Cipher208

Copy link
Copy Markdown
Owner

What happened

tests/test_shared/fixtures/status_broadcast_rows.jsonl pins real close-out
broadcasts from this project's history. One row (line 27) was a recon report on
a private host
: node names, a tailnet address, the ssh user, a key path, an
API-key variable name, AmneziaWG parameters, VPN subnets and ports. The repo is
public, so that row was a readable map of a private network.

No credentials were in it, and that is exactly why nothing fired: gitleaks matches
credential shapes (provider prefixes, entropy, key headers). Infrastructure
disclosure is made of names, paths and roles, so the scan that ran three hours
after the row landed went green on a tree that already contained it.

What this PR does

1. Substitutes identifiers in the fixture (a5a0727). Status words, commit
hashes and dated stamps are untouched — the detector's co-occurrence contract and
the fixture's catch-rate assertions depend on them. Placeholders are deliberately
non-matching (<ssh-user>, <ssh-key>, PROVIDER_TOKEN) so the new house rules
stay silent here while still catching the real thing.

Also fixed five other files naming nodes and services in passing, including two
vmNNNNNNN hostnames in test_dream_anchor.py and test_skill_pipeline.py.

2. Adds the missing detection layer (ce73ac3) — .gitleaks.toml with rules
for Tailscale/CGNAT addresses, tailnet DNS names, provider machine ids, backup ssh
accounts, private key paths, secret-bearing variable names, AmneziaWG parameters
and config paths, tunnel key material. Default rules stay on; this is a layer, not
a replacement. Wired into pre-commit.

RFC 1918 is deliberately not matched: examples, docker config and tests here use
10.x legitimately, and a rule that cries wolf gets muted.

3. Writes the rule down (3e589aa) — CONTRIBUTING.md § Test Data and Fixtures,
with the concrete list and the note that the existing scan does not cover this.

Verification

  • The new rules find the original leak commit: six rules fire on it, where the
    default ruleset was silent.
  • The tracked tree produces zero findings.
  • The pre-commit hook fails a planted leak (exit 1) — checked, not assumed.
  • tests/ green; ruff, ruff-format, mypy and the full pre-commit gate pass on
    every commit in this branch.

The design constraint worth knowing

This file is committed to a public repo, so the config is published too. The first
draft included a "regression pin" listing our exact hostnames — which would have
republished the identifiers it guarded. Every rule now describes a shape, never
our literals. Specific literals belong in an untracked local overlay.

Not in scope

History is not rewritten. A public archive fork holds the original object and
serves it by SHA, so a rewrite here would erase nothing while breaking every clone.

For the reviewer

  • master is protected with strict required checks, so this needs a PR.
  • Nobody with a clone has to do anything: no history was changed.

…ntifiers

The broadcast fixture pinned real close-out broadcasts from this project's own
history, and one of them was a recon report on a private host: node names, a
tailnet address, the ssh user, a key path, an API-key variable name, AmneziaWG
parameters, VPN subnets and ports. The repo is public, so that row was a
readable map of a private network — not a credential leak, but disclosure of
what is where and how to reach it.

Why nothing caught it: gitleaks matches credential shapes (provider prefixes,
entropy, key headers). This row is made of names, paths and roles, so the scan
that ran three hours after it landed went green on a tree containing it.

Substitute identifiers only. Status words, commit hashes and dated stamps are
untouched — the detector's co-occurrence contract and the fixture's catch-rate
assertions depend on them. Same for five smaller test files that named nodes
and services in passing, including two hostnames of the vmNNNNNNN family.

Placeholders are deliberately non-matching (<ssh-user>, <ssh-key>,
PROVIDER_TOKEN) so the house gitleaks rules added later stay silent on this
file while still catching the real thing.

Tests: tests/ green (577 passed).
The fixture that leaked a host recon report was not carelessness — the project's
own practice was to pin real history rows verbatim, and nothing said not to. Put
the rule where a contributor will actually look, with the concrete list: node
names, CGNAT and VPN ranges, ssh users, key paths, the names of secret-bearing
variables, tunnel parameters, service inventories, personal names.

Record why the existing scan does not cover this. gitleaks matches credential
shapes; infrastructure disclosure is made of names, paths and roles, and has no
shape to match.

Substitute, do not delete: an anonymised recon row still tests the classifier,
and the blast radius of an over-cautious fixture is zero. There is no way to
un-publish a fork.
Credential scanning was the only control on this repository's public surface,
and the incident it missed had no credentials in it. The leaked row was a host
recon report: node names, a tailnet address, the ssh user, a key path, an
API-key variable name, tunnel parameters and ports. gitleaks matches shapes —
provider prefixes, entropy, key headers — so it matched none of that and went
green.

Add the missing half: rules for the disclosure itself, tuned narrow so they
stay trusted. Tailscale/CGNAT addresses, tailnet DNS names, provider machine
ids, backup ssh accounts, private key paths, secret-bearing variable names,
AmneziaWG parameters and config paths, tunnel key material. Default rules stay
on; this is a layer, not a replacement.

RFC 1918 addresses are deliberately not matched — examples, docker config and
tests here use 10.x legitimately, and a rule that cries wolf gets ignored.

Verified three ways: the file's own rules find the 2026-09-13 leak commit
(six rules fire on it, the default ruleset was silent); the tracked tree
produces zero findings; and the hook fails a planted leak (exit 1).

The rules describe shapes, never our literal identifiers. A "regression pin"
listing the exact hostnames was the first draft and would have republished the
identifiers it guarded — the file is committed to a public repo, so the config
is published too. Specific literals belong in an untracked local overlay.
@github-actions github-actions Bot added the test label Oct 7, 2026
@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: Cipher208/a-memory/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6434f190-a183-48ce-9299-f6f04553d7b0
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The first pass replaced the recon in row 27 of the status-broadcast fixture
but stopped there. The same broadcast register names the private estate in
eight more rows and in three other files, so the tree still carried:

- `vps2` — the real node name, in the fixture (rows 21, 27-31), in
  test_broadcast.py and in test_continuity_actuality.py
- `~/keyvault/vps2/amnezia-configs-backup.tgz` — the path of the archive
  holding the AmneziaWG server keys. This is the one that matters: it does
  not name a tunnel, it names where the keys are kept.
- `keyvault` used alone, later in the same register
- `/home/murat` — the operator home, in the fixture, in two docs and in a
  test comment

Substituted with the same placeholder convention the first pass used
(`node-b`, `<keyvault>`, `<home>`). Deliberately left alone: `10.x` and the
port numbers, following the existing decision that generic RFC 1918
addresses stay unmatchable, and `198.51.100.12`, which is the RFC 5737
documentation range.

The fixture's catch-rate assertions and the detector contract are
untouched: 33 rows, all valid JSON, full suite 2087 passed.
@Cipher208
Cipher208 enabled auto-merge October 7, 2026 22:10
@Cipher208
Cipher208 merged commit 1f3c63a into master Oct 7, 2026
19 checks passed
@Cipher208
Cipher208 deleted the fix/fixtures-must-not-ship-house-recon branch October 7, 2026 22:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant