1
0
Fork 0

feat(output): decode hex-wrapped credentials at the persist gate

Gap 1 closed. The output gate finds runs of 32+ hex digits
(calibration.HEX_WRAP_MIN_LEN), decodes both byte alignments, keeps text
that is at least 80% printable (the base64 bar, now one shared helper in
entropy) and runs the credential patterns on it. A hex-wrapped AWS key
surfaces as decoded:egress:aws-access-key-id with "in hex run @ N" as
evidence; the value is never in it. Credentials only: a hex-wrapped
injection is not decoded.

Measured before and after on the same trees: all 3583 clean documents
(three FP populations and the 78 pinned OKF concepts) are identical line
for line under both presets (disposition, labels, severity, count,
offset); OKF import stays 47/31. They and 569 READMEs hold 2403 hex runs
of 32+ digits, none of which decodes to text. scan_output CPU 122.9 s
before, 123.4 s after; one solid hex run at the 1M cap 4.2 s -> 5.8 s.

14 new tests (10 red for the right reason first), 6 of 6 mutations red.
Matrix 146/146 + 5/5 gaps, suite 1274 passed, ReDoS sweep 169 patterns.
This commit is contained in:
Kjell Tore Guttormsen 2026-11-01 17:06:44 +01:00
commit 0eab4345bf
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
14 changed files with 265 additions and 40 deletions

View file

@ -222,8 +222,9 @@ Conceding these is itself a control. The full list is in the guard's `README.md`
- **Text-only, extracted-text-only.** No file parsing in the core; what survives
text extraction (macros, OLE objects, OCR-embedded instructions, render/font
stego, encrypted files) is out of scope beyond the sanitizer's character layer.
- **Secret egress: base64-wrapped is caught, hex-wrapped is not** (a documented
boundary — decode the transport layer first if you need it scanned).
- **Secret egress: base64- and hex-wrapped are caught (hex since 1.10.0); hex with
separators, other encodings and nested wraps are not** (a documented boundary —
decode the transport layer first if you need it scanned).
## 9. Where to read more (in the guard repo)

View file

@ -623,11 +623,14 @@ items; this is the full list, each with the mechanism.
the most common shape in real documentation. The residual: a *clicked* link to an
attacker-controlled page can have its `location.hash` read by that page's script,
so a fragment payload on a link (not an image) is uncovered.
- **Secret egress: base64-wrapped is caught, hex-wrapped is not.** The output gate
decodes base64 blobs and re-scans the plaintext, so a base64-*wrapped* secret
surfaces as `decoded:egress:*`. `entropy` exposes decoded plaintext for base64
only, so hex (and other encodings, or nested wraps) is a deliberate boundary —
decode the transport layer first if you need it scanned.
- **Secret egress: base64- and hex-wrapped are caught, other wraps are not.** The
output gate decodes base64 blobs and, since 1.10.0, hex runs of 32 characters or
more (both byte alignments, so one stray digit cannot hide the credential), and
re-scans the plaintext, so a wrapped secret surfaces as `decoded:egress:*`. Hex
split by separators (`41:4b`, `41 4b`, `\x41`), other encodings and nested wraps
are a deliberate boundary — decode the transport layer first if you need it
scanned. The hex plaintext goes to the credential patterns only: an injection
wrapped in hex is not decoded (one wrapped in base64 is).
- **Prose that merely mentions `<script>` fires `hybrid-xss:script-tag`.** The
pattern matches the opening tag and no longer requires `</script>`, so a
@ -686,7 +689,7 @@ items; this is the full list, each with the mechanism.
missed; on one preset it is held for review instead of halted.
- **The ReDoS sweep has a measured sensitivity floor, not a clean bill of
health.** All 168 compiled patterns across all eleven regex-bearing modules are
health.** All 169 compiled patterns across all eleven regex-bearing modules are
swept arm by arm — payloads synthesised per run from each pattern's own
skeleton, so `[`, `[system]` and `[system](` are each probed separately rather
than relying on generic units, and each pattern is timed in the call mode the
@ -816,8 +819,11 @@ today (WARN) that the rule would stop. Because those 3,583 hold no external imag
569 package READMEs (from installed npm and PyPI packages, deduplicated by sha256)
were measured too.
1. **Hex-wrapped secret egress** — `entropy` decodes base64 only (above). Open.
Measured to close without new alarms; next in line.
1. **Hex-wrapped secret egress** — **closed in 1.10.0.** The output gate decodes
hex runs of 32 characters or more and runs the credential patterns on the text
(see "Secret egress" above). Measured as built: every one of the 3,583 documents
keeps its disposition, labels and severities. They and the 569 READMEs hold
2,403 hex runs this long (checksums and ids), and none decodes to text.
2. **Semantic / factual poisoning** — invisible to token analysis (above).
**Measured reason:** the cheapest rule, a factual claim of the form "is made of"
or "was completed in", gave 0 new alarms on the 3,583 and fired on 3 of the 569

View file

@ -283,6 +283,49 @@ skjøtene raden melder. Matrisesaken står nå som fanget (`lexicon:count`), og
ligger fortsatt ubehandlet i innboksen deres. Rad 1 er ikke framme før jobben deres
har kjørt lista vår.
## V.9 Økt 4, hull 1 lukket (01.11.2026, release 1.10.0)
**Hull 1 (heks-innpakket hemmelighet):** utgangsporten finner løp på minst 32
heks-sifre (`calibration.HEX_WRAP_MIN_LEN`), dekoder dem og kjører
hemmelighetsmønstrene på klarteksten. Funnet heter `decoded:egress:*`, som for
base64, og beviset sier `in hex run @ <offset>`. Matrisesaken `hex-wrapped` står nå
som fanget og sjekker at funnet kom fra heks-løpet, ikke fra en base64-dekoding.
`tests/test_gap_register.py` har `CLOSED = {"dedup": "count", "hex-wrapped": "hex-wrapped"}`.
**To avvik fra regelen målt i § V.6**, begge for å stå imot en enkel omgåelse:
- Begge byte-justeringene dekodes. Med bare én ville ett løst heks-siffer foran
nøkkelen (eller et oddetall-løp) gjemt den.
- Kravet er minst 80 % skrivbare tegn, samme krav som base64-klarteksten møter (én
delt hjelper i `entropy`), ikke 100 %. Med 100 % ville én søppelbyte foran nøkkelen
gjemt den.
Begge målt som bygget (under), så § V.6-tallet er ikke gjenbrukt.
Bare hemmelighetsmønstrene kjøres på klarteksten. En heks-innpakket injeksjon dekodes
ikke (en base64-innpakket gjør det). Det står i LIMITATIONS.
- **TDD:** 14 nye tester i `tests/test_output.py` erstatter den gamle hull-testen. De ti
som krever et funn var røde av riktig grunn (ingen funn), de fire negative holdt
allerede. Kalibreringstesten (`HEX_WRAP_MIN_LEN` frosset og lest fra
`calibration`) ble skrevet før konstanten, men ikke kjørt rød da. Den er rød med
33. Netto +14 tester. Mutasjonssjekk: bare justering 0, gulv 34, gulv 30, 100 % skrivbart,
steget fjernet og bevis-suffikset fjernet gir hver minst én rød test (6 av 6).
Gulv 30 overlevde først, og gulvtesten fikk en sak som dekker den.
- **Vaktregelen:** målt før og etter på samme trær. Alle 3 583 dokumenter har samme
disposisjon under begge forhåndsinnstillingene og samme etiketter,
alvorlighetsgrader, `count` og offset (utdataene er identiske linje for linje).
OKF-importen står på 47 fail-secure / 31 warn, uendret. 0 nye alarmer.
- **Overflaten:** de 3 583 og de 569 README-ene har **2 403** heks-løp på minst 32
tegn i **2 351** dokumenter (5 + 2 311 + 4 + 0 + 31; sjekksummer og id-er). **0**
dekodes til tekst, så regelen får aldri noe å skanne. Positiv kontroll: matrisetekstens
løp dekodes og fyrer, og et løp med vanlig tekst dekodes uten å fyre.
- **Ytelse:** `scan_output` over de 3 583 tok 122,9 s CPU før og 123,4 s etter. Ved
taket på 1 000 000 tegn koster ett sammenhengende heks-løp 5,8 s mot 4,2 s før,
og 31-tegns løp (under gulvet) 4,5 s mot 4,2 s.
- **ReDoS:** sveipet teller 169 mønstre (nytt: `_HEX_RUN_RE`). Fire kjøringer ga 1, 0,
0 og 0 tidsflagg (den kjente støyen), og output-tabellen alene 0 av 14.
- **Rad 2:** hull 1 og 4 er lukket, og 2, 3, 5 og 7 står med målt begrunnelse. Hull 6
er åpent og neste: regelen må kreve mer av klarteksten og måles på nytt (§ V.6).
## Forrige retning (godkjent av operatøren 28.10.2026, nådd 31.10)
> «llm-ingestion-guard bygger på nyeste utgave av hver trusselkatalog den viser til,