Pre-release hardening from an independent adversarial review; each fixed
test-first (failing test -> fix -> green). 214 tests pass.
- entropy (M1): decode-and-rescan now runs BEFORE false-positive suppression,
so an SRI/media-prefixed injection blob is still decoded and lexicon-rescanned.
Suppression gates only the entropy finding, never the decode.
- output/disposition (M3): the invisible-carrier invariant now holds on the
persist gate. scan_output flags zero-width/BIDI presence and disposition
treats those + lexicon:unicode-tags-present as any-tier carriers, so a carrier
in model output fails secure even under a trusted policy.
- contract (M2): assert_credential_allowlist catches a bare <PROVIDER>_KEY
(e.g. STRIPE_KEY) that the old regex silently missed (fail-open). Deliberately
broad: also flags PARTITION_KEY/SORT_KEY as loud, allowlistable FPs -- fail-loud
beats fail-silent for an isolation control.
- disposition (m6): guard runs decide inside its guarded block -> total
fail-closed even on a malformed report.
- output (m4): egress placeholder suppression anchors word markers (example,
todo, ...) to a word boundary, closing a fail-open where a real secret merely
containing such a word was suppressed.
Docs: CHANGELOG Security subsection; README honest-limit for lexicon dedup (m5,
documented tradeoff, not fixed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HyRCQMocjZ6SmSQ6JidJ2k
Module 11 (final) — the top-level wiring. The library never makes the model call
(no SDK imported by the core), so the public surface is the toolkit plus two
library-side bookends around the caller's tool-less transform (Form 1, chosen
with the operator over an export-only toolkit and a full orchestrator — the
bookends fit existing pipelines with least friction, encode the two halves the
library can stand for, and impose no control flow):
- prepare_input(text, source=INPUT) -> PreparedInput(fenced, nonce, report):
§6 steps 1-2, sanitize THEN fence (carrier can never smuggle a forged
delimiter). Merged report carries both steps' findings; renders no disposition.
- screen_output(text, policy, *, provenance, transform_failed) -> DispositionResult:
§6 steps 6-7, scan_output under guard() so a scanner error fails CLOSED
(FAIL_SECURE, never a silent persist). transform_failed routes the compound
forced-fallback rule.
- __all__ exports the full framework-agnostic surface: detectors, result types,
disposition machinery + presets, contract asserters, the grounding seam.
Docs: README refreshed from the stale "brief / pre-implementation" line to a v0.1
alpha status with a Form-1 quickstart, the §6 adopt-this checklist, and an honest
-limitations section (structural unsolvability at the text layer; semantic
poisoning invisible to lexicon+entropy; text-only, no multimodal). CHANGELOG
seeded; CLAUDE.md remote/status lines corrected (remote IS set, no longer
brief-stage).
10 wiring tests (public surface, prepare_input compose, screen_output fail-closed
+ compound). 189 green (showcase + corpora follow).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HyRCQMocjZ6SmSQ6JidJ2k
Add .gitignore (Python stack + obligatory lines; /STATE.md kept local-only
until a private remote exists), CHANGELOG.md, and the Communication patterns
section in CLAUDE.md. STATE.md is gitignored — must never reach the public
open/ mirror.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8GmKRCdsPjWYAKWsNgeQS