Runtime security-corpus gate for the Phase 21 scheduled-refresh path per D-21.
Every existing security-corpus fixture acts as a refusal trigger on the
scheduled path. If freshly-fetched metadata trips a known-bad shape
(xml-crypto 2025 signature-wrapping family, PortSwigger Fragile-Lock
namespace-confusion shapes, etc.), this gate refuses the apply with
{:error, %Relyra.Error{type: :corpus_violation}} so the wrapper sets
auto_suspended_reason: :corpus_violation per the LOCKED enum.
The corpus manifest physically lives at priv/security_corpus.json so
both this runtime gate AND the test corpus reader at
test/security/xml/corpus_security_test.exs can read the same source
of truth without crossing the lib/test boundary in either direction.
Detection model
The gate evaluates the candidate XML against byte-level refusal shapes
derived from each manifest fixture's expected_error_type. The
pre-parse refusal shapes (:doctype_forbidden, :entity_expansion_forbidden,
:payload_too_large) catch the trust-boundary attacks the gate is
responsible for at the metadata-XML byte level — these are the same
pre-parse early-rejection conditions the hardened parser
(Relyra.Security.XML.PureBeam) already enforces, but routed through
the Phase-21 :corpus_violation typed error so the wrapper can set the
matching auto_suspended_reason.
Pure: no Repo, no Req, no telemetry. Reads priv/security_corpus.json
once at compile time (a @manifest module attribute).
Summary
Functions
Checks freshly-fetched metadata XML against the LOCKED security-corpus
regression fixtures. Returns :ok if no fixture's refusal-shape
matches; {:error, %Relyra.Error{type: :corpus_violation, details: %{matched_fixture_id: id, class: class, expected_error_type: type}}}
if any does.
Returns the loaded manifest fixtures (one map per fixture).
Returns the path the gate reads the manifest from at compile time.
Functions
@spec check( binary(), keyword() ) :: :ok | {:error, Relyra.Error.t()}
Checks freshly-fetched metadata XML against the LOCKED security-corpus
regression fixtures. Returns :ok if no fixture's refusal-shape
matches; {:error, %Relyra.Error{type: :corpus_violation, details: %{matched_fixture_id: id, class: class, expected_error_type: type}}}
if any does.
xml is the raw bytes; opts accepts :max_bytes to override the
default 5 MB cap that mirrors the Phase-21 stricter Req profile (D-20).
@spec manifest() :: [map()]
Returns the loaded manifest fixtures (one map per fixture).
@spec manifest_path() :: String.t()
Returns the path the gate reads the manifest from at compile time.