
An XML firewall applies policy to XML messages before forwarding them to a service. Useful protection combines transport controls, parser restrictions, schema checks, authorization, and observable failure handling. Design controls around the complete request path.
Assign controls to each layer
| Layer | Specify | Collect evidence |
|---|---|---|
| Transport and gateway | Identity, allowed routes, sizes and rates | Accepted identity, destination, rejection logs. |
| Parser | DTD/entity policy and resource limits | Runtime settings and rejected fixtures. |
| Message contract | Names, versions, structure and values | Pinned schema and validation cases. |
| Application | Object/action authorization and business rules | Decision and resulting side effects. |
TLS protects a connection; validation checks a document contract; authorization controls an action on a resource. Test each separately. A gateway can validate a message that later reaches a differently configured application parser, so include the downstream path.
For SOAP, agree the envelope version, operation routing, attachments, and message-security requirements. Use the supported security implementation for that stack, including signature verification and reference handling where required.
Control resource loading explicitly
External entities can cause a parser to access resources outside the submitted document. OWASP’s XXE prevention guidance gives settings for different runtimes. For a service with no DTD requirement, reject document type declarations and disable associated loading and entity features.
List every component that can resolve resources: parser, schema validator, XInclude, stylesheet loader, and XSLT runtime. When approved external schemas are required, distribute trusted copies and use explicit resolution rules. Untrusted input should not select arbitrary resources.
Network restrictions and filesystem isolation reinforce library settings. lxml’s resolver guide distinguishes document resolution from transformation access controls. Read the exact implementation’s documentation instead of guessing equivalent option names.
Choose measurable limits
Measure ordinary and legitimate worst-case requests. Bound compressed and expanded body sizes, nesting where supported, record counts, execution time, concurrency, and retries. The chosen limits should serve the supported contract while containing the cost of a few bad requests.
As a fictional planning example, a service whose approved messages remain below 40 KiB might pilot a 64 KiB body limit. Validate that hypothesis against actual workloads. It still needs decompression accounting and execution budgets: entity expansion and compressed-body expansion are distinct resource risks.
Capture a correlation ID, policy version, rejection category, and timing. Avoid recording complete sensitive payloads by default. Client-facing errors should help authorized callers repair requests without disclosing internal paths or secrets.
Verify controlled failure behavior
Use your own isolated test service and small bounded fixtures. The XML practice pack includes a harmless internal-entity fixture and a parser that refuses document type declarations. Run python selftest.py. It tests the supplied exercise, not your gateway configuration.
| Fixture | Expected behavior | Additional check |
|---|---|---|
| Valid authorized message | Accepted once | Correct destination and application result. |
| Malformed XML or invalid value | Rejected before business processing | No partial write; useful correlation ID. |
| DTD in a DTD-free contract | Rejected | No resource resolution attempted. |
| Body over the approved cap | Rejected at intended layer | Bounded resource use and predictable error. |
| Valid message for another user’s object | Authorization denied | No data or side effect exposed. |
Repeat after gateway, parser, schema, or framework changes. For resource-loading tests use an instrumented local sentinel you control; real files and production addresses are unnecessary. Record observed behavior against each expected result. One successful rejection establishes one tested behavior, not complete coverage.