Absorbs the cache size management and LRU eviction work (#55). All four textual conflicts resolved in favor of main's implementation, with this branch's mechanical lint conformance re-applied on top: - internal/config/config.go: took main's cache_max_bytes wiring (CacheMaxBytes, cacheMaxBytesExplicit) verbatim and expressed the key through this branch's constant convention as keyCacheMaxBytes. - internal/imgcache/cache.go: took main's disabled-cache guards, LRU touch on lookup, and variant_content size accounting verbatim; re-applied this branch's signature wrapping for lll. - internal/imgcache/storage.go: took main's new writeIfAbsent helper and its temp-file cleanup defer verbatim. The auto-merge had silently dropped that defer in favor of this branch's inline cleanup form; restored so the cleanup semantics that arrived from main are intact. - TODO.md: kept both sides' Completed Steps entries. .golangci.yml resolves to this branch's canonical version (sha256 021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb). make build and make test are green; #55's code is not yet conformant with the canonical lint config, which the following commits address.
6.4 KiB
6.4 KiB
Workflow
- branch (from
main) - do the work in Next Step
- move Next Step to the top of Completed Steps
- move the top item of Future Steps into Next Step
- commit (
TODO.mdchanges in the same commit as the work) - merge to
mainif the branch is not protected, otherwise open a PR - push
Status
pre-1.0. No git tags exist. Recent work extracted the internal/magic,
internal/allowlist, internal/httpfetcher, and internal/signature
packages. The gosec findings from the 2026-07-06 survey are resolved
and make check is green on main. The disk cache is now size-bounded
with LRU eviction (cache_max_bytes), closing the unbounded disk
growth DoS vector.
Next Step
P1: implement blocked networks configuration to extend SSRF protection
Completed Steps
- 2026-08-07 update golangci-lint to v2.12.2 with the canonical
.golangci.yml(v2 schema,default: allminus six disabled linters,lll88, tests included): bumped the pinnedgolangci/golangci-lint:v2.12.2-alpineimage inDockerfileand the release-archive sha256 pins inscript/bootstrap; fixed all 747 findings the stricter config surfaced (notablyparalleltest,wsl_v5,goconst,lll,noinlineerr,err113,errcheck,testpackage— white-box test files renamed to*_internal_test.go); three//nolint:tagliatelledirectives keep the snake_case JSON wire/disk formats unchanged;make checkgreen - 2026-08-07 implement cache size management and eviction (closes
#51): new
cache_max_bytesconfig key validated by the startup framework (explicit values used exactly with no floor,0disables the disk cache entirely, omitted defaults to max(75% of free space on the filesystem containing<state_dir>/cache/, 500 MiB), logged at startup); processed variants are now tracked in the database (a newvariant_contenttable and an LRU timestamp onsource_content) so total usage is two SUMs, never a directory scan on the hot path; a background goroutine evicts globally least-recently-used entries (variants and source blobs merged) to the limit, woken by a periodic ticker and by write-pressure notifications from stores; a source blob and ALL of itssource_metadatareferences are deleted in one transaction before the file is unlinked, so multi-referenced blobs are never removed while referenced and rows never point at deleted files; a startup and periodic reconciliation pass adopts untracked variant files, drops rows for missing files, removes unreachable source blobs, and sweeps stale temp files - 2026-08-07 validate configuration on startup, fail fast on bad
config (closes #52): a config value that is set but unparseable or
invalid aborts startup naming the key and value (defaults apply only
to omitted keys), unknown config keys abort startup, a malformed
config file aborts instead of being skipped, and
state_diris verified creatable and writable before the listener binds - 2026-08-07 manual test pass of the auth and encrypted URL flows
against a locally built and running
pixad(built frommainat6573b9d, port 18099, local throwaway config); all six checks passed, plus all nine tests inscripts/manual-test.sh(closes #49):- visit
/and see the login form: HTTP 200,Pixa - Loginpage withname="key"password form - wrong key shows an error: POST
/withkey=wrong-keyreturned HTTP 200 login page containing "Invalid signing key" - correct signing key shows the generator form: POST
/returned HTTP 303 to/withSet-Cookie: pixa_session=...; HttpOnly; Secure; SameSite=Strict; GET/with that cookie renderedPixa - URL Generatorwith the/generateform and logout link - a generated encrypted URL serves the image: POST
/generate(ttl=3600) produced a/v1/e/<token>/img.jpegURL that returned HTTP 200,Content-Type: image/jpeg, an 800x600 baseline JPEG of 61706 bytes - an expired URL (short TTL) returns 410: a ttl=1 URL fetched
after 3 s returned HTTP 410 Gone with
{"error":"URL has expired","status":410,...} - logout redirects back to login: GET
/logoutreturned HTTP 303 to/withSet-Cookie: pixa_session=; Max-Age=0; subsequent GET/rendered the login form again
- visit
- 2026-08-07 fix the two remaining gosec findings (G124 in
internal/session): session cookies now always carry
Secure/HttpOnly/SameSite=Strict on both the set and clear paths;
make checkgreen (closes #47) - 2026-07-07 Adopted scripts-to-rule-them-all:
script/entrypoints, Makefile shims, README Entrypoints section - 2026-04-07 extract magic byte detection into internal/magic (#42)
- 2026-03-25 extract allowlist package from internal/imgcache (#41)
- 2026-03-25 move schema_migrations table creation into 000.sql (#36)
- 2026-03-20 enforce and document exact-match-only signature verification (#40)
- 2026-03-20 bound imageprocessor.Process input read to prevent unbounded memory use (#37); consolidate appname into an internal/globals constant (#34)
- 2026-03-18 parse version prefix from migration filenames (#33)
- 2026-03-15 QA audit fixes for 1.0/MVP readiness (#25)
- 2026-03-02 split Dockerfile with pre-built golangci-lint stage for faster CI (#23)
- 2026-02-25 repo policy compliance: CI workflow, hash-pinned images, golangci-lint and gosec fixes of that date (#14); arm64 Docker build fix (#16)
- 2026-01-08 WebP and AVIF encoding support via govips (both former P0 image processing items, now done)
Future Steps
- P1: rate limit global concurrent upstream fetches to prevent resource exhaustion
- P1: strip EXIF and other metadata from processed images (privacy)
- P2: security
- referer blacklist
- per-IP rate limiting
- per-origin rate limiting
- P2: HTTP response handling
- Last-Modified headers
- Vary header for content negotiation
- X-Request-ID propagation
- P2: auto format selection (format=auto based on Accept header)
- P2: configuration
- add all configuration options from README
- environment variable overrides
- YAML config file support
- P2: operational
- optional Sentry error reporting
- comprehensive request logging
- Prometheus performance metrics
- integration tests for the image proxy flow
- load tests to verify the 1k to 5k req/s target
- P2: documentation
- configuration options
- API endpoints
- deployment guide
- example nginx or caddy reverse proxy config