"""active_content — report-only detection of active content (the EchoLeak class). Query-time guardrails guard the answer; this guards the *persisted artifact*. Active-content constructs in persisted text become an exfiltration channel the moment a renderer touches them: a markdown image URL is auto-fetched zero-click (the EchoLeak class, CVE-2025-32711), a link invites the click, raw active HTML executes. ``lexicon`` and ``entropy`` cannot see these carriers — they are neither injection strings nor high-entropy blobs — so this detector is the gate's coverage for OWASP LLM05 (Improper Output Handling). This module is the canonical home of the active-content pattern table. Two consumers share it: * :func:`scan_active_content` (here) — **report-only**: findings feed ``scan_output`` and thence disposition; the text is never touched. * :func:`~llm_ingestion_guard.neutralize.neutralize` — the separate, opt-in **mutator** that defangs the same constructs for human audit. One deliberate asymmetry between the two: the scanner flags a construct only when its URL is absolute or protocol-relative. A relative in-document link has no attacker-reachable endpoint, and flagging it would silently over-block legitimate wiki/OKF content (design principle 5) — cross-linking is those formats' core mechanism. ``neutralize`` keeps its broader defang-anything behavior: it is opt-in, and bracketed dots in a relative path are auditable, not blocking. The two predicates are therefore separate symbols — :func:`is_active_tag` for the scanner, :func:`is_defangable_tag` for the mutator. They were one symbol until 0.6.0, imported by name across modules, so narrowing the scanner would have moved the mutator silently. **The asymmetry covers raw HTML too** (0.6.0). It previously applied only to the markdown paths: a tag was active if it carried a URL attribute *at all*, so an MDX ```` — a doc-relative route on a name outside the active set — carried HIGH. It now requires an external target, the rule the markdown paths have applied since 0.3.1. ```` left the active *name* set in the same change: its whole affordance is its ``href``, which the URL-attribute branch still catches, while the attribute-less ```` of Azure APIM policy XML has no affordance in any renderer. Measured together rather than one at a time — the classes co-occur — the pair frees 25 of 133 non-WARN documents on the reference corpus and 2 each on the two wiki corpora, at unchanged recall. Method and numbers: ``docs/rawhtml-census.py``; residuals: ``docs/LIMITATIONS.md``. **Raw HTML grades on carrier too, and a tag that names no target is inert** (0.7.0). Two changes that had to ship together, because they co-occur: * the *carrier split* — ````/```` are click-required, so they report as ``active:raw-html-link`` at MEDIUM, the grade the markdown inline link has carried since 0.3.1. Until 0.6.1 the same URL was LOW as ``[t](url)`` and HIGH as ````: an asymmetry produced by syntax, not by affordance. * the *no-URL narrowing* — a tag whose entire affordance IS the URL it names (``_URL_AFFORDANCE_TAGS``), carrying no URL attribute at all, has no affordance in any renderer. This is ````'s argument from 0.6.0 applied to the rest of the name branch, and it frees ````, ````, ``