The HTTP service: assets, probe, and the full sharp surface

Exposes sharp and ffprobe with their own vocabulary — operations are named what
sharp names them, ffprobe fields come back spelled the way ffprobe spells them —
so the tools' own documentation applies and there is no house dialect to learn.

Safety comes from one architectural invariant rather than from filtering: the
caller never supplies a path, a URL or a protocol. Input is an id we issued, output
is a name we chose. That removes the entire class of attack that makes "just pass
the flags through" a remote shell, which is precisely what makes it safe to open up
everything else. docs/SECURITY.md has the reasoning and the specific attacks.

The vocabulary is a data table, so the validator and docs/SHARP.md are generated
from one source and cannot drift. Two things are deliberately absent from it:
withMetadata, because that is how a client's GPS coordinates get published, and
limitInputPixels, because a caller who could raise it could turn a 1 KB PNG into a
40 GB allocation.

Verified against a running server, not asserted: traversal ids rejected, unknown
operations and typo'd parameters rejected by name, out-of-range values rejected
with the bound, a GPS-tagged photo coming out clean end to end, video probe, Range
requests, and a rejected upload leaving nothing on disk.

Found while building: config threw at import if AV_CLUB_TOKEN was unset, so
generating documentation required a production secret. The token is read at boot in
server.ts now — still a refusal to start, no longer a module-scope landmine.

ffmpeg is designed in docs/API.md and not built yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Pastilhas
2026-08-20 02:37:57 +01:00
co-authored by Claude Opus 5
parent 81c8e17a8e
commit 1597fccd75
15 changed files with 2153 additions and 8 deletions
+17
View File
@@ -0,0 +1,17 @@
# Copy to .env and fill in. The service refuses to start without AV_CLUB_TOKEN.
# The bearer token every request must carry. Generate with:
# openssl rand -hex 32
AV_CLUB_TOKEN=
AV_CLUB_PORT=3020
# Where stored assets live. Deliberately NOT inside the repo: a deploy that replaces the working
# tree would otherwise delete every original ever uploaded, silently.
# AV_CLUB_ASSETS_DIR=/var/lib/av-club/assets
# Limits. Each exists because its absence is a denial of service — see docs/SECURITY.md.
# AV_CLUB_MAX_UPLOAD_BYTES=536870912
# AV_CLUB_JOB_TIMEOUT_MS=120000
# AV_CLUB_MAX_OUTPUT_DIMENSION=16383
# AV_CLUB_MAX_CONCURRENT_JOBS=2