Complete defensive security architecture — every layer, every rule, every path.
The Trinity Beast Infrastructure implements defense-in-depth across 11 distinct security layers. No single layer is trusted to stop all threats — each layer assumes the one above it has been breached. Traffic must survive every gauntlet before reaching the data layer.
Design Philosophy: Assume breach at every boundary. Every layer operates independently. If CloudFront falls, WAF catches it. If WAF is bypassed, application rate limiting stops it. If rate limiting fails, network isolation contains it. If network isolation is compromised, encryption at rest protects the data. No single point of failure in the security chain.
| # | Layer | Technology | Purpose |
|---|---|---|---|
| 1 | Edge CloudFront + Shield | CloudFront, Shield Standard | DDoS absorption, TLS termination, geographic distribution |
| 2 | Edge WAF | AWS WAF v2 (2 WebACLs) | IP reputation, rate limiting, SQL injection, known exploits, honeypot traps |
| 3 | AppApplication | Go middleware stack | API key auth, per-tier rate limiting, input validation, CORS, HMAC verification |
| 4 | Network VPC Isolation | 2 VPCs + peering | Compute and data on separate networks, no public IPs on data layer |
| 5 | DataData Protection | Aurora, ElastiCache | TLS in transit, encryption at rest, security groups, no public endpoints |
| 6 | EdgeFlame Path (UDP) | NLB + application protocol | Stateless UDP with cryptographic validation, no TCP overhead |
| 7 | OpsDetection | GuardDuty, CloudWatch | Behavioral anomaly detection, alarm-driven response |
| 8 | Ops AutoOps AI | Bedrock + EventBridge | AI-powered threat correlation, auto-blocking, self-healing |
| 9 | OpsAudit Trail | CloudTrail, VPC Flow Logs | Complete API audit, network flow forensics |
| 10 | Network IAM | AWS IAM | Least-privilege roles, no shared credentials, service-scoped policies |
| 11 | DataSecrets | Secrets Manager | Rotatable credentials, no hardcoded secrets in code or environment |
flowchart TB
subgraph INTERNET["🌐 Public Internet"]
USER["👤 Legitimate User"]
ATTACKER["💀 Attacker"]
SCANNER["🤖 Scanner/Bot"]
end
subgraph EDGE["Layer 1 — Edge Defense"]
CF["CloudFront CDN
TLS 1.3 · Shield Standard
DDoS Absorption"]
CFWAF["CloudFront WAF
IP Reputation · Common Rules
Known Bad Inputs"]
end
subgraph ALB_LAYER["Layer 2 — API Gateway"]
ALB["Application Load Balancer
TCP :443 → :8080/:9090"]
ALBWAF["ALB WAF
Rate Limit 2000/5min
Admin Rate 100/5min
SQL Injection · Honeypot"]
NLB["Network Load Balancer
UDP :2679/:2680"]
end
subgraph APP["Layer 3 — Application (ECS Fargate)"]
direction LR
MAIN["BeastMain
2 vCPU · 6 GB"]
MIRROR["BeastMirror
2 vCPU · 6 GB"]
LRS["BeastLRS
2 vCPU · 6 GB"]
WEBHOOK["BeastWebhook
Push Only · No Inbound"]
end
subgraph NETWORK["Layer 4 — Network Isolation"]
ECSVPC["ECS VPC
10.0.0.0/16"]
PEER["VPC Peering
pcx-0e988b7230cf451b8"]
DATAVPC["Data VPC
172.31.0.0/16"]
end
subgraph DATA["Layer 5 — Data Layer"]
AURORA["Aurora PostgreSQL 17.7
TLS · Encrypted at Rest
Private Subnet Only"]
VALKEY["ElastiCache Serverless
Valkey 9.0 · TLS · AUTH Token
Private Subnet Only"]
end
subgraph DETECT["Layer 7–8 — Detection & Response"]
GD["GuardDuty
VPC Flow · CloudTrail · DNS"]
CW["CloudWatch
37 Alarms"]
AUTOOPS["AutoOps AI
Bedrock Threat Analysis
Self-Heal · Auto-Block"]
end
USER --> CF
ATTACKER --> CF
SCANNER --> CF
CF --> CFWAF
CFWAF -->|"Static Assets"| USER
USER -->|"API Requests"| ALB
ATTACKER -->|"API Attacks"| ALB
ALB --> ALBWAF
ALBWAF -->|"Passed"| APP
ALBWAF -->|"Blocked"| ATTACKER
USER -->|"UDP Price Feed"| NLB
NLB --> APP
APP --> ECSVPC
ECSVPC --> PEER
PEER --> DATAVPC
DATAVPC --> AURORA
DATAVPC --> VALKEY
GD -.->|"Monitors"| NETWORK
CW -.->|"Monitors"| APP
AUTOOPS -.->|"Auto-blocks"| ALBWAF
linkStyle 0,1,2 stroke:#ef4444,stroke-width:2px
linkStyle 7,8 stroke:#10b981,stroke-width:2px
linkStyle 9 stroke:#ef4444,stroke-width:2px
linkStyle 10 stroke:#f97316,stroke-width:2px
linkStyle 11,12,13,14,15 stroke:#a855f7,stroke-width:2px
Diagram 1.1: Complete Security Architecture — All Layers
The outermost perimeter. All website traffic enters through CloudFront — a globally distributed CDN with 450+ edge locations. API traffic hits the ALB directly (DNS-routed). Both paths are protected by AWS Shield Standard.
| Property | Value |
|---|---|
| Distribution ID | E110PRKEIYQVLL |
| Domain | cpmp-site.org / www.cpmp-site.org |
| Origin | S3 bucket (trinity-beast-website-east2) |
| TLS | TLS 1.2+ enforced (TLS 1.3 preferred) |
| HTTP → HTTPS | Redirect all HTTP to HTTPS |
| WAF WebACL | CreatedByCloudFront-449feaa5 |
| Shield | Standard (automatic DDoS protection) |
| Geo Restriction | None (global access) |
AWS Shield Standard provides automatic protection against the most common Layer 3/4 DDoS attacks:
Shield Standard is free, automatic, and always-on. No configuration required. It protects both the CloudFront distribution and the ALB.
TLS Everywhere: Every connection in the system uses TLS. Browser → CloudFront (TLS 1.3). Browser → ALB (TLS 1.2+). ECS → Aurora (TLS). ECS → ElastiCache (TLS + AUTH). There is no unencrypted path anywhere in the infrastructure.
| Connection | Protocol | Certificate |
|---|---|---|
| Browser → CloudFront | TLS 1.2/1.3 | ACM (auto-renewed) |
| Browser → ALB (API) | TLS 1.2+ | ACM (auto-renewed) |
| ECS → Aurora | TLS (rds-ca-rsa2048-g1) | RDS CA bundle |
| ECS → ElastiCache | TLS + AUTH token | ElastiCache managed |
| Lambda → AWS APIs | TLS 1.2+ | AWS SDK default |
Two separate WAF WebACLs protect different attack surfaces. The CloudFront WAF guards the static website. The ALB WAF guards the API — and carries the heavier rule set including rate limiting, SQL injection detection, and the honeypot trap system.
CreatedByCloudFront-449feaa5)| Rule | Action | Purpose |
|---|---|---|
AWS-AWSManagedRulesAmazonIpReputationList | Block | Known malicious IPs, botnets, compromised hosts |
AWS-AWSManagedRulesCommonRuleSet | Block | OWASP Top 10, path traversal, file inclusion, XSS |
AWS-AWSManagedRulesKnownBadInputsRuleSet | Block | Log4j, Spring4Shell, known exploit payloads |
trinity-beast-api-waf)| Rule | Priority | Action | Purpose |
|---|---|---|---|
AWS-AWSManagedRulesAmazonIpReputationList | 1 | Block | Known malicious IPs |
AWS-AWSManagedRulesCommonRuleSet | 2 | Block | OWASP Top 10 (SizeRestrictions_BODY excluded for /newsletter/*) |
AWS-AWSManagedRulesKnownBadInputsRuleSet | 3 | Block | Known exploits (excluded for /newsletter/*) |
AWS-AWSManagedRulesSQLiRuleSet | 4 | Block | SQL injection patterns in query strings, body, headers |
RateLimit-Global-2000 | 5 | Block | Global rate limit: 2000 requests per 5 minutes per IP |
RateLimit-Admin-6000 | 6 | Block | Admin endpoint rate limit: 6000 requests per 5 minutes per IP (renamed from RateLimit-Admin-100 2026-08-23 after raising the threshold) |
AutoOps-BlockedIPs | 7 | Block | Dynamic block list — IPs added by honeypot processor and manual blocks |
Newsletter Endpoints: /newsletter/* paths are excluded from SizeRestrictions_BODY (CommonRules) and KnownBadInputsRuleSet to allow base64-encoded HTML email content in POST bodies. These endpoints are admin-key protected — only the digest Lambda can call them.
The tbi-autoops-blocked-ips IP set (ID: 8d55de25-8ba5-4982-8c41-f4316c9bd50d) is managed automatically by the honeypot processor Lambda. IPs are added when they hit honeypot trap endpoints. As of this writing the set holds 473 IPs (5% of the WAF IP set's 10,000-address cap) — 444 honeypot-dated (90-day expiry) and 29 unmanaged (manual/undated, never auto-expire).
bash scripts/kcc.sh block-ip 1.2.3.4bash scripts/kcc.sh block-country "RU CN" (geo-match rule)bash scripts/kcc.sh lockdown-admin $(curl -s ifconfig.me) (priority 0 — blocks all /admin/* except your IP)The honeypot-triggered portion of the block list ages out after 90 days rather than growing without bound. A WAF IP set caps at 10,000 addresses, and at the observed rate of new honeypot hits, an unmanaged list would fill that cap within roughly two years and then silently stop accepting new blocks — the failure mode of an "unbounded" design is not "the list gets big," it's "blocking eventually stops working."
Manual blocks made via block-ip are never auto-expired — those carry no honeypot evidence and are assumed to be intentional, permanent decisions. Only IPs positively attributed to the honeypot are dated and pruned.
Why a separate ledger, rather than trusting the per-IP honeypot:ip:<ip> Valkey hash directly: those records turned out to be inconsistent in practice — some carry a short TTL, some are permanent, and some blocked IPs have no record at all. Expiry logic built on top of that would silently mis-date or lose IPs. Instead, a dedicated sorted set (honeypot:blocklist:blocked_at, member=IP, score=block epoch) is written the moment the honeypot processor issues a block — before the flaky per-IP record can expire — and that ledger is what honeypot-prune reads to decide age.
The ledger itself is made durable across a Valkey wipe by three stores that agree with each other: the WAF IP set is membership truth (durable, AWS-managed), a private S3 JSON object (s3://aws-waf-logs-trinity-beast/honeypot/blocklist-ledger.json) is the durable timestamp record, and the Valkey sorted set is the fast working copy. BeastReconciler reconciles all three every night, and the honeypot processor Lambda writes to the ledger at block time — so a Valkey flush can never orphan a block's age.
bash scripts/kcc.sh honeypot-prune (dry-run, default)bash scripts/kcc.sh honeypot-prune --apply12 decoy endpoints that no legitimate user would ever access. Any hit immediately identifies the source as a scanner or attacker:
| Trap Endpoint | Mimics |
|---|---|
/wp-admin | WordPress admin panel |
/wp-login.php | WordPress login |
/.env | Environment file exposure |
/.git/config | Git repository exposure |
/phpmyadmin | Database admin panel |
/admin/login | Generic admin login |
/xmlrpc.php | WordPress XML-RPC |
/actuator | Spring Boot actuator |
/api/v1/admin | Generic API admin |
/debug/vars | Go debug endpoint |
/server-status | Apache status page |
/telescope | Laravel Telescope |
Behavior on hit: 2-second tarpit delay (wastes scanner time) → log IP, user-agent, path to Valkey → queue for WAF auto-block → daily digest includes all honeypot activity.
The Go application implements its own security stack independent of AWS infrastructure. Even if WAF is completely bypassed, the application enforces authentication, authorization, rate limiting, and input validation at every endpoint.
Every non-public endpoint requires a valid API key in the Authorization: Bearer header. Keys are validated against a multi-tier cache:
Invalid keys receive an immediate 401 Unauthorized with full UME envelope. No information leakage about why the key failed.
| Tier | QPS Limit | Burst | Monthly Cap | Cache TTL |
|---|---|---|---|---|
| Free | 2 | 5 | 1000 | 5 min |
| Pro | 10 | 20 | 50000 | 5 min |
| Enterprise | 50 | 100 | 500000 | 5 min |
| Unlimited | None | None | None | 5 min |
| Lifetime | None | None | None | 5 min |
| Partner | None | None | None | 60 min |
Rate limiting is enforced per API key using a token bucket algorithm with ElastiCache-backed counters. Exceeding the limit returns 429 Too Many Requests.
All /admin/* endpoints require the admin API key (X-Admin-Key header). This key is:
trinity-beast-admin-secrets): not in code, not in any environment variable, not in the database or cachebash scripts/kcc.sh rotate-admin-key rotate --yes, which confirms every node before it finishes; the firewall logs redact the admin header/admin/*)bash scripts/kcc.sh lockdown-adminThe CORS middleware echoes the requesting origin only for cpmp-site.org and www.cpmp-site.org with credentials: true. All other origins receive Access-Control-Allow-Origin: * (public API — no credentials). This prevents credential-bearing cross-origin requests from unauthorized domains.
400 Bad Request — no silent defaults that could cost moneyOutbound webhook deliveries include an HMAC-SHA256 signature in the X-Signature header. Subscribers verify the signature using their webhook secret to confirm the payload originated from The Trinity Beast and was not tampered with in transit.
Compute and data live on completely separate networks. There is no path from the public internet to the data layer. The only bridge is a VPC peering connection with strict security group rules.
| VPC | CIDR | Purpose | Internet Access |
|---|---|---|---|
vpc-03deaddb7083cd59c | 10.0.0.0/16 | ECS VPC — compute (Fargate tasks, Lambda) | Yes (IGW for ECS, VPC endpoints for Lambda) |
vpc-0876ee7be3a677f26 | 172.31.0.0/16 | Data VPC — storage (Aurora, ElastiCache) | None — completely private |
Critical Design Decision: The Data VPC has NO internet gateway, NO NAT gateway, and NO public subnets. Aurora and ElastiCache are unreachable from the internet by network architecture — not just by security group rules. Even if every security group were misconfigured to allow 0.0.0.0/0, traffic still cannot route to the data layer from outside.
Peering connection pcx-0e988b7230cf451b8 bridges the ECS VPC to the Data VPC. Route tables in the ECS subnets send 172.31.0.0/16 traffic through the peering connection. Only specific ports are allowed through security groups.
| Security Group | VPC | Inbound Rules | Used By |
|---|---|---|---|
sg-050b617f93b2388f6 | ECS VPC | ALB health checks, inter-container | ECS tasks, queued-writer Lambda |
sg-03fa8e2b34d54ffb0 | Data VPC | TCP 5432 from 10.0.1.0/24, 10.0.2.0/24, 10.0.6.0/24 only | Aurora cluster |
sg-0b0d55e13dad621a1 | ECS VPC | TCP 443 + 5432 from ECS SG | VPC endpoints (Secrets Manager, SQS) |
Aurora's security group only accepts connections from the three ECS subnets — not from the entire ECS VPC CIDR, and certainly not from anywhere else. This is defense-in-depth: even if an attacker compromises an ECS task, they can only reach Aurora on port 5432 with valid credentials.
Lambda functions in the ECS VPC cannot reach AWS services over the internet (no public IP on Lambda ENIs). VPC endpoints provide private connectivity:
vpce-020d39818b2046944 — Secrets Manager (DB credential retrieval)vpce-0f52837e401b8960e — SQS (message receive/delete)The crown jewels — Aurora PostgreSQL and ElastiCache (Valkey). Both are encrypted at rest, encrypted in transit, and accessible only from the ECS VPC via peering.
| Property | Value |
|---|---|
| Engine | PostgreSQL 17.7 (Aurora Serverless v2) |
| Encryption at Rest | AES-256 (AWS KMS managed key) |
| Encryption in Transit | TLS required (rds.force_ssl = 1) |
| Public Access | Disabled — no public endpoint |
| Network | Data VPC private subnets only |
| Authentication | Username/password via Secrets Manager |
| Backup | Automated daily snapshots, 7-day retention |
| I/O Mode | Optimized I/O (no per-IOPS charges) |
| Property | Value |
|---|---|
| Engine | Valkey 9.0, Serverless (auto-scaling, no fixed nodes) |
| Capacity | Max 30 GB storage / 10,000 ECPU/s configured limits |
| Availability | Multi-AZ, auto-heals across AZs — no manual intervention |
| Encryption at Rest | AES-256 (AWS managed) |
| Encryption in Transit | TLS required (always-on) |
| Authentication | AUTH token (stored in Secrets Manager) |
| Public Access | Disabled — VPC-only |
| Network | Data VPC private subnets only |
ℹ️ Migrated from a fixed cache.r7g.2xlarge node to ElastiCache Serverless on July 2, 2026. The Serverless proxy layer handles cluster-mode routing transparently — no application-code change was needed.
Zero Public Surface: Neither Aurora nor ElastiCache has a public endpoint. They exist only within the Data VPC's private subnets. The only way to reach them is through the VPC peering connection from the ECS VPC — which requires being an ECS task or a VPC-attached Lambda with the correct security group.
The UDP price feed is the fastest path into the system — raw speed with zero TCP handshake overhead. It's called the Flame Path because data travels at wire speed, burning through with no connection state, no retransmission, no negotiation. But speed without security is recklessness. The Flame Path has its own security model.
🔥 The Flame Path Philosophy: UDP is stateless by design. There are no connections to hijack, no sessions to steal, no handshakes to intercept. The security model is fundamentally different from TCP — it relies on cryptographic validation of every single packet rather than connection-level trust.
| Component | Value |
|---|---|
| Load Balancer | Trinity-Beast-UDP-NLB (Network Load Balancer) |
| Ports | 2679 (primary), 2680 (secondary) |
| Protocol | UDP (stateless, connectionless) |
| DNS | udp.cpmp-site.org |
| Target | ECS Fargate tasks (same containers as TCP) |
| Health Check | TCP on port 8081 (dedicated health port — isolated from application traffic) |
Common UDP security concerns and how the Flame Path addresses them:
| Concern | TCP Risk | Flame Path Mitigation |
|---|---|---|
| IP Spoofing | SYN cookies prevent | Response goes to spoofed IP — attacker gains nothing (price data is public anyway) |
| Amplification | N/A (connection-based) | Response ≤ request size. Cannot amplify. |
| Replay Attacks | Sequence numbers | Replayed request returns current price — no state change, no harm |
| Eavesdropping | TLS encrypts | Price data is public information. Nothing secret in the payload. |
| Flood Attacks | Connection limits | NLB absorbs at wire speed + per-key rate limiting drops excess |
Design Insight: The Flame Path works because the data it carries (cryptocurrency prices) is inherently public. There is no secret to protect in transit. The API key protects access metering and rate limiting — not data confidentiality. This is why UDP without TLS is acceptable here but would not be acceptable for private data.
flowchart LR
subgraph CLIENT["Partner / Subscriber"]
APP["Application
UDP Client"]
end
subgraph EDGE["Edge"]
NLB["NLB
UDP :2679/:2680
Wire Speed"]
end
subgraph ECS["ECS Fargate"]
UDP_HANDLER["UDP Handler
Zero-Alloc v6
Per-Packet Auth"]
CACHE["sync.Map
Price Cache
Sub-μs Lookup"]
RATE["Rate Limiter
Token Bucket
Per-Key"]
end
APP -->|"🔥 UDP Packet
[API_KEY + ASSET]"| NLB
NLB -->|"Forward"| UDP_HANDLER
UDP_HANDLER -->|"1. Validate Key"| RATE
RATE -->|"2. Check Limit"| CACHE
CACHE -->|"3. Price Response"| UDP_HANDLER
UDP_HANDLER -->|"🔥 UDP Response
[PRICE_DATA]"| APP
style NLB fill:#7f1d1d,stroke:#ef4444,color:#fca5a5
style UDP_HANDLER fill:#1a0f0a,stroke:#f97316,color:#fdba74
style CACHE fill:#064e3b,stroke:#10b981,color:#6ee7b7
style RATE fill:#1e3a5f,stroke:#60a5fa,color:#93c5fd
linkStyle 0 stroke:#f97316,stroke-width:3px
linkStyle 5 stroke:#f97316,stroke-width:3px
Diagram 6.1: The Flame Path — UDP Security Flow
Passive monitoring that watches everything and alerts on anomalies. This layer doesn't block — it detects, correlates, and escalates.
| Property | Value |
|---|---|
| Detector ID | 18ceef6f8dddcf6082473cc7016ee458 |
| Data Sources | VPC Flow Logs, CloudTrail, DNS query logs |
| Finding Types | Reconnaissance, instance compromise, credential compromise, data exfiltration |
| Integration | Severity ≥ 7 → EventBridge rule tbi-ops-guardduty-high-finding → Bedrock threat analysis Lambda |
GuardDuty uses machine learning to establish behavioral baselines and detect deviations. It monitors:
ℹ️ There is no TrinityBeast-GuardDuty-Finding CloudWatch alarm — one existed briefly but was deleted 2026-07-30 because AWS/GuardDuty publishes no finding metric, so it could never actually fire. The EventBridge rule above is the real integration path and does not depend on a metric alarm.
| Alarm | Threshold | Action |
|---|---|---|
Trinity-Beast-ALB-UnhealthyTargets | ≥ 1 unhealthy target | SNS → self-heal |
Trinity-Beast-Aurora-CPU-High | CPU > 80% | SNS → notify |
Trinity-Beast-Aurora-Connections-High | Connections > threshold | SNS → notify |
Trinity-Beast-ECS-CPU-High / -LRS / -Mirror | CPU > 80% | SNS → notify |
Trinity-Beast-ElastiCache-ECPU-High | ECPU above 60% of the 10,000/s cap (360,000 per minute) for 15 minutes | SNS → notify |
Trinity-Beast-ElastiCache-Storage-High | Storage > 24 GB of 30 GB max | SNS → notify |
Trinity-Beast-ElastiCache-Throttled | ThrottledCmds > 0 | SNS → notify |
Trinity-Beast-*-Service-Count-Low (Main/Mirror/LRS) | Running tasks < desired | SNS → self-heal |
Trinity-Beast-NLB-UnhealthyTargets | ≥ 1 unhealthy target | SNS → self-heal |
Trinity-Beast-S3-Size-Unusual-Growth | Unusual growth rate | SNS → notify |
ℹ️ Migrated to ElastiCache Serverless: the previous -CPU-High / -Memory-High node-based alarms no longer apply — ElastiCache moved from a fixed cache.r7g.2xlarge node to Serverless on July 2, 2026, and the alarms above (ECPU, storage, throttled commands) are the Serverless-native replacements.
| Alarm | Target Group | Load Balancer |
|---|---|---|
Trinity-Beast-ALB-UnhealthyTargets | trinity-beast-fargate-group | TCP-ALB |
Trinity-Beast-LRS-TargetGroup-Unhealthy | trinity-beast-lrs-9090 | TCP-ALB |
Trinity-Beast-NLB-UnhealthyTargets | Trinity-Beast-UDP-2679-TG | UDP-NLB |
Trinity-Beast-UDP-2680-Unhealthy | Trinity-Beast-UDP-2680-TG | UDP-NLB |
Trinity-Beast-PrivateLink-8080-Unhealthy | trinity-beast-pl-tcp-8080 | TCP-NLB (internal) |
Trinity-Beast-PrivateLink-9090-Unhealthy | trinity-beast-pl-tcp-9090 | TCP-NLB (internal) |
| Alarm | Target Group | Load Balancer |
|---|---|---|
Trinity-Beast-Webhook-Test-8090-Unhealthy | trinity-beast-webhook-test-tg | TCP-ALB |
Trinity-Beast-UDP-2681-Unhealthy | Trinity-Beast-UDP-2681-TG | UDP-NLB |
The only alarms in the account sourced from CloudWatch Logs metric filters (on /aws/ecs/trinity-beast) rather than a native AWS service metric:
| Alarm | Threshold | Action |
|---|---|---|
Trinity-Beast-ParamLoad-AuroraFallback-Recurring | ≥ 3 in 1h | SNS → notify |
Trinity-Beast-Startup-MigrationFailure | ≥ 1 in 1h | SNS → notify |
alarm-audit found with no alarm of any kind)| Alarm | Threshold | Action |
|---|---|---|
Trinity-Beast-Webhook-Service-Count-Low | Running tasks < 1 | SNS → self-heal |
Trinity-Beast-Translate-Worker-Count-Low | Running tasks < 1 | SNS → self-heal |
Trinity-Beast-Translation-DLQ-NotEmpty | Messages visible > 0 | SNS → investigate |
ℹ️ The webhook service is a paying product ($30–$420/mo push tiers) and could otherwise have stopped delivering silently — this is why it has its own dedicated task-count alarm rather than relying on the generic ECS alarms above.
| Alarm | Threshold | Action |
|---|---|---|
TrinityBeast-API-4xx-Spike | Threshold 300 | SNS → investigate |
TrinityBeast-API-5xx-Spike | Target 5xx, threshold 9 | SNS → self-heal |
TrinityBeast-API-ELB-5xx-Spike | ELB-generated 5xx, threshold 9 | SNS → self-heal |
TrinityBeast-WAF-HighBlockRate | Block rate spike | SNS → investigate |
Target-5xx and ELB-5xx are distinct signals: target-5xx means the container itself returned an error; ELB-5xx means the ALB couldn't get a usable response from any target at all (502/503/504).
TBI-Security-IAM-Changes, TBI-Security-EC2-RunInstances — CloudTrail metric-filter alarmsTargetTracking-service/trinity-beast-fargate-cluster/* (6: AlarmHigh + AlarmLow × main/mirror/lrs) — auto-created by ECS Application Auto Scaling to drive the scaling policies. Never delete these.Anomaly Detection is fully retired — 0 anomaly alarms remain. All four original ML-based anomaly detectors (RequestRate, Latency, ErrorRate, CacheHitRate) were deleted by 2026-08-26. Each flapped repeatedly against genuinely healthy infrastructure — a 300-second Average of a ratio metric collapses on any window with a miss and no hit, and TargetResponseTime is bimodal enough (fast cached reads mixed with slower admin/report calls) that no anomaly band could sit still. They were also triggering no-op self-heal Step Function runs on every flap. The replacement for latency monitoring is a static p99 > 300ms alarm (queued, not yet built). Do not re-introduce ML anomaly detection on these metrics without a materially different approach — this was tried and it did not work.
flowchart TB
subgraph SIGNALS["Detection Signals"]
WAF_BLOCKS["WAF Block Events"]
GD_FINDINGS["GuardDuty Findings"]
CW_ALARMS["CloudWatch Alarms"]
HONEYPOT["Honeypot Hits"]
ANOMALY["Anomaly Detectors"]
end
subgraph ROUTING["Event Routing"]
EB["EventBridge
Pattern Matching"]
SNS["SNS
tbi-ops-notifications"]
end
subgraph RESPONSE["Automated Response"]
SF["Step Function
tbi-ops-health-check-heal"]
BEDROCK["Bedrock Analysis
Claude Sonnet 4.6
Threat Correlation"]
HONEYPOT_PROC["Honeypot Processor
Every 5 min"]
SELF_HEAL["Self-Heal Lambda
Restart · Redeploy"]
WAF_ACTION["WAF Action Lambda
Block IP · Unblock"]
NOTIFY["Notify Lambda
Formatted SES Email"]
end
subgraph ACTIONS["Outcomes"]
BLOCK["🚫 WAF Block"]
RESTART["🔄 ECS Restart"]
EMAIL["📧 Alert Email"]
ESCALATE["🚨 Page Cory"]
end
WAF_BLOCKS --> EB
GD_FINDINGS --> EB
CW_ALARMS --> SNS
CW_ALARMS --> EB
HONEYPOT --> HONEYPOT_PROC
ANOMALY --> SNS
SNS --> NOTIFY
EB -->|"Alarm State Change"| SF
EB -->|"Severity ≥ 7"| BEDROCK
HONEYPOT_PROC --> WAF_ACTION
SF --> SELF_HEAL
SELF_HEAL --> RESTART
BEDROCK -->|"HIGH/CRITICAL"| WAF_ACTION
BEDROCK -->|"Report"| NOTIFY
WAF_ACTION --> BLOCK
NOTIFY --> EMAIL
SF -->|"Unknown failure"| ESCALATE
style BEDROCK fill:#1a0f2e,stroke:#a855f7,color:#c4b5fd
style SF fill:#1e3a5f,stroke:#60a5fa,color:#93c5fd
style NOTIFY fill:#064e3b,stroke:#10b981,color:#6ee7b7
style ESCALATE fill:#7f1d1d,stroke:#ef4444,color:#fca5a5
Diagram 7.1: Threat Detection & Response Pipeline
The AI layer that turns raw signals into intelligent action. Amazon Bedrock (Claude Sonnet 4.6) correlates WAF blocks, honeypot hits, rate limit events, and usage anomalies to produce threat assessments and recommend actions.
tbi-ops-bedrock-analyze Lambdaautoops:threats:daily) for the daily digest| Trigger | Detection | Automated Response | Notification |
|---|---|---|---|
| Health check failure | ALB unhealthy target alarm | Identify failed task → restart → verify recovery | [SELF-HEALED] email |
| Service count low | Running tasks < desired | Force new deployment on affected service | [SELF-HEALED] email |
| 5xx spike | Error rate alarm | Check if one node or all → restart single or escalate | [WARNING] or [CRITICAL] |
| WebSocket feed death | Stale price detection | Wait 60s → recheck → force-deploy if still dead | [SELF-HEALED] email |
| Honeypot hit | Trap endpoint accessed | Queue IP → WAF block (every 5 min batch) | Included in daily digest |
| GuardDuty HIGH | Severity ≥ 7 finding | Bedrock analysis → auto-block if recommended | [CRITICAL] email + assessment |
Bedrock is advisory, not authoritative. The AI suggests actions. Step Functions decide whether to execute. Lambda performs the action. If the AI recommends something destructive (killing a service, revoking all keys), it escalates to Cory instead of acting. Safe actions (blocking a single IP) execute automatically.
The system is designed to minimize Bedrock costs during quiet periods:
Every action, every API call, every network flow is logged. When an incident occurs, the forensic trail is complete — who did what, when, from where.
| Property | Value |
|---|---|
| Trail Name | trinity-beast-events-trail |
| Scope | Multi-region (all AWS API calls) |
| Events | Management events + data events (S3, Lambda) |
| Storage | S3 bucket with lifecycle rules |
| Integration | GuardDuty reads CloudTrail for anomaly detection |
CloudTrail captures every AWS API call made in the account — console, CLI, SDK, and service-to-service. This includes:
Active on both VPCs. Captures every network flow (accepted and rejected) with source/destination IP, port, protocol, bytes, and action. Used by:
| Log Group | Content | Retention |
|---|---|---|
/aws/ecs/trinity-beast | All application logs — BeastMain, BeastMirror, BeastLRS, BeastWebhook (single shared group, distinguished by CLUSTER_NODE in the log line) | 90 days |
/aws/ecs/trinity-beast-sync | BeastReconciler (nightly sync job) | 90 days |
/aws/lambda/trinity-beast-receipt | Payment processing | 90 days |
/aws/lambda/trinity-beast-queued-writer | Usage log batch inserts | 90 days |
/aws/lambda/trinity-beast-email-sender | Async SES delivery | 90 days |
/aws/lambda/tbi-ops-* (notify, self-heal, waf-action, honeypot-processor, bedrock-analyze, digest) | AutoOps Lambda execution | 90 days |
/aws/vpc/trinity-beast-flowlogs | VPC Flow Logs, both VPCs | Lifecycle-managed (see Audit Trail below) |
Pre-built forensic routines for incident investigation:
bash scripts/kcc.sh trace-ip 1.2.3.4 — WAF blocks + usage logs for an IPbash scripts/kcc.sh trace-key <api_key_id> — Key owner, activity, IPs usedbash scripts/kcc.sh trace-email user@email.com — Account, keys, transactions, ticketsbash scripts/kcc.sh threat-log — WAF blocks by rule (24h) + GuardDuty findingsbash scripts/kcc.sh threat-status — Current block list + alarm stateEvery service has its own IAM role with the minimum permissions required. No shared credentials. No wildcard policies. No cross-service privilege escalation paths.
| Role | Used By | Key Permissions | Cannot Do |
|---|---|---|---|
ecsTaskExecutionRole | ECS task startup | Pull ECR images, write CloudWatch Logs | Cannot access Aurora, S3, or any data |
ecsTaskRole | Running ECS containers | Read-only on exactly two secrets (named by full ARN), SQS send, S3 read/write, ElastiCache connect | Cannot write any secret, cannot read any other secret, cannot modify IAM, cannot access other accounts |
tbi-autonomous-ops-role | The 4 AutoOps Lambdas that read the admin key | ECS update-service/stop-task, WAF update-ip-set, Bedrock invoke-model, SES send-email, SNS publish, SQS receive/delete, read the admin secret only | Cannot read the database or Stripe secret, cannot modify IAM, cannot access Aurora directly |
tbi-autonomous-ops-noadmin-role | tbi-ops-notify, tbi-ops-self-heal, tbi-ops-waf-action | The same operational permissions, with no secret access of any kind | Cannot read any secret, cannot modify IAM |
trinity-beast-queued-writer-role | Queued writer Lambda | VPC access, Secrets Manager, SQS consume | Cannot modify any infrastructure |
trinity-beast-receipt-lambda-role | Receipt Lambda | Read its own Stripe secret and the admin secret, SES send-email | Cannot read the database secret, cannot modify infrastructure |
tbi-translate-role | Translation engine (worker + Lambdas) | Bedrock invoke-model, S3 read/write, CloudFront invalidation, SES, SNS | Cannot access Aurora directly, cannot modify IAM |
* resource ARNs: Every policy specifies exact resource ARNs where possiblequeued-writer is in the VPC (needs Aurora). All others are outside (don't need it, shouldn't have it)Single Operator: Cory is the sole human with AWS console/CLI access. There are no other IAM users. The root account has MFA enabled. Day-to-day operations use SSO via AWS Identity Center. ECS Exec provides emergency container access (IAM-protected, no admin key needed).
Every secret this system uses lives in AWS Secrets Manager, and nowhere else. Not in code, not in an environment variable, not in the database, not in the cache. That became true for the last credential on 11 October 2026, when the admin key and the Stripe secret were moved out of the database and cache copies they had also been kept in.
The reason is worth stating plainly, because it is the lesson behind this whole layer: a secret kept in two places is only as safe as the least protected one. A copy in a database row or a cache field is readable by anything that can read rows or fields, it survives a rotation that only updated the original, and it can be exported into somewhere public without anyone noticing. One home means one thing to protect, one thing to rotate, and one place to audit.
What services carry instead is the name of a secret, never its value. DB_SECRET_NAME=trinity-beast-secrets in an environment variable is a label; reading it gets you nothing without an IAM role that is allowed to fetch that secret. The two ECS jobs that need the admin key get it through a task-definition secrets block, which means AWS injects the value at task start and it never appears in a task definition, a log or a process list.
| Secret | Contents | Read By |
|---|---|---|
trinity-beast-admin-secrets | The admin API key (ADMIN_KEY) | The 4 ECS services, 6 Lambdas, the translation worker and sync job (task-definition secrets block), and the operator shell |
trinity-beast-secrets | Aurora database credentials, Stripe keys, SES SMTP credentials | The 4 ECS services (at boot) and the queued-writer Lambda |
trinity-beast-receipt-secrets | Stripe keys for the receipt Lambda, deliberately isolated from the database credentials | The receipt Lambda only |
trinity-beast-ses-smtp | SES SMTP credentials | The email sender path |
| RDS-managed master secret | The Aurora master credential, managed by RDS itself | RDS |
Who may read what is enforced by IAM, not by convention. The ECS services may read exactly two secrets and may write none — their policy names those two by full ARN, so a new secret added to the account is unreachable by them until someone grants it deliberately. The same principle split the AutoOps Lambdas on 11 October: the three that never need a key (tbi-ops-notify, tbi-ops-self-heal, tbi-ops-waf-action) now run on a role with no secret access at all, and the grant on the database-and-Stripe secret was removed from the whole AutoOps family, which 30 days of CloudTrail confirmed none of them had ever used.
Payment credentials follow the same one-home rule. Until 11 October 2026 the Stripe secret was also kept in a database parameter row and a cache field, and the servers read it from the cache. They now read it once at boot from AWS Secrets Manager and the copies are gone. The receipt Lambda keeps its own separate secret, so the component that handles payment webhooks never holds database credentials, and the component that talks to the database never needs the payment keys.
The admin key (tbcc-admin-*) is the most sensitive application-level credential. Its security model:
trinity-beast-admin-secrets) only (encrypted at rest)bash scripts/kcc.sh rotate-admin-key rotate --yes — new version, every node confirmed within secondsRateLimit-Admin-6000) limits /admin/* to 6,000 requests per 5 minutes per IPtbcc-admin- prefix plus 64 hexadecimal characters — 256 bits, or 2256 possible values. Even at the WAF ceiling of 6,000 attempts per 5 minutes, exhaustive search would outlast the universe by an unimaginable margin. Guessing is not the threat model; a copy left somewhere readable is, which is why there are no copies.Because the key has one home, rotating it is one write. Everything else follows on its own, and the tool proves it rather than assuming it.
rotate-admin-key check reads the current state and refuses to go on if anything is wrong, including a stray copy anywhere it should not be.rotate-admin-key clipboard copies the key for pasting, without ever displaying it.No part of this touches a Lambda environment variable, a task definition, a database row or a source file, because none of those holds the key.
If a rotation leaves the admin API unreachable, recovery is a Secrets Manager operation and nothing else:
rotate-admin-key rollback --yes — makes the previous version current again, then runs the same confirmation sweep.PutSecretValue, which requires an IAM identity permitted to do it.Editing the database no longer recovers anything, and that is deliberate. Older versions of this runbook said to connect to Aurora and fix a key row. Since 11 October 2026 the servers ignore that row entirely, so the edit would appear to succeed and change nothing — the most expensive kind of instruction to follow in an outage. The admin API also fails closed: a server with no key loaded refuses every administrative request rather than falling back to allowing them.
ECS Exec remains the side entrance, protected by IAM alone. All 4 services run with it enabled on purpose, so an operator with the right AWS identity can always get inside a container and read the live state, even when the application-level key is unusable. What changed is only what you do once you are in there: the fix lives in Secrets Manager, not in a table.
The complete security posture of The Trinity Beast Infrastructure — assessed across all 11 layers.
| Layer | Status | Maturity | Notes |
|---|---|---|---|
| 1. Edge (CloudFront + Shield) | ACTIVE | Production | TLS 1.3, DDoS absorption, global CDN |
| 2. WAF (2 WebACLs) | ACTIVE | Production | 7 rules on ALB, 3 on CloudFront, dynamic block list |
| 3. Application Security | ACTIVE | Production | Per-key auth, per-tier rate limiting, input validation |
| 4. Network Isolation | ACTIVE | Production | 2 VPCs, no public data endpoints, peering with SG rules |
| 5. Data Protection | ACTIVE | Production | Encryption at rest + in transit, private subnets only |
| 6. Flame Path (UDP) | ACTIVE | Production | Per-packet auth, no amplification, NLB wire-speed |
| 7. Detection (GuardDuty + CW) | ACTIVE | Production | 37 alarms (us-east-2) + 1 (us-east-1) + GuardDuty ML. Anomaly detection retired 2026-08-26 — see Layer 7 note. |
| 8. AutoOps AI | ACTIVE | Production | Self-heal, auto-block, Bedrock threat analysis every 5 min |
| 9. Audit Trail | ACTIVE | Production | CloudTrail multi-region, VPC flow logs both VPCs |
| 10. IAM | ACTIVE | Production | Least-privilege, no cross-role trust, single operator |
| 11. Secrets Management | ACTIVE | Production | Secrets Manager, rotatable admin key, ECS Exec recovery |
| Entry Point | Protection Layers | Risk Level |
|---|---|---|
| Website (CloudFront) | Shield → CloudFront WAF → S3 (static, no execution) | Minimal |
| TCP API (ALB) | Shield → ALB WAF → App Auth → Rate Limit → VPC → SG | Low |
| UDP API (NLB) | NLB → Per-Packet Auth → Rate Limit → No State | Low |
| Admin Endpoints | WAF Rate Limit → Admin Key → IP Lockdown → Rotation | Controlled |
| Data Layer (Aurora) | No public endpoint → VPC peering only → SG → TLS → Auth | Minimal |
| Data Layer (Valkey) | No public endpoint → VPC only → TLS → AUTH token | Minimal |
To reach the data layer, an attacker would need ALL of the following simultaneously:
Each step is independently difficult. Combined, they represent a near-impossible chain. And at every step, GuardDuty, CloudTrail, and the AutoOps AI are watching.
Overall Security Posture: STRONG
11 active defense layers. Zero public data endpoints. AI-powered threat detection. Automated self-healing. Single-operator access model. Encryption everywhere. Complete audit trail. The infrastructure is production-ready from a security perspective.
Assessment by Kiro — September 2, 2026. Based on current deployed configuration, not theoretical design.