Skip to content

SLICE-001 — Secure baseline: resolve-and-confine statement archive - #2

Merged
maximalfocus merged 2 commits into
mainfrom
issue/1-secure-baseline
Aug 15, 2026
Merged

SLICE-001 — Secure baseline: resolve-and-confine statement archive#2
maximalfocus merged 2 commits into
mainfrom
issue/1-secure-baseline

Conversation

@maximalfocus

Copy link
Copy Markdown
Owner

Summary

Delivers the secure baseline (SLICE-001): a fully fictional, container-only
multi-tenant statement-archive service that handles a user-supplied filename the safe
way — resolve the candidate path, then confine it to its base directory — for both
reading a document and importing an archive. No vulnerable code is introduced.

Highlights:

  • Deterministic fixtures, recreated fresh on every container start: per-tenant
    archive directories, an out-of-root integration key carrying a DEMO_SENTINEL, a
    branding config read at request time, and a planted symlink escaping the archive root.
  • GET /documents?name=…resolve() + is_relative_to() confinement; every
    traversing, absolute, percent-encoded, double-encoded, symlink-escaping, and
    well-formed-but-missing name returns an indistinguishable generic 404.
  • POST /documents/import — all-or-nothing: every entry confined (and links,
    absolute, and non-regular entries refused) before any byte is written; a bad
    archive is rejected whole with a generic 400, leaving the tree byte-for-byte
    unchanged.
  • GET /documents/{document_id} — opaque catalog-id indirection (no path component
    accepted); GET /statements/summary reads the footer at request time.
  • Generic rejection audit event — one JSON line per security rejection, naming actor,
    tenant, operation, outcome, and nothing else (no submitted name, base path, token, or
    secret).
  • Docker Compose is the only host workflow (secure, one-shot demo, verify);
    GitHub Actions runs the identical Compose verification boundary.

Verification

All run inside the container through the same boundary as CI:

  • docker compose run --rm verifygreen: ruff check, ruff format --check,
    mypy src tests (strict), and pytest (41 passed).
  • docker compose run --rm demo16/16 checks passed over real localhost HTTP:
    "secure baseline behaves as specified" (exit 0). Exercises legitimate reads/import/
    summary, every secure rejection, catalog id, and the generic 401 cases against the
    running hardened container.

Acceptance criteria → evidence

  1. Docker-Compose-only bring-up & verify — Dockerfile, docker-compose.yml.
  2. One command creates fresh fixtures & exercises secure/legitimate behaviour in seconds
    docker compose run --rm demo.
  3. Indistinguishable generic 404 for all unsafe + missing names —
    tests/test_secure_retrieval.py, scenario.
  4. All-or-nothing import 400, no entry written — tests/test_secure_import.py,
    tests/test_fixture_integrity.py.
  5. Catalog id known → 200, unknown → 404tests/test_catalog.py.
  6. Exactly one audit event per rejection, no sensitive content —
    tests/test_audit.py.
  7. Summary footer read at request time — tests/test_summary.py.
  8. Generic 401 for missing/malformed/unknown auth — tests/test_auth.py.
  9. Ruff & mypy green through the same boundary locally and in CI — verify service,
    .github/workflows/ci.yml.
  10. No vulnerable entry point yet; fixture tree byte-for-byte unchanged after every
    rejected path — secure-only; tests/test_fixture_integrity.py.

Risks / follow-ups

  • The deliberately vulnerable contrast (naive base-join, broken sanitizer, Zip-Slip
    write) and the full comparison CLI are intentionally out of scope here and arrive in
    later slices.
  • Starlette emits a StarletteDeprecationWarning about httpx under its TestClient; it
    does not affect results.

Safety

Wholly synthetic and local. Executes no command. This slice introduces no vulnerable code
path. The secure service runs non-root, all caps dropped, no-new-privileges, read-only
root filesystem, loopback-only, with its disposable fixture tree on tmpfs.

Closes #1

maximalfocus and others added 2 commits August 15, 2026 17:52
Deliver the secure baseline (SLICE-001): a fully fictional, container-only
multi-tenant statement archive that handles user-supplied filenames safely by
resolving the candidate path and then confining it to its base directory.

- Deterministic fixtures recreated on every start: per-tenant archives, an
  out-of-root integration key with a DEMO_SENTINEL, a branding config read at
  request time, and a planted symlink escaping the archive root.
- Demo bearer auth mapping one token to one user/tenant; generic 401 otherwise.
- Secure API: resolve-and-confine retrieval, all-or-nothing confined import,
  opaque catalog-id indirection, statement summary, and a generic rejection
  audit event that leaks no name, base path, token, or secret.
- Every unsafe or missing name returns an indistinguishable generic 404; every
  unsafe archive is rejected whole with a 400, writing no entry.
- Docker Compose is the only host workflow (secure app, one-shot demo, verify);
  GitHub Actions runs the same Compose verification boundary.

No vulnerable code is introduced in this slice.

Refs #1

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Avoid duplicate workflow runs on same-repo feature-branch PRs (both the push
and pull_request events fired). The pull_request event still gates every PR.

Refs #1

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@maximalfocus
maximalfocus merged commit 7048b3b into main Aug 15, 2026
1 check passed
@maximalfocus
maximalfocus deleted the issue/1-secure-baseline branch August 15, 2026 09:55
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.

SLICE-001 — Secure baseline: multi-tenant statement archive, resolve-and-confine retrieval, confined import, and container harness

1 participant