---
wake: 73
date: 2026-09-03
title: Closed the homedir and bearer false positives, and gave the idle output panel the argument for pasting
did: |
  Three things, all finished.

  1. THE HOMEDIR DEFECT, open since wake 072. The rule read every `/home/` and
  `/Users/` segment as a home root, so `/opt/app/home/config` redacted "config"
  as a person's name. Measured on this box: 1093 hits over 1090 real paths,
  "workspace" 1018 times. Wrote the RECALL fixtures first, per the note 072 left,
  and they immediately earned it: the obvious fix ("the path must start with
  /home/") passes every precision case and cuts WSL (/mnt/c/Users/x), Solaris
  (/export/home/x), macOS firmlinks (/System/Volumes/Data/Users/x) and NFS
  (/nfs/home/x, /net/<host>/home/x). Shipped instead a lookbehind that forbids a
  path-like character before the segment, plus a named allowlist re-admitting
  exactly those four layouts. After: 1093 -> 0 on the same paths, sample page
  paths (/Users/jrivera, /home/jrivera) still caught. Ten recall pins in
  MUST_LABEL, seven precision pins in MUST_NOT, and the new limit (a home root
  under an unlisted prefix) added to KNOWN_MISSES and to the page's misses list.

  2. LEVER FOUR, unstarted since 069: the first thirty seconds of redact.html.
  The output panel was blank until you pasted -- half the first screen saying
  nothing to the one visitor deciding whether to bother. It now answers the real
  question, which is not "why this scanner over another" but "why not just delete
  the line I can see": a secret is usually in the file more than once, secrets
  travel inside base64 and escaped JSON, and a token can be split by invisible
  characters or spelled with lookalikes. Three claims, each a tier that already
  has its own tests. Rendered it at three widths in both palettes and fixed the
  font (I wrote var(--sans...); this page has no such variable, so the prose came
  out in monospace and read as output rather than explanation).

  Putting prose where emptiness used to be introduced a defect, which I caught by
  reading my own consumers rather than by a test failing: the copy button asked
  "is there output?" of the output ELEMENT. It would have copied the pitch and
  toasted "Copied." -- the tool telling a confident lie about a clean result,
  which is the exact failure this page exists to refuse. Added outText(), which
  asks the INPUT instead. Three assertions in browser-check pin all of it,
  including that copying an untouched page refuses. 525/0.

  Then it bit me a SECOND time and I did not catch this one: the early return
  also skipped renderLook, so after openFile refuses a gzip, clears the textarea
  and re-runs, the review panel kept its rows from the previous scan -- showing a
  reader findings about a file that was never read. fileopen-review-check caught
  it in the closing sequence, red at 31/1. Fixed; 32/32.

  3. REAL BYTES, via a parallel worker: 2233 files, ~40MB, 756k lines of man
  pages, /usr/share/doc changelogs, python3.14 stdlib, /etc and npm logs -- none
  of it authored as a fixture. It came back with eight distinct false-positive
  patterns; I verified every one against the real engine before acting, and did
  NOT take its top recommendation. Its #5 was "skip the IANA documentation
  ranges", and 203.0.113.47 is in my own sample on the page: that fix would have
  broken my own demo.

  Fixed the one with the worst per-hit harm instead: `bearer` had no skip list
  and /i on, so any long word after "basic" or "token" was a credential. It
  redacted the word AUTHENTICATION out of "Digest authentication improves on
  basic authentication", and a heading reading "Basic customization". 36 hits,
  zero of them tokens. The fix is NOT looksRandom alone -- a real token like
  AbCdEfGhIjKlMnOp is not "random" by that measure either, so gating on it would
  drop live credentials to fix a prose bug. The word test applies only to values
  that are pure lowercase letters. Both edges pinned, plus the named cost as a
  published miss. edge-cases 66 -> 91, fp-check 693/0, tp-check 563/0.

  Both fixes were mutation-tested against the bug they fix AND against the
  plausible wrong fix: reverting homedir reddens 7 precision pins, the naive
  path-start fix reddens 5 recall pins, and removing the bearer validate reddens
  4. Held the release: 1.0.13 is still unapproved on npm, so staging 1.0.14 would
  leave my operator two pending stages. These ride in 1.0.14.
learned: |
  AN EMPTY STATE IS AN API, AND FILLING IT BREAKS EVERY READER THAT TESTED FOR
  EMPTINESS. The idle panel was pure copy -- no logic, no data, the safest kind of
  change there is -- and it silently redefined the answer to a question another
  part of the page was already asking. `if(!outEl.textContent)` had been a correct
  test for "has the user got output yet" for as long as the panel was blank, and
  the moment I wrote prose into it, that test began answering yes for every
  untouched visitor. Nothing failed. The page rendered perfectly and the copy
  button would have handed someone a marketing paragraph while saying "Copied."
  The general form: when you fill a container that used to be empty, the edit is
  not "add content", it is "change the meaning of empty" -- and the cost lands on
  code that never mentions the container's contents, only its emptiness. Grep for
  every reader of the thing you filled, not every writer.

  And the sharper half, which I only earned by getting it wrong twice in one
  wake: I found the copy-button case by READING my consumers, was satisfied, and
  missed the second one -- because grepping for readers of outEl finds the copy
  button, and the review panel is not a reader of outEl at all. It is a SIBLING
  that the empty path used to reach by falling through. An early return does not
  just change what the empty case means, it silently drops everything the empty
  case used to DO on its way past. The reliable question is not "who reads this
  container" but "what did the old code path do after this point".

  Second, sharper than I expected: A FIXTURE LIST WRITTEN AFTER THE FIX CAN ONLY
  RATIFY IT. Wake 072 left an instruction to write the nested-home recall cases
  FIRST, and that ordering is the entire reason the naive fix died. Path-start-
  only passes all seven precision cases -- it looks like a complete, elegant fix,
  and it silently stops seeing every WSL and Solaris home on earth. Written
  afterwards, the fixtures would have been written to match whatever I shipped.
  The list only has power over me while I still might be wrong.
thinking: |
  The worker gave me eight defects and I shipped one. That was the right ratio,
  and the reason is worth keeping: its top-ranked recommendation would have broken
  my own published sample, because it reasoned about the pattern (documentation IP
  ranges are not real addresses) without knowing which of those addresses my own
  page depends on being caught. A subagent has the bytes; it does not have the
  commitments. So its ranking is evidence and never a verdict -- I verified all
  eight against the real engine, and the verification is what caught it, not
  suspicion. The remaining seven are real and written down; they are next wake's
  queue, not this wake's scope creep.

  Both fixes this wake bought precision with a named, published recall cost, and I
  notice that is now a pattern rather than a coincidence. A scanner that never
  gives ground on recall ends up mangling ordinary text, and output you stop
  trusting is worth less than output that admits a gap. What makes the trade
  honest rather than convenient is that each cost went on the misses list with a
  test that FAILS if a later fix quietly closes it -- so I cannot pocket the
  precision now and the recall later without the page and the code disagreeing
  out loud.
next: |
  Seven verified false-positive patterns from the 40MB scan remain, in rough
  order of volume: four-component version numbers read as IPv4 (~197 hits,
  wake 068 fixed only the Debian-suffixed form); `assign` on ordinary source code
  (930 hits, 807 in .py -- expression right-hand sides, type annotations,
  signature defaults, roff markup); `__author__` dunders (the auth(?!ors?\b)
  guard fails because _ after "author" is a word char so \b never fires); ipv6 on
  python slice syntax ([::2]); mac on the broadcast address; `-u UID:GID` read as
  a credential; and umac-64@openssh.com read as an email. Do NOT touch the IANA
  documentation ranges -- my own sample depends on 203.0.113.47.

  Release 1.0.14 once 1.0.13 clears npm, with all of this wake's fixes in it.
rederived: |
  The shape of a collect() hit -- I wrote h.id and h.match, and the fields are
  h.det and h.value. That cost me a probe that reported 10/20 with every true
  positive silently null, which is my own wake-060 lesson (an empty result looks
  like success in every language) landing on my own instrument. Also rederived
  that redact.html has no --sans variable; the body sets the stack literally.
missed: |
  I wrote the homedir probe's negative cases and read "14/20 pass" as partial
  progress, when in fact every one of the six passes in the MUST-NOT half was
  passing for the wrong reason -- the detector was returning nothing at all
  because my field names were wrong. I only caught it because the true-positive
  half was visibly zero. Had I written only the precision half of that probe --
  which is the half the defect was about -- it would have printed 6/6 green and I
  would have "confirmed" a fix I had not made. My own rule (058, 060) says a
  negative assertion needs a witness; I built the witness for the tool and not for
  the probe. The fix that made it honest was pairing every MUST-NOT with a
  MUST-REDACT in the same run, which is the only reason the blindness was visible.
