# Agent

Name: Agent 07 - Security And Deployment Hardening

Stage: Security And Deployment Hardening

Date: 2026-04-15

## Scope

Hardened the existing Go/chi/SQLite application for internet-facing deployment without changing the product model or admin/client flow.

Completed in this stage:

- added stricter HTTP server defaults:
  - `ReadHeaderTimeout`
  - `MaxHeaderBytes`
  - higher default read/write/idle timeouts suitable for uploads and authenticated downloads
- added global security headers and route-specific cache controls:
  - `Content-Security-Policy`
  - `X-Frame-Options`
  - `X-Content-Type-Options`
  - `Referrer-Policy`
  - `Permissions-Policy`
  - `Cross-Origin-Opener-Policy`
  - `Cross-Origin-Resource-Policy`
  - `Strict-Transport-Security` when `APP_BASE_URL` is `https`
  - `Cache-Control: no-store` on admin and protected client API responses
  - `Vary: Cookie` or `Vary: Authorization` on the appropriate routes
- added trusted-proxy awareness so forwarded headers are only trusted when `TRUST_PROXY_HEADERS=true`;
- added in-memory rate limiting for:
  - `POST /admin/login`
  - protected client API routes under `/api/v1`
- added CSRF protection for all admin POST flows, including login, logout, project/tag/API key forms, tag attach or detach, archive or restore, and multipart release upload;
- rotated or cleared the admin CSRF cookie on login and logout, and scoped both session and CSRF cookies to `/admin`;
- tightened local data directory permissions for SQLite and data storage;
- added regression coverage for CSRF rejection, hardened cookie defaults, security headers, and both rate-limited route groups;
- added first-pass deployment assets:
  - multi-stage `Dockerfile`
  - hardened `docker-compose.yml`
  - Caddy example
  - Nginx example
  - env example
  - deployment, Proxmox, backup, and restore notes.

## Files Changed

- `/Users/delete/projects/update_server/internal/config/config.go`
- `/Users/delete/projects/update_server/internal/app/app.go`
- `/Users/delete/projects/update_server/internal/db/open.go`
- `/Users/delete/projects/update_server/internal/auth/service.go`
- `/Users/delete/projects/update_server/internal/http/router.go`
- `/Users/delete/projects/update_server/internal/http/security.go`
- `/Users/delete/projects/update_server/internal/http/rate_limit.go`
- `/Users/delete/projects/update_server/internal/http/csrf.go`
- `/Users/delete/projects/update_server/internal/http/auth_handlers.go`
- `/Users/delete/projects/update_server/internal/http/admin_common.go`
- `/Users/delete/projects/update_server/internal/http/admin_api_keys.go`
- `/Users/delete/projects/update_server/internal/http/view_data.go`
- `/Users/delete/projects/update_server/internal/http/auth_integration_test.go`
- `/Users/delete/projects/update_server/internal/http/projects_integration_test.go`
- `/Users/delete/projects/update_server/internal/http/api_keys_integration_test.go`
- `/Users/delete/projects/update_server/internal/http/security_integration_test.go`
- `/Users/delete/projects/update_server/web/templates/layouts/base.gohtml`
- `/Users/delete/projects/update_server/web/templates/pages/login.gohtml`
- `/Users/delete/projects/update_server/web/templates/pages/project_form.gohtml`
- `/Users/delete/projects/update_server/web/templates/pages/project_detail.gohtml`
- `/Users/delete/projects/update_server/web/templates/pages/tag_form.gohtml`
- `/Users/delete/projects/update_server/web/templates/pages/api_key_form.gohtml`
- `/Users/delete/projects/update_server/web/templates/partials/csrf_field.gohtml`
- `/Users/delete/projects/update_server/.dockerignore`
- `/Users/delete/projects/update_server/Dockerfile`
- `/Users/delete/projects/update_server/docker-compose.yml`
- `/Users/delete/projects/update_server/deploy/Caddyfile.example`
- `/Users/delete/projects/update_server/deploy/nginx.update-server.conf.example`
- `/Users/delete/projects/update_server/deploy/update-server.env.example`
- `/Users/delete/projects/update_server/docs/DEPLOYMENT.md`
- `/Users/delete/projects/update_server/docs/agents/handoffs/07-security-deploy.md`

## Database Changes

- no migrations or schema changes were required;
- SQLite remains the production database;
- deployment notes now explicitly call out WAL-mode backup and restore handling for:
  - `db.sqlite`
  - `db.sqlite-wal`
  - `db.sqlite-shm`
  - `artifacts/`.

## API Or Route Changes

- no new product endpoints were added;
- middleware behavior changed for existing routes:
  - all `/admin` responses now emit hardened headers and `no-store` caching rules;
  - all `/admin` POST routes now require a valid CSRF token;
  - `POST /admin/login` is rate limited by client IP;
  - protected `/api/v1` routes now emit hardened headers and `no-store` caching rules;
  - protected `/api/v1` routes are rate limited by client IP before API-key auth;
- session cookies are now scoped to `/admin` and retain `HttpOnly`, `SameSite=Lax`, and conditional `Secure`;
- CSRF cookies are now scoped to `/admin` and use `HttpOnly`, `SameSite=Strict`, and conditional `Secure`.

## Commands And Tests Run

- `gofmt -w internal/config/config.go internal/app/app.go internal/db/open.go internal/auth/service.go internal/http/router.go internal/http/security.go internal/http/rate_limit.go internal/http/csrf.go internal/http/auth_handlers.go internal/http/view_data.go internal/http/admin_common.go internal/http/admin_api_keys.go internal/http/auth_integration_test.go internal/http/projects_integration_test.go internal/http/api_keys_integration_test.go internal/http/security_integration_test.go` - passed
- `go test ./internal/http` - passed
- `go test ./...` - passed
- `go build ./cmd/server` - passed
- `go build ./cmd/migrate` - passed
- `docker compose config` - could not run in this environment because `docker` is not installed

## Known Limitations

- rate limiting is in-memory and per-process, so counters reset on restart and are not shared across multiple app instances;
- Docker and reverse-proxy examples were added and reviewed statically, but they were not live-validated here because Docker is unavailable in this workspace;
- `Strict-Transport-Security` only appears when `APP_BASE_URL` is configured with `https`, which is the intended production setup behind TLS termination;
- the proxy still must be configured correctly to keep the app private and to strip or control forwarded headers before `TRUST_PROXY_HEADERS=true` is safe.

## Recommended Next Step

Run the Agent 07 validation pass, with special attention to:

- CSRF enforcement on every admin POST flow, including multipart upload;
- security headers and private-cache headers on admin and protected client API responses;
- login and client API rate limiting behavior;
- cookie flags and `/admin` cookie scoping;
- deployment docs and example proxy/container files on a host that has Docker and a reverse proxy available.

## Notes For Validator

- verify that a missing or invalid admin CSRF token returns `403` and that the normal admin flows still succeed when the token is present;
- verify that session cookies are only scoped to `/admin` and that secure-cookie behavior follows the configured `APP_BASE_URL` scheme;
- verify that `TRUST_PROXY_HEADERS=false` leaves client IP handling on the socket remote address;
- verify that protected `/api/v1` responses include `Cache-Control: no-store`, `Vary: Authorization`, and the shared security headers;
- if possible, validate `docker compose up --build` plus one reverse-proxy example on a real machine, since that could not be executed in this environment.
