feat(sanitize,fence,neutralize): reject oversize input instead of half-transforming it
The scanners cap by truncating: they return findings, so reading a prefix costs detection in the tail and nothing else. The three transform surfaces return *content*, where the same move is not available — a shortened document is silent data loss, and a transformed prefix followed by an untransformed tail is a bypass, since the attacker chooses where in the document the payload sits. So they fail secure instead. Above MAX_INPUT_CHARS (1 000 000) sanitize, fence and neutralize raise OversizeInputError. sanitize is step 1 of prepare_input and only ever removes, so that one refusal bounds the whole input path. OversizeInputError subclasses ContractViolation: a pipeline already bracketing its quarantined stage keeps failing closed rather than meeting a type it has never heard of. It inherits the alert-routable property too — sizes in the message, refusing surface in details, no input in either. Invariant now pinned across all three: returned text is always fully transformed, or not returned at all. Still uncapped and recorded in LIMITATIONS: scan_active_content called directly (through scan_output it inherits that cap) and the okf link graph. Both are detection-shaped, so truncate-and-flag transfers unchanged — mechanical, not policy. 699 tests (+23), coverage 128/128 + 6/6, ReDoS sweep 0 candidates / 150.
This commit is contained in:
parent
adf93e47fb
commit
2d98d6809d
10 changed files with 272 additions and 21 deletions
|
|
@ -18,6 +18,12 @@ Three asserts, matching the reusable-contract checklist (BRIEF §6, steps 3-4):
|
|||
env so a hijacked stage cannot even read a credential it was never granted;
|
||||
the assert then passes by construction.
|
||||
|
||||
A fourth asserter lives here for the same reason — it enforces rather than
|
||||
reports — though it belongs to the transform path rather than the quarantine
|
||||
checklist: **(d) bounded transform input**, :func:`assert_within_input_cap`,
|
||||
which the three content-returning surfaces call so an oversize document is
|
||||
refused instead of half-transformed.
|
||||
|
||||
Reference: ``claude-code-llm-wiki`` ``tools/wiki_ingest/enrich.py``
|
||||
``assert_quarantine`` — a pipeline-specific quarantine gate, generalized here
|
||||
into framework-agnostic, reusable pieces.
|
||||
|
|
@ -51,6 +57,40 @@ class ContractViolation(Exception):
|
|||
self.details = details
|
||||
|
||||
|
||||
class OversizeInputError(ContractViolation):
|
||||
"""A transform surface was handed more text than it will transform.
|
||||
|
||||
A *subclass*, not a sibling: a pipeline that already brackets its quarantined
|
||||
stage in ``except ContractViolation`` keeps failing closed rather than
|
||||
meeting an exception type it has never heard of. :attr:`details` names the
|
||||
surface that refused; the sizes go in the message, and neither carries any
|
||||
of the input, so the exception stays alert-routable like its parent.
|
||||
"""
|
||||
|
||||
|
||||
def assert_within_input_cap(text: str, *, surface: str, max_input_chars: int) -> None:
|
||||
"""Raise :class:`OversizeInputError` unless ``text`` fits the transform cap.
|
||||
|
||||
The write-time asserter for the *transform* surfaces (d). Where the scanners
|
||||
bound their work by truncating — findings are lossy in the tail and nothing
|
||||
else — a transform returns content, so a prefix-only result is either silent
|
||||
data loss or an untransformed tail the payload can be positioned into. The
|
||||
invariant these three keep instead is: returned text is always fully
|
||||
transformed, or not returned at all.
|
||||
|
||||
``max_input_chars`` is the largest accepted size, not the smallest rejected
|
||||
one.
|
||||
"""
|
||||
size = len(text)
|
||||
if size > max_input_chars:
|
||||
raise OversizeInputError(
|
||||
f"{surface}: input {size} chars exceeds cap {max_input_chars}; "
|
||||
"refused rather than partially transformed",
|
||||
code="oversize-input",
|
||||
details=(surface,),
|
||||
)
|
||||
|
||||
|
||||
# --- (a) tool-less transform -----------------------------------------------
|
||||
|
||||
# Populated tool surface across Anthropic + OpenAI request shapes. An empty
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue