# Security Audit: User Preferences API ## Summary **Overall Assessment: PASS** The User Preferences API feature has a small, well-contained attack surface. The implementation follows defensive coding practices: parameterized SQL queries, allowlist-based input validation, and proper error mapping. No critical or high severity findings were identified. Two medium findings and two low/informational findings are noted below. ## Static Analysis Results | Tool | Result | |------|--------| | `go vet ./...` | PASS - no warnings or errors | | `golangci-lint` | Not available in environment (not installed) | ## OWASP Assessment | Category | Status | Notes | |----------|--------|-------| | **A01 - Broken Access Control** | ADVISORY | No authentication or authorization on endpoints. Spec explicitly marks auth as out-of-scope, expecting upstream gateway enforcement. See Medium Finding #1. | | **A02 - Cryptographic Failures** | PASS | No cryptographic operations. No sensitive data stored (theme, language, notifications only). | | **A03 - Injection** | PASS | All SQL queries use parameterized placeholders (`$1`, `$2`, `$3`). JSONB values marshaled by `encoding/json` and passed as parameters. No string concatenation in queries. | | **A04 - Insecure Design** | PASS | Hexagonal architecture with clean separation of concerns. Domain validation via allowlists. Deep merge logic is well-tested. | | **A05 - Security Misconfiguration** | PASS | `.env.example` has `APP_DEBUG=true` and `AUTH_ENABLED=false` but these are example/development defaults, not production config. `.env` files are gitignored. | | **A06 - Vulnerable Components** | PASS | Uses standard library and well-maintained dependencies (`chi`, `uuid`, `sqlx`). No known vulnerabilities in deps used by this feature. | | **A07 - Auth Failures** | ADVISORY | Auth is opt-in and not enabled for preference routes. Intentional per spec - see Medium Finding #1. | | **A08 - Data Integrity** | PASS | All preference values validated against allowlists before persistence. `theme` restricted to `light/dark/system`, `digest` to `daily/weekly/never`, `language` must be non-empty. | | **A09 - Logging & Monitoring** | PASS | Successful updates are logged with `user_id`. Errors bubble through `app.Wrap()` which handles error logging. No PII is logged (user_id is a UUID, not a name/email). | | **A10 - SSRF** | PASS | No outbound HTTP requests. No user-controlled URLs. Service only communicates with PostgreSQL. | ## Critical Findings None. ## High Findings None. ## Medium Findings ### M1: No Authentication or Authorization on Endpoints **Severity:** Medium **Location:** `internal/api/routes.go:29-30` **Description:** Both GET and PUT preference endpoints are publicly accessible without authentication. Any caller can read or modify any user's preferences by knowing their UUID. **Mitigating Factors:** The spec explicitly declares auth as out-of-scope. The design document states this service is a backend store, not a user-facing endpoint, with upstream services/gateways expected to enforce authorization. UUIDs are not guessable. **Recommendation:** When this service is exposed beyond internal networks, add `auth.Middleware()` to the preference route group. Consider adding authorization checks to ensure the authenticated user matches the `user_id` path parameter. ### M2: No Request Body Size Limit Explicitly Configured **Severity:** Medium **Location:** `cmd/server/main.go`, `internal/api/routes.go` **Description:** There is no explicit request body size limit configured for the PUT endpoint. While the framework likely has a default, a very large malicious JSON payload could consume memory. **Mitigating Factors:** The `app.BindAndValidate()` function and the framework's default body limits likely cap this. The strict JSON binding with `validate:"required"` also limits accepted fields. **Recommendation:** Consider adding an explicit `http.MaxBytesReader` or framework-level body size limit (e.g., 64KB would be more than sufficient for preference payloads). ## Low/Informational Findings ### L1: `.env.example` Contains Development Credentials **Severity:** Low **Location:** `.env.example:18,21` **Description:** The `.env.example` file contains `JWT_SECRET=dev-secret-change-in-production` and `DATABASE_URL` with default dev credentials (`dev:dev`). These are clearly marked as development values and `.env` files are properly gitignored. **Mitigating Factors:** This is a `.env.example` template, not an actual `.env` file. The `.gitignore` properly excludes `.env`, `.env.local`, and `*.env`. **Recommendation:** No action required. The naming convention is clear. ### L2: Language Field Validation is Minimal **Severity:** Low **Location:** `internal/domain/preferences.go:58-60` **Description:** The `language` field only validates that it's non-empty. It does not validate against the BCP 47 standard referenced in the spec. While this is not a security vulnerability, it allows arbitrary string values. **Mitigating Factors:** The value is stored in a JSONB column and never used in SQL construction, templates, or URL building. The risk is limited to data quality, not security. **Recommendation:** Consider adding BCP 47 format validation if language values are used in locale-sensitive operations downstream. ## Recommendations 1. **Before production deployment:** Add authentication middleware to the preference route group and implement authorization checks to verify the requesting user matches the `user_id` in the path. 2. **Optional hardening:** Add explicit request body size limits for the PUT endpoint. 3. **Data quality:** Consider BCP 47 validation for the language field if used for locale operations. 4. **No immediate action required** - the current implementation is secure for its intended use as an internal backend service with external auth enforcement.