Skip to main content

Security — Topic Index

Where every security concern lives, in code and in docs. This page is a map; the substance is in authentication.md (tokens, refresh, lockout, ALTCHA, RBAC), request-pipeline.md (the layered request defense, JWT internals, password hashing, headers), and hardening.md (validation, uploads, secrets, production checklist). The system-wide context is in ../architecture.md.

Threat model in one paragraph

The app is an internet-facing ERP holding payroll and HR data, deployed as one isolated stack per client. What it actively defends against: credential attacks (per-IP rate limiting, exponential account lockout, ALTCHA proof-of-work in production), token theft (15-minute access tokens paired with 14-day rotating refresh tokens with family reuse-detection), privilege escalation (every endpoint authorizes server-side via @PreAuthorize against DB-resolved permissions — the frontend's activeRole switcher is a UI lens only, never a security boundary), XSS/clickjacking (CSP, X-Frame-Options: DENY at both backend and nginx), secret leakage (env-only secrets, gitleaks in CI, fail-fast startup validation), and hostile uploads (global multipart size caps). Accepted tradeoffs, stated plainly: tokens live in localStorage (an XSS that defeats CSP could read them), rate-limit and ALTCHA-replay state are in-memory (fine for the one-backend-per-client deployment model), and access tokens cannot be revoked before their 15-minute expiry.

Concern → code → doc

ConcernEnforced in codeCovered in
Authentication (JWT)JwtTokenManager.java, JwtAuthenticationFilter.java, AuthController.javaauthentication.md §Login; request-pipeline.md §The JWT in Detail
Authorization (RBAC / @PreAuthorize)SecurityConfiguration.java (@EnableMethodSecurity), PermissionConstants.java, UserDetailsServiceImpl.javaauthentication.md §Method-Level Authorization + §RBAC Data Model; request-pipeline.md §Layer 4
Password storage (Argon2id + BCrypt rehash)PasswordEncoderConfiguration.javaArgon2PasswordEncoder(16, 32, 1, 19456, 2), legacy BCrypt transparently re-hashed on loginrequest-pipeline.md §Password Security
Refresh tokens & revocationRefreshTokenService.java, V75__auth_hardening.sqlauthentication.md §Refresh & Logout
Rate limiting (Bucket4j)LoginRateLimitFilter.java — 10 req/min per IP+path, HTTP 429authentication.md §Account Lockout, Rate Limiting & ALTCHA
CAPTCHA (ALTCHA proof-of-work)AltchaVerificationFilter.java, AltchaService.java, AltchaProperties.java; widget: altcha-captcha.tsxauthentication.md §Account Lockout, Rate Limiting & ALTCHA
Account lockoutLoginAttemptService.java — 5 failures → 15 min, doubling to a 24 h cap, HTTP 423authentication.md §Account Lockout, Rate Limiting & ALTCHA
CORSSecurityConfiguration.java corsConfigurationSource() — env-driven allowlist (CORS_ALLOWED_ORIGINS), never *request-pipeline.md §Layer 1
CSRF (disabled — deliberately)SecurityConfiguration.java .csrf(csrf -> csrf.disable()) — auth is Authorization: Bearer, not cookies, so the browser never auto-attaches credentialsrequest-pipeline.md §Best Practices #2
Input validation@Valid + Jakarta constraints on request DTOs; GlobalControllerAdvice.java maps violations to 400; Zod client-side is UX-onlyhardening.md §Input validation
File-upload limitsapplication.yml — multipart max-file-size: 10MB / max-request-size: 12MB; upload endpoints in JobApplicantController.java and GovernmentFilingController.javahardening.md §File-upload restrictions
Security headers (backend + nginx)SecurityConfiguration.java .headers(...); frontend/nginx.conf per-location add_headerhardening.md §Security headers; request-pipeline.md §Security Headers
Secrets managementEnv vars only; JwtProperties.java validateSecret() fail-fast; onboard-client.sh per-client openssl rand; gitleaks.yml CI scanhardening.md §Secrets management
Audit loggingAuditService.javauser_log table; AuditInterceptor.java records 403 denials; auth events Auth.login, Auth.login.failed, Auth.login.lockedOut, Auth.refreshTokenReuseDetectedauthentication.md §Refresh & Logout (reuse detection); audit surface itself is UI-browsable via AuditLogController.java
WebSocket authWebSocketAuthChannelInterceptor.java — validates the JWT from the STOMP CONNECT frame's Authorization header; connection refused otherwiseNot covered in a dedicated doc — the interceptor is the whole story: same JwtTokenManager validation as HTTP, applied at CONNECT time
Actuator surfaceapplication.yml exposes only health,prometheus; each listed individually in PUBLIC_ENDPOINTS (never /actuator/**)hardening.md §Actuator surface
Production checklistdocker-compose.client.template.yml (Swagger off, ALTCHA on), onboard-client.shhardening.md §Production checklist; ../deployment/vps-guide.md