2026-04-22 - 2026-10-22
Overview
3 Releases published by 1 user
Published
v1.0.0-rc3
Published
v1.0.0-rc2
Published
v1.0.0-rc1
132 Pull requests merged by 2 users
Merged
#325 Serve a cache hit with no database read or write (closes #309)
Merged
#324 Store a source the way a variant is stored, one row and one file per source URL (closes #308)
Merged
#323 One image request type instead of five (closes #307)
Merged
#322 Run only script/cibuild in the Gitea workflow (closes #203)
Merged
#278 Keep WebP, AVIF and JPEG XL encodes at the limits under 40 seconds (closes #272)
Merged
#273 Answer 415 for a source whose first bytes do not match its Content-Type (closes #253)
Merged
#306 Let a bug fix change the test that pins the behavior the bug calls wrong (closes #280)
Merged
#304 Re-vendor the canonical files from sneak/prompts at 0653579 (closes #221)
Merged
#305 Read a trusted_proxies entry in the IPv4-mapped form as its IPv4 range (closes #303)
Merged
#279 Space reconciliation passes by ten times the last pass's time (closes #250)
Merged
#302 Read a blocked_networks entry in the IPv4-mapped form as its IPv4 range (closes #301)
Merged
#300 Refuse local-use NAT64 addresses, and IPv4-compatible ones in blocked_networks (closes #299)
Merged
#298 Refuse an IPv6 address that holds an internal IPv4 address (closes #297)
Merged
#296 Fetch an IPv6 source host written without a port (closes #295)
Merged
#292 Escape state_dir in the db_url derived from it (closes #290)
Merged
#291 Keep a side computed for a size with one side 0 at 1 or more (closes #283)
Merged
#294 Make one image URL per source on the generator page for host case and an empty path (closes #293)
Merged
#289 Fetch the source path and query URL-encoded, not joined as text (closes #288)
Merged
#284 Crop the centre before resizing for fit=cover (closes #282)
Merged
#287 Refuse an image URL whose source path or query holds a # (closes #286)
Merged
#285 Refuse an image URL whose host is not a host name or IP address (closes #275)
Merged
#281 Document how a source query goes into an image URL, and accept it (closes #255)
Merged
#261 Default max_concurrent_processing to half the CPUs pixa sees (closes #242)
Merged
#276 Re-vendor the canonical files from sneak/prompts at e3f79c4 (closes #221)
Merged
#277 Label request metrics with standard HTTP methods only (closes #274)
Merged
#265 Logging out ends every login session (closes #256)
Merged
#263 Sync cached files and refuse ones left empty or partial (closes #252)
Merged
#271 Stop a transcode whose request has ended before it encodes (closes #243)
Merged
#270 Tell upaas operators to set the cache size limit (closes #251)
Merged
#260 Label request metrics by route, not by path (closes #254)
Merged
#262 Remove expired negative cache rows in the eviction loop (closes #244)
Merged
#268 Serve a cached variant while its source URL is in the negative cache (closes #245)
Merged
#266 Lower-case the image URL's host and require a signature for a port (closes #246)
Merged
#269 Escape : and % in the signed and cached path and query (closes #247)
Merged
#267 Accept a chunked upstream body of exactly the maximum size (closes #248)
Merged
#264 Allowlist no host in the example config (closes #249)
Merged
#259 Refuse a fit=outside size whose output exceeds the maximum (closes #257)
Merged
#258 Refuse a size whose computed side exceeds the maximum (closes #241)
Merged
#239 Remove the unused cache lifetime setting (closes #69)
Merged
#240 Refuse SVG sources and never save an unknown input as JPEG (closes #68)
Merged
#235 Keep counts not written by the request's deadline in memory (closes #224)
Merged
#237 Limit each client's requests to the image routes (closes #236)
Merged
#234 Save PNG compressed, WebP at effort 4 and AVIF at effort 1 (closes #232)
Merged
#233 Serve JPEG XL when a request names no format (closes #222)
Merged
#230 Serve and accept JPEG XL as an image format (part of #222)
Merged
#231 Run pixad on the Alpine release it is built on (closes #229)
Merged
#228 Eviction no longer reads a whole table while requests wait on the database (closes #227)
Merged
#226 Install libvips JPEG XL support and require it at startup (part of #222)
Merged
#225 Queue SQLite writes on one connection so none fails as locked (closes #223)
Merged
#220 Serve the format auto, chosen from the Accept header (closes #88)
Merged
#219 Format the markdown with prettier in script/fmt and script/fmt-check (closes #100)
Merged
#218 Run lint and tests as the lint and test phases of the Dockerfile, built with --no-cache (closes #202)
Merged
#209 Add script/loadtest to measure throughput, latency and memory (closes #81)
Merged
#216 Fetch the tags in the CI checkout (closes #208)
Merged
#217 Keep config.yml out of git and the Docker build context (closes #212)
Merged
#214 Keep local config files out of the Docker build context (closes #211)
Merged
#215 Move pixad's startup from cmd/pixad into internal/app (closes #206)
Merged
#213 Ignore .claude/ in .gitignore (closes #204)
Merged
#210 Keep secrets out of the Docker build context at every depth (closes #205)
Merged
#207 Replace REPO_POLICIES.md with the canonical copy from sneak/prompts (closes #196)
Merged
#195 Add an integration test of the image proxy flow (closes #80)
Merged
#201 Test that a URL made on the generator page with a ttl expires (closes #199)
Merged
#197 Refuse image requests whose Referer is on referer_blocklist (closes #90)
Merged
#194 Move the example config to configs/, delete scripts/ and CONVENTIONS.md (closes #97)
Merged
#200 Wait on a busy SQLite database and turn on WAL mode (closes #198)
Merged
#193 Make the periodic reconciliation test wait for the startup pass (closes #189)
Merged
#188 Count what the cache holds in the default cache_max_bytes (closes #184)
Merged
#191 Test logging in, logging out, the URL generator and /v1/e/ tokens (closes #77)
Merged
#185 Drop unsafe-inline from the Content-Security-Policy (closes #125)
Merged
#186 Replace the deprecated gomodguard with gomodguard_v2 by re-vendoring .golangci.yml (closes #57)
Merged
#181 Abort startup on a config file pixa cannot read (closes #176)
Merged
#192 Merge TODO.md with git's union merge (closes #190)
Merged
#187 Stop TestEvictionRunsOnPeriodicSchedule racing the evictor (closes #183)
Merged
#182 Write the deployment guide and an example Caddy config (closes #89)
Merged
#178 Test the metrics basic auth, CORS preflight, login logging and metrics recording (closes #79)
Merged
#177 Test the image route's signature check and error answers (closes #76)
Merged
#179 Return and pass on request IDs, and give /v1/e/ ETag, 304 and HEAD (closes #84)
Merged
#174 Document every route, encrypted URLs and the config file search (closes #75)
Merged
#173 Stop cache eviction in progress at shutdown (closes #102)
Merged
#175 Remove unimplemented Purge and three unused interfaces from imgcache (closes #73)
Merged
#171 Remove idle upstream host semaphores and delete a variant's .meta file with it (closes #87)
Merged
#172 Make the README's storage paths, formats, TLS and metrics match the code (closes #74)
Merged
#169 Exit with the shutdown's code and wait for image processing (closes #86)
Merged
#170 Run the checks on every script/cibuild and script/docker build (closes #101)
Merged
#160 Share one fetch and transcode among concurrent misses (closes #65)
Merged
#162 Send CORS headers only from the image routes (closes #98)
Merged
#167 Stamp the tag or short commit in a plain docker build (closes #166)
Merged
#165 Read the architecture at run time, not via a Buildarch ldflag (closes #164)
Merged
#161 Container makes /var/lib/pixa usable before starting pixad
Merged
#157 Keep variant content types in memory for cache hits (closes #70)
Merged
#154 Answer image requests with 503 in maintenance mode (closes #71)
Merged
#148 Bound concurrent image processing and upstream fetches (closes #64)
Merged
#156 Install Dockerfile build dependencies through script/bootstrap (closes #95)
Merged
#142 Add the four settings the README documented but pixa lacked (closes #61)
Merged
#155 Move migrations to internal/db/migrations (closes #96)
Merged
#153 Correct trusted_proxies advice and state signature padding (closes #150)
Merged
#144 Make the cache stats count what is cached, fetched and transcoded (closes #56)
Merged
#152 Give pixad a fixed uid and gid 65532 (closes #151)
Merged
#146 Keep max-age within an expiring image URL's lifetime (closes #63)
Merged
#118 next -> main (1.0.0 milestone)
Merged
#145 Strip metadata from processed images (closes #82)
Merged
#143 Rate limit login attempts per client address (closes #66)
Merged
#141 Refuse an unparseable exp with 400; log swallowed cache errors (closes #72)
Merged
#140 Refuse an empty fit on /v1/image/ with 400 (closes #139)
Merged
#138 Refuse a q outside 1-100 on /v1/image/ with 400
Merged
#136 Abort startup on an unknown PIXA_ environment variable (closes #133)
Merged
#137 Describe what --health-interval does in docker-smoke on current Docker (closes #132)
Merged
#126 fix: validate dimensions and fit mode on encrypted URLs (closes #62)
Merged
#135 Start on a fresh upaas volume and document running under upaas (closes #129)
Merged
#122 build: run all linting in Docker through script/lint (closes #104)
Merged
#131 Every setting can be given as an environment variable (closes #128)
Merged
#116 Include quality and fit in the URL signature (closes #60)
Merged
#130 Add a Docker HEALTHCHECK and make docker-smoke (closes #111)
Merged
#127 Resolve real client IP behind trusted proxies (closes #94)
Merged
#124 feat: blocked_networks config and extended SSRF ranges (closes #67)
Merged
#120 Take the image signing key from PIXA_SIGNING_KEY and refuse the example placeholder (closes #110)
Merged
#123 Harden http.Server: slowloris timeouts and form body limit (closes #92)
Merged
#121 feat: add HSTS, CSP, and Permissions-Policy security headers (closes #91)
Merged
#114 docs: update TODO.md Workflow and Status for the next branching model (closes #106)
Merged
#119 test: cover redirect SSRF and semaphore release in httpfetcher (closes #78)
Merged
#115 fix: script/test conditional-verbose-rerun with -cover (closes #59)
Merged
#117 CSRF protection on the login and URL-generator forms (closes #93)
Merged
#105 next -> main (1.0.0 milestone)
Merged
#54 Update golangci-lint to v2.12.2 with canonical config
Merged
#55 feat: cache size management and LRU eviction (closes #51)
Merged
#53 feat: validate configuration on startup, fail fast on bad config (closes #52)
Merged
#50 docs: record manual test pass of auth and encrypted URL flows (closes #49)
Merged
#46 refactor: extract signature package from imgcache
Merged
#48 fix: set Secure/HttpOnly/SameSite on session cookies (closes #47)
Merged
#43 refactor: extract httpfetcher package from imgcache
Merged
#45 scripts-to-rule-them-all
Merged
#44 Update TODO.md: standard structure and Workflow section
3 Pull requests proposed by 1 user
Proposed
#147 main -> prod: first deploy of pixa
Proposed
#149 next -> main
Proposed
#326 Declare only the tables and columns pixa writes and reads (closes #310)
132 Issues closed from 1 user
Closed
#309 Serve a cache hit with no database read or write
Closed
#308 Store a source the way a variant is stored: one row and one file per source URL
Closed
#307 One image request type instead of five
Closed
#203 The Gitea workflow runs script/docker-smoke besides script/cibuild
Closed
#272 WebP, AVIF and JPEG XL outputs with an alpha channel take longer to encode than the request may wait
Closed
#253 A source whose first bytes do not match its Content-Type answers 500 and is fetched again on every request
Closed
#280 May a bug fix change a test that pins exactly the behaviour the bug calls wrong, without asking each time?
Closed
#221 Re-vendor the canonical files from sneak/prompts at dd4027b
Closed
#303 A trusted_proxies entry written in the IPv4-mapped form is accepted but never matches
Closed
#250 The reconciliation pass costs one database query per cached file and runs back to back on a large cache
Closed
#301 A blocked_networks entry written in the IPv4-mapped form is accepted but refuses nothing
Closed
#299 The local-use NAT64 range and the operator's blocked_networks list miss IPv4 addresses embedded in IPv6
Closed
#297 The private-address check misses an IPv4 address embedded in an IPv6 one
Closed
#295 An IPv6 source host written without a port is refused by the fetcher and answers 500
Closed
#290 A state_dir holding %, ? or # breaks the database URL pixa derives from it
Closed
#283 A size with one side 0 whose computed side rounds down to 0 comes out square, can ask for tens of GB, or answers 500
Closed
#293 The generator page gives one upstream image several cache keys for host case and an empty path
Closed
#288 The source URL is built by joining the decoded path and query as text, so some characters reach the upstream wrong
Closed
#282 A thin source requested with fit=cover makes libvips allocate a huge uncropped image
Closed
#286 A # written into the source path or query gives another unsigned spelling of an allowlisted image
Closed
#275 A ? written into the host segment of an image URL gives another unsigned spelling of an allowlisted source
Closed
#255 The README gives no way to put a source query into a signed image URL, and the in-tree builder makes one that fails
Closed
#242 Memory per image and the default number of images processed at once are not bounded or documented for the container
Closed
#274 The request metrics label every distinct HTTP method name, so made-up methods grow memory without bound when metrics are on
Closed
#256 Logging out does not end the login session
Closed
#252 A cached file left empty or partial by a crash is served or re-fetched forever
Closed
#243 Image processing runs past the request's deadline and keeps its slot
Closed
#251 The upaas section calls the cache size limit optional, but its default takes three quarters of a shared filesystem
Closed
#254 The request metrics label every distinct URL path, so unauthenticated requests grow memory without bound when metrics are on
Closed
#244 Negative-cache rows are never purged, so the database grows without bound
Closed
#245 A cached variant is refused while its source URL is in the negative cache
Closed
#246 The host segment is not normalized before the allowlist, the per-host limit and the cache keys
Closed
#247 The signed data and the cache key are ambiguous between path and query
Closed
#248 A chunked upstream body of exactly the maximum size is refused
Closed
#249 The example config allowlists public hosts that carry anyone's uploads
Closed
#257 fit=outside with both sides given makes an output of unbounded size
Closed
#241 A request with one dimension 0 makes an output of unbounded size
Closed
#69 CacheConfig.CacheTTL is configured but never read — cached variants never expire
Closed
#68 Decide on SVG: it is accepted and rendered by libvips, and format=orig silently returns JPEG
Closed
#113 Delete five stale remote branches
Closed
#103 Tracking: 1.0.0 sequencing and critical path
Closed
#238 TestService_Get_WaitsForSlotBeforeReadingCachedSource fails when the host is busy
Closed
#224 TestService_Get_ReturnsByItsDeadline fails when the host is busy
Closed
#236 One client can keep every image processing slot busy: the image routes have no limit per client
Closed
#232 PNG output is saved uncompressed, and WebP, AVIF and GIF at the lowest effort
Closed
#222 JPEG XL as a first-class format, and the default output when none is specified
Closed
#229 The runtime image runs pixad on an older Alpine and libvips than it was built with
Closed
#227 Requests wait behind the eviction pass's whole-table queries on the one database connection
Closed
#223 Concurrent SQLite writes still fail with "database is locked" under load, so next is red
Closed
#88 Implement format=auto (Accept-header content negotiation)
Closed
#100 Markdown is never formatted: script/fmt covers only Go, and no prettier config exists
Closed
#202 Lint and tests do not run as the lint and test phases of the Dockerfile, built with --no-cache
Closed
#81 No load test verifying the documented 1k-5k req/s target
Closed
#208 The CI checkout fetches no tags, so a version stamped from git describe would be a bare commit
Closed
#212 config.yml, the config file Getting Started creates, is kept out of neither git nor the Docker build context
Closed
#211 Local config files that can hold the signing key still reach the Docker build context
Closed
#206 cmd/pixad/main.go holds logic that belongs under internal/
Closed
#204 .gitignore does not ignore .claude/
Closed
#205 .dockerignore keeps secrets out only at the repository root
Closed
#196 REPO_POLICIES.md no longer matches the canonical copy in sneak/prompts
Closed
#80 No integration test of the end-to-end image proxy flow
Closed
#199 No test that a URL made on the generator page with a ttl expires (410)
Closed
#90 Implement referer blocklist to prevent hotlinking
Closed
#97 Repo layout: non-canonical root files, a confusing scripts/ directory, and a stale reformatted CONVENTIONS.md
Closed
#198 SQLite writes are dropped with "database is locked": no busy timeout, and WAL is never turned on
Closed
#189 TestPeriodicReconciliationAdoptsFileThatAppearsAfterStartup can pass without a periodic pass
Closed
#184 The default cache_max_bytes shrinks as the cache fills: each restart can evict most of the cache
Closed
#77 The auth UI and encrypted-URL flows have zero Go tests — only a manual shell script
Closed
#125 Remove unsafe-inline from the Content-Security-Policy: move inline handlers and runtime Tailwind styles into served assets
Closed
#57 Migrate .golangci.yml from deprecated gomodguard to gomodguard_v2
Closed
#176 A config file pixa cannot read is skipped silently instead of aborting startup
Closed
#190 Every merge to next makes the other open PRs conflict in TODO.md
Closed
#183 Make next green: TestEvictionRunsOnPeriodicSchedule races the evictor's reconciliation and fails make test
Closed
#89 Write the deployment guide and an example reverse proxy config
Closed
#79 internal/middleware: CORS, logging, and the metrics auth gate are untested
Closed
#76 internal/handlers has 4 tests: the 401 signature boundary and every error path are untested
Closed
#84 Response header gaps: no Vary, X-Request-ID not returned or propagated, /v1/e/ lacks ETag/HEAD/304
Closed
#75 Document the auth UI, encrypted-URL routes, q/fit parameters, and the undocumented config keys
Closed
#102 Eviction loop is not context-cancellable; shutdown ignores its deadline
Closed
#73 Remove dead abstractions in imgcache: unimplemented Purge and three interfaces with no implementers
Closed
#87 httpfetcher.hostSems grows without bound, and VariantStorage.Delete orphans the .meta sidecar
Closed
#74 README describes storage paths, formats, and features that do not match the code
Closed
#86 Shutdown correctness: exit code always 0, two competing signal handlers, Sentry calls os.Exit from a goroutine
Closed
#101 script/cibuild can report a green it did not earn: docker layer cache can skip make check entirely
Closed
#65 No request coalescing: K concurrent misses for the same variant cause K upstream fetches and K transcodes
Closed
#98 CORS wildcard is applied at the router root, covering the cookie-authenticated routes
Closed
#166 docker build . stamps dev, not the git tag or short commit
Closed
#164 Remove Buildarch from CONVENTIONS.md: read the architecture at run time
Closed
#163 Run every build, test and lint step in the Dockerfiles at nice -n 19
Closed
#159 The container sets its data directory permissions itself before startup
Closed
#70 Cache.metaCache is dead: every cache hit does open+stat+ReadFile+JSON parse instead of the in-memory lookup README promises
Closed
#158 script/docker-smoke: run the container with a 2 GiB memory limit
Closed
#71 maintenance_mode is configurable and reported by the healthcheck but does not affect any request
Closed
#64 P1: no global cap on concurrent image processing — unbounded memory under load
Closed
#95 Dockerfile duplicates dependency installs inline instead of running script/bootstrap, and omits -trimpath / -s -w
Closed
#61 P1: four config keys documented in README do not exist and now abort startup
Closed
#96 Migrations are not at the mandated internal/db/migrations/ path or filenames
Closed
#150 README: trusted_proxies advice and signature padding mislead operators
Closed
#56 Cache.Stats reads never-populated output_content/request_cache tables, so TotalItems and TotalSizeBytes are always 0
Closed
#151 Give the pixad user a fixed uid and gid outside the login range
Closed
#63 Expiring URLs are served with Cache-Control: max-age=31536000, immutable
Closed
#82 P1 privacy: EXIF and other metadata are not stripped from processed images
Closed
#66 P1 security: no rate limiting or lockout on the signing-key login form
Closed
#72 Silently swallowed errors: unparseable exp yields 401 instead of 400, sidecar write failures logged nowhere
Closed
#139 /v1/image/ serves an empty fit= as cover instead of answering 400
Closed
#134 /v1/image/ serves an invalid q as the default instead of answering 400
Closed
#133 An unknown PIXA_ environment variable is ignored instead of aborting startup
Closed
#132 script/docker-smoke: the comment on --health-interval describes old Docker behavior
Closed
#62 P1: /v1/e/ encrypted URLs bypass dimension and fit-mode validation
Closed
#17 µPaaS deployment setup
Closed
#129 Start on a fresh upaas volume, and a README section on running under upaas
Closed
#104 Run all linting in Docker via Dockerfile.lint + script/lint
Closed
#99 Port is not overridable with the PORT environment variable
Closed
#128 Every setting can be given as an environment variable
Closed
#60 P1 security: q and fit are outside the HMAC signature, allowing 500x cache/transcode amplification from one signed URL
Closed
#111 Prove the Docker image runs: HEALTHCHECK instruction and a make docker-smoke target that starts the image and checks the healthcheck
Closed
#94 Blocks 1.0: no trusted-proxy handling — every log line and login record shows the proxy's IP
Closed
#67 P1: blocked networks config + SSRF gaps (CGNAT, benchmark, NAT64 ranges not blocked)
Closed
#110 Docker image bakes the example config with a placeholder signing key; take the key from the environment and refuse the placeholder
Closed
#92 Blocks 1.0: http.Server missing IdleTimeout and ReadHeaderTimeout (slowloris), no request body size limits
Closed
#91 Blocks 1.0: missing HSTS, CSP, and Permissions-Policy security headers
Closed
#106 TODO.md documents a branch-from-main workflow that no longer matches the next branching model
Closed
#78 httpfetcher: redirect-to-private-IP SSRF and semaphore release are untested
Closed
#59 script/test doesn't follow the REPO_POLICIES conditional-verbose-rerun pattern (always runs -v)
Closed
#93 Blocks 1.0: no CSRF protection on the cookie-authenticated POST / and POST /generate forms
Closed
#107 Add hash-pinned Dockerfile.lint and make script/lint Docker-only
Closed
#108 Prove the Docker lint gate actually lints: defeat layer cache + negative control
Closed
#58 script/lint does not pin the golangci-lint binary version, so local runs can false-green against a stale linter
Closed
#51 P0: implement cache size management and eviction
Closed
#52 P0: validate configuration on startup, fail fast on bad config
Closed
#49 P0: manual test pass of the auth and encrypted URL flows
Closed
#47 Fix remaining gosec lint findings on main
149 Issues created by 1 user
Opened
#47 Fix remaining gosec lint findings on main
Opened
#49 P0: manual test pass of the auth and encrypted URL flows
Opened
#51 P0: implement cache size management and eviction
Opened
#52 P0: validate configuration on startup, fail fast on bad config
Opened
#56 Cache.Stats reads never-populated output_content/request_cache tables, so TotalItems and TotalSizeBytes are always 0
Opened
#57 Migrate .golangci.yml from deprecated gomodguard to gomodguard_v2
Opened
#58 script/lint does not pin the golangci-lint binary version, so local runs can false-green against a stale linter
Opened
#59 script/test doesn't follow the REPO_POLICIES conditional-verbose-rerun pattern (always runs -v)
Opened
#60 P1 security: q and fit are outside the HMAC signature, allowing 500x cache/transcode amplification from one signed URL
Opened
#61 P1: four config keys documented in README do not exist and now abort startup
Opened
#62 P1: /v1/e/ encrypted URLs bypass dimension and fit-mode validation
Opened
#63 Expiring URLs are served with Cache-Control: max-age=31536000, immutable
Opened
#64 P1: no global cap on concurrent image processing — unbounded memory under load
Opened
#65 No request coalescing: K concurrent misses for the same variant cause K upstream fetches and K transcodes
Opened
#66 P1 security: no rate limiting or lockout on the signing-key login form
Opened
#67 P1: blocked networks config + SSRF gaps (CGNAT, benchmark, NAT64 ranges not blocked)
Opened
#68 Decide on SVG: it is accepted and rendered by libvips, and format=orig silently returns JPEG
Opened
#69 CacheConfig.CacheTTL is configured but never read — cached variants never expire
Opened
#70 Cache.metaCache is dead: every cache hit does open+stat+ReadFile+JSON parse instead of the in-memory lookup README promises
Opened
#71 maintenance_mode is configurable and reported by the healthcheck but does not affect any request
Opened
#72 Silently swallowed errors: unparseable exp yields 401 instead of 400, sidecar write failures logged nowhere
Opened
#73 Remove dead abstractions in imgcache: unimplemented Purge and three interfaces with no implementers
Opened
#74 README describes storage paths, formats, and features that do not match the code
Opened
#75 Document the auth UI, encrypted-URL routes, q/fit parameters, and the undocumented config keys
Opened
#76 internal/handlers has 4 tests: the 401 signature boundary and every error path are untested
Opened
#77 The auth UI and encrypted-URL flows have zero Go tests — only a manual shell script
Opened
#78 httpfetcher: redirect-to-private-IP SSRF and semaphore release are untested
Opened
#79 internal/middleware: CORS, logging, and the metrics auth gate are untested
Opened
#80 No integration test of the end-to-end image proxy flow
Opened
#81 No load test verifying the documented 1k-5k req/s target
Opened
#82 P1 privacy: EXIF and other metadata are not stripped from processed images
Opened
#83 Capture upstream Last-Modified/ETag and support conditional revalidation
Opened
#84 Response header gaps: no Vary, X-Request-ID not returned or propagated, /v1/e/ lacks ETag/HEAD/304
Opened
#85 No pixa-specific Prometheus metrics, and metrics are not collected at all unless credentials are configured
Opened
#86 Shutdown correctness: exit code always 0, two competing signal handlers, Sentry calls os.Exit from a goroutine
Opened
#87 httpfetcher.hostSems grows without bound, and VariantStorage.Delete orphans the .meta sidecar
Opened
#88 Implement format=auto (Accept-header content negotiation)
Opened
#89 Write the deployment guide and an example reverse proxy config
Opened
#90 Implement referer blocklist to prevent hotlinking
Opened
#91 Blocks 1.0: missing HSTS, CSP, and Permissions-Policy security headers
Opened
#92 Blocks 1.0: http.Server missing IdleTimeout and ReadHeaderTimeout (slowloris), no request body size limits
Opened
#93 Blocks 1.0: no CSRF protection on the cookie-authenticated POST / and POST /generate forms
Opened
#94 Blocks 1.0: no trusted-proxy handling — every log line and login record shows the proxy's IP
Opened
#95 Dockerfile duplicates dependency installs inline instead of running script/bootstrap, and omits -trimpath / -s -w
Opened
#96 Migrations are not at the mandated internal/db/migrations/ path or filenames
Opened
#97 Repo layout: non-canonical root files, a confusing scripts/ directory, and a stale reformatted CONVENTIONS.md
Opened
#98 CORS wildcard is applied at the router root, covering the cookie-authenticated routes
Opened
#99 Port is not overridable with the PORT environment variable
Opened
#100 Markdown is never formatted: script/fmt covers only Go, and no prettier config exists
Opened
#101 script/cibuild can report a green it did not earn: docker layer cache can skip make check entirely
Opened
#102 Eviction loop is not context-cancellable; shutdown ignores its deadline
Opened
#103 Tracking: 1.0.0 sequencing and critical path
Opened
#104 Run all linting in Docker via Dockerfile.lint + script/lint
Opened
#106 TODO.md documents a branch-from-main workflow that no longer matches the next branching model
Opened
#107 Add hash-pinned Dockerfile.lint and make script/lint Docker-only
Opened
#108 Prove the Docker lint gate actually lints: defeat layer cache + negative control
Opened
#109 Review toward 1.0: identify and file all next steps
Opened
#110 Docker image bakes the example config with a placeholder signing key; take the key from the environment and refuse the placeholder
Opened
#111 Prove the Docker image runs: HEALTHCHECK instruction and a make docker-smoke target that starts the image and checks the healthcheck
Opened
#112 Release: tag v1.0.0 on main once the milestone closes
Opened
#113 Delete five stale remote branches
Opened
#125 Remove unsafe-inline from the Content-Security-Policy: move inline handlers and runtime Tailwind styles into served assets
Opened
#128 Every setting can be given as an environment variable
Opened
#129 Start on a fresh upaas volume, and a README section on running under upaas
Opened
#132 script/docker-smoke: the comment on --health-interval describes old Docker behavior
Opened
#133 An unknown PIXA_ environment variable is ignored instead of aborting startup
Opened
#134 /v1/image/ serves an invalid q as the default instead of answering 400
Opened
#139 /v1/image/ serves an empty fit= as cover instead of answering 400
Opened
#150 README: trusted_proxies advice and signature padding mislead operators
Opened
#151 Give the pixad user a fixed uid and gid outside the login range
Opened
#158 script/docker-smoke: run the container with a 2 GiB memory limit
Opened
#159 The container sets its data directory permissions itself before startup
Opened
#163 Run every build, test and lint step in the Dockerfiles at nice -n 19
Opened
#164 Remove Buildarch from CONVENTIONS.md: read the architecture at run time
Opened
#166 docker build . stamps dev, not the git tag or short commit
Opened
#168 Recommendations for a clearer, simpler object and data model
Opened
#176 A config file pixa cannot read is skipped silently instead of aborting startup
Opened
#180 Test the /metrics basic auth and metrics recording through the router (needs approval to change an existing test)
Opened
#183 Make next green: TestEvictionRunsOnPeriodicSchedule races the evictor's reconciliation and fails make test
Opened
#184 The default cache_max_bytes shrinks as the cache fills: each restart can evict most of the cache
Opened
#189 TestPeriodicReconciliationAdoptsFileThatAppearsAfterStartup can pass without a periodic pass
Opened
#190 Every merge to next makes the other open PRs conflict in TODO.md
Opened
#196 REPO_POLICIES.md no longer matches the canonical copy in sneak/prompts
Opened
#198 SQLite writes are dropped with "database is locked": no busy timeout, and WAL is never turned on
Opened
#199 No test that a URL made on the generator page with a ttl expires (410)
Opened
#202 Lint and tests do not run as the lint and test phases of the Dockerfile, built with --no-cache
Opened
#203 The Gitea workflow runs script/docker-smoke besides script/cibuild
Opened
#204 .gitignore does not ignore .claude/
Opened
#205 .dockerignore keeps secrets out only at the repository root
Opened
#206 cmd/pixad/main.go holds logic that belongs under internal/
Opened
#208 The CI checkout fetches no tags, so a version stamped from git describe would be a bare commit
Opened
#211 Local config files that can hold the signing key still reach the Docker build context
Opened
#212 config.yml, the config file Getting Started creates, is kept out of neither git nor the Docker build context
Opened
#221 Re-vendor the canonical files from sneak/prompts at dd4027b
Opened
#222 JPEG XL as a first-class format, and the default output when none is specified
Opened
#223 Concurrent SQLite writes still fail with "database is locked" under load, so next is red
Opened
#224 TestService_Get_ReturnsByItsDeadline fails when the host is busy
Opened
#227 Requests wait behind the eviction pass's whole-table queries on the one database connection
Opened
#229 The runtime image runs pixad on an older Alpine and libvips than it was built with
Opened
#232 PNG output is saved uncompressed, and WebP, AVIF and GIF at the lowest effort
Opened
#236 One client can keep every image processing slot busy: the image routes have no limit per client
Opened
#238 TestService_Get_WaitsForSlotBeforeReadingCachedSource fails when the host is busy
Opened
#241 A request with one dimension 0 makes an output of unbounded size
Opened
#242 Memory per image and the default number of images processed at once are not bounded or documented for the container
Opened
#243 Image processing runs past the request's deadline and keeps its slot
Opened
#244 Negative-cache rows are never purged, so the database grows without bound
Opened
#245 A cached variant is refused while its source URL is in the negative cache
Opened
#246 The host segment is not normalized before the allowlist, the per-host limit and the cache keys
Opened
#247 The signed data and the cache key are ambiguous between path and query
Opened
#248 A chunked upstream body of exactly the maximum size is refused
Opened
#249 The example config allowlists public hosts that carry anyone's uploads
Opened
#250 The reconciliation pass costs one database query per cached file and runs back to back on a large cache
Opened
#251 The upaas section calls the cache size limit optional, but its default takes three quarters of a shared filesystem
Opened
#252 A cached file left empty or partial by a crash is served or re-fetched forever
Opened
#253 A source whose first bytes do not match its Content-Type answers 500 and is fetched again on every request
Opened
#254 The request metrics label every distinct URL path, so unauthenticated requests grow memory without bound when metrics are on
Opened
#255 The README gives no way to put a source query into a signed image URL, and the in-tree builder makes one that fails
Opened
#256 Logging out does not end the login session
Opened
#257 fit=outside with both sides given makes an output of unbounded size
Opened
#272 WebP, AVIF and JPEG XL outputs with an alpha channel take longer to encode than the request may wait
Opened
#274 The request metrics label every distinct HTTP method name, so made-up methods grow memory without bound when metrics are on
Opened
#275 A ? written into the host segment of an image URL gives another unsigned spelling of an allowlisted source
Opened
#280 May a bug fix change a test that pins exactly the behaviour the bug calls wrong, without asking each time?
Opened
#282 A thin source requested with fit=cover makes libvips allocate a huge uncropped image
Opened
#283 A size with one side 0 whose computed side rounds down to 0 comes out square, can ask for tens of GB, or answers 500
Opened
#286 A # written into the source path or query gives another unsigned spelling of an allowlisted image
Opened
#288 The source URL is built by joining the decoded path and query as text, so some characters reach the upstream wrong
Opened
#290 A state_dir holding %, ? or # breaks the database URL pixa derives from it
Opened
#293 The generator page gives one upstream image several cache keys for host case and an empty path
Opened
#295 An IPv6 source host written without a port is refused by the fetcher and answers 500
Opened
#297 The private-address check misses an IPv4 address embedded in an IPv6 one
Opened
#299 The local-use NAT64 range and the operator's blocked_networks list miss IPv4 addresses embedded in IPv6
Opened
#301 A blocked_networks entry written in the IPv4-mapped form is accepted but refuses nothing
Opened
#303 A trusted_proxies entry written in the IPv4-mapped form is accepted but never matches
Opened
#307 One image request type instead of five
Opened
#308 Store a source the way a variant is stored: one row and one file per source URL
Opened
#309 Serve a cache hit with no database read or write
Opened
#310 Make the schema file describe only the tables and columns pixa uses
Opened
#311 Count hits, misses, fetches and transforms as Prometheus counters instead of the cache_stats table
Opened
#312 Remove the code nothing in production uses
Opened
#313 Build the image pipeline in the fx graph, with each default declared once in the config
Opened
#314 Take the signature, the expiry and the URL scheme off the image request
Opened
#315 Derive every key from the signing key in one place
Opened
#316 One handler body and one error mapping for the two image routes
Opened
#317 Split internal/imgcache into the image proxy and the cache
Opened
#318 One file store type for cached sources and variants
Opened
#319 Decide a fetched image's format from its bytes once
Opened
#320 One table of settings in internal/config
Opened
#321 Describe the implemented model in README.md with a list of its concepts
1 Unresolved Conversation
Open
#39
Refactor: split internal/imgcache into focused packages