Harden XTCE schema validation against local file read (CWE-73) and SSRF (CWE-918) - #267
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #267 +/- ##
==========================================
- Coverage 94.92% 94.49% -0.43%
==========================================
Files 48 49 +1
Lines 3904 4163 +259
==========================================
+ Hits 3706 3934 +228
- Misses 198 229 +31 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
158b3d6 to
eb0ed24
Compare
Treat a document-supplied xsi:schemaLocation as untrusted: reject local filesystem paths (CWE-73) and restrict schema URLs to https on an allowlisted, non-internal host (CWE-918). Bundle the OMG XTCE 1.2 schema so the standard case validates offline with no network request. - Bundle SpaceSystem.xsd; resolve it offline by URL before any download. - Add allowed_schema_hosts / allow_insecure_http / allow_schema_download to validate_xtce and the spp validate CLI, with SPP_ALLOWED_SCHEMA_HOSTS and SPP_ALLOW_INSECURE_HTTP env overrides; export DEFAULT_ALLOWED_SCHEMA_HOSTS. - Block internal/link-local IP-literal hosts (169.254.169.254, 127.0.0.1). - Restore trusted local_xsd: absolute paths work from any cwd again. - Cache only content that validates as XSD; cap download size; 0600 perms. - Use accurate error codes (DISALLOWED_SCHEMA_LOCATION) and surface messages. - Move mock_schema_download fixture to conftest; add security regression tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
medley56
left a comment
There was a problem hiding this comment.
🤖 This review was generated with Claude and reviewed by Gavin (@medley56).
Hi @blakedehaas — thanks for kicking this off. Getting the SSRF/LFI hardening started was the hard part, and the structure you set up (a dedicated schema-load path, the caching layer, the test scaffolding) is what we built the final version on top of. As code owner I took the branch the rest of the way, and I want to walk you through what we changed and why so nothing here is a surprise.
The core reframe: a document's xsi:schemaLocation is fully attacker-controlled whenever we validate an untrusted document, so the fix has to treat it as untrusted at every step — while keeping the operator-supplied local_xsd fully trusted. The original approach guarded some of the surface but left the highest-severity path open, so we re-centered everything on that trust boundary.
What was still exploitable (and is now closed):
- SSRF (the 7.4 High) was not actually mitigated. The scheme check allowed any
http/httpsURL, so the advisory's own PoC —http://169.254.169.254/latest/meta-data/...— sailed through. We replaced the scheme-only check with https-by-default + a host/URL allowlist + an internal-address guard, so metadata/loopback/private targets are blocked outright. - LFI was only partly closed.
startswith("/")missed Windows/UNC paths, and a sibling-directory string-prefix bug let some traversals through. Document-derived local paths are now rejected wholesale (any non-http(s)location) — a local schema must come throughlocal_xsd.
What we added:
- Bundled the OMG XTCE 1.2 schema, so the standard case validates offline with no network request — this removes the SSRF surface for the common path entirely and makes validation robust to OMG being unreachable.
- Configurable, safe-by-default controls:
allowed_schema_hosts(hosts or exact URLs),allow_insecure_http(dangerous opt-in), andallow_schema_download, all mirrored on the CLI and with env-var overrides. Default download stays on but allowlisted.
What we fixed from the first iteration: restored absolute local_xsd (it was being silently rewritten to a bare filename), stopped caching un-validated bytes, added a download size cap, gave rejections accurate/surfaced error codes, and moved the mock_schema_download fixture into conftest.py (the integration test couldn't find it, so it was erroring at setup).
Full rationale is in the changelog and docs/source/users.md; details are in the inline comments. Happy to hop on a call if any of the decisions are worth talking through — especially the "download on-by-default but allowlisted" choice, since that was the main judgment call.
djesernik
left a comment
There was a problem hiding this comment.
This LGTM in terms of addressing the vulnerability.
I do think there may be some issues of semantics with the use of host (which can mean example.org:8080) where hostname (just example.org) would be more appropriate.
Summary
Hardens XTCE schema validation against two reported vulnerabilities in
validate_xtce. Both share one root cause: a document'sxsi:schemaLocationis attacker-controlled but was used to read files and make network requests without restriction. Fixes #266.Problems being fixed
xsi:schemaLocationat any URL, causing the validating host to issue outbound requests — e.g. the cloud metadata endpointhttp://169.254.169.254/latest/meta-data/iam/security-credentials/for IAM credential theft — and the response was cached to disk, persisting exfiltrated data./etc/hostname,../../etc/passwd), causing arbitrary local files to be read as a "schema".How we fix it
The document-supplied
xsi:schemaLocationis now treated as untrusted, while an operator-suppliedlocal_xsdremains trusted. Schema resolution is:local_xsd(trusted). Opened directly at any path.httpson an allowlisted host (defaultwww.omg.org). Internal/link-local targets (169.254.169.254,127.0.0.1, private ranges) are always blocked. Local filesystem paths from a document are rejected outright — uselocal_xsd.Additional hardening:
allowed_schema_hosts(hosts or exact URLs),allow_insecure_http(dangerous opt-in; host allowlist + internal-address guard still apply),allow_schema_download. Default download stays on but allowlisted.DEFAULT_ALLOWED_SCHEMA_HOSTSis exported.Behavior changes
xsi:schemaLocationis a local path or a non-allowlisted / non-https URL is now rejected — passlocal_xsd, extend the allowlist, or (for http) opt in explicitly.local_xsd/--local-xsdagain works from any working directory (fixes a regression that rewrote it to a bare filename).Testing
New
tests/unit/test_xtce/test_validation_security.pyasserts every advisory PoC is rejected with no outbound request, plus positive coverage for the allowlist (arg/env/exact-URL), theallow_insecure_httpopt-in still blocking internal IPs, offline bundle validation, and cache behavior. Full suite passes; docs and changelog updated.PR description drafted with Claude and reviewed by Gavin (@medley56).