Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .changeset/phi-scan-widen-the-walk-roots.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
"@cosyte/cli": patch
---

The PHI scanner's all-mode walk now covers this package's whole authored corpus. It was rooted at
`test/__fixtures__` and `src` only, so 89 of 123 tracked files were scanned by neither of its two
routes; it now roots at `src`, `test` and `scripts`, opening 72 tracked files instead of 34.

Measured back to back on the base commit rather than inferred: a dashed SSN and an off-domain address
written into a file under `test/`, in this package's own inline-message shape (a whole HL7 message as
one TypeScript string literal with escape sequences between its segments), exited 0 with `OK, no
hits` in all mode while naming the same file explicitly reported both at exit 1 over the same bytes.
A file written under `scripts/` behaved identically. Both routes now report both. Every one of the 38
newly opened files was read by hand: every message literal is a placeholder and the only SSN and
email shapes anywhere are the scanner's own declared synthetic payload, so the gap was one of
enumeration rather than a live exposure.

`test` replaces `test/__fixtures__` rather than joining it, because the roots must stay disjoint: each
is walked independently and the results concatenated, so a nested root would enumerate every file
beneath it twice. The fixture directory is still watched, through the other condition of the
unobserved-root rule, wherever the repository tracks files under it. One cover was lost and is stated
rather than implied away: where nothing is tracked under it, an empty fixture directory no longer
refuses. `scripts/` is included because the scanner, its allow-list and its override log all live
there, so the one directory guaranteed to hold identifier-shaped text was the one nothing enumerated;
all nine files there were measured against the detector before the root was declared.

Scanning `scripts/` has one consequence worth knowing before it surprises someone: the allow-list
documents an id entry as matching a social-security, medical-record or member-id shape, and the
dashed-id check consults no allow-list, so declaring one in the dashed shape now reports a hit on the
allow-list itself. Nothing shipped is affected, and the remedy is to write the synthetic id in a shape
the check does not match, never to weaken the check or to exempt the file.

The scanner's own test file carries violator literals on purpose and is the single exempt path. That
exemption is applied after the file is read, so it still counts as observed and an unreadable one
still refuses; it is scoped to the sweep, so naming the file explicitly still reports every hit; and
it is per path rather than a pattern.

What this does not change: the detector is still the cross-cutting SSN and email floor, now over 38
more files, and structured field-level detection remains unimplemented. A test pins that limit.
`--staged` is unchanged, because widening it would change what a commit is blocked on.
51 changes: 51 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,57 @@ still do. Each entry was assigned to the release whose tag first contains it, re

### Fixed

- **`pnpm phi-scan`'s all-mode walk was rooted at `test/__fixtures__` and `src` only, so 89 of this
repository's 123 tracked files were scanned by NEITHER of its two routes
(PHI-SCAN-WALK-ROOT-SCOPE).** The walk now roots at **`src`, `test` and `scripts`**, which opens 72
tracked files instead of 34.
- **Measured back to back on the base commit, and re-derived for this repository rather than ported
from a sibling.** A dashed SSN and an off-domain address written into `test/planted.test.ts`, in
this repository's own inline-message shape (a whole HL7 message as one `.ts` string literal with
`\r` escapes between its segments), exited **0** with `OK, no hits` in all mode, while naming the
same file in paths mode reported both at **exit 1** over the same bytes. A file written under
`scripts/` behaved identically. Both routes now report both.
- **All 38 newly opened files were hand-read.** Every message literal is a placeholder and the only
SSN/email shapes anywhere are the scanner's own declared synthetic payload, so the gap was one of
**enumeration**, not a live exposure: the defect was that the gate could not see those files, so
nothing would have caught a real value if one appeared.
- **`test` REPLACES `test/__fixtures__` rather than joining it.** Roots must stay disjoint: each is
walked independently and the results concatenated, so a nested root would enumerate every file
beneath it twice and report each hit twice. The fixture directory is still watched, through the
observation rule's other condition, **wherever git tracks files under it** (here, seven); only the
root a refusal is filed under changed.
- **One cover was LOST, and it is stated rather than implied away:** where git tracks NOTHING under
`test/__fixtures__`, an empty one no longer refuses. As a declared root it refused by the
opened-nothing floor whatever git carried; as an ordinary directory it contributes no entry and the
reconciliation has no expected path to miss. A test pins it.
- **`scripts/` is a root because the scanner, its allow-list and its override log live there**, so
the one directory guaranteed to hold PHI-shaped text was the one nothing enumerated. All nine
files there were measured against the detector before the root was declared: no hits.
- **The cost of that root nobody would guess: the allow-list's own bytes.** The allow-list documents
`ID <value>` as a synthetic id "matching an SSN / MRN / member-id shape", and the dashed-SSN check
consults no allow-list, so an `ID` entry written in that dashed shape now reds the gate on the
allow-list itself. Latent today (the shipped file declares its only id in the `MRN-` form, and a
test pins that). **The remedy is to spell the id in a shape the check does not match**, never to
make that check consult the allow-list (which would delete a detection) and never to exempt the
file (which would leave the likeliest place for a real value unswept).
- **A narrowing that came with it:** `test/__fixtures__` is no longer a declared root, so a live or
dangling symbolic link at that path is now an enumerated entry and is refused outright, whatever
it points at. Only the three top-level roots can still be followed.
- **`test/scripts/phi-scan.test.ts` is exempt from the sweep, and is the only exempt path.** It
carries violator literals on purpose, as the positive half of the scanner's own tests. The
exemption is applied **after the read**, so the file still counts as observed and an unreadable
one still refuses; it is **scoped to the sweep**, so naming the file explicitly still reports
every hit; and it is **per path, never a pattern**.
- **What this does NOT buy, stated because a wider reading would be false:** the detector is still
the cross-cutting SSN + email floor, over 38 more files. Structured, field-level detection remains
unimplemented, and a test now pins that limit rather than leaving it as prose.
- **`--staged` is deliberately unchanged**, because widening it changes what a commit is blocked on.
The two routes therefore differ widely, and the scanner's own documentation says by how much.
- **Four claims about which directories are deliberately NOT scan roots are now checks rather than
prose**, after two of them were measured false: `docs-content/` and `.changeset/` each carry one
tracked non-markdown file, and the repository root is not clean under the detector (the `author`
field carries a real off-domain contact address). `documentation/` and `.github/` were correct.

- **`pnpm phi-scan` printed `OK, no hits` and exited 0 over a corpus it never opened
(PHI-SCAN-OBSERVED-NOTHING-IS-GLOBAL).** A declared scan root that the walk never observed is now
a refusal at **exit 2**, in the all-mode sweep CI runs. Each root's walk is reconciled against
Expand Down
Loading
Loading