Skip to content

fix: the leet near-miss floor was set three positions too high (#825) - #877

Merged
raeq merged 2 commits into
mainfrom
fix/leet-near-miss-floor
Sep 1, 2026
Merged

fix: the leet near-miss floor was set three positions too high (#825)#877
raeq merged 2 commits into
mainfrom
fix/leet-near-miss-floor

Conversation

@raeq

@raeq raeq commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Closes #825. Refs #762.

The defect

The leet branch has two sub-paths: the decode is a lexicon word, or the decode is one edit from one. The first has a floor of three; the second carried an uncommented >= 6 since #393.

leet_sub maps '1' to 'i', not 'l' — correct, since 1 looks like i in most faces. The consequence is that a 1-for-l substitution never decodes exactly, so the exact path can never see it and only the near-miss path can. Below six characters, that path does not run.

>>> disarm.inspect_anomalies("l0gin", lexicon=BRANDS).reason
'"l0gin" decodes to the word "login"'
>>> disarm.inspect_anomalies("1ogin", lexicon=BRANDS).reason
None                                   # same brand, same five letters, one digit each

Measured against the issue's table: 0 of 5 five-letter brands, 7 of 7 six-letter ones.

Choosing the new value

The issue argued the floor should go entirely, on the grounds that the exact path fires below it and produces false positives anyway. I measured that and it does not hold. Over 65 ordinary digit-bearing tokens (mp3, k8s, sha1, i18n, rtx4090, 1080p, …) against a 234k-word lexicon, plus 8 single-substitution brand spoofs:

floor false positives / 65 spoofs caught / 8
3 22 8
4 12 8
5 5 7
6 (current) 4 3

The exact path contributes 4 of those 65 on its own, so removing the floor adds 18 new false positives — k8skos, b2bbab, es6es. The floor is doing real work. It was set three positions too high, not wrongly present.

Five is the knee: one false positive more than six (top10, decode topio), and it more than doubles the spoofs caught. Four costs eight more to gain one (1yft, four letters).

After: 4 of 5 and 7 of 7. 1yft is the deliberate cost and has its own pinned test.

The table lives on the constant's doc comment, so the next person to touch the floor does not have to redo the measurement — which is why it had no comment in the first place.

Two rows of #726's table moved

gn0r3! and !dm1n were asserted clean, and the test named the six-character floor as the reason:

nearest()'s single-edit rescue declines them too because it requires a six-character decode.

That is a test freezing the floor's own defect as the correct answer. Both are one edit from the word they imitate — ignore and admin — and both are now reported. The test is inverted rather than deleted, and explains what changed.

Also

The near-miss path was undocumented. docs/user-guide/anomaly-detection.md described the exact path only, which predicts neither paypa1 firing nor app1e staying clean. Both paths and both floors are now on the page.

tests/test_leet_near_miss_floor.py pins both sides of the boundary — the four newly-caught spoofs, the seven that must not regress, 1yft below the floor, and eight short technical tokens that must not start firing. Its negative cases use a lexicon of one-edit neighbours rather than the decodes themselves; seeding the decode would have made them exact-path hits and measured the wrong path.

Full gate green: cargo test 723, pytest 6,275, doc tests 41 pages, clippy both feature sets, perf_lint.sh, cargo doc, mkdocs --strict. Gate is in the shared core, so every binding inherits it; no binding-specific work.

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings September 1, 2026 16:27
@raeq
raeq enabled auto-merge (squash) September 1, 2026 16:27
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

📄 Docs preview: https://8eb3edc3.disarm-docs.pages.dev

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new near-miss floor test contains inaccurate decode commentary/test data and one updated assertion is weaker than necessary, reducing the regression value of the test suite.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR fixes a coverage gap in the inspect_anomalies leet detector by lowering the near-miss (1-edit) decode floor from 6 to 5, so short 1-for-l spoofs (which can’t hit the exact-decode path) are detected consistently across brand-length tokens. It also updates tests, documentation, and the changelog to reflect the two leet sub-paths and the rationale for the chosen threshold.

Changes:

  • Lower leet near-miss decode minimum length to 5 via a named constant in the Rust core.
  • Update and add pytest coverage to pin behavior on both sides of the new boundary (including previously “clean” cases that are now correctly reported).
  • Document the near-miss leet path and record the fix and measurement rationale in docs and CHANGELOG.
File summaries
File Description
src/anomalies.rs Introduces NEAR_MISS_MIN_LEN = 5 and applies it to the leet near-miss (nearest) path with expanded rationale.
tests/test_leet_segmentation.py Updates #726-related expectations for cases now caught via the near-miss path.
tests/test_leet_near_miss_floor.py Adds new regression tests pinning the near-miss floor boundary and intended false-positive guards.
docs/user-guide/anomaly-detection.md Updates leet documentation to describe both exact and near-miss paths and their floors.
CHANGELOG.md Records the behavior change, rationale, and related test expectation updates.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/test_leet_near_miss_floor.py Outdated
Comment thread tests/test_leet_segmentation.py Outdated
raeq added a commit that referenced this pull request Sep 1, 2026
#877 review, two notes, both correct.

The negative cases described `k8s` as decoding to `kos` and `b2b` to `bab`.
Those are the words `nearest()` *matched* in the measurement, not the decodes —
`leet_sub` maps '8' to 'b' and '2' to 'z', so they are `kbs` and `bzb`. The
lexicon was built from the wrong list, which left it unclear which entries were
one edit from what.

Each row is now an explicit (token, decode, neighbour) triple, so the one-edit
relationship is checkable instead of described. The decode is never in the
lexicon: seeding it would make these exact-path hits and the test would measure
the wrong path.

That exposed a second gap the note did not raise. A token that never enters the
leet branch — too short a base, a literal number — passes a "does not fire" test
for a reason with nothing to do with the floor, and two of the original rows
(`x86`, `3pm`) were exactly that. Every row now has a companion assertion that
puts the decode itself in the lexicon and requires the exact path to fire, which
proves the branch is reachable and the near-miss floor is the only thing
declining it.

The second note: `Finding.detail` for `leet` is exactly the matched lexicon word,
so the `in` check is now `==`. Containment would accept a longer word that merely
contains the expected one.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Richard Quinn <quinn.richard@gmail.com>
@raeq
raeq force-pushed the fix/leet-near-miss-floor branch from bd1e5aa to 6ee7e14 Compare September 1, 2026 16:45
raeq added a commit that referenced this pull request Sep 1, 2026
#877 review, two notes, both correct.

The negative cases described `k8s` as decoding to `kos` and `b2b` to `bab`.
Those are the words `nearest()` *matched* in the measurement, not the decodes —
`leet_sub` maps '8' to 'b' and '2' to 'z', so they are `kbs` and `bzb`. The
lexicon was built from the wrong list, which left it unclear which entries were
one edit from what.

Each row is now an explicit (token, decode, neighbour) triple, so the one-edit
relationship is checkable instead of described. The decode is never in the
lexicon: seeding it would make these exact-path hits and the test would measure
the wrong path.

That exposed a second gap the note did not raise. A token that never enters the
leet branch — too short a base, a literal number — passes a "does not fire" test
for a reason with nothing to do with the floor, and two of the original rows
(`x86`, `3pm`) were exactly that. Every row now has a companion assertion that
puts the decode itself in the lexicon and requires the exact path to fire, which
proves the branch is reachable and the near-miss floor is the only thing
declining it.

The second note: `Finding.detail` for `leet` is exactly the matched lexicon word,
so the `in` check is now `==`. Containment would accept a longer word that merely
contains the expected one.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Richard Quinn <quinn.richard@gmail.com>
raeq added 2 commits September 1, 2026 19:01
The `leet` branch has two sub-paths: the decode is a lexicon word, or the decode
is one edit from one. The first has a floor of three. The second carried an
uncommented `d.chars().count() >= 6` from #393.

`leet_sub` maps `'1'` to `'i'` rather than `'l'`. That is correct — `1` looks
like `i` in most faces — but it means a `1`-for-`l` substitution never decodes
*exactly*, and can only ever be caught by the second path. Below six characters
that path does not run, so the whole substitution class went unreported on short
targets. Same brand, same five letters, one digit each: `l0gin` was caught and
`1ogin` was not. Measured, 0 of 5 five-letter brands against 7 of 7 six-letter
ones.

Now five, as a named constant carrying the measurement. Over 65 ordinary
digit-bearing tokens (`mp3`, `k8s`, `sha1`, `i18n`, `rtx4090`, …) against a
234k-word lexicon, and 8 single-substitution brand spoofs:

  floor | false positives / 65 | spoofs caught / 8
  ------|----------------------|------------------
    3   |         22           |        8
    4   |         12           |        8
    5   |          5           |        7
    6   |          4           |        3

Five costs exactly one false positive more than six — `top10`, whose decode
`topio` is one edit from a word — and more than doubles the spoofs caught. Four
costs eight more to gain one (`1yft`, four letters), which is the wrong side of
the knee.

The issue argued the floor should go entirely, on the grounds that the exact
path already fires below it and produces false positives anyway. The measurement
does not support that: the exact path contributes 4 of the 65 and removing the
floor takes the total to 22. The floor is doing work; it was set too high. That
reasoning is on the constant, so the next reader does not repeat the measurement.

Two rows of #726's table moved with it. `gn0r3!` and `!dm1n` were asserted
**clean**, and the test gave the six-character floor as the reason — freezing the
floor's own defect as the correct answer. Both are one edit from the word they
imitate, `ignore` and `admin`, and both are now reported. The test is inverted
rather than deleted, and says why.

The near-miss path was also undocumented. `docs/user-guide/anomaly-detection.md`
described the exact path only, which predicts neither `paypa1` firing nor `app1e`
staying clean.

Refs #762
Closes #825

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Richard Quinn <quinn.richard@gmail.com>
#877 review, two notes, both correct.

The negative cases described `k8s` as decoding to `kos` and `b2b` to `bab`.
Those are the words `nearest()` *matched* in the measurement, not the decodes —
`leet_sub` maps '8' to 'b' and '2' to 'z', so they are `kbs` and `bzb`. The
lexicon was built from the wrong list, which left it unclear which entries were
one edit from what.

Each row is now an explicit (token, decode, neighbour) triple, so the one-edit
relationship is checkable instead of described. The decode is never in the
lexicon: seeding it would make these exact-path hits and the test would measure
the wrong path.

That exposed a second gap the note did not raise. A token that never enters the
leet branch — too short a base, a literal number — passes a "does not fire" test
for a reason with nothing to do with the floor, and two of the original rows
(`x86`, `3pm`) were exactly that. Every row now has a companion assertion that
puts the decode itself in the lexicon and requires the exact path to fire, which
proves the branch is reachable and the near-miss floor is the only thing
declining it.

The second note: `Finding.detail` for `leet` is exactly the matched lexicon word,
so the `in` check is now `==`. Containment would accept a longer word that merely
contains the expected one.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Richard Quinn <quinn.richard@gmail.com>
@raeq
raeq force-pushed the fix/leet-near-miss-floor branch from 6ee7e14 to 2256b41 Compare September 1, 2026 17:03
@raeq
raeq merged commit 9f3d912 into main Sep 1, 2026
22 checks passed
@raeq
raeq deleted the fix/leet-near-miss-floor branch September 1, 2026 17:08
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.

The leet near-miss path is floored at demangled length 6, so 1ogin screens clean while l0gin is caught

2 participants