Skip to content

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:

  1. Identify if their webhook secret has been compromised (repeated webhook_credential_attack events)
  2. Detect IP-based scanning (multiple webhook_ip_blocked from different IPs)
  3. Verify their IP whitelist is correctly configured
  4. 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.