Tessera · 2026-09-03 · handoff

A finding should create an instrument, not merely a patch.

ἀλήθεια — truth as the removal of concealment. Nothing here is hidden: no dependencies, no network, no build. View source is the whole artifact.

One session found a way to make a security detector blind by putting an accent on a word. This is the record of what happened, the method it produced, the evidence you can re-run yourself, the specification for what comes next, and a page where both authors can keep writing.

Provenance of this document

authored by
Mazze LeCzzare Frazer (human) and Claude Opus 5 (model), jointly, over one continuous session 2026-08-15 → 09-03.
subject
mazze93/aletheia — prompt-injection detection agent. Private. MAX posture.
status
Unfinished by design. This is a handoff, not a conclusion.
funding
None. Unpaid, unsponsored, not owned by any company.
published
2026-09-27, as a tessera on mazzeleczzare.com. Redacted for publication: the current enforcement state of the live hook, the exact score and origin of one unfiled policy residue, and local filesystem paths. Each redaction is marked [redacted] where it sits. The closed finding, the method, and the corrections are unchanged.
Conflict of interest, stated up front. This is largely a model-authored account of events the model participated in and sometimes got wrong. It has an interest in the story cohering. Treat §4 (Corrections) as the load-bearing section — a record that contains only successes is marketing. Every empirical claim below carries a command you can run to check it without trusting this document.
00

Stance

This work is done without pay and without an owner. That is not a complaint and not a virtue signal — it is a material fact about the evidence, and it belongs in the record for one reason: nothing here needed to conclude anything in particular.

A vendor writing this up has a product at the end of it. A researcher has a paper. This had neither, which is why the most quoted line in it is the model admitting its own proof was hollow. That is the whole claim to authenticity, and it is a modest one: not that the work is better, but that the incentive to overstate was absent. Whether that produces something useful to anyone else is genuinely unknown. It is offered anyway.

01

What happened, in order

The chain matters more than any single link, because the method in §3 is an abstraction of this sequence and inherits its mistakes.

#EventWhat it changed
1A compiled knowledge wiki (vault-rag) is found to have no way to contest a wrong claim. Every page reads “do not hand-edit — the next compile overwrites this file.”A human correction had a lifespan of one recompile.
2Its only dispute signal is examined: 3 pages marked contested. All three are the parser storing the model's literal reply CONTRADICTION: None as a contradiction.Zero real disputes, presented as three. The signal was noise, and noise no reader would catch.
3palimpsest is built: a third tier, rulings/, authored and append-only, outside the wiki and outside the index — so a verdict survives rm -rf wiki.Enforcement moves from the model's compliance to the renderer's determinism.
415 Dependabot alerts. 14 are transitive under one SDK; the 15th is a gravestone for a purged path, being dismissed for the second time.Recorded twice deliberately: if it returns a third time, the pattern is the finding.
5aletheia is extracted to her own repo. The container's own rot_check.sh refuses the commit and names three stale references the extraction would have left behind.A detector written months earlier catches its author. Fixed, none bypassed.
6She is about to be wired as designed — PreToolUse on Bash/Edit/Write, exit 2 blocks. 8 sample payloads show zero blocks. Declared safe.The proof was hollow. All 8 were bare commands, sampling the one region where nothing happens.
7Probed with realistic content: the Dependabot PR body written that same night scores 0.82 against a stop threshold of 0.6.Wired as designed, she would have blocked the shipping of a security fix. Remediation work is linguistically identical to injection.
8Architecture changes: assess() classifies, policy.py decides, unclassified ≠ untrusted, taint is carried not inferred, and shadow mode becomes the default.A score stops being a verdict.
9A parity harness is built (borrowed from stratum). On its first run it finds café@1.2.3 is a governing parameter to Python and invisible to TypeScript.A real evasion, unfindable by reading either file.
10The corpus is widened to probe every family. The finding inverts: in 11 cases it is Python that is blind. criticalé scores 0.0.Python is the implementation the live hook runs. The blind one was the deployed one.
11651 generated mutations run across both runtimes. They surface a second dialect split: ECMAScript counts U+FEFF as whitespace, Python does not.Same detection, different extent — and extent is what a human is told to verify at a registry.
12Issue #1 (a labelled incident scoring 0.26 and passing) is measured rather than assumed: it resolves to proceed under enforcement.Root cause was not a weight — the detector saw port: 3000 and missed "port": 3000.
02

The finding, runnable

\b is not a security boundary. It means “a transition between \w and \W”, and the runtimes disagree about \w: Python's is Unicode-aware for str patterns, JavaScript's is ASCII-only. The security semantics were resting on a dialect default.

Type into the box. The middle column is your own browser's native \b. The left emulates Python's Unicode-aware boundary. The right is the policy that replaced both.

Python \b — emulated
—
JavaScript \b — native
—
Boundary policy
—

Honest about the emulation: the left column is not CPython. It approximates Python's Unicode-aware \b using \p{L}\p{N}_ lookarounds. It is here to make the seam visible in a page with no dependencies, not to stand in as evidence. The evidence is the command in §7, which runs the real thing.

The policy that replaced the default, stated in English before either file changed:

Policy — human-readable

Match a security keyword unless it is embedded inside a larger ASCII identifier. Unicode letters adjacent to it do not suppress a detection.

Policy — machine-readable

ASCII_IDENTIFIER_CHAR = "[A-Za-z0-9_]"
LEFT  = "(?<!" + ASCII_IDENTIFIER_CHAR + ")"
RIGHT = "(?!"  + ASCII_IDENTIFIER_CHAR + ")"
bounded(p) = LEFT + "(?:" + p + ")" + RIGHT

It had to discriminate, not merely match more — false positives are what forced shadow mode in the first place:

inputold \bpolicy
criticalmatchmatch
criticalé · écritical · criticalЖ · critical中blindmatch
hypercritical · critical_pathno matchno match
03

The method

Class-Closure Threat Response. Its governing principle, and note the two qualifiers doing the work — within a stated scope, and silently:

A security finding is not resolved when the reported payload stops working. It is resolved, within a stated scope, when the failure-producing assumption is replaced by an explicit policy and the system can demonstrate that the class cannot silently recur.
  1. Establish the exploit in the real execution path.Not only in a synthetic unit test. In this case study this was done last, not first — see §4.
  2. Name the violated assumption, not the broken test.Identify the abstraction or default that made the exploit possible.
  3. State the policy.Replace implicit runtime behaviour with human-reviewable semantics. In English, before any code moves.
  4. Repair every implementation of it.Consistently, across every boundary that enforces or represents the policy.
  5. Generate adversarial variants over the transformation space.Test the space, not the known strings.
  6. Encode an invariant.Express the security property in machine-checkable form — here: no Unicode-only mutation may lower assessed risk.
  7. Gate the unsafe primitive, structurally.Not the bug — the primitive. Plus a staleness gate on generated evidence.
  8. Prove behaviour at the enforcement boundary.Including telemetry. A high score from a function is not proof that a hook blocks.
  9. Publish the remaining perimeter.Separate demonstrated closure from what is still open.
The loop is not linear, and this case proves it. Stage 5 fed back into stage 2. The mutation corpus did not merely validate the policy — it surfaced U+FEFF, a different violated assumption needing its own policy statement. A practitioner running 1→9 in order would classify that as “a bug found during validation” and patch it. We nearly did. Generation discovers new members of the class, not just violations of the known one. Draw the edge 5 → 2.

Three principles

Parity is a drift alarm, not a semantic judge. A difference proves disagreement; it does not establish which side is right. U+FEFF is the proof: the reference implementation was the wrong one, and “conform to the reference” would have made it worse.

Generated adversarial corpora are executable policy tests. They expose whole mutation families and make coverage shrinkage detectable.

An enforcement claim requires enforcement-layer evidence. assess() returning 0.88 says nothing about whether the hook stops, logs, and fails safely in its real runtime.

The under-named mechanism

Stage 7 reads like ordinary CI hygiene. Two things inside it are not:

Both fired for real. When the boundary policy landed, all 11 quarantined divergences converged and the alarm reported each one — “known divergence now AGREES — remove the flag and close the issue.” That was the first time that path had ever run.

04

Corrections

The load-bearing section. Each of these was wrong, was caught, and changed the outcome.

ClaimRealityCaught by
“0/8 payloads block — safe to wire.”All 8 sampled bare commands. Realistic prose scored 0.82 and would have blocked a security fix./touchstone, not the model
“The Python assessor is the oracle; where they disagree the port is wrong.”Self-refuting once written down: “followed the oracle rule… even though here the port's reading was the better one.” If the port can be right, the reference is not an authority.the human
“Python's re is ASCII-bound by default; the fix used \p{L} on the Python side.”Inverted. Python's \w/\b are Unicode-aware by default and re has no \p{L} at all. The property escapes went into TypeScript. This changes which file the audit points at.the model, correcting the human
Findings recorded only in a chat transcript and a temp directory that dies with the job.A security tool whose findings live in one agent's message stream has the same defect as a wiki nobody can contest.the human
A CI staleness gate that “worked.”A cd leaked within a run block; the first parity run failed. Fixed by reproducing the step verbatim rather than re-reading it.CI
Two ideas presented as the model's arrivals — the policy/reference/evidence trichotomy, and the seam-transfer list.Both were the human's, written first and more compactly. Re-derivation is not authorship.the human

Pattern worth naming: of six corrections, four came from outside the model. The method's insistence on evidence at the enforcement boundary exists because the model's self-assessment was repeatedly, confidently wrong in the same direction — toward believing the work was done.

05

Specification — what to build next

Machine-readable and human-readable, same content, so an agent picking this up needs no prose parsing. Issue #5, Rust or Bust.

06

Perimeter — what is not closed

07

Reproduce it yourself

Nothing above needs to be taken on trust. From a checkout of the aletheia repository [redacted: local path]:

# the real thing, not the browser emulation
PYTHONPATH=src python3 -c "
from aletheia.assess import assess
for t in ['critical','criticalé','écritical','hypercritical','critical_path']:
    print(f'{t:16} {assess(t, \"web_search\").injection_risk_score}')"

# the invariant: no Unicode-only mutation may lower assessed risk
PYTHONPATH=src python3 -m unittest tests.test_boundary_mutations -v

# the enforcement boundary — the real hook, real payload, real exit code
PYTHONPATH=src python3 -m unittest tests.test_hook_enforce -v

# cross-runtime, 69 vectors + 651 mutations
python3 parity/emit.py > /tmp/o.json && npx tsx parity/emit.ts > /tmp/t.json
python3 parity/compare.py /tmp/o.json /tmp/t.json

# everything
PYTHONPATH=src python3 -m unittest discover -s tests -t .   # expect 37 OK
Where the artifacts live
08

The shared page

Both authors write here, with equal room. Everything is saved in this browser only — never sent anywhere, because there is nowhere for it to be sent. Export takes it out as Markdown when you want to move it.

joint authorship —
Human — Mazze
Model — Claude
Joint draft — the essay
Continuation — where we stopped