Skip to content

fix: a target that matches everything, a leak guard that never failed, and two CLI answers - #109

Merged
donislawdev merged 5 commits into
masterfrom
fix/blast-radius-and-leak-guard
Aug 6, 2026
Merged

fix: a target that matches everything, a leak guard that never failed, and two CLI answers#109
donislawdev merged 5 commits into
masterfrom
fix/blast-radius-and-leak-guard

Conversation

@donislawdev

Copy link
Copy Markdown
Owner

Five changes, each its own commit. Two are safety or guard holes found by reading a set of
general engineering rule documents against this code; two make the CLI answer a bad input the
way the rest of the tool already does; one gives the size ratchet the knob it was missing.

Everything here was checked against the code before it was written: a rule that this project
already followed was not turned into a change (that is what most of the reading produced -
nothing).

A target that matches everything is not a target

Start with impairment on and --target *, and the tool stayed quiet, because a target WAS set -
to every process on the machine. The blast-radius warning is the one that keeps a run from taking
the network down with the shell that started it, and an expression that narrows nothing is exactly
the case it exists for.

Matcher.covers_everything answers by PROBING, not by pattern-matching the text: each matcher
class carries a small group of representative values, and an expression that accepts the whole
group covers everything. *, re:.*, 1-65535, 0.0.0.0/0 and a PID range spanning the space
all trip it; a real target does not, and neither does a real target with an exclusion beside it.
Probing is why a NEW spelling of "everything" cannot slip past - it never had to be listed.

Show the leak guard failing, and teach it the class it missed

tools/check_public_text.py runs in CI and had reported "clean" many times, which proved it runs -
not that it catches anything. There was no test that ever saw it FAIL.

tests/test_public_text_guard.py now feeds it each thing it is supposed to catch and asserts it
says no. Writing those cases found the gap: the guard knew patterns (a path shape, an address
shape), so a value that is only private because it is THIS machine's was invisible to it. The list
of those values lives outside the repository, in an ignored file, and the guard reports a hit
without printing the value - printing it would put it in the CI log, which is the thing being
prevented. With no list present the guard behaves exactly as before.

One known gap is pinned by a test rather than papered over: Polish written without diacritics
passes the "is this English" check, because it is indistinguishable from English by that check.
The test records it so the next reader learns it from the suite instead of from a merged PR.

Report every bad value in one run

--loss 500 --latency -5 --dup 900 named the latency and stopped. Three mistakes, three runs.

settings.range_errors is a second reader of the same field registry, not a replacement. The form
still fails on the first field: it is typed into LIVE, and complaints about fields nobody has
reached yet are noise. A command line arrives FINISHED, where the opposite is true.
validate_ranges keeps its contract, so no window caller moved.

Suggest the nearest preset when one is mistyped

An unknown --preset answered with all seventeen canonical ids. The tool already offers a nearest
match for a mistyped config key and a mistyped scenario key, so preset names were the one closed
vocabulary left without it.

The suggestion is drawn from what a person can TYPE - the translated names as well as the ids.
Somebody reaching for "modem56k" writes that, not "presets.modem56k", and a search over ids alone
finds nothing close and stays silent exactly when it is needed.

The size ratchet gets the knob that sees creep

The ratchet capped the largest file and the largest function, so a hundred files could climb to
one line under the ceiling and it would not notice. It now also caps how many files and functions
sit in the top band, and every failure message states the remaining headroom, so the number in the
message is the one you would have to justify raising. A third test keeps the crowd counts honest:
it fails if they are set so far above reality that they could never fire.

Verification

  • python -m pytest tests - full suite, green (run as the last action, after the prose edits).
  • python smoke_gui.py - green.
  • python tools/check_public_text.py --commits origin/master..HEAD - 5 blocks, clean.
  • Every new guard was mutation-tested: the change was broken on purpose and the test had to fail.
    Three mutations SURVIVED on the first attempt and each one found a real defect in the test rather
    than in the product - a fake missing winfo_toplevel, a fallback the fake never reached, and a
    first regex narrow enough to walk around. All three are fixed and now fail as intended.

🤖 Generated with Claude Code

donislawdev and others added 5 commits August 6, 2026 01:02
`targeting_is_set` asked whether the expression had any positive term.
That catches `!chrome.exe`, which names only things to spare, and misses
`*`, which is a positive term naming the whole machine. Measured before
the fix: `--loss 100 --target *` printed no warning while `--loss 100`
alone did, so the expression that bounded nothing looked safer than no
expression at all. That is the scenario the project's own rule about never
breaking traffic globally exists for.

`Matcher.bounds_nothing` now carries both halves in one property, and the
one property is the point - reaching for either alone is how this got
through. It answers by asking the compiled terms against groups of probe
values rather than by reading their text, because "matches everything" is
not a syntactic property: `>0` and `0-999999` cover every process without
a wildcard in sight, and `*.*` covers only names containing a dot despite
looking universal. The probes are grouped by address family, because a
rule can cover the whole of IPv4 and none of IPv6 and still bound nothing
worth having.

It is a heuristic and says so. The direction is deliberate: a false
positive costs one advisory line, a false negative costs the user's
network.

The guard checks each unbounded form beside a genuinely narrow one, so a
fix that merely warns more often does not pass. The existing live rig for
this warning still reports five of five, including two real captures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ssed

`check_public_text.py` stands between this project and the one surface it
cannot take back, it runs in CI, and it had reported clean many times -
which proves it ran, not that it looks. The first canary found it catching
a user-profile path, an e-mail address, a quoted person and a token shape,
and letting a private network address straight through, while the
convention it enforces names addresses first.

A pattern for private address ranges was measured and rejected: 36 tracked
files legitimately carry one, including the CIDR examples in both READMEs,
the help text, the error messages and this project's own probes. A guard
that fires on correct content is switched off within a week and takes the
true hits with it.

So the check is split the way a leak detector has to be. The engine stays
in the repository and knows only the shape; the literals live outside
version control and are read at run time. The report names the file and
the line and never the matched value, because its output goes into CI
logs, which are exactly as public as the commit. A missing list is
announced rather than passed over in silence.

The canary is now a test rather than something somebody runs once. One of
its cases pins a known gap: text in the other language written without
diacritics passes, because that check is a diacritics detector - the first
draft of the canary assumed otherwise and would have reported the rule as
covered. Another one was caught by an existing guard while being written:
a leak detector may not spell out the strings it hunts for, so every
address in it is assembled at run time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A ceiling on the worst single item watches one thing growing to a record.
It is blind to the shape this project actually drifts into: several files
climbing together, none of them a record, all of them past the point where
a person can follow them.

Proven rather than argued. A 950-line module split into fifty small
functions passes every ceiling in the file and is caught only by the new
count of items already in the top band.

The band and both counts are measurements, not choices. The distribution
here is steeply skewed - one module sits at the ceiling and the next is at
sixty percent of it - so a tighter band would be a constant and would say
nothing, while at seventy percent the next candidate is real and has about
a hundred and thirty lines to go. A second test keeps the counts exact
rather than generous, because a ratchet parked one above the truth admits
the next arrival in silence, which is the drift it exists to catch.

Failure messages now carry the remaining headroom. The largest module had
twelve lines left and nothing said so until it ran out.

Also completes the test that keeps the metric honest. Proving comments do
not move the measure is satisfied perfectly by a measure that counts
nothing, and a ceiling standing on a stuck metric passes for ever while
the code grows underneath it. The symmetric half is now checked too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A command line arrives FINISHED, so one problem per run means the user
fixes it, runs again and meets the next one. Measured before the change:
`--loss 500 --latency -5 --dup 900` named the latency and stopped. Three
mistakes, three runs.

`settings.range_errors` is a second reader of the same field registry,
not a replacement. The form still fails on the first field, because it is
typed into LIVE and complaints about fields nobody has reached yet are
noise; `validate_ranges` keeps its contract, so no GUI caller moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An unknown --preset answered with all seventeen canonical ids and left
the reader to spot the difference. The tool already offers a nearest
match for a mistyped config key and a mistyped scenario key, so a preset
name was the one closed vocabulary still without it.

The suggestion is drawn from what a person can TYPE, which is why it
searches the translated names as well as the ids: somebody reaching for
"modem56k" writes that, not "presets.modem56k", and a search over ids
alone finds nothing close and stays silent exactly when it is needed.
A second, folded pass catches a near-miss that differs only in case or
diacritics - the same tolerance an exact match already gets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@donislawdev
donislawdev merged commit 15e59ef into master Aug 6, 2026
7 checks passed
@donislawdev
donislawdev deleted the fix/blast-radius-and-leak-guard branch August 6, 2026 08:46
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