A comprehensive enterprise security platform designed to prevent vendor fraud through zero-trust principles, segregation of duties, and cryptographic audit trails.
┌─────────────────────────────────────────────────────────────────┐
│ Browser (React 18) │
└────────────────────┬────────────────────────────────────────────┘
│ HTTPS / JWT
┌────────────────────▼────────────────────────────────────────────┐
│ Spring Boot 3.2.x Backend (Java 17) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Security Layer │ │
│ │ • JWT Authentication with 20-min TTL │ │
│ │ • Role-Based Access Control (RBAC) │ │
│ │ • Rate Limiting (Redis-backed) │ │
│ │ • CORS Configuration │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Core Services │ │
│ │ • Vendor Management │ │
│ │ • Change Request Workflow │ │
│ │ • Out-of-Band Verification │ │
│ │ • Audit & Compliance │ │
│ │ • Security Alerts & Monitoring │ │
│ └──────────────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────────────┘
┌───────────┼───────────┬──────────────┐
│ │ │ │
┌────▼───┐ ┌────▼────┐ ┌───▼──────┐ ┌────▼────┐
│ SQL │ │ Redis │ │ Mailpit │ │Prometheus
│Server │ │ Cache │ │(SMTP) │ │Grafana
│(Data) │ │ │ │ │ │
└────────┘ └─────────┘ └──────────┘ └─────────┘
| Threat | Defense | Implementation |
|---|---|---|
| T1: Compromised AP Clerk Account | Role separation, JWT with short TTL + revocation, out-of-band verification, login rate limiting, audit log | SecurityConfig, JwtTokenProvider, RateLimitService, AuthService, AuditService |
| T2: Insider Collusion / Self-Approval | DB CHECK constraint + app-layer 403 + @PreAuthorize + SECURITY_ALERT | ChangeRequestService, ChangeRequestController, VendorChangeRequest model |
| T3: Social Engineering / BEC | Verification code (HMAC-hashed, attempt-limited) sent to PRE-CHANGE contact only, plus mandatory cooling-off | NotificationService, ChangeRequestService (Phase 3) |
| T4: Direct DB Tampering | Hash-chained audit log using server-computed epoch-ms timestamps | AuditService, AdminController verify-chain endpoint |
- Backend: Java 17 + Spring Boot 3.2.x + Spring Security
- Database: Microsoft SQL Server 2022
- Cache/Session: Redis 7.x
- Migrations: Flyway (SQL Server dialect)
- Auth: JWT (jjwt 0.12.x), HS256, 20-min TTL, Redis revocation
- Encryption: AES-256-GCM for bank account data
- GeoIP: MaxMind GeoLite2-City.mmdb
- Mail: Mailpit (local SMTP for dev)
- Observability: Prometheus + Micrometer + Grafana
- Frontend: React 18 + Vite + Tailwind CSS
- Containers: Docker Compose
- Testing: Testcontainers + JUnit5
- Docker & Docker Compose
- Node.js 18+
- Java 17 SDK (for local development)
- Git
- MaxMind GeoLite2-City database (free tier)
git clone https://github.com/your-org/argus.git
cd argus
cp .env.template .env# JWT Secret (HS256 key - 32 bytes)
openssl rand -hex 32
# Copy output to JWT_SECRET in .env
# Encryption Key (AES-256 - 32 bytes, base64)
openssl rand -base64 32
# Copy output to ENCRYPTION_KEY in .env
# Verify Code Pepper (32 bytes)
openssl rand -hex 32
# Copy output to VERIFY_CODE_PEPPER in .env- Create free account at https://www.maxmind.com/en/geolite2/geolite2-city
- Generate license key in Account > Manage License Keys
- Download
GeoLite2-City.mmdb - Place at
./geoip/GeoLite2-City.mmdb
Fill in database password and other required variables:
DB_PASSWORD=YourSQLServerPassword123!
GRAFANA_PASSWORD=YourGrafanaPassword
# ... other secrets from steps 2-3docker compose up -dThis starts:
- SQL Server (port 1433)
- Redis (port 6379)
- Mailpit UI (http://localhost:8025)
- Prometheus (http://localhost:9090)
- Grafana (http://localhost:3001)
- Backend API (http://localhost:8080)
- Frontend UI (http://localhost:5173)
# SQL Server ready?
curl http://localhost:8080/actuator/health
# All services up?
docker compose ps| Username | Password | Role | Notes |
|---|---|---|---|
| admin | ArgusAdmin2024! | ADMIN | Must change password on first login |
| ap_clerk1 | ClerkPass2024! | AP_CLERK | Ready to use |
| approver1 | ApproverPass2024! | APPROVER | Ready to use |
Swagger UI available at: http://localhost:8080/swagger-ui.html
POST /api/auth/login— Authenticate user (returns JWT)POST /api/auth/logout— Revoke token (add to blacklist)POST /api/auth/change-password— Change user password
GET /api/vendors— List all vendorsGET /api/vendors/{id}— Get vendor (bank account encrypted, never plaintext)POST /api/vendors— Create vendor (ADMIN only)PUT /api/vendors/{id}— Update vendor (ADMIN only)DELETE /api/vendors/{id}— Soft delete vendor (ADMIN only)
POST /api/change-requests— Submit change request (AP_CLERK/ADMIN)GET /api/change-requests— List pending requests (APPROVER/ADMIN)POST /api/change-requests/{id}/approve— Approve (APPROVER/ADMIN, checks SoD)POST /api/change-requests/{id}/reject— Reject (APPROVER/ADMIN)
POST /api/vendors/{id}/change-requests— Submit bank account change request (AP_CLERK)GET /api/vendors/{id}/change-requests— List change requests for vendorGET /api/change-requests?status=X— List change requests filtered by statusPOST /api/change-requests/{id}/verify— Verify out-of-band code (AP_CLERK)POST /api/change-requests/{id}/approve— Approve change request (APPROVER, SoD enforced)POST /api/change-requests/{id}/reject— Reject change request (APPROVER)
GET /api/admin/ping— Health check (ADMIN only)GET /api/admin/audit?limit=N— Retrieve recent audit log entries (ADMIN only, default limit 100)GET /api/admin/audit/verify-chain— Verify audit log chain integrityPOST /api/admin/users/{id}/revoke-sessions— Revoke all sessions for user
GET /api/alerts— All security alerts (ADMIN only)GET /api/alerts/unresolved— Unresolved alerts onlyPOST /api/alerts/{id}/resolve— Mark alert as resolved
All tables in database ArgusDB. See backend/src/main/resources/db/migration/ for Flyway migrations.
USERS— User accounts with rolesVENDORS— Vendor master data (encrypted bank accounts)VENDOR_CHANGE_REQUESTS— Change request workflowAUDIT_LOG— Hash-chained tamper-evident logSECURITY_ALERTS— Security incidentsHONEYTOKEN_VENDORS— Decoy vendorsNOTIFICATIONS— Email/SMS logs
- JWT with 20-minute TTL
- Revocation via Redis blacklist keyed by
jticlaim - Double enforcement: SecurityConfig + @PreAuthorize
- All tokens checked against blacklist on EVERY request
- DB Constraint:
CHECK (approved_by IS NULL OR approved_by <> requested_by) - App Layer: Explicit if-statement in
ChangeRequestService.approve() - Security Alert: Auto-logs attempt to
SECURITY_ALERTStable - HTTP Status: Returns 403 Forbidden (never approves, regardless of other state)
- 6-digit code sent to vendor's CURRENT contact email (pre-change)
- Code hashed with HMAC-SHA256 + server pepper (not plain SHA-256)
- Max 5 verification attempts, then auto-EXPIRED + alert
- Mandatory 24-hour cooling-off period after verification
- Approver cannot approve until both verified AND cooling-off elapsed
- Hash-chained using
entry_hash = SHA256(entityType|entityId|action|userId|timestamp_ms|payloadHash|prevHash) - Critical: Uses
timestamp_ms(BIGINT epoch milliseconds), never DATETIME2 (driver rounding breaks chain) - First entry genesis:
prevHash = "0"×64 - Any row modification breaks every subsequent hash
- Admin endpoint
/api/admin/audit/verify-chainwalks chain and reports first broken entry
- Login: keyed by (IP + username), max 5 attempts / 10 min → 429 + BRUTE_FORCE_ATTEMPT alert
- General endpoints: keyed by (userId + endpoint), configurable, default 20 req/min
- Redis-backed sliding window
- Bank account numbers: AES-256-GCM with unique IV per record
- Stored format:
base64(iv) : base64(ciphertext) - Key from
ENCRYPTION_KEYenv var (base64, 32 bytes) - Never returned in API responses — queries always return encrypted
- 2 seeded "Decoy Vendor Alpha" and "Decoy Vendor Beta"
- Accessing outside maintenance window (02:00-03:00) → HONEYTOKEN_TRIGGERED alert
- Silent alerting: API returns normal 200 response (attacker doesn't know they tripped trap)
- Velocity Check: >5 change requests per vendor in 10 min → VELOCITY_ANOMALY alert
- Impossible Travel: >900 km/h required speed between logins → IMPOSSIBLE_TRAVEL alert
- Server-side GeoIP: Location resolved from request IP via MaxMind (never client-supplied)
- Dev Override (GEOIP_TEST_IP_HEADER_ENABLED=true, dev-only, must be false in prod):
- Allows X-Debug-Test-IP header to override resolved IP
- Logs WARNING and creates IMPOSSIBLE_TRAVEL alert every time used
- Ensures dev-mode geolocation is never silently forgotten in production
✅ Docker Compose stack
✅ Spring Boot skeleton + SecurityConfig
✅ Flyway migrations + seed users
✅ JWT + Redis revocation
✅ Rate limiting
✅ Audit service (hash-chained)
✅ Alert system
✅ Login / Logout / Change Password endpoints
✅ Admin ping & verify-chain endpoints
- ✅ Vendor CRUD endpoints (POST/GET/PUT/DELETE)
- ✅ Change request submission
- ✅ Self-approval blocking (403 + alert + audit)
- ✅ DB CHECK constraint enforcement
- ✅ 6-digit code generation & HMAC hashing
- ✅ Email notification via Mailpit
- ✅ Verify endpoint with attempt limiting
- ✅ Cooling-off period enforcement
- ✅ Status transitions (PENDING → VERIFICATION_SENT → VERIFIED)
- ✅ Hash-chained audit entries
- ✅ Chain integrity verification
- ✅ Admin dashboard showing audit trail
- ✅ Honeytoken seeding
- ✅ Access detection & silent alerting
- ✅ Velocity anomaly detection
- ✅ Impossible travel with MaxMind GeoIP
- ✅ Dev-mode IP override (X-Debug-Test-IP header)
- ✅ General-purpose rate limiting on all endpoints
- ✅ Micrometer custom metrics (alerts, pending requests, chain status)
- ✅ Grafana dashboard with live data
- ✅ Automated JUnit5 + Testcontainers integration test suite
- ✅ Login page (force password change on first login)
- ✅ Clerk Dashboard (vendor management, change requests)
- ✅ Approver Dashboard (approval queue, red banner for 403 attempts)
- ✅ Admin Dashboard (audit trail, alerts, honeytoken management)
- ✅ Fully wired to real backend
cd backend
mvn clean testTests use Testcontainers to spin up real SQL Server & Redis. All security scenarios validated:
- Login rate limiting
- JWT revocation
- Self-approval rejection
- Verify-code brute-force lockout
- Chain-tamper detection
- Honeytoken trigger
- Velocity anomaly
- Impossible travel (with dev IP override)
| Service | URL |
|---|---|
| Frontend | http://localhost:5173 |
| Swagger API Docs | http://localhost:8080/swagger-ui.html |
| Prometheus Metrics | http://localhost:9090 |
| Grafana Dashboard | http://localhost:3001 (admin / $GRAFANA_PASSWORD) |
| Mailpit Inbox | http://localhost:8025 |
| SQL Server | localhost:1433 (use SQL Server Management Studio) |
- Download SSMS: https://learn.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms
- Open SSMS
- Connect:
- Server name:
localhostorlocalhost,1433 - Authentication: SQL Server Authentication
- Login:
sa - Password:
[Your DB_PASSWORD from .env]
- Server name:
- Browse:
- Expand Databases
- Select ArgusDB
- Expand Tables
- Right-click table → Edit Top 200 Rows to view data
# Inside Docker container
docker exec -it argus-sqlserver-1 /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P YourPassword
# Then run SQL:
> SELECT * FROM ArgusDB.dbo.USERS;
> SELECT * FROM ArgusDB.dbo.SECURITY_ALERTS WHERE resolved = 0;
> GO- Download: https://learn.microsoft.com/en-us/sql/azure-data-studio/download-azure-data-studio
- Create new connection: Servers → Create a connection
- Same credentials as SSMS above
- Browse databases and run queries
| Variable | Description | Example |
|---|---|---|
DB_HOST |
SQL Server hostname | sqlserver |
DB_PORT |
SQL Server port | 1433 |
DB_NAME |
Database name | ArgusDB |
DB_USER |
Database user | sa |
DB_PASSWORD |
Database password | (strong password) |
JWT_SECRET |
HS256 key (32 bytes, hex) | openssl rand -hex 32 |
ENCRYPTION_KEY |
AES-256 key (32 bytes, base64) | openssl rand -base64 32 |
VERIFY_CODE_PEPPER |
HMAC pepper (32 bytes, hex) | openssl rand -hex 32 |
CORS_ALLOWED_ORIGINS |
Allowed frontend origins | http://localhost:5173 |
COOLING_OFF_HOURS |
Hours before approval allowed | 24 |
ARGUS_VELOCITY_MAX_REQUESTS |
Max requests per window | 5 |
ARGUS_VELOCITY_WINDOW_MINUTES |
Velocity check window | 10 |
ARGUS_HONEYTOKEN_MAINTENANCE_START |
Honeytoken maintenance start | 02:00 |
ARGUS_HONEYTOKEN_MAINTENANCE_END |
Honeytoken maintenance end | 03:00 |
ARGUS_RATE_LIMIT_REQUESTS_PER_MINUTE |
General rate limit | 20 |
GEOIP_DB_PATH |
Path to MaxMind .mmdb | /geoip/GeoLite2-City.mmdb |
GEOIP_TEST_IP_HEADER_ENABLED |
Allow X-Debug-Test-IP (DEV ONLY) | false |
MAILPIT_SMTP_HOST |
Mail server hostname | mailpit |
MAILPIT_SMTP_PORT |
Mail server port | 1025 |
GRAFANA_PASSWORD |
Grafana admin password | (strong password) |
All logs are structured JSON via Logback and sent to:
- Console: For immediate debugging
- File:
logs/argus.log(rotated daily, max 100MB per file, max 30 days retention)
Security: VERIFY_CODE_PEPPER, JWT_SECRET, ENCRYPTION_KEY, and DB_PASSWORD are NEVER logged, even at DEBUG level.
- Cause: Database not ready yet
- Fix: Wait 30-60 seconds for SQL Server to initialize. Check:
docker logs argus-sqlserver-1
- Cause: Flyway migrations haven't run
- Fix: Check backend logs:
docker logs argus-backend-1. Migrations run automatically on startup.
- Cause: JWT expired or blacklisted
- Fix: Login again to get fresh token
- Cause: MaxMind .mmdb not placed in
./geoip/ - Fix: See "MaxMind GeoIP Setup" section above. Phase 5 requires this.
- Generate strong random secrets for all env vars (don't use demo values)
- Set
GEOIP_TEST_IP_HEADER_ENABLED=false(MUST be false) - Use managed SQL Server (don't expose to public internet)
- Use managed Redis with TLS (don't expose)
- Enable HTTPS/TLS on API and frontend
- Implement log aggregation (ELK, Splunk, etc.)
- Set up monitoring & alerting for security events
- Regular backups of SQL Server
- Implement WAF (Web Application Firewall)
- Disable demo user accounts (admin, ap_clerk1, approver1)
- Review and customize security policies per organization
- Penetration testing & security audit
Decision: Use SHA-256 hash chain with timestamp_ms (BIGINT epoch-ms), not DATETIME2.
Rationale: JDBC drivers round DATETIME2 to variable precision, breaking chain integrity detection. Epoch-ms is immutable and driver-independent.
Decision: Always resolve location from request IP via MaxMind, never accept client-supplied coordinates.
Rationale: Clients can lie about their location (e.g., VPN, proxy, browser spoofing). Server-side resolution ensures integrity of impossible-travel detection.
Decision: Hash codes with HMAC-SHA256 + pepper, not plain SHA-256.
Rationale: If code hash is compromised and database leaked, plain SHA-256 allows offline dictionary attacks (6-digit codes have only ~1M possibilities). HMAC with unknown pepper prevents this.
Decision: Honeytoken access triggers alert but returns HTTP 200 (no error).
Rationale: If honeytokens returned errors, attackers would know they tripped a trap and could adapt. Silent alerting maximizes intelligence value.
Proprietary - Argus Platform
All rights reserved.
For issues, questions, or security concerns:
- Check Common Issues section
- Review backend logs:
docker logs argus-backend-1 - Review frontend logs: Browser DevTools (F12)
- Contact security team for vulnerabilities (DO NOT open public issues)
Current Phase: All Phases Complete ✅
Last Updated: July 2026
Status: Ready for End-to-End Testing