Attack Detection & Response¶
4pass combines edge-level protection (AWS WAF) with application-level detection (rate limiting, audit logging, email alerts) to identify and respond to attacks in real time. WAF handles volumetric and known-pattern attacks at the network edge; the application classifies and alerts on business-logic attacks that WAF cannot detect.
WAF Rules¶
The AWS WAFv2 Web ACL is associated with the Application Load Balancer and evaluates rules in priority order. The first matching rule determines the action.
| Priority | Rule Name | Action | Statement | Targets | Purpose |
|---|---|---|---|---|---|
| 1 | Admin IP Restriction | Block | NOT in admin_ips IP set AND path matches /setup/*, /deployment/* |
Infrastructure endpoints | Prevents unauthorized access to admin/migration endpoints |
| 2 | TradingView Webhook Allow | Allow | In tradingview_ips IP set AND path matches /webhook/* |
Webhook endpoints | Exempts TradingView IPs from rate limits (shared IPs serve all users) |
| 3 | Login Rate Limit | Block | Rate-based: 100 req/5min per IP | /auth/login, /auth/register, /auth/forgot-password |
Prevents credential stuffing and brute force |
| 4 | Blanket Rate Limit | Block | Rate-based: 2,000 req/5min per IP | All requests | DDoS protection; scales to 10K at 10K users (waf_rate_limit variable) |
| 5 | AWS Common Rule Set | Block/Count | Managed rule group: AWSManagedRulesCommonRuleSet |
All requests | SQL injection, XSS, path traversal, file inclusion |
| 6 | AWS Known Bad Inputs | Block | Managed rule group: AWSManagedRulesKnownBadInputsRuleSet |
All requests | Log4j, Java deserialization, known exploit payloads |
| 7 | AWS IP Reputation | Block | Managed rule group: AWSManagedRulesAmazonIpReputationList |
All requests | Botnets, Tor exit nodes, known malicious IPs |
flowchart TB
REQ["Incoming Request"] --> R1{"Priority 1<br/>Admin IP<br/>Restriction"}
R1 -->|"Admin path + wrong IP"| BLOCK1["BLOCK"]
R1 -->|"Pass"| R2{"Priority 2<br/>TradingView<br/>Webhook Allow"}
R2 -->|"TV IP + /webhook/*"| ALLOW["ALLOW<br/>(skip rate limits)"]
R2 -->|"Pass"| R3{"Priority 3<br/>Login<br/>Rate Limit"}
R3 -->|">100 req/5min"| BLOCK3["BLOCK"]
R3 -->|"Pass"| R4{"Priority 4<br/>Blanket<br/>Rate Limit"}
R4 -->|">2000 req/5min"| BLOCK4["BLOCK"]
R4 -->|"Pass"| R5{"Priority 5-7<br/>AWS Managed<br/>Rules"}
R5 -->|"SQLi/XSS/CVE"| BLOCK5["BLOCK"]
R5 -->|"Pass"| APP["Application"]
TradingView IP Exemption
TradingView sends webhooks for all users from a shared set of IPs. If these IPs hit the blanket rate limit, legitimate orders for all users would be blocked. Rule 2 exempts these IPs from WAF rate limits. Per-user abuse is handled by the application-level rate limiter instead (60 req/min per user).
AWS Managed Rule Overrides¶
The Common Rule Set includes rules that may produce false positives for trading payloads. Specific rules are set to Count mode instead of Block:
| Rule | Override | Reason |
|---|---|---|
SizeRestrictions_BODY |
Count | Trading payloads may exceed default body size limits |
CrossSiteScripting_BODY |
Count | JSON payloads with special characters trigger false positives |
GenericRFI_BODY |
Count | URL-like strings in trading configuration fields |
All overridden rules still log to CloudWatch for manual review.
Application-Level Rate Limiting¶
The application implements per-user rate limiting using a Redis sorted set sliding window, complementing WAF's per-IP approach.
| Property | Value |
|---|---|
| Algorithm | Sorted set sliding window (ZADD + ZREMRANGEBYSCORE + ZCARD) |
| Key format | ratelimit:{user_id} |
| Window | 60 seconds |
| Default limit | 60 requests per minute |
| Failure mode | Fail-open — Redis errors pass through silently |
| Applied to | Webhook and trading endpoints |
flowchart LR
REQ["Request<br/>(user_id=42)"] --> CLEAN["ZREMRANGEBYSCORE<br/>remove entries<br/>older than 60s"]
CLEAN --> ADD["ZADD<br/>timestamp:uuid"]
ADD --> COUNT["ZCARD<br/>count entries<br/>in window"]
COUNT --> CHECK{"Count ><br/>60?"}
CHECK -->|"Yes"| REJECT["429 Too Many<br/>Requests"]
CHECK -->|"No"| PASS["Proceed"]
PASS --> EXPIRE["EXPIRE key<br/>65s TTL"]
Why both WAF and application rate limiting?
| Dimension | WAF (Edge) | Application |
|---|---|---|
| Key | Per-IP | Per-user (authenticated) |
| Layer | Network edge (before app) | Application (after auth) |
| Scope | All requests | Webhook + trading only |
| Purpose | DDoS, volumetric attacks | Per-user abuse, runaway bots |
| Failure mode | Hard block | Fail-open (never blocks trading) |
Attack Types Detected¶
The application classifies attacks by type and responds with appropriate HTTP status codes, audit events, and email alerts.
| Attack Type | Trigger | HTTP Status | Email Alert | Audit Action |
|---|---|---|---|---|
webhook_ip_blocked |
Request IP not in user's whitelist | 403 Forbidden |
Yes Immediate | Yes |
webhook_timestamp_expired |
Request timestamp beyond max_request_age |
401 Unauthorized |
Yes Immediate | Yes |
webhook_credential_attack |
Invalid webhook secret (constant-time comparison) | 401 Unauthorized |
Yes Immediate | Yes |
webhook_replay_attack |
Duplicate request hash found in Redis | 409 Conflict |
Yes Immediate | Yes |
webhook_payload_invalid |
Malformed or missing required fields in request body | 400 Bad Request |
No | Yes |
csrf_validation_failed |
CSRF token missing or doesn't match session | 403 Forbidden |
No | Yes |
Real-Time Alerting¶
When a security event is classified as an attack, the system sends an email alert to the affected user with full context.
flowchart TB
ATTACK["Attack Detected<br/>(e.g., wrong webhook secret)"] --> LOG["log_audit()<br/>Store in audit_logs table"]
LOG --> ALERT["send_attack_alert_email()"]
ALERT --> RATE{"Rate limit check<br/>1 email per user<br/>per 30 minutes"}
RATE -->|"Already sent recently"| SKIP["Skip email<br/>(avoid spam)"]
RATE -->|"OK to send"| EMAIL["Send email to user"]
EMAIL --> CONTENT["Email contains:<br/>• Attack type<br/>• Source IP address<br/>• Timestamp<br/>• Account name<br/>• Additional details"]
Alert properties:
| Property | Detail |
|---|---|
| Delivery | Asynchronous (non-blocking, best-effort) — never delays request processing |
| Rate limiting | Maximum 1 email per user per 30 minutes (prevents alert fatigue) |
| Error handling | All exceptions caught and logged — alert failures never interfere with request flow |
| Content | Attack type, source IP, timestamp, affected account name, human-readable description |
Alert Design Philosophy
Alerts are intentionally fire-and-forget. The _send_attack_alert() function wraps all logic in a try/except to ensure that email service failures, rate limiter errors, or any other issue never causes a webhook request to fail. Security monitoring should never degrade the primary trading function.
Audit Logging¶
Every security-relevant operation is logged via the log_audit() function into the audit_logs PostgreSQL table.
| Field | Type | Content |
|---|---|---|
id |
Serial | Auto-incrementing primary key |
user_id |
FK → users | The authenticated user (or target user for webhook attacks) |
action |
String | Event type: webhook_order, webhook_credential_attack, login, etc. |
success |
Boolean | Whether the operation succeeded |
ip_address |
String | Client IP extracted via trusted proxy chain (X-Forwarded-For) |
user_agent |
String | Request User-Agent header |
request_path |
String | Full request URL path |
request_method |
String | HTTP method (GET, POST, etc.) |
error_message |
String | Failure reason (null on success) |
resource_type |
String | Affected resource type (order, account, session, etc.) |
resource_id |
String | Specific resource identifier |
metadata |
JSONB | Arbitrary structured data (attack details, request age, trading account ID, etc.) |
created_at |
Timestamp | Event timestamp |
Example audit entries:
{
"action": "webhook_credential_attack",
"success": false,
"ip_address": "203.0.113.42",
"error_message": "Invalid webhook secret",
"metadata": {
"attack_type": "wrong_secret",
"trading_account_id": 15
}
}
{
"action": "webhook_replay_attack",
"success": false,
"ip_address": "198.51.100.7",
"error_message": "Replay attack detected (duplicate request)",
"metadata": {
"payload_hash": "a1b2c3d4e5f6..."
}
}
Security Dashboard¶
Users can monitor attacks against their accounts through the dashboard:
| Endpoint | Purpose |
|---|---|
GET /settings/security-attacks |
Paginated list of security events for the authenticated user |
Dashboard shows:
- Attack counts by type (credential attacks, replay attempts, IP violations)
- Recent events with timestamps and source IPs
- Affected trading accounts
- Historical attack patterns
This enables users to:
- Identify if their webhook secret has been compromised (repeated
webhook_credential_attackevents) - Detect IP-based scanning (multiple
webhook_ip_blockedfrom different IPs) - Verify their IP whitelist is correctly configured
- Decide whether to rotate their webhook token and secret
Security Headers¶
Every HTTP response includes a comprehensive set of security headers, injected by the SecurityHeadersMiddleware (replaces NGINX security-headers.conf when running behind ALB).
| Header | Value | Protection |
|---|---|---|
Strict-Transport-Security |
max-age=31536000; includeSubDomains; preload |
Forces HTTPS for 1 year; eligible for browser preload lists |
Content-Security-Policy |
Restrictive policy with explicit allowlists for scripts, styles, images, connections, frames | Prevents XSS, inline injection, unauthorized resource loading |
X-Content-Type-Options |
nosniff |
Prevents browsers from MIME-sniffing responses away from declared content-type |
X-Frame-Options |
SAMEORIGIN |
Prevents clickjacking by disallowing cross-origin iframe embedding |
X-XSS-Protection |
1; mode=block |
Enables legacy browser XSS auditor (defense-in-depth for older browsers) |
Cross-Origin-Opener-Policy |
same-origin |
Isolates browsing context to prevent Spectre-class side-channel attacks |
Cross-Origin-Resource-Policy |
same-origin |
Prevents other origins from reading this origin's resources |
Cross-Origin-Embedder-Policy |
unsafe-none |
Relaxed to avoid breaking CDN resources (Tailwind, Vue, Font Awesome) |
Referrer-Policy |
strict-origin-when-cross-origin |
Sends full referrer for same-origin, origin-only for cross-origin |
Permissions-Policy |
geolocation=(), microphone=(), camera=(), payment=() |
Explicitly disables browser APIs the application doesn't use |
CSP Allowlists
The Content-Security-Policy includes explicit allowlists for TradingView widgets (s3.tradingview.com), Cloudflare Turnstile (challenges.cloudflare.com), Google Analytics, and CDN resources. Each allowlist entry corresponds to a specific frontend feature — no wildcard origins are permitted.