Skip to content

privacy: cover the postal half of "address", which no gate could see - #82

Merged
PipFoweraker merged 1 commit into
mainfrom
redact-live-addresses
Aug 20, 2026
Merged

privacy: cover the postal half of "address", which no gate could see#82
PipFoweraker merged 1 commit into
mainfrom
redact-live-addresses

Conversation

@PipFoweraker

Copy link
Copy Markdown
Owner

What this is not

An audit for residential addresses of real people found none -- not in this repo, and not in pdoom1, pdoom1-website or pdoom-dashboard. No data file is changed by this PR and no redaction was warranted.

Every address-shaped string in the tracked tree is organisational: 10 occurrences across 4 files, all data/serveable/api/timeline_events/. They are university departments, a national library, an innovation centre and a defence contractor's published point of contact, extracted from academic paper title pages by the same unparsed-PDF import behind the three email tombstones. Categorised as NOT the exposure. Details, counts and severities are in the coordination report rather than here, since this page is public.

What the audit actually found

Nothing could have caught a home address if one had arrived.

redact_emails.py --ci gates the tracked zones, but RESIDUE requires an at-sign or the wreckage of one, so no setting of it could ever match a street. check_all.py labelled that check no email addresses in tracked zones while the tool printed No address-shaped text in any tracked zone. The word "address" doing double duty across those two lines is why the gap read as coverage.

What this adds

  • scripts/privacy/scan_postal.py -- unit/apartment prefixes, Australian localities, coordinate pairs at dwelling resolution (4+ decimals is ~11m), personal handsets, and streets in number-first and number-last order.
  • tests/test_postal_gate.py -- 13 must-fire, 15 must-not-fire.
  • Wired into check_all.py and data-integrity.yml as two gating checks.

The scanner prints paths and line numbers only, never matched text, because CI logs are public.

The design problem was staying unmuted

A guard that fired on all 10 institutional addresses would be switched off within a week, and test_privacy_gate.py already records why that is worse than no guard. So the discriminator is an INSTITUTION token in the surrounding window, not a narrower street list: a published affiliation address travels with the name of the institution that published it; a leaked home address does not. That is a fact about how the two come to exist, not how they are spelled.

Accepted bound, named rather than hidden: a home address within 160 characters of the word "University" would be missed.

Three false-positive families were measured against the real corpus and are locked in as must-not-fire -- a float and a sha256 digest each containing a well-formed Australian mobile number, and a year (SA 2019), now rejected by checking the postcode against the block Australia Post issued to that state.

Verification

before after
check_all.py gating 15 PASS 17 PASS

RED run observed per Workshop 2 ruling R6: a deliberate bad input appended to a tracked doc made all five detector families fire and --ci exit 1; green again on restore.

History

Not rewritten, deliberately. The institutional addresses reach back to 5526156. History rewriting on a repo with clones is Pip's call, and there is no residential exposure that would motivate it.

Escalation, outside this repo

pdoom1-website publishes 6 academic contact emails that this repo has already scrubbed -- in public/data/events.json, in 6 rendered HTML pages, and in OpenGraph meta tags. Its own guard has the brace-group bug that 12c0455 fixed here. pdoom1 carries the same in its vendored copy. Separate PRs; flagged in the report.

Do not merge without Pip's review.

…wo things

An audit for residential addresses of real people found none, in this repo or
in pdoom1, pdoom1-website and pdoom-dashboard. Every address-shaped string in
the tracked tree is an organisational one -- ten of them, all university
departments, a national library, an innovation centre and a defence
contractor's published point of contact, carried in from academic paper title
pages by the same unparsed-PDF import that caused the email exposures. No
redaction was warranted, so this commit changes no data.

What the audit did find is that nothing could have caught a home address if one
had arrived. redact_emails.py gates the tracked zones, and RESIDUE requires an
at-sign or the wreckage of one, so no setting of it could ever match a street.
check_all.py labelled that check "no email addresses in tracked zones" while
the tool itself printed "No address-shaped text in any tracked zone", and the
word "address" doing double duty across those two lines is the whole reason
the gap read as coverage for as long as it did.

scan_postal.py covers the postal half: unit and apartment prefixes, Australian
localities, coordinate pairs at dwelling resolution, personal handsets, and
streets in both number-first and number-last order.

The design problem was not detection, it was staying unmuted. A guard that
fired on all ten institutional addresses would be switched off within a week,
and test_privacy_gate.py already records why that is worse than no guard. So
the discriminator is an INSTITUTION token in the surrounding window rather than
a narrower street list: a published affiliation address travels with the name
of the institution that published it and a leaked home address does not, which
is a fact about how the two come to exist rather than about how they are
spelled. The accepted bound is a home address within 160 characters of the word
"University", named here rather than hidden.

Two false-positive families were measured against the real corpus and both are
"the digit run continues past the match": a float, "Training power draw (W)":
"202329.0409413959", and a sha256 digest, each containing a well-formed
Australian mobile number. A third was a year, "Released SA 2019", which is now
rejected by checking the postcode against the block Australia Post actually
issued to that state. All three are MUST_NOT_FIRE cases.

The scanner prints paths and line numbers and never the matched text, because
CI logs are public on a public repository.

RED run observed per Workshop 2 ruling R6, on a deliberate bad input appended
to a tracked doc: all five detector families fired and --ci exited 1. Green
again on restore. check_all is 17 gating checks, all passing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@PipFoweraker
PipFoweraker force-pushed the redact-live-addresses branch from 932eff8 to ef196ad Compare August 20, 2026 04:22
@PipFoweraker

Copy link
Copy Markdown
Owner Author

Rebased onto main (932eff8ef196ad). One conflict, in scripts/validation/check_all.py, resolved by keeping both insertions — five commits had landed on main since 14 August, each appending its own entry to the same list, and this branch appends two more. Textual collision, nothing semantic:

("scan cross-reference check fires", ["tests/test_scan_cross_references.py"], True),   # from main
("no dwelling addresses in tracked zones", ["scripts/privacy/scan_postal.py", "--ci"], True),
("postal gate can still fail", ["tests/test_postal_gate.py"], True),

Verified locally after the rebase:

  • python tests/test_postal_gate.py13 must-fire and 15 must-not-fire cases pass
  • python scripts/privacy/scan_postal.py --ci → clean, exit 0
  • python scripts/validation/check_all.py → both new gates PASS

One gating check fails, and it is not this branch's. references in prose resolve reports 13 unresolved-commit warnings. It fails identically on a clean origin/main worktree, so it is pre-existing red on main rather than anything the rebase introduced. Most of the entries look like sibling-repo SHAs that want the repo@sha form the checker itself suggests, plus a placeholder (a3b2c1d4e5f6) appearing in three docs.

Also worth recording against this PR's escalation section: the pdoom1-website half is closed. check-published-emails.py returns PASS there, and its #322 (merged 2026-08-20) documents all three surfaces as clean since 2026-08-10, because the daily sync pulled the corpus this repo had already cleaned at source. The pdoom1 half I could not confirm — grepping that repo for RESIDUE or redact_emails returns nothing, so either the vendored copy is named differently or the claim is stale. Do not read my grep as proof of absence.

Still holding for Pip's review per the body — not merging.

@PipFoweraker
PipFoweraker merged commit cbfc25b into main Aug 20, 2026
3 of 4 checks passed
@PipFoweraker

Copy link
Copy Markdown
Owner Author

Merged at cbfc25b with Assert data and pipeline invariants red. Recording why, because a merge over a red check with no explanation makes the next reader redo the work.

The red was inherited, not caused. The same job failed on main's two preceding pushes — runs 32322351876 (01:48Z) and 32304961003 (2026-08-19 21:39Z) — with the identical terminal line REFERENCE CHECK FAILED. Main went red between the 09:19Z run, which passed, and 21:39Z the same day.

What was failing was one gating finding, not the 114 warnings the log tail shows. check_references.py reports gating findings: 1 against warnings: 114, and the warning families (unqualified-issue: 72, unresolved-commit, synced-file, accepted-drift) are non-gating by deliberate design — the tool's docstring cites this repo's ASCII backlog as the reason. The single gate was docs/design/REVIEW_THE_BULK_2026-08-19.md:306 naming data/serveable/api/meta/corpus_review.json, a path that document proposes building, introduced by 2cdb118. None of this PR's four files appear anywhere in the report.

Fixed on main in af03c68 as a PLANNED entry in config/reference_drift.json. check_references.py now exits 0.

This PR's two new gates were verified passing, which CI could not show because the reference check failed first:

[PASS] no dwelling addresses in tracked zones      26.2s
[PASS] postal gate can still fail                   0.2s  13 must-fire and 15 must-not-fire cases pass

One thing this PR diagnosed and did not fix. The commit message identifies the root cause as wording — "check_all.py labelled that check 'no email addresses in tracked zones' while the tool itself printed 'No address-shaped text in any tracked zone', and the word 'address' doing double duty across those two lines is the whole reason the gap read as coverage" — but neither string changed. Current output:

[PASS] no email addresses in tracked zones     29.7s  No address-shaped text in any tracked zone.
[PASS] no dwelling addresses in tracked zones  26.2s  No dwelling-shaped postal, geographic or telephone text...

The overclaiming line now sits directly above the check that actually covers addresses, so a reader scanning output sees address coverage asserted twice and cannot tell which one means it. redact_emails.py should say email-shaped. Not changed here; flagged for a follow-up.

Also still open, from this PR's escalation section: pdoom1-website publishes 6 academic contact emails this repo has scrubbed, and its guard carries the brace-group bug that 12c0455 fixed here, as does pdoom1's vendored copy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant