Repository navigation
test(privacy): stop fixtures shipping real host recon, and scan for infrastructure disclosure - #88
Merged
Conversation
…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.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
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. Comment |
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What happened
tests/test_shared/fixtures/status_broadcast_rows.jsonlpins real close-outbroadcasts 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, commithashes 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 rulesstay silent here while still catching the real thing.
Also fixed five other files naming nodes and services in passing, including two
vmNNNNNNNhostnames intest_dream_anchor.pyandtest_skill_pipeline.py.2. Adds the missing detection layer (
ce73ac3) —.gitleaks.tomlwith rulesfor 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.xlegitimately, 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
default ruleset was silent.
tests/green; ruff, ruff-format, mypy and the full pre-commit gate pass onevery 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
masteris protected with strict required checks, so this needs a PR.