ἀλήθεια — 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.
mazze93/aletheia — prompt-injection detection agent. Private. MAX posture.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.
The chain matters more than any single link, because the method in §3 is an abstraction of this sequence and inherits its mistakes.
| # | Event | What it changed |
|---|---|---|
| 1 | A 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. |
| 2 | Its 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. |
| 3 | palimpsest 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. |
| 4 | 15 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. |
| 5 | aletheia 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. |
| 6 | She 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. |
| 7 | Probed 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. |
| 8 | Architecture 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. |
| 9 | A 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. |
| 10 | The 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. |
| 11 | 651 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. |
| 12 | Issue #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. |
\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.
\b — emulated\b — nativeHonest 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:
Match a security keyword unless it is embedded inside a larger ASCII identifier. Unicode letters adjacent to it do not suppress a detection.
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:
| input | old \b | policy |
|---|---|---|
critical | match | match |
criticalé · écritical · criticalЖ · critical中 | blind | match |
hypercritical · critical_path | no match | no match |
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.
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.
Stage 7 reads like ordinary CI hygiene. Two things inside it are not:
\b reappearing fails the build, whatever the tests say.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.
The load-bearing section. Each of these was wrong, was caught, and changed the outcome.
| Claim | Reality | Caught 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.
Machine-readable and human-readable, same content, so an agent picking this up needs no prose parsing. Issue #5, Rust or Bust.
index and query have still never been run live, and the authored rulings/ tier — named in its own ADR as the one thing worth backing up — still has no backup. owner: mazzeNothing 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
src/aletheia/boundary.py · typescript/src/boundary.ts — the policy, stated twiceparity/unicode_boundary_mutations.py — the generator (21 atoms × 8 scripts × format chars × 2 edges)tests/test_boundary_inventory.py — the primitive gatetests/test_hook_enforce.py — enforcement-boundary evidencedocs/journal/DECISIONS.md — dated, reversible decisionsdocs/journal/SESSION-2026-08-15.md — the narrative recordSECURITY.md — posture, known gaps, pre-enforcement checklistBoth 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.