This document describes the security model of the Secure Wallet API, the controls implemented, and the decisions made following OWASP Top 10 (2025) — the current official version published on November 6, 2025 at the Global AppSec Conference.
| Asset | Threat | Implemented Control |
|---|---|---|
| User credentials | Brute force | Rate limiting + lockout (5 attempts, 30 min) |
| Passwords in DB | Database theft | BCrypt hashing (strength 12) |
| Stolen Access Token | Session hijacking | Short expiration: 15 minutes |
| Stolen Refresh Token | Long-term impersonation | Rotation + revocation stored in DB |
| Sensitive operations | Fraud / unauthorized access | Mandatory 2FA |
| Data in transit | MITM | HTTPS required in production |
| SQL Injection | DB manipulation | Prepared statements via Hibernate |
| Sensitive data in logs | Accidental exposure | Log4j2 — no passwords/tokens logged |
OWASP Top 10 2025 was published on November 6, 2025 at the Global AppSec Conference in Washington D.C. It is the current and official version.
| # | OWASP 2025 Risk | Status | How it is mitigated in this project |
|---|---|---|---|
| A01 | Broken Access Control (includes SSRF) | ✅ | Spring Security + roles (ADMIN, USER). API performs no outbound calls (SSRF N/A) |
| A02 | Cryptographic Failures | ✅ | BCrypt strength 12 + JWT HS256 + HTTPS in production |
| A03 | Software Supply Chain Failures 🆕 | ✅ | OWASP Dependency Check en CI (failOnCVSS ≥ 7) + pinned dependencies in pom.xml |
| A04 | Injection | ✅ | Hibernate prepared statements + Bean Validation on all endpoints |
| A05 | Security Misconfiguration | ✅ | Hidden error messages, no stack traces to client, secure headers |
| A06 | Insecure Design | ✅ | Documented threat modeling, transaction limits, threshold-based 2FA |
| A07 | Identification & Authentication Failures | ✅ | Dual-token JWT (15min/7d) + TOTP 2FA + lockout + 30min inactivity |
| A08 | Software and Data Integrity Failures | ✅ | CodeQL in GitHub Actions, SQL scripts versioned in Git |
| A09 | Security Logging & Monitoring Failures | ✅ | Full auditing in audit_logs, Log4j2, all security events logged |
| A10 | Mishandling of Exceptional Conditions 🆕 | ✅ | Centralized GlobalExceptionHandler never “fail open”, no sensitive data in errors |
POST /auth/login
→ Validate credentials (username + BCrypt password)
→ Verify account is not locked
→ If 2FA required → POST /auth/2fa/verify
→ Generate Access Token (15 min) + Refresh Token (7 days)
→ Register session in sessions table
→ Audit log login event
→ Return tokens to client
POST /auth/refresh
→ Validate Refresh Token (signature + expiration + exists in DB)
→ Rotate Refresh Token (invalidate old one, generate new one)
→ Return new Access Token (15 min) + new Refresh Token
POST /auth/logout
→ Invalidate Refresh Token in DB (sessions table)
→ Audit log logout event
→ Access Token naturally expires within 15 minutes
| Parameter | Value | Reason |
|---|---|---|
| Access Token TTL | 15 minutes | Short window if stolen |
| Refresh Token TTL | 7 days | Security/UX balance |
| Max inactivity | 30 minutes | Banking standard — invalidates session |
| Reauthentication | Required for sensitive operations | See 2FA section |
| Concurrent sessions | Allowed (tracked in DB) | Auditable per device |
A stolen Access Token (via XSS, log leak, etc.) can only be used for 15 minutes. With 24h tokens, an attacker gets nearly a full day of access. For a wallet, 15 minutes is the right balance between security and user experience (refresh is transparent when implemented correctly).
Compatible with Google Authenticator, Authy, and similar apps.
| Scenario | 2FA Required |
|---|---|
| Transfers > $100 | ✅ Required |
| Withdrawals > $100 | ✅ Required |
| Password change | ✅ Required |
| Email change | ✅ Required |
| Administrative operations | ✅ Required |
| Login from new device | |
| Balance inquiry | ❌ Not required |
For an educational/portfolio wallet, $100 reflects better security practices. A $5,000 threshold implies smaller amounts are not important — but in financial security, thresholds must be low to protect everyday transactions.
- HTTPS/TLS required in production
- HTTP allowed only in local development
- BCrypt with strength 12
- Passwords are never stored in plaintext or reversible formats
- Balances and amounts stored as
DECIMAL(19,4)— no rounding - Sensitive fields (e.g., account numbers) considered for column-level encryption using
PostgreSQL (
pgcrypto) — see ADR-007 - Audit logs never store sensitive data (passwords, full tokens)
- Minimum 256-bit entropy
- Stored exclusively as environment variable
JWT_SECRET - Never in source code, never in logs
ddl-autois nevercreateorupdatein any environment- Stack traces never reach the client
- Passwords are never logged or returned in any response
- JWT secret is never hardcoded — always environment variable
- Financial operations always run inside
@Transactional - Every security event generates an entry in
audit_logs - Input validation is required on all endpoints (
@Valid)
This is an academic/portfolio project.
If you find a vulnerability, open an issue with the security label.