Skip to content

Harden XTCE schema validation against local file read (CWE-73) and SSRF (CWE-918) - #267

Merged
medley56 merged 13 commits into
mainfrom
Issues/266-path-validation-and-ssrf
Aug 24, 2026
Merged

Harden XTCE schema validation against local file read (CWE-73) and SSRF (CWE-918)#267
medley56 merged 13 commits into
mainfrom
Issues/266-path-validation-and-ssrf

Conversation

@blakedehaas

@blakedehaas blakedehaas commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Hardens XTCE schema validation against two reported vulnerabilities in validate_xtce. Both share one root cause: a document's xsi:schemaLocation is attacker-controlled but was used to read files and make network requests without restriction. Fixes #266.

Problems being fixed

  • SSRF — CWE-918 (7.4 High). A crafted document could point xsi:schemaLocation at any URL, causing the validating host to issue outbound requests — e.g. the cloud metadata endpoint http://169.254.169.254/latest/meta-data/iam/security-credentials/ for IAM credential theft — and the response was cached to disk, persisting exfiltrated data.
  • LFI — CWE-73. The same field could point at an absolute or traversal filesystem path (/etc/hostname, ../../etc/passwd), causing arbitrary local files to be read as a "schema".

How we fix it

The document-supplied xsi:schemaLocation is now treated as untrusted, while an operator-supplied local_xsd remains trusted. Schema resolution is:

  1. Bundled schema (offline). The standard OMG XTCE 1.2 schema ships in the package, so the common case validates with no network request — this alone removes the SSRF surface for typical use.
  2. local_xsd (trusted). Opened directly at any path.
  3. Allowlisted download. Any other URL is fetched only if it is https on an allowlisted host (default www.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 — use local_xsd.

Additional hardening:

  • Downloaded content is size-capped and cached only after it validates as an XSD, so a non-schema response can never be persisted.
  • New options (API + CLI, with env-var overrides): 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_HOSTS is exported.

Behavior changes

  • A document whose xsi:schemaLocation is a local path or a non-allowlisted / non-https URL is now rejected — pass local_xsd, extend the allowlist, or (for http) opt in explicitly.
  • Absolute local_xsd / --local-xsd again works from any working directory (fixes a regression that rewrote it to a bare filename).

Testing

New tests/unit/test_xtce/test_validation_security.py asserts every advisory PoC is rejected with no outbound request, plus positive coverage for the allowlist (arg/env/exact-URL), the allow_insecure_http opt-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).

@blakedehaas
blakedehaas requested a review from medley56 as a code owner July 20, 2026 17:04
@blakedehaas blakedehaas changed the title Update error message in validation.py path validation and ssrf Jul 20, 2026
@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.37380% with 27 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.49%. Comparing base (6de220f) to head (080a9de).

Files with missing lines Patch % Lines
space_packet_parser/xtce/validation.py 90.15% 13 Missing ⚠️
tests/conftest.py 35.00% 13 Missing ⚠️
tests/unit/test_xtce/test_validation_security.py 99.14% 1 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@medley56
medley56 force-pushed the Issues/266-path-validation-and-ssrf branch from 158b3d6 to eb0ed24 Compare August 7, 2026 20:36
medley56 and others added 2 commits August 7, 2026 21:33
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 medley56 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.

🤖 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/https URL, 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 through local_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), and allow_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.

Comment thread space_packet_parser/xtce/validation.py
Comment thread space_packet_parser/xtce/validation.py
Comment thread space_packet_parser/xtce/validation.py
Comment thread space_packet_parser/xtce/validation.py
Comment thread space_packet_parser/xtce/validation.py
Comment thread space_packet_parser/xtce/validation.py
Comment thread space_packet_parser/xtce/validation.py
Comment thread tests/unit/test_xtce/test_validation_security.py
@medley56 medley56 changed the title path validation and ssrf Harden XTCE schema validation against local file read (CWE-73) and SSRF (CWE-918) Aug 7, 2026
@medley56
medley56 requested a review from djesernik August 7, 2026 23:52

@djesernik djesernik left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

@medley56
medley56 merged commit d21e9f6 into main Aug 24, 2026
21 checks passed
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.

path validation and ssrf

3 participants