Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/source/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Security

- Fix a local file read vulnerability (CWE-73) and a Server-Side Request Forgery vulnerability
(CWE-918) in `validate_xtce`. A document-supplied `xsi:schemaLocation` is now treated as
untrusted: absolute and relative local filesystem paths are rejected (use `local_xsd` for a
local schema), and schema URLs are fetched only over `https` from an allowlisted host (default
`www.omg.org`), with requests to internal/link-local addresses (e.g. `169.254.169.254`,
`127.0.0.1`) always blocked. Downloaded content is size-capped and is written to the cache only
after it validates as an XSD, so a non-schema response can no longer be persisted to disk.
[#266](https://github.com/lasp/space_packet_parser/issues/266)

### Added

- Bundle the standard OMG XTCE 1.2 schema with the package so documents referencing it validate
offline with no network request.
- Add `allowed_schema_hosts`, `allow_insecure_http`, and `allow_schema_download` options to
`validate_xtce` (and the corresponding `--allowed-schema-host`, `--allow-insecure-http`, and
`--no-schema-download` flags to `spp validate`). The allowlist may also be set via the
`SPP_ALLOWED_SCHEMA_HOSTS` environment variable, and insecure http via `SPP_ALLOW_INSECURE_HTTP`.
The default allowlist is exported as `DEFAULT_ALLOWED_SCHEMA_HOSTS`.

### Fixed

- `validate_xtce(local_xsd=...)` and `spp validate --local-xsd` again accept absolute paths from
any working directory (a regression that silently rewrote them to a bare filename in the current
directory). Schema-fetch failures are now reported with accurate error codes
(`DISALLOWED_SCHEMA_LOCATION` rather than a misleading `MISSING_SCHEMA_LOCATION`).

## [6.1.2] - 2026-04-02

### Fixed
Expand Down
50 changes: 49 additions & 1 deletion docs/source/users.md
Original file line number Diff line number Diff line change
Expand Up @@ -542,10 +542,58 @@ e.g.
https://www.omg.org/spec/XTCE/20180204/SpaceSystem.xsd">
```

### Schema resolution and network security

Because an XTCE document's `xsi:schemaLocation` is attacker-controllable when you validate a
document from an untrusted source, schema resolution is deliberately locked down (see the
security advisories addressed in the changelog: local file read / CWE-73 and SSRF / CWE-918).
Schema validation resolves a schema in this order:

1. **Bundled schema (offline).** The standard OMG XTCE schema ships with the package. A document
referencing `https://www.omg.org/spec/XTCE/20180204/SpaceSystem.xsd` (or the `http` variant)
validates against the bundled copy with **no network request** — this is the common case and
requires no configuration.
2. **`local_xsd` (trusted).** A schema path you pass explicitly is trusted and opened directly,
from anywhere on the filesystem (absolute or relative).
3. **Allowlisted download.** Any other `xsi:schemaLocation` URL is fetched **only** if it is an
`https` URL whose host is on the allowlist (default: `www.omg.org`). URLs pointing at internal
or link-local addresses (e.g. `169.254.169.254`, `127.0.0.1`) are always rejected.

A **local filesystem path** appearing in a document's `xsi:schemaLocation` is never opened — pass
`local_xsd` instead to validate against a local schema.

Controls (all available on `validate_xtce(...)` and the `spp validate` CLI):

| Option | Env var | Default | Purpose |
| ----------------------- | -------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------- |
| `allowed_schema_hosts` | `SPP_ALLOWED_SCHEMA_HOSTS` (comma-separated) | `{"www.omg.org"}` | Hosts and/or exact URLs a schema download may target. |
| `allow_insecure_http` | `SPP_ALLOW_INSECURE_HTTP` | `False` | **Dangerous.** Permit `http` (not just `https`). The host allowlist and internal-address guard still apply. |
| `allow_schema_download` | — | `True` | When `False`, make no network request (bundled schemas and `local_xsd` only). |

To allow an additional mirror while keeping the default, extend the exported constant:

```python
from space_packet_parser import DEFAULT_ALLOWED_SCHEMA_HOSTS, validate_xtce

result = validate_xtce(
"my_xtce.xml",
level="schema",
allowed_schema_hosts=[*DEFAULT_ALLOWED_SCHEMA_HOSTS, "schemas.example.org"],
)
```

### CLI Validation

```shell
spp --log-level=DEBUG validate my_xtce.xml --local-schema my_xsd.xml --level all
# Validate against the schema referenced in the document (bundled/allowlisted)
spp --log-level=DEBUG validate my_xtce.xml --level all

# Validate against a trusted local schema
spp validate my_xtce.xml --local-xsd my_xsd.xsd --level all

# Allow an additional schema host, or disable network access entirely
spp validate my_xtce.xml --allowed-schema-host schemas.example.org
spp validate my_xtce.xml --no-schema-download --local-xsd my_xsd.xsd
```

### Programmatic Validation
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["space_packet_parser"]
# Bundled XSD schemas are data files that must ship inside the wheel.
artifacts = ["*.xsd"]

[tool.hatch.build.targets.sdist]
include = [
Expand Down
3 changes: 2 additions & 1 deletion space_packet_parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
from space_packet_parser.common import SpacePacket
from space_packet_parser.generators import ccsds_generator
from space_packet_parser.xtce.definitions import XtcePacketDefinition
from space_packet_parser.xtce.validation import validate_xtce
from space_packet_parser.xtce.validation import DEFAULT_ALLOWED_SCHEMA_HOSTS, validate_xtce

__all__ = [
"ccsds_generator",
"DEFAULT_ALLOWED_SCHEMA_HOSTS",
"SpacePacket",
"XtcePacketDefinition",
"load_xtce",
Expand Down
33 changes: 32 additions & 1 deletion space_packet_parser/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,35 @@ def parse(
)
@click.option("--timeout", type=int, default=30, help="Timeout in seconds for schema downloads")
@click.option("--local-xsd", type=click.Path(exists=True, path_type=Path), help="Local XSD file for schema validation")
def validate(file_path: Path, level: str, timeout: int, local_xsd: Path) -> None:
@click.option(
"--allowed-schema-host",
"allowed_schema_hosts",
multiple=True,
help="Host or exact URL that a document-derived schema download may target. May be given "
"multiple times. Defaults to www.omg.org (or the SPP_ALLOWED_SCHEMA_HOSTS environment variable).",
)
@click.option(
"--allow-insecure-http",
is_flag=True,
default=False,
help="DANGEROUS: permit http (not just https) schema URLs. The host allowlist and internal-address "
"guard still apply. Only use for trusted internal mirrors.",
)
@click.option(
"--no-schema-download",
is_flag=True,
default=False,
help="Never download schemas over the network; use only bundled schemas and --local-xsd.",
)
def validate(
file_path: Path,
level: str,
timeout: int,
local_xsd: Path,
allowed_schema_hosts: tuple[str, ...],
allow_insecure_http: bool,
no_schema_download: bool,
) -> None:
"""Validate an XTCE document."""
logging.info(f"Validating XTCE file: {file_path}")
logging.info(f"Validation level: {level}")
Expand All @@ -234,6 +262,9 @@ def validate(file_path: Path, level: str, timeout: int, local_xsd: Path) -> None
print_results=False,
raise_on_error=False,
local_xsd=local_xsd,
allowed_schema_hosts=list(allowed_schema_hosts) or None,
allow_insecure_http=allow_insecure_http,
allow_schema_download=not no_schema_download,
)

# Display results in rich format (complementing the print_results from validate_xtce)
Expand Down
19 changes: 19 additions & 0 deletions space_packet_parser/xtce/schemas/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Bundled XTCE schemas

These XSD files are bundled with `space_packet_parser` so that schema validation of XTCE
documents that reference the standard OMG schema works **offline**, with no network request.
Resolving the schema locally (instead of downloading the URL named in a document's
`xsi:schemaLocation`) is both faster and removes the SSRF/LFI attack surface for the common case.

| File | XTCE version | `targetNamespace` | Upstream source |
| ----------------- | ------------ | --------------------------------------- | -------------------------------------------------------- |
| `SpaceSystem.xsd` | 1.2 | `http://www.omg.org/spec/XTCE/20180204` | <https://www.omg.org/spec/XTCE/20180204/SpaceSystem.xsd> |

These schemas are published by the Object Management Group (OMG) and remain under OMG's
copyright and license terms. They are redistributed here unmodified; the small set of
lxml-compatibility fixups (see `_fix_known_schema_issues` in `../validation.py`) are applied
at load time and are **not** baked into these files.

The mapping from schema URL to bundled file lives in `_BUNDLED_SCHEMAS` in `../validation.py`.
To add another version, drop the `.xsd` here and add an entry keyed on its scheme-insensitive
`host/path`.
Loading