1
0
Fork 0
llm-ingestion-pipeline-secu.../tests/test_calibration.py
Kjell Tore Guttormsen 7430d7855a
feat(active): grade a short URL token that base64-decodes to text as carrying
Gap 6 closed. _is_opaque has a fourth branch for host labels and path
segments of 8-19 chars (calibration.URL_SHORT_B64_MIN_LEN). It fires when
the token is alphanumeric, not 1 mod 4, not itself a word, and its base64
plaintext reads as text: only letters, digits, space and . , : @ _ - + / !
? ' =, at least 80% letters/digits/spaces (URL_SHORT_B64_TEXT_SHARE), and
no lowercase-to-uppercase seam. ![x](https://evil.test/aGVsbG8gd29ybGQ)
is now HIGH; a link with it MEDIUM.

"Printable" alone fired on Pygments, a Norwegian path word and a JSON size
parameter. Each condition removes a measured twin (Pygments, aktuelle,
Slovenly, {"w":200}, PT00M01S, endsWith). Measured before building, with
the shipped tokenizer: 0 of 22,725 URLs in the 3,583 clean documents and
577 package READMEs, 0 of 530,625 dictionary words, 9 of 336,122
identifiers and prose tokens (all real base64 of text). Recall 32 of 35.

Measured before and after on the same trees: all 4,160 documents are
byte-identical under both presets (disposition, labels, severity, count,
offset); OKF import stays 47/31; positive control WARN -> FAIL_SECURE.
is_ordinary_url about 4 us slower per URL; 1M-char worst case unchanged.

26 new tests (attacks red first), 11 of 11 mutations red. Matrix 147/147 +
4/4 gaps, suite 1300 passed, ReDoS sweep 173 patterns, 0 flags on the new.
2026-11-01 20:23:23 +01:00

170 lines
6.7 KiB
Python

"""test_calibration — freeze the shared calibration surface (Session D).
Session D consolidated every tunable threshold into
``llm_ingestion_guard.calibration`` so the Node port can mirror *exactly* the
same numbers. These tests are the frozen contract in two halves:
1. the raw values themselves (the tuple the port shares), and
2. the proof that each detector actually *sources* its threshold from here —
so the freeze is a live single-source-of-truth, not a dead copy that can
silently drift from the value the code uses.
Changing a calibration number is a deliberate recalibration: it must break a
test here first.
"""
from __future__ import annotations
from llm_ingestion_guard import calibration as cal
from llm_ingestion_guard.report import Severity
# --- frozen raw values ------------------------------------------------------
def test_entropy_thresholds_frozen():
assert (cal.ENTROPY_CRITICAL_H, cal.ENTROPY_CRITICAL_LEN) == (5.4, 128)
assert (cal.ENTROPY_HIGH_H, cal.ENTROPY_HIGH_LEN) == (5.1, 64)
assert (cal.ENTROPY_MEDIUM_H, cal.ENTROPY_MEDIUM_LEN) == (4.7, 40)
def test_entropy_shape_floors_frozen():
assert cal.ENTROPY_BASE64_FLOOR_LEN == 100
assert cal.ENTROPY_HEX_FLOOR_LEN == 64
def test_lexicon_selfsafety_frozen():
assert cal.MAX_SCAN_CHARS == 1_000_000
assert cal.ROT13_MIN_LEN == 40
def test_output_selfsafety_frozen():
assert cal.MAX_CONNSTR_VALUE == 256
assert cal.MAX_STYLE_ATTR_CHARS == 2048
def test_hex_wrap_floor_frozen_and_sourced():
# Gap 1 (1.10.0): the shortest hex run the output gate decodes and rescans
# for credentials. 32 characters is 16 bytes.
from llm_ingestion_guard import output
assert cal.HEX_WRAP_MIN_LEN == 32
assert output.HEX_WRAP_MIN_LEN is cal.HEX_WRAP_MIN_LEN
def test_hidden_markup_thresholds_frozen():
assert cal.HIDDEN_FONT_MAX_PX == 4.0
assert cal.HIDDEN_COLOUR_TOLERANCE == 8
def test_output_module_sources_hidden_markup_from_calibration():
from llm_ingestion_guard import output
assert output.HIDDEN_FONT_MAX_PX is cal.HIDDEN_FONT_MAX_PX
assert output.HIDDEN_COLOUR_TOLERANCE is cal.HIDDEN_COLOUR_TOLERANCE
assert output.MAX_STYLE_ATTR_CHARS is cal.MAX_STYLE_ATTR_CHARS
def test_cognitive_load_lengths_frozen():
assert cal.COGNITIVE_LOAD_MIN_LEN == 2500
assert cal.COGNITIVE_LOAD_TAIL_START == 2000
def test_disposition_rank_frozen():
assert cal.DISPOSITION_RANK == {
"warn": 0,
"quarantine_review": 1,
"fail_secure": 2,
}
def test_active_content_severity_frozen():
assert cal.ACTIVE_CONTENT_SEVERITY == {
"markdown-image": Severity.HIGH,
"markdown-link": Severity.MEDIUM,
"reference-link": Severity.MEDIUM,
"autolink": Severity.MEDIUM,
"raw-html": Severity.HIGH,
"raw-html-link": Severity.MEDIUM,
"data-uri": Severity.HIGH,
}
def test_url_shape_thresholds_frozen():
# 0.3.1: severity grades on URL shape. These floors sit above every
# legitimate documentation URL token measured on 2026-07-25 (worst: H=4.08)
# and below the base64/hex payload segments an exfil path uses (4.36-4.54).
assert cal.ACTIVE_CONTENT_ORDINARY_SEVERITY is Severity.LOW
assert (cal.URL_OPAQUE_ENTROPY_H, cal.URL_OPAQUE_MIN_LEN) == (4.4, 24)
assert cal.URL_OPAQUE_HEX_MIN_LEN == 32
def test_short_text_base64_floors_frozen():
# Gap 6 (1.11.0): a URL token of 8+ chars whose base64 plaintext reads as
# text. 8 chars is the shortest token that decodes to 6 bytes; the 0.8 share
# rejects `PT00M01S` (`==43MR`, 4 of 6) and keeps `hunter2!` (7 of 8).
assert cal.URL_SHORT_B64_MIN_LEN == 8
assert cal.URL_SHORT_B64_TEXT_SHARE == 0.8
def test_no_detector_emitted_low_before_the_url_shape_change():
"""The floor change (any finding -> MEDIUM+) is only honest as a *patch* if
nothing that shipped before it emitted LOW — otherwise it would silently
loosen an existing consumer's gate. The lexicon is the only table-driven
severity source; assert it still holds no LOW/INFO pattern."""
from llm_ingestion_guard.lexicon import load_lexicon
assert not [p for p in load_lexicon()
if p.severity in (Severity.LOW, Severity.INFO)]
# --- binding: each detector reads its threshold from calibration ------------
# The freeze is meaningful only if the modules actually READ these values. An
# import alias binds the SAME object, so identity (`is`) proves the single
# source of truth rather than a coincidental equal copy.
def test_entropy_module_sources_from_calibration():
from llm_ingestion_guard import entropy
assert entropy._CRITICAL_H is cal.ENTROPY_CRITICAL_H
assert entropy._CRITICAL_LEN is cal.ENTROPY_CRITICAL_LEN
assert entropy._HIGH_H is cal.ENTROPY_HIGH_H
assert entropy._HIGH_LEN is cal.ENTROPY_HIGH_LEN
assert entropy._MEDIUM_H is cal.ENTROPY_MEDIUM_H
assert entropy._MEDIUM_LEN is cal.ENTROPY_MEDIUM_LEN
assert entropy._BASE64_FLOOR_LEN is cal.ENTROPY_BASE64_FLOOR_LEN
assert entropy._HEX_FLOOR_LEN is cal.ENTROPY_HEX_FLOOR_LEN
def test_lexicon_module_sources_from_calibration():
from llm_ingestion_guard import lexicon
assert lexicon.MAX_SCAN_CHARS is cal.MAX_SCAN_CHARS
assert lexicon._ROT13_MIN_LEN is cal.ROT13_MIN_LEN
def test_output_module_sources_from_calibration():
# The bound is baked into the compiled patterns, so `is` on a module
# attribute cannot prove sourcing here -- assert the compiled regex carries
# the calibrated number instead.
from llm_ingestion_guard import output
assert output.MAX_CONNSTR_VALUE is cal.MAX_CONNSTR_VALUE
connstr = [p for p in output._SECRET_PATTERNS if p.id.endswith("-connstr")]
assert len(connstr) == 4
for pattern in connstr:
assert f"{{1,{cal.MAX_CONNSTR_VALUE}}}" in pattern.regex.pattern
def test_disposition_module_sources_from_calibration():
from llm_ingestion_guard import disposition
from llm_ingestion_guard.disposition import Disposition
# Enum-keyed rank reconstructed from calibration's value-keyed source.
assert disposition._DISPOSITION_RANK == {
Disposition.WARN: 0,
Disposition.QUARANTINE_REVIEW: 1,
Disposition.FAIL_SECURE: 2,
}
def test_active_content_module_sources_from_calibration():
from llm_ingestion_guard import active_content
assert active_content._SEVERITY is cal.ACTIVE_CONTENT_SEVERITY
assert active_content._ORDINARY_SEVERITY is cal.ACTIVE_CONTENT_ORDINARY_SEVERITY
assert active_content._OPAQUE_H is cal.URL_OPAQUE_ENTROPY_H
assert active_content._OPAQUE_MIN_LEN is cal.URL_OPAQUE_MIN_LEN
assert active_content._OPAQUE_HEX_LEN is cal.URL_OPAQUE_HEX_MIN_LEN
assert active_content._SHORT_B64_MIN_LEN is cal.URL_SHORT_B64_MIN_LEN
assert active_content._SHORT_B64_TEXT_SHARE is cal.URL_SHORT_B64_TEXT_SHARE