Harden image ingress: magic bytes, re-encode, limits, metadata stripping, rate control #63
No reviewers
Labels
No Label
area/backend
area/data
area/frontend
area/model
area/privacy
area/release
area/safety
blocked
human-gate
priority/P0
priority/P1
priority/P2
state:review
type/docs
type/epic
type/feature
type/ops
type/research
type/security
type/test
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: stackchain/timmy-talking-turd#63
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "free/hy3-timmy-16"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #16
What
Treats every uploaded image as hostile on the
/api/analyzepath:PK\x03\x04archives and<script>payloads inside image bytes are rejected as polyglots.scripts/reencode_image.py) with a 15 s timeout; EXIF, GPS IFD, XMP and PNG tEXt chunks are dropped by construction./api/analyze; over-limit requests get429+Retry-After, no payload retention.Evidence
tests/image-ingress.test.jswritten first against synthetic hostile fixtures and observed failing (module missing), then implemented.check:syntaxandcheck:diffclean,npm audit --omit=dev: 0 vulnerabilities, secrets grep clean.Boundaries preserved
Timeouts, base-path handling, CSP/safety headers, agent endpoints and existing manual fallback messaging untouched. No live deploy, no credentials, no external uploads.
Independent exact-head review of
ccb227921e7e169a312e92aa769cbc2bffef530e: CHANGES REQUESTED. The branch has useful ingress work and the exact local gates pass (81/81 Node, 20/20 deploy, syntax/diff/audit), but it is not deployable or resource-bounded yet.Blocking reproductions:
Production runtime cannot execute the re-encoder. The service launches plain
python3, but Pillow is only pinned inrequirements-test.txt; no immutable production interpreter/venv is provisioned or selected by the systemd template. Under the actualtimmy-staginguser and service-like PATH,python3 -c 'import PIL'returnsModuleNotFoundError. CI passes only because it installs the test requirement. Provision a root-owned pinned production runtime, use an absolute interpreter path, and add a deployment/health contract that performs one synthetic re-encode.Dimension/bomb rejection happens after full decompression.
scripts/reencode_image.py:43-54callsprobe.load()before checking width/height. Pillow's default bomb threshold is ~89M pixels and a warning is not rejected. The committed 12000x12000 fixture decoded to 163,032 KiB RSS before returningdimensions; it emittedDecompressionBombWarning, notDecompressionBombError. Check dimensions beforeload(), set a strict pixel ceiling at or below the accepted dimensions, treat warnings as errors, and prove bounded RSS/time.No concurrency bound around expensive decode subprocesses. The fixed-window limiter allows ten requests to start together. Several 163 MiB decoders can exceed the service's 512 MiB
MemoryMaxand restart Timmy. Add a small fail-closed semaphore/queue bound before decode/provider work and verify overload never spawns excess children.The advertised bounded per-client limiter is neither bounded nor per-client behind Caddy.
src/rate-limiter.js:15-20only removes expired entries; 20,000 unexpired unique keys were accepted and grew heap by ~3.5 MiB. In staging,req.socket.remoteAddressis Caddy loopback, so every user shares one quota. Enforce a hard map cap/eviction policy and choose/test an explicit trusted-proxy or intentionally global key contract without trusting spoofable headers.Release gates do not own the new runtime.
scripts/build_release.py:136-140omitssrc/image-ingress.js,src/rate-limiter.js, andscripts/reencode_image.pyfrom its explicit syntax/compile list and does not verify the production Pillow runtime. Add them and the runtime smoke to immutable release validation.Gitea CI is currently red at this head. Its recorded failure is the pre-existing timing-sensitive malformed-base-path test (
/%252e%252e/gittimed out); the same exact-head local suite passed, so that specific result is not classified as an ingress defect, but exact-head CI must still be green before acceptance.Preserve the successful boundaries: magic-based JPEG/PNG/WebP acceptance, safe JPEG output and metadata removal, provider-not-called hostile fixtures, sanitized fixed ingress errors, base path/CSP, and no real medical data. Do not merge/deploy until RED→GREEN correction and independent reproduction.
Supplemental independent hostile review of exact head
ccb2279confirmed the formal block and added four correction cases:<SCRIPT>, appended HTML, and ZIP EOCD (PK\x05\x06) appended to a valid JPEG were accepted. Re-encoding strips the tail before provider upload, but the issue explicitly requires malformed/polyglot rejection.unavailable, but Node maps every non-dimension subprocess failure tocorrupt, yielding a misleading client 400 instead of processing-unavailable/manual fallback 503.Independent passes retained: magic sniffing independent of declared MIME; provider receives only re-encoded JPEG; EXIF/GPS/text metadata removed; targeted malformed fixtures rejected; module-relative script lookup; temp directory is private and cleanup is unconditional. Correction lane was restarted with the complete contract. No merge/deploy.
Exact-head hostile review of
643f774c78c0: REQUEST_CHANGES / do not merge. The two controller-appended commits only repair CI Python provisioning/import smoke; they do not change the reviewed runtime behavior. Independent exact-functional-head review reproduced:python3; service unit does not setTIMMY_PYTHON; startup/health never verifies re-encode runtime. A missing interpreter still advertises health200andvisionEnabled:trueuntil upload.Historical bomb, polyglot, canonical-base64, body timeout, temp cleanup, request-time 503/manual fallback, and re-encoded-provider-byte blockers passed. Canonical CI must turn green at this exact head too. Required: one real expensive-work ceiling covering decode through provider completion; fail-closed runtime startup/health bound to the deployed interpreter; sublinear bounded limiter eviction; and right-to-left trusted-hop parsing plus enforced/documented proxy header overwrite. No merge/deploy.
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Gitea.