The sidecar's `docker compose up` command lacked `--pull never`, so
Docker Compose would attempt to pull the caddy image from ghcr.io when
the local image was missing or stale. Since the sidecar has no registry
credentials this failed with 403 Forbidden.
Closes#117
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- C1: Replace all ClickHouse string interpolation with parameterized queries
(query_params) to eliminate SQL injection in analytics endpoints
- C3: Strip Caddy placeholder patterns from redirect rules, protected paths,
and Authentik auth endpoint to prevent config injection
- C4: Replace WAF custom directive blocklist with allowlist approach — only
SecRule/SecAction/SecMarker/SecDefaultAction permitted; block ctl:ruleEngine
and Include directives
- H2: Validate GCM authentication tag is exactly 16 bytes before decryption
- H3: Validate forward auth redirect URIs (scheme, no credentials) to prevent
open redirects
- H4: Switch 11 analytics/WAF/geoip endpoints from session-only requireAdmin
to requireApiAdmin supporting both Bearer token and session auth
- H5: Add input validation for instance-mode (whitelist) and sync-token
(32-char minimum) in settings API
- M1: Add non-root user to l4-port-manager Dockerfile
- M5: Document Caddy admin API binding security rationale
- Document C2 (custom config injection) and H1 (SSRF via upstreams) as
intentional admin features
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- C1: Replace all ClickHouse string interpolation with parameterized queries
(query_params) to eliminate SQL injection in analytics endpoints
- C3: Strip Caddy placeholder patterns from redirect rules, protected paths,
and Authentik auth endpoint to prevent config injection
- C4: Replace WAF custom directive blocklist with allowlist approach — only
SecRule/SecAction/SecMarker/SecDefaultAction permitted; block ctl:ruleEngine
and Include directives
- H2: Validate GCM authentication tag is exactly 16 bytes before decryption
- H3: Validate forward auth redirect URIs (scheme, no credentials) to prevent
open redirects
- H4: Switch 11 analytics/WAF/geoip endpoints from session-only requireAdmin
to requireApiAdmin supporting both Bearer token and session auth
- H5: Add input validation for instance-mode (whitelist) and sync-token
(32-char minimum) in settings API
- M1: Add non-root user to l4-port-manager Dockerfile
- M5: Document Caddy admin API binding security rationale
- Document C2 (custom config injection) and H1 (SSRF via upstreams) as
intentional admin features
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New l4_proxy_hosts table and Drizzle migration (0015)
- Full CRUD model layer with validation, audit logging, and Caddy config
generation (buildL4Servers integrating into buildCaddyDocument)
- Server actions, paginated list page, create/edit/delete dialogs
- L4 port manager sidecar (docker/l4-port-manager) that auto-recreates
the caddy container when port mappings change via a trigger file
- Auto-detects Docker Compose project name from caddy container labels
- Supports both named-volume and bind-mount (COMPOSE_HOST_DIR) deployments
- getL4PortsStatus simplified: status file is sole source of truth,
trigger files deleted after processing to prevent stuck 'Waiting' banner
- Navigation entry added (CableIcon)
- Tests: unit (entrypoint.sh invariants + validation), integration (ports
lifecycle + caddy config), E2E (CRUD + functional routing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>