Skip to content

Sharpen the threat model: honored JAXP contracts, XInclude, and expansion limits - #66

Merged
ppkarwasz merged 10 commits into
apache:mainfrom
ppkarwasz:docs/honored-jaxp-contracts
Sep 1, 2026
Merged

Sharpen the threat model: honored JAXP contracts, XInclude, and expansion limits#66
ppkarwasz merged 10 commits into
apache:mainfrom
ppkarwasz:docs/honored-jaxp-contracts

Conversation

@ppkarwasz

@ppkarwasz ppkarwasz commented Sep 1, 2026

Copy link
Copy Markdown
Member

Makes explicit several things the securing recipes already rely on, or already do, but that the threat model and package Javadoc either left unsaid or stated more broadly than the code delivers.

Honored JAXP contracts — the in-scope requirement that an implementation respect the contract of the settings a recipe uses extends to the JAXP API contracts themselves:

  • a method handed a SAXSource carrying an XMLReader parses with that reader, as the SAXSource contract requires;
  • a method handed a StAXSource reads from the stream or event reader it carries, as the StAXSource contract implies (the reader must arrive positioned on START_DOCUMENT or START_ELEMENT, and the source is consumed during processing);
  • a SchemaFactory, Validator or ValidatorHandler locates the external resources it needs through the resolver installed on it, as setResourceResolver requires — "uses a LSResourceResolver when it needs to locate external resources" — with each schema language defining what counts as one. The same contract states a factory's resolver is not inherited by the Schema, Validator and ValidatorHandler objects it creates, which is why the securing installs the floor on each of them rather than on the factory alone.

An implementation that ignores what it was handed — parsing with an internal parser of its own, or reaching an external resource without consulting the resolver — works outside the securing. Guarding against that would be a valid hardening, but the deviation is a contract violation in that implementation, not a vulnerability here: such a report is triaged OUT-OF-SCOPE: foreign implementation.

XInclude resolution — the converse case. setXIncludeAware turns the processor on, but no part of JAXP says which resolver, if any, a processor consults for an xi:include href. The securing gates XInclude through the entity-resolver floor, so the guarantee is restricted to implementations following the Xerces convention of routing that fetch through the EntityResolver; an implementation that resolves an href without consulting it fetches outside the securing, and a report demonstrated only there is out of scope. A matching bullet is added to What is out of scope.

Entity expansion: count versus volume — the model claimed every implementation's secure default "bounds entity expansion tightly enough to reject entity-expansion denial of service such as Billion Laughs". That holds for the exponential shape it names, because those defaults bound the number of expansions. It over-claims for the volume those expansions produce, which is a separate limit: the stock JDK sets one by default (jdk.xml.totalEntitySizeLimit, jdk.xml.maxGeneralEntitySizeLimit), while Woodstox (com.ctc.wstx.maxCharacters, com.ctc.wstx.maxTextLength) and external Xerces leave the equivalent unset. The two dimensions are now stated apart, with the volume bound placed where the other processing limits already sit — with the operator, who sizes it or provisions for the load instead — and a Known Non-Findings entry routes a report of that shape.

Where the expansion bound comes from (package-info.java) — the same guarantee said expansion is bounded "by the platform's secure-processing limit". That is inaccurate for StAX: JSR-173 defines no secure-processing feature, and the stock JDK's XMLInputFactory rejects FEATURE_SECURE_PROCESSING outright (Woodstox, inverting the expectation, accepts it). On StAX the bound comes from the implementation's own defaults, applied unconditionally — the JDK installs its XMLSecurityManager at construction, Woodstox caps entity count at 100000 — which is why the StAX recipe sets no limit of its own and has nothing to probe. The bullet now names the source of the limits. The other two guarantees are untouched: the resolver floor is universal.

Two contract-hygiene fixes to the same document, so that every released version is covered by exactly one model version and every named entry point exists:

  • the modeled release line becomes 1.0.x; it previously named a 0.1.x line the component never had, leaving the shipped 1.0.0 artifact with no covering model version under the document's own "triaged against the model as it stood at that version" rule;
  • the caller-supplied-parser exclusion pointed at HardeningSAXParserFactory, a class this library does not ship — corrected to SecureSAXParserFactory.

🤖 Generated with Claude Code

Make explicit, under "Assumptions about the environment", that the
securing recipes rely on the JAXP implementation honoring the SAXSource
and StAXSource contracts: the reader supplied inside the Source is the
one that performs the parse. An implementation that substitutes an
internal parser of its own violates that contract, so a report built on
such a substitution is triaged OUT-OF-SCOPE: foreign implementation;
defending against it would be hardening, not a fix.

Also bump the modeled release line to 1.0.x.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CLnTBsvmYtxzNTWVGNyz33
JAXP specifies no contract for XInclude resolution: setXIncludeAware
turns the processor on, but nothing in the API says which resolver a
processor consults for an xi:include href. The securing gates XInclude
through the entity-resolver floor, so the guarantee holds on the
implementations that follow the Xerces convention of routing the fetch
through the EntityResolver, and a report demonstrated only on an
implementation that resolves an href without consulting it is out of
scope.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CLnTBsvmYtxzNTWVGNyz33
The caller-supplied-parser exclusion pointed at
HardeningSAXParserFactory, a class this library does not ship; the
entry point that returns a secured reader is SecureSAXParserFactory.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CLnTBsvmYtxzNTWVGNyz33
The schema recipe rests on the SchemaFactory, Validator and
ValidatorHandler contracts for setResourceResolver: each locates the
external resources it needs through the resolver installed on it, with
the schema language defining what counts as one. Record that alongside
the reader-injection contracts, together with the same contract's
statement that a factory's resolver is not inherited by the objects it
creates, which is why the floor is installed on each of them.

An implementation that stores a resolver but reaches an external
resource without consulting it works outside the securing, on the same
terms as one that ignores an injected reader.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CLnTBsvmYtxzNTWVGNyz33
The model claimed every implementation's secure default bounds entity
expansion tightly enough to reject an entity-expansion denial of
service. That holds for the exponential shape it names, Billion Laughs,
because the defaults bound the number of expansions; it over-claims for
the volume those expansions produce, which is a separate limit the
stock JDK sets by default and Woodstox and external Xerces leave unset.

State the two dimensions apart, and place the volume bound where the
other processing limits already sit: with the operator, who sizes it or
provisions for the load instead. A matching entry under Known
Non-Findings routes a report of that shape.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CLnTBsvmYtxzNTWVGNyz33
@ppkarwasz ppkarwasz changed the title Document honored JAXP contracts in the threat model Sharpen the threat model: honored JAXP contracts, XInclude, and expansion limits Sep 1, 2026
The guarantee said internal expansion is bounded "by the platform's
secure-processing limit", which is inaccurate for StAX: JSR-173 defines
no secure-processing feature, and the stock JDK's XMLInputFactory
rejects it outright. The bound there comes from the implementation's
own defaults, applied unconditionally, which is why the StAX recipe
sets no limit of its own and has nothing to probe.

Say where the limits come from instead. The other two guarantees are
unchanged: the resolver floor is universal.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CLnTBsvmYtxzNTWVGNyz33

@garydgregory garydgregory left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ppkarwasz
A section needs clarification. See my comment please.

Comment thread src/site/markdown/threat_model.md Outdated

@garydgregory garydgregory left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@ppkarwasz
ppkarwasz merged commit 1497239 into apache:main Sep 1, 2026
17 checks passed
@ppkarwasz
ppkarwasz deleted the docs/honored-jaxp-contracts branch September 1, 2026 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants