Skip to content

fix: contain deserialized payload artifacts#106

Open
Hinotoi-agent wants to merge 2 commits into
microsoft:mainfrom
Hinotoi-agent:fix/artifact-load-containment
Open

fix: contain deserialized payload artifacts#106
Hinotoi-agent wants to merge 2 commits into
microsoft:mainfrom
Hinotoi-agent:fix/artifact-load-containment

Conversation

@Hinotoi-agent

Copy link
Copy Markdown

Summary

This is the focused follow-up requested in #57 (comment).

It keeps the load-time artifact containment hardening only: when deserializing a persisted payload collection, artifact references now have to remain under the collection's artifacts/ directory after normalization and symlink resolution.

What changed

  • Adds _resolve_artifact_path() in rampart/payloads/_store.py for deserialized artifact references.
  • Rejects persisted artifact paths that are absolute, contain .., or do not start with artifacts/.
  • Resolves the final path and rejects symlink escapes outside the collection's artifacts/ directory.
  • Adds regression tests for traversal, absolute/non-artifacts paths, and symlink escape handling.

Scope notes

  • This does not add Payload.id validation.
  • This does not add rampart/core/payload_ids.py.
  • This does not change _copy_file_artifact() or OneDriveSurface.upload_async().
  • This is framed as defense-in-depth for untrusted/shared collection deserialization, not as a high-severity payload-ID vulnerability.

Test plan

Host-local:

uv run pytest tests/unit/payloads/test_payload_store_security.py -q
uv run pytest tests/unit/payloads/test_store.py tests/unit/payloads/test_payload_store_security.py -q
uv run ruff check rampart/payloads/_store.py tests/unit/payloads/test_payload_store_security.py
uv run ruff format --check rampart/payloads/_store.py tests/unit/payloads/test_payload_store_security.py
uv run ty check rampart/payloads/_store.py tests/unit/payloads/test_payload_store_security.py
uv run pytest -q
python -m compileall -q rampart tests
git diff --check

Results:

  • Focused new tests: 5 passed
  • Existing payload store + new tests: 18 passed
  • Full suite: 617 passed, 5 skipped
  • Ruff check/format: passed
  • Ty check: passed
  • Compileall/diff whitespace check: passed

Container validation:

docker run --rm -e CI=true -v "$PWD":/work -w /work python:3.12-slim bash -lc 'apt-get update -qq && apt-get install -y -qq git cargo >/dev/null && python -m pip install --quiet uv && uv run pytest tests/unit/payloads/test_store.py tests/unit/payloads/test_payload_store_security.py -q && uv run ruff check rampart/payloads/_store.py tests/unit/payloads/test_payload_store_security.py && uv run ty check rampart/payloads/_store.py tests/unit/payloads/test_payload_store_security.py && git diff --check'

Result: passed (18 passed; ruff, ty, and diff whitespace checks passed).

Comment on lines +353 to +361
if artifact_path.is_absolute() or ".." in artifact_path.parts:
msg = f"Invalid artifact path: {artifact!r}. Must stay under artifacts/."
raise ValueError(msg)
if (
len(artifact_path.parts) < _MIN_ARTIFACT_PATH_PARTS
or artifact_path.parts[0] != "artifacts"
):
msg = f"Invalid artifact path: {artifact!r}. Must be under artifacts/."
raise ValueError(msg)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@Hinotoi-agent

Copy link
Copy Markdown
Author

Thanks — updated in 261a966 to use Path.relative_to("artifacts") for the lexical artifacts-prefix check.

I kept the explicit absolute/.. rejection and the resolved containment check afterward so the security behavior remains unchanged for traversal and symlink escapes.

Validation:

uv run pytest tests/unit/payloads/test_payload_store_security.py -q
# 5 passed
uv run pytest tests/unit/payloads/test_store.py tests/unit/payloads/test_payload_store_security.py -q
# 18 passed
uv run ruff check rampart/payloads/_store.py tests/unit/payloads/test_payload_store_security.py
uv run ruff format --check rampart/payloads/_store.py tests/unit/payloads/test_payload_store_security.py
uv run ty check rampart/payloads/_store.py tests/unit/payloads/test_payload_store_security.py
python -m compileall -q rampart tests
git diff --check

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