Docs·a619bf7·Updated Jun 7, 2026·69 ADRs
Back
ADR-052implemented
ADR-052: Security Hardening — OWASP Top 10 Baseline
ADR-052: Security Hardening — OWASP Top 10 Baseline
Date: 2026-05-10 Status: Implemented Sprint: 54
Context
No security audit had been performed on Karmyq prior to Sprint 54. An OWASP Top 10 review identified vulnerabilities across six categories: SQL injection, broken access control, CORS misconfiguration, missing security headers, JWT token lifetime, and PII in log output.
Decision
Close all identified vulnerabilities in one sprint using targeted fixes at the exact vulnerability layer. No architectural changes — only hardening of existing patterns.
Changes Made
SQL Injection (A03)
- Added
ALLOWED_CLEANUP_TABLESwhitelist tobatchHardDelete()in cleanup-service. The function accepted raw table names interpolated into a SQL query; now throws if the table is not in the allowlist. - Fixed schema typo
community.members→communities.membersin cleanup-service admin auth check (was causing 403s on all admin endpoints).
Broken Access Control (A01)
- Added
authMiddlewareper-route to 8 unauthenticated endpoints in reputation-service:/karma/:userId,/trust/:userId,/trust/:userId/:communityId,/community-trust/:communityId,/leaderboard/:communityId,/history/:userId,/badges/:userId,/users/:userId/badges. - Per-route (not router-level) to avoid breaking internal service-to-service calls.
CORS Misconfiguration (A05)
- Replaced
cors()(origin:*) with an allowlist-based origin check across all 10 services. - Origin list is configured via
ALLOWED_ORIGINSenv var (comma-separated, trimmed). Default:http://localhost:3000. Production:https://karmyq.com.
Security Headers Missing (A05)
- Added
helmet()middleware to all 10 services (9 TypeScript + geocoding-service JavaScript).
JWT Token Lifetime (A07)
- Changed access token lifetime from 7 days to 1 hour.
- Added
auth.refresh_tokenstable with rotation and replay protection. - New
POST /auth/refreshendpoint: accepts refresh token, validates, rotates (marks old as used+revoked, issues new), returns new access token + refresh token. - Replay attack protection: if a refresh token is presented after already being used (
used_at IS NOT NULL), all tokens for that user are revoked. - Frontend: module-level
isRefreshingflag andpendingRequestsqueue to handle concurrent 401s correctly. - Refresh tokens stored in localStorage (consistent with existing access token storage — acceptable risk for demo platform).
PII in Logs (A09)
- Removed email field from all auth failure log entries in auth-service (
warnanderrorcalls on login/register failures).
Consequences
- Existing 7-day sessions are invalidated on deploy (users must re-login). Acceptable for demo env.
- All services now require
ALLOWED_ORIGINSenv var for non-localhost CORS. Set in.env.demoashttps://karmyq.com. - The
POST /auth/refreshendpoint replaces the old access-token-based refresh that was exposed under the same path. Old clients that pass an access token in the body will get a 400 (refreshToken required). - DB migration required on deploy:
infrastructure/postgres/migrations/20260510-refresh-tokens.sql.