Skip to content

Harden model downloads, output disclosure, and supply chain - #1

Merged
nathan-v merged 1 commit into
mainfrom
harden/model-download-and-disclosure
Jul 18, 2026
Merged

Harden model downloads, output disclosure, and supply chain#1
nathan-v merged 1 commit into
mainfrom
harden/model-download-and-disclosure

Conversation

@nathan-v

Copy link
Copy Markdown
Owner

Security-hardening pass on the model registry and container build, from a 7-finding review. No behavior change for trusted zoo models; the mocked suite (108 tests) passes and a full default-profile docker build succeeds.

Findings addressed

  1. Unbounded model download size. registry._fetch now enforces a per-model byte cap twice: it rejects a declared Content-Length over the limit, then tracks a running total during streaming so a lying server can't overshoot (partial .part cleaned up). Added a total-cache byte cap alongside the existing count cap. New: CORALAPI_MAX_MODEL_BYTES (256 MB), CORALAPI_MAX_MODEL_CACHE_BYTES (4 GB); label sidecars get a tight 16 MB cap.
  2. No format allowlist. CORALAPI_ALLOWED_IMAGE_FORMATS (opt-in) → 415 on a disallowed decoded format. Full PIL content validation still runs regardless.
  3. Error leak on reset. /v1/reset failures route through errors.py instead of returning str(exc).
  4. No model integrity. Optional sha256 pinning via CORALAPI_MODEL_CHECKSUMS; a mismatched download is deleted and rejected.
  5. Supply chain. Dockerfile pins uv by version (was :latest) and verifies the libedgetpu .deb and any URL-sourced tflite wheel against a sha256sum build-arg, failing the build on mismatch.
  6. Path disclosure. /v1/models returns filenames not absolute paths; /readyz, /v1/status, and the reset response report device counts, not sysfs paths.
  7. Unbounded K8s models volume. models emptyDir gets sizeLimit: 5Gi.

Verification

  • 108 tests pass; new coverage for byte caps, checksum match/mismatch, path redaction, format allowlist, and the reset error-leak.
  • ruff check / ruff format --check clean.
  • Full docker build of the default (avx2) profile succeeds, exercising the new checksum verification and uv pin.

Redeploy note

Rebuilding the compat profile now requires passing its libedgetpu checksum (documented in the README build-profiles table):
--build-arg LIBEDGETPU_SHA256=4ffb6c2251b61535afb6b368d821211a914d2d4783770ec7e6001c073c2d193b

Addresses a 7-finding security review of the model registry and
container build. No behavior change for trusted zoo models.

- Model download size caps (registry): per-model byte cap enforced on
  the declared Content-Length and again as a running total, plus a
  total-cache byte cap. New settings CORALAPI_MAX_MODEL_BYTES and
  CORALAPI_MAX_MODEL_CACHE_BYTES; label sidecars get a tight own cap.
- Optional model integrity pinning via CORALAPI_MODEL_CHECKSUMS: a
  download whose sha256 does not match is deleted and rejected.
- Image format allowlist via CORALAPI_ALLOWED_IMAGE_FORMATS (415 on a
  disallowed decoded format); full PIL content validation still runs.
- Stop leaking internals: /v1/reset failures route through errors.py;
  /v1/models returns filenames not absolute paths; /readyz, /v1/status,
  and the reset response report device counts, not sysfs paths.
- Supply chain: pin the uv image by version and verify the libedgetpu
  .deb and any URL-sourced tflite wheel against a sha256 build-arg.
- Bound the K8s models emptyDir with sizeLimit: 5Gi.

Docs: README config table, build profiles, and security model; SECURITY.md.
Tests: byte caps, checksum match/mismatch, path redaction, format
allowlist, reset error-leak. 108 passing.
@nathan-v
nathan-v merged commit d3e6eea into main Jul 18, 2026
3 checks passed
@nathan-v
nathan-v deleted the harden/model-download-and-disclosure branch July 18, 2026 03:09
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.

1 participant