Commit Graph
100 Commits
Author SHA1 Message Date
sneak 744f20861a Update TODO.md: standard structure and Workflow section
check / check (push) Successful in 1m42s
2026-07-06 21:09:14 +02:00
sneak a1c0ae0a44 fix: auto-detect native deps, skip nix-shell in Docker
check / check (push) Failing after 43s
Check for pkg-config at make-time; if present (Docker, existing
nix-shell) run commands directly, otherwise wrap with nix-shell.
2026-02-25 20:11:02 +07:00
sneak 429926fb71 chore: use nix-shell for CGO-dependent Makefile targets
Wrap test, lint, and build targets with nix-shell to provide
pkg-config, vips, libheif, and golangci-lint automatically.
2026-02-25 20:08:04 +07:00
sneak ce6db7627d fix: resolve all golangci-lint errors
- Add blank lines before return statements (nlreturn)
- Remove unused metaCacheMu field and sync import (unused)
- Rename unused groups parameter to _ (revive)
- Use StorageFilePerm constant instead of magic 0600 (mnd, gosec)
- Add nolint directive for vipsOnce global (gochecknoglobals)
2026-02-25 19:58:37 +07:00
sneak 454de2f170 fix: restore original whitelist hosts in config.example.yml 2026-02-25 19:53:23 +07:00
sneak 133d9e5a4a chore: consolidate duplicate example config files 2026-02-25 19:48:03 +07:00
sneak 73f1073d61 chore: restructure README with required policy sections 2026-02-25 19:47:34 +07:00
sneak d0fe5e7334 chore: pin Docker images by hash and run make check in build 2026-02-25 19:47:14 +07:00
sneak c4fc1e1548 chore: update .dockerignore to policy standards 2026-02-25 18:22:35 +07:00
sneak 39fa0a5d05 chore: update .gitignore to policy standards 2026-02-25 18:22:33 +07:00
sneak 2f53b49a88 chore: add Gitea Actions CI workflow 2026-02-25 18:22:24 +07:00
sneak ce360880f7 chore: add REPO_POLICIES.md from prompts repo 2026-02-25 18:22:19 +07:00
sneak 9d71fabdd7 chore: add GPL-3.0 LICENSE file 2026-02-25 18:22:13 +07:00
sneak 8a2b630864 chore: add .editorconfig 2026-02-25 18:18:06 +07:00
sneak 0000188265 chore: add hooks target and 30s test timeout to Makefile
Add missing fmt-check to .PHONY, add hooks target for pre-commit
hook installation, and add 30-second timeout to test target per
repo policy.
2026-02-25 18:18:00 +07:00
sneak be293906bc Add type-safe hash types for cache storage
Define ContentHash, VariantKey, and PathHash types to replace
raw strings, providing compile-time type safety for storage
operations. Update storage layer to use typed parameters,
refactor cache to use variant storage keyed by VariantKey,
and implement source content reuse on cache misses.
2026-01-08 16:55:20 -08:00
sneak 555f150179 Add additional hosts to dev whitelist 2026-01-08 16:14:05 -08:00
sneak 87a8393537 Change encrypted URL format to /v1/e/{token}/img.{ext}
Add trailing filename to encrypted URLs for better browser compatibility.
The filename is ignored by the server but helps browsers identify content type.
2026-01-08 16:14:01 -08:00
sneak 6ab0d4a5b9 Simplify log source to file.go:line format
Replace verbose source object with simple "file.go:line" string
for cleaner log output.
2026-01-08 16:13:56 -08:00
sneak 982accd549 Suppress verbose vips logging output
Initialize libvips with LogLevelError to prevent info-level messages
from polluting the JSON log stream.
2026-01-08 16:13:52 -08:00
sneak 3849128c45 Remove runtime nil checks for always-initialized components
Since signing_key is now required at config load time, sessMgr, encGen,
and signer are always initialized. Remove unnecessary nil checks that
were runtime failure paths that can no longer be reached.

- handlers.go: Remove conditional init, always create sessMgr/encGen
- auth.go: Remove nil checks for sessMgr
- imageenc.go: Remove nil check for encGen
- service.go: Require signing_key in NewService, remove signer nil checks
- Update tests to provide signing_key
2026-01-08 15:58:44 -08:00
sneak 02dedd433b Require signing_key at startup, add default config
- Add config validation: signing_key required, minimum 32 characters
- Server now fails to start without valid signing_key (no more runtime errors)
- Add config.example.yml with default whitelist hosts
- Copy config to /etc/pixa/config.yml in Docker image
- Update entrypoint to use --config /etc/pixa/config.yml
- Add config.dev.yml for local Docker development
- Mount dev config in make devserver
2026-01-08 15:48:37 -08:00
sneak d2e2e319be Create /var/lib/pixa directory in Docker image for database/cache
- Change default StateDir from ./data to /var/lib/pixa (proper Unix convention)
- Create directory owned by pixad user in Dockerfile
- Set WORKDIR to /var/lib/pixa
2026-01-08 15:37:25 -08:00
sneak fc49e69d8b Add make devserver target for local Docker development 2026-01-08 15:22:25 -08:00
sneak 78f844fca5 Switch to govips for native CGO image processing
- Replace gen2brain/avif, gen2brain/webp, disintegration/imaging with govips
- govips uses libvips via CGO for fast native image processing
- Add libheif-dev to Dockerfile for AVIF support
- Add docker-test Makefile target for running tests in Docker
- Update processor.go to use vips API for decode, resize, encode
- Add TestMain to initialize/shutdown vips in tests
- Remove WASM-based libraries (gen2brain) in favor of native codecs

Performance improvement: AVIF encoding now uses native libheif instead of
WASM, significantly reducing encoding time for large images.
2026-01-08 15:16:34 -08:00
sneak 4b2d85010e Add two-stage Dockerfile with CGO support
- Build stage: golang:1.24-alpine with vips-dev for CGO image libs
- Runtime stage: alpine:3.21 with vips runtime only
- Pass VERSION build arg for ldflags embedding
- Add 'make docker' target to build image with git version
2026-01-08 15:05:49 -08:00
sneak 467237c849 Add AVIF option to URL generator 2026-01-08 13:12:51 -08:00
sneak ca4446e10a Implement AVIF encoding support
- Add AVIF case to encode() using gen2brain/avif
- Add AVIF to SupportedOutputFormats()
- Use default encoder speed of 6 for balance of speed/quality
2026-01-08 13:12:34 -08:00
sneak 32f9166ece Add failing test for AVIF encoding
Test verifies that images can be encoded to AVIF format.
Currently fails because AVIF encoding is not implemented.
Removes the rejection test for AVIF output format.
2026-01-08 13:11:12 -08:00
sneak 1bdf0a9424 Implement AVIF decoding support
- Add github.com/gen2brain/avif dependency (CGO-free, WASM-based)
- Update decode() to try AVIF after WebP
- Add AVIF to SupportedInputFormats()
2026-01-08 13:10:34 -08:00
sneak 615586fcea Add failing test for AVIF decoding
Test verifies that AVIF images can be decoded and processed.
Currently fails because AVIF decoding is not implemented.
2026-01-08 13:09:40 -08:00
sneak 9bfae69ccf Fix logging: add response_bytes to middleware, cache_key to handler
- Middleware now tracks and logs bytes written via response_bytes
- Handler logs cache_key for cache hit debugging
- Changed "served encrypted image" to "image served" (only URL is encrypted)
2026-01-08 13:05:10 -08:00
sneak e5135b3697 Add 1-minute expiration option and quality presets to URL generator
Quality is now a dropdown with named presets:
- Potato (25), Low (50), Medium (70), High (85), Ultra (100)

Added 1-minute TTL option for testing short-lived URLs.
2026-01-08 12:52:14 -08:00
sneak 77c6744383 Add upstream connection info and download metrics to logging
- Capture TLS version, cipher suite, HTTP version, and remote addr
- Add download bitrate using go-humanize SI formatting
- Use consistent WxH format for dimensions (not struct notation)
- Rename input/output to src/dst for consistency
- Add separate "upstream fetched" log with connection details
2026-01-08 12:47:31 -08:00
sneak 7d0ac0a139 Remove Buildarch from ldflags, use runtime.GOARCH instead
The architecture is available at runtime via stdlib, no need to bake
it in at build time.
2026-01-08 12:38:24 -08:00
sneak 15d9439e3d Add fetch/conversion metrics and improve logging
FetchResult now includes:
- StatusCode: HTTP status from upstream
- FetchDurationMs: time to fetch from upstream
- RemoteAddr: upstream server address

SourceMetadata now stores:
- ContentLength: size from upstream
- FetchDurationMs: fetch timing
- RemoteAddr: for debugging

Image conversion log now includes:
- host: source hostname (was missing)
- path: source path (renamed from file)
- convert_ms: image processing time
- quality: requested quality setting
- fit: requested fit mode
2026-01-08 12:34:26 -08:00
sneak 4426387d1c Fix hot cache to include ContentType and SizeBytes
Hot cache entries now store all data needed to serve a cache hit
without any database access:
- OutputHash (for file lookup)
- ContentType (for Content-Type header)
- SizeBytes (for Content-Length header)

Previously hot cache only stored OutputHash, causing empty
Content-Type headers on cached WebP responses.
2026-01-08 12:28:17 -08:00
sneak 51a1ae4a13 Add failing test for hot cache ContentType
Hot cache lookups must return ContentType to serve correct
Content-Type headers. Currently returns empty string.
2026-01-08 12:25:01 -08:00
sneak 10b5cc7063 Fix proportional scaling when single dimension is 0
When only width or height is specified (the other being 0), scale the
image proportionally to maintain aspect ratio. Previously, 0 was passed
directly to the resize function which produced a 0x0 image.
2026-01-08 12:20:58 -08:00
sneak 817d760b4d Add failing tests for proportional scaling
When only one dimension is provided (e.g., width=400, height=0),
the image should scale proportionally. Currently returns 0x0.
2026-01-08 12:20:19 -08:00
sneak 70d55977c0 Add WebP encoding support
Uses github.com/gen2brain/webp - a CGO-free library that uses WASM via
wazero runtime for encoding. WebP decoding was already supported.

- Add gen2brain/webp dependency for encoding
- Implement WebP encoding in processor.go
- Add FormatWebP to SupportedOutputFormats
- Re-enable WebP option in generator form dropdown
- Mark WebP encoding as complete in TODO.md
2026-01-08 11:55:45 -08:00
sneak 0c9eb35bd2 Add failing test for WebP encoding support
TDD: This test expects WebP encoding to succeed. It currently fails
because WebP encoding is not implemented (returns ErrUnsupportedOutputFormat).
The test will pass once we add the gen2brain/webp library.
2026-01-08 11:54:03 -08:00
sneak aab43db44a Add WebP and AVIF encoding support to P0 TODO 2026-01-08 11:12:59 -08:00
sneak 064ab10607 Add no-silent-fallback rule to CLAUDE.md
Never silently fall back to a different setting when a user's parameter
explicitly specifies a value. Return an error for invalid explicit values;
only apply defaults for omitted parameters.
2026-01-08 11:08:33 -08:00
sneak 37af10cc2b Update generator form for supported formats and Never expiry
- Remove WebP and AVIF options (encoding not supported)
- Add GIF option (encoding is supported)
- Add 'Never' TTL option as default
2026-01-08 11:08:28 -08:00
sneak b55b75cbe7 Fix silent fallbacks for unsupported formats and fit modes
- Return ErrUnsupportedOutputFormat for WebP/AVIF encoding
- Return ErrInvalidFitMode for unknown fit mode values
- Add ValidateFitMode() for input validation
- Validate fit mode at handler level before processing

Silent fallbacks violate the principle of least surprise and mask bugs.
When a user explicitly specifies a value, we should either honor it or
return an error - never silently substitute a different value.
2026-01-08 11:08:22 -08:00
sneak df6d347e68 Add tests for unsupported output format errors
Tests verify that WebP and AVIF encoding requests return
ErrUnsupportedOutputFormat instead of silently falling back
to a different format.
2026-01-08 11:08:16 -08:00
sneak 014c144d73 Add 'Never' expiry option for encrypted URLs
- Make ExpiresAt optional in CBOR (omitempty) for smaller tokens
- Treat ExpiresAt=0 as 'never expires' in parser
- URL-encode token with url.PathEscape() for safety
- Add 'Never' as default TTL option in generator form
2026-01-08 11:08:11 -08:00
sneak d9f4e2038e Add manual test script for auth and encrypted URLs
scripts/manual-test.sh tests:
1. Healthcheck endpoint
2. Login page displays
3. Wrong password shows error
4. Correct password shows generator
5. Generate encrypted URL
6. Fetch image via encrypted URL
7. Fetch image via direct proxy (whitelisted)
8. Logout redirects to login
9. Expired URL returns 410
2026-01-08 10:53:02 -08:00
sneak b233871241 Add detailed logging for image conversions on cache miss
Log includes:
- file path
- input/output format
- input/output size in bytes
- input/output dimensions
- size ratio (percentage)

Also adds InputWidth, InputHeight, InputFormat to ProcessResult
2026-01-08 10:44:34 -08:00
sneak 02de534cc2 Reorganize TODO.md: remove completed, prioritize for 1.0
P0 Critical: Manual testing, cache eviction, config validation
P1 Production: Blocked networks, rate limiting, EXIF stripping
P2 Nice to have: Everything else
2026-01-08 10:41:00 -08:00
sneak d8bb374f73 Merge feature/http-response-handling: ETag, HEAD, conditional requests 2026-01-08 10:09:14 -08:00
sneak 774ee97ba1 Update TODO.md: mark HTTP response handling items complete
Completed:
- ETag generation and validation
- Conditional requests (If-None-Match)
- HEAD request support
- Metrics endpoint with auth (already implemented)
2026-01-08 10:09:10 -08:00
sneak 1f809a6fc9 Implement ETag, HEAD requests, and conditional requests
- Add ETag generation based on output content hash (first 16 chars)
- Add ContentLength to ImageResponse from cache
- Add LoadWithSize method to ContentStorage
- Add GetOutputWithSize method to Cache
- Handle HEAD requests returning headers only
- Handle If-None-Match conditional requests returning 304
- Register HEAD route for image proxy endpoint
2026-01-08 10:08:38 -08:00
sneak 4df3e44eff Add failing tests for ETag, HEAD requests, and conditional requests
TDD: Write tests first before implementation for:
- ETag generation and consistency in service layer
- HEAD request support (headers only, no body)
- Conditional requests with If-None-Match header (304 responses)
2026-01-08 10:06:18 -08:00
sneak aed9dd6e8d Merge feature/graceful-shutdown-and-sanitization: Add security headers middleware 2026-01-08 10:02:34 -08:00
sneak 6f423af65d Update TODO.md: mark graceful shutdown and sanitization as complete 2026-01-08 10:02:29 -08:00
sneak 2e349a8b83 Implement security headers middleware
Adds X-Content-Type-Options, X-Frame-Options, Referrer-Policy,
and X-XSS-Protection headers to all responses.
2026-01-08 10:02:17 -08:00
sneak 5de7a26735 Add failing tests for security headers middleware
Tests for X-Content-Type-Options, X-Frame-Options, Referrer-Policy,
X-XSS-Protection headers on responses.
2026-01-08 10:01:36 -08:00
sneak 9592175238 Merge feature/security-validations: Add input dimension and path traversal validation 2026-01-08 08:50:43 -08:00
sneak 90be4e7763 Update TODO.md: mark security validations as complete 2026-01-08 08:50:37 -08:00
sneak 95408e68d4 Implement max input dimensions and path traversal validation
- Reject input images exceeding MaxInputDimension (8192px) to prevent DoS
- Detect path traversal: ../, encoded variants, backslashes, null bytes
2026-01-08 08:50:18 -08:00
sneak c964feac7e Add failing tests for input dimension and path traversal validation
Tests for:
- ErrInputTooLarge when input image exceeds MaxInputDimension
- ErrPathTraversal for ../, encoded traversal, backslashes, null bytes
2026-01-08 08:48:11 -08:00
sneak 857be30e82 Update TODO.md: mark auth/encrypted URLs feature as complete 2026-01-08 08:43:23 -08:00
sneak 06c99b6f4e Merge feature/auth-and-encrypted-urls: Add login, sessions, and encrypted URLs 2026-01-08 07:41:28 -08:00
sneak f601e17812 Add implementation plan for auth and encrypted URLs feature 2026-01-08 07:39:31 -08:00
sneak 6355886dba Add vendoring and repo size guidelines to CLAUDE.md 2026-01-08 07:39:27 -08:00
sneak 2cbafe374c Add mock fetcher and service tests for imgcache
Introduces Fetcher interface, mock implementation for testing,
and ApplyMigrations helper for test database setup.
2026-01-08 07:39:18 -08:00
sneak 1f0ec59eb5 Wire up auth routes and encrypted URL endpoint
Add session manager and encurl generator to handlers.
Register /, /logout, /generate, /v1/e/{token}, /static/* routes.
2026-01-08 07:38:44 -08:00
sneak 08d6e264ed Add auth and encrypted image handlers
Login page, logout, URL generator form, and /v1/e/{token}
endpoint for serving images from encrypted URLs.
2026-01-08 07:38:15 -08:00
sneak aad5e59d23 Add static files and HTML templates for web UI
Embedded Tailwind CSS and login/generator templates.
Self-contained with no external dependencies.
2026-01-08 07:38:09 -08:00
sneak c033e918f0 Add encurl package for encrypted URL tokens
CBOR-encoded payloads with NaCl secretbox encryption.
Supports expiration, image parameters with omitempty defaults.
2026-01-08 07:38:05 -08:00
sneak 041b18f651 Add session package for encrypted cookie management
Uses gorilla/securecookie with keys derived via HKDF.
30-day TTL, HttpOnly, Secure, SameSiteStrict cookies.
2026-01-08 07:37:58 -08:00
sneak 3f4f345d1c Add seal package for authenticated encryption
Provides HKDF-SHA256 key derivation and NaCl secretbox
(XSalsa20-Poly1305) encryption/decryption utilities.
2026-01-08 07:37:53 -08:00
sneak 1e890ffbcc Fix spelling, punctuation, and capitalization in CLAUDE.md 2026-01-08 05:29:08 -08:00
sneak f244d9c7e0 Add per-host connection limits for upstream fetching
- Add upstream_connections_per_host config option (default: 20)
- Implement per-host semaphores to limit concurrent connections
- Semaphore released when response body is closed
- Prevents overwhelming origin servers with parallel requests
2026-01-08 05:19:20 -08:00
sneak 49ff72dfa8 Add bin/ to gitignore 2026-01-08 05:14:11 -08:00
sneak 6304556837 Refactor to serve all responses from cached files on disk
- StoreOutput now returns output hash for immediate retrieval
- Cache misses now serve from disk file after storing (same as hits)
- Log served_bytes from actual io.Copy result (avoids stat calls)
- Remove ContentLength field usage for cache hits (stream from file)
- Fix tests to properly check all return values
2026-01-08 05:11:55 -08:00
sneak 1a97f42cd8 Add detailed logging for image requests with cache status and timing 2026-01-08 05:04:08 -08:00
sneak 6a20406b0f Add -config flag using cobra to specify config file path 2026-01-08 04:58:05 -08:00
sneak 271527679e Add example config file with whitelisted hosts 2026-01-08 04:09:06 -08:00
sneak 9647829ac9 Support YAML list format for whitelist_hosts config 2026-01-08 04:08:51 -08:00
sneak 3fcf9d9146 Add failing test for YAML list format in whitelist_hosts config 2026-01-08 04:08:11 -08:00
sneak cc0fd29954 Update TODO.md with completed image processing items 2026-01-08 04:02:53 -08:00
sneak fd2d108f9c Wire up image handler endpoint with service orchestration
- Add image proxy config options (signing_key, whitelist_hosts, allow_http)
- Create Service to orchestrate cache, fetcher, and processor
- Initialize image service in handlers OnStart hook
- Implement HandleImage with URL parsing, signature validation, cache
- Implement HandleRobotsTxt for search engine prevention
- Parse query params for signature, quality, and fit mode
2026-01-08 04:01:53 -08:00
sneak 5462c9222c Add pure Go image processor with resize and format conversion
Implements the Processor interface using disintegration/imaging library.
Supports JPEG, PNG, GIF, WebP decoding and JPEG, PNG, GIF encoding.
Includes all fit modes: cover, contain, fill, inside, outside.
2026-01-08 03:54:50 -08:00
sneak 9629139989 Add tests for cache service
Tests cover: lookup miss/hit, store source/output, negative caching,
negative cache expiry, hot cache, output retrieval, stats, and cleanup.
2026-01-08 03:39:23 -08:00
sneak 11295db68a Exclude vendor directory from format checks 2026-01-08 03:37:58 -08:00
sneak d9e57de108 Add TDD and commit workflow rules to CLAUDE.md 2026-01-08 03:36:22 -08:00
sneak b14c897408 Update TODO.md with completed caching layer items 2026-01-08 03:36:05 -08:00
sneak 6d32b7ee23 Fix formatting in fetcher and signature tests 2026-01-08 03:36:00 -08:00
sneak 30c5c077e5 Add cache service with hot cache and negative caching
Implements cache lookup with in-memory hot path, source/output storage,
negative caching for failed fetches, TTL expiration, and statistics tracking.
2026-01-08 03:35:55 -08:00
sneak 2f20c71da0 Add content-addressable storage and cache key generation
ContentStorage stores blobs at <dir>/<ab>/<cd>/<sha256> paths.
MetadataStorage stores JSON at <dir>/<host>/<path_hash>.json.
CacheKey generates unique keys from request parameters.
2026-01-08 03:35:50 -08:00
sneak 4595929275 Add embedded SQL migrations system
Migrations are stored in schema/*.sql and embedded via go:embed.
Applied migrations are tracked in schema_migrations table.
Initial schema includes source_content, source_metadata, output_content,
request_cache, negative_cache, and cache_stats tables.
2026-01-08 03:35:43 -08:00
sneak 27eb9fb513 Add HMAC signature specification to README
Documents the signature scheme: HMAC-SHA256 over host:path:query:width:height:format:expiration
with base64url encoding and Unix timestamp expiration.
2026-01-08 03:35:36 -08:00
sneak 30d63e80dc Add magic byte detection for image format validation
Implements format detection by checking file magic bytes for JPEG, PNG,
GIF, WebP, AVIF, and SVG. Includes validation against declared Content-Type.
2026-01-08 03:35:29 -08:00
sneak 9ff44b7e65 Update TODO.md with completed core features 2026-01-08 03:02:24 -08:00
sneak cc2c40bfbf Implement HMAC-SHA256 signature generation and verification 2026-01-08 03:02:09 -08:00
sneak 2b0ccd6052 Implement host whitelist for source domains 2026-01-08 03:00:53 -08:00
sneak 018c280267 Add ParseImagePath for chi wildcard and upstream fetcher with SSRF protection 2026-01-08 02:59:48 -08:00