The taint detector today only fires when a source tool_output segment appears verbatim in a sink tool_input (see src/taintline/detectors/taint.py, TaintDetector.run). Real flows often launder data through an intermediate tool: an untrusted source output feeds a middle tool (e.g. a formatter/transform), whose output then reaches the sink. That two-hop flow is currently missed. This rule stays fully deterministic: it is a pure provenance walk over trace.in_order() with no LLM, no network, no clock, no RNG in the check path.
Acceptance criteria
The taint detector today only fires when a source tool_output segment appears verbatim in a sink tool_input (see
src/taintline/detectors/taint.py,TaintDetector.run). Real flows often launder data through an intermediate tool: an untrusted source output feeds a middle tool (e.g. a formatter/transform), whose output then reaches the sink. That two-hop flow is currently missed. This rule stays fully deterministic: it is a pure provenance walk overtrace.in_order()with no LLM, no network, no clock, no RNG in the check path.Acceptance criteria
TaintConfig(pydantic/frozen dataclass, mypy strict; no loose dicts); default keeps current single-hop behavior easy to reason about.Finding.messageandspan_idsname every hop on the path so the flow is auditable; output remains deterministic (sorted, no rng/timestamps).tests/fixtures/seeded/cover a positive two-hop flow, a sanitized two-hop flow (suppressed), and a negative (no verbatim carry). Domain-neutral tool names.tests/test_determinism.py-style byte-identical verdict across repeated runs andPYTHONHASHSEEDvalues.