AWS Builder Center

Cerberus: A Three-Headed Static Analysis Agent for Code Security, Logic, and Architecture

Most static analysis tools solve one problem well. Security scanners find vulnerabilities. Linters catch style issues. Architecture review tools surface cloud anti-patterns. But production incidents rarely have a single cause. This is the gap Cereberus addresses.

Cerberus: A Three-Headed Static Analysis Agent for Code Security, Logic, and Architecture

AWS Builders Skill Sprint — June 2026 - By Arun Hariharan

The Problem with "Good Enough" Code Analysis

Most static analysis tools solve one problem well. Security scanners find vulnerabilities. Linters catch style issues. Architecture review tools surface cloud anti-patterns. But production incidents rarely have a single cause.
A race condition becomes a security vulnerability when an attacker exploits the window between check and use. An architectural flaw compounds a logic error when a polling loop runs inside a Lambda function that charges per millisecond. These categories are not cleanly separable in real codebases — they bleed into each other.
This is the gap that Cerberus addresses.
Named after the three-headed guardian of Greek mythology, Cerberus analyzes code along three independent dimensions simultaneously, then surfaces how those dimensions interact. Built with the Strands Agents SDK, Amazon Nova Pro, and the AWS Documentation MCP server, it functions not as a report generator but as a conversational analysis partner — one that explains root causes, not just line numbers.

Three Heads, One Guardian

The core insight behind Cerberus is that most bugs stem from a false assumption. The developer assumed user input was safe. Assumed state persisted between check and use. Assumed a list would never grow large enough for O(n²) to matter. Identifying the assumption is more useful than identifying the symptom.
Cerberus organizes its analysis around three lenses that correspond to where false assumptions tend to live:

Head 1: Security

Security vulnerabilities almost always trace back to one assumption: that input, the environment, or some external dependency can be trusted. Cerberus scans for over twenty-five patterns across eight categories:
  • Hardcoded credentials: passwords, API keys, AWS access keys, database connection strings
  • Injection flaws: SQL, OS command, XXE
  • Authentication weaknesses: disabled SSL verification, JWT signature bypass
  • Cryptographic issues: MD5, SHA-1, the use of random instead of secrets
  • Insecure deserialization: pickle.loads, unsafe yaml.load, marshal
  • Path traversal via user-controlled file paths
Findings are classified as either Critical (active exploitation likely — hardcoded secrets, injection flaws, auth bypass) or High (serious risk — weak crypto, path traversal).

Head 2: Logic

Logic flaws are harder to categorize because they are not inherently dangerous — they are dangerous because of a false assumption about how the world behaves. Cerberus scans for:
  • Race conditions (TOCTOU patterns: check-then-use on file system state)
  • Resource leaks (files and sockets opened without context managers or finally blocks)
  • Null pointer risks (unguarded .get() calls, unvalidated dictionary access)
  • Algorithmic inefficiency (O(n²) list membership tests, sort-after-every-append)
  • Infinite loops with no reachable exit
  • Type confusion (type() comparisons instead of isinstance())
  • Bare except clauses that swallow SystemExit and KeyboardInterrupt
For each finding, Cerberus states the false assumption explicitly. A TOCTOU finding reads: "Assumes file system state persists between check and use — false in concurrent environments." This framing is deliberate: the goal is to change how a developer thinks, not just to flag a line.

Head 3: Architecture

Architectural issues are often invisible in small codebases. They become expensive at scale. Cerberus looks for:
  • AWS-specific anti-patterns: boto3 clients instantiated inside loops, polling with time.sleep in Lambda functions, hardcoded regions
  • Scalability blockers: global mutable state, in-process dict caches that won't survive horizontal scaling, SQLite in production
  • Coupling issues: wildcard imports, deep relative import chains, hardcoded URLs that prevent environment parity
  • Observability gaps: functions with no logging, try blocks without finally
  • Error handling failures: silent except: pass, printing exceptions to stdout, raising Exception with a generic message

Architecture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
User query
│
▼
Cerberus Agent
(Amazon Nova Pro + system prompt)
│
▼
Tool selection
├── cerberus_scan() # Single file, all three heads
├── cerberus_directory_scan() # Recursive directory analysis
├── cerberus_best_practices() # Remediation guides with code examples
├── AWS Documentation MCP # Real-time AWS best practices
└── mem0_memory # Cross-session finding persistence
│
├── Head 1: Security patterns
├── Head 2: Logic patterns
└── Head 3: Architecture patterns
│
▼
Finding classification
(severity, category, line, code snippet)
│
▼
Root cause analysis + remediation
via agent reasoning and MCP queries
│
▼
Conversational response
The scanning engine itself is straightforward: regex patterns organized by category, applied line-by-line, with severity determined by which category matched. The intelligence lives in the agent layer — Nova Pro synthesizes the raw findings, explains the underlying assumptions, queries the AWS Documentation MCP for current best practices, and produces remediation guidance that is specific rather than generic.

Why MCP Changes the Output Quality

Traditional static analysis tools are frozen at the moment they were built. Their advice reflects what their authors knew when they wrote the rules. The AWS Documentation MCP integration means Cerberus can retrieve current guidance — the actual AWS recommendation for Lambda security, the current Secrets Manager API pattern, the latest guidance on RDS Proxy and connection pooling — and incorporate it into its response.
The difference in practice is between "use environment variables" and "store this in Secrets Manager under the /prod/service/key naming convention, retrieve it outside the handler for warm-container reuse, and attach a resource policy that restricts access to this Lambda's execution role." The second answer requires knowing what AWS currently recommends and how it fits the user's context. That is what the MCP layer provides.

Detection in Practice

A race condition the code review missed

1
2
3
4
# Vulnerable
if os.path.exists(config_file):
with open(config_file) as f:
config = json.load(f)
Cerberus finding:
1
2
3
4
5
6
7
8
9
HEAD 2 - LOGIC | HIGH | Line 12
Category : Race Conditions
Issue : TOCTOU race condition — check then use
Assumption violated: File system state persists between
os.path.exists() and open(). It does not.

In a concurrent environment, another process can delete or replace
the file between the check and the open call. The correct pattern
is to attempt the operation and handle the exception.
Remediation:
1
2
3
4
5
try:
with open(config_file) as f:
config = json.load(f)
except FileNotFoundError:
config = get_default_config()

A Lambda cost problem hiding as normal code

1
2
3
4
5
def lambda_handler(event, context):
for record in event["Records"]:
s3 = boto3.client("s3")
dynamodb = boto3.client("dynamodb")
process_record(record, s3, dynamodb)
Cerberus finding:
1
2
3
4
5
6
7
8
9
HEAD 3 - ARCHITECTURE | MEDIUM | Line 3
Category : AWS Anti-Patterns
Issue : boto3 client instantiated inside loop

Each client instantiation involves credential resolution and
connection setup. In a Lambda processing 500 records, this creates
1,000 unnecessary client objects per invocation. Clients should be
initialized once at module level and reused across the handler's
lifetime and across warm invocations.
Remediation:
1
2
3
4
5
6
7
# Module level — initialized once, reused across warm invocations
s3 = boto3.client("s3")
dynamodb = boto3.client("dynamodb")

def lambda_handler(event, context):
for record in event["Records"]:
process_record(record, s3, dynamodb)

All three heads on a single function

This is where Cerberus earns its name. Security tools would catch the first issue. Logic analyzers might catch the second. Architecture tools might catch the third. Cerberus catches all three and shows how they relate.
1
2
3
4
5
6
7
8
def get_user_role(username):
query = f"SELECT role FROM users WHERE name = '{username}'"
cursor.execute(query)
result = cursor.fetchone()
role = result[0]
if role == "admin":
return True
return False
Cerberus findings:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
HEAD 1 - SECURITY | CRITICAL | Line 2
Category : SQL Injection
Issue : String formatting used to construct SQL query
Attack : username = "' OR '1'='1" grants universal access
Fix : Parameterized queries

HEAD 2 - LOGIC | HIGH | Line 4
Category : Null Pointer Risk
Issue : cursor.fetchone() returns None for unknown users —
result[0] raises TypeError
Assumption violated: The query always returns a row. It does not.

HEAD 3 - ARCHITECTURE | MEDIUM | Line 1
Category : Observability Gaps
Issue : No logging — permission check failures are invisible
in production
Secure refactor:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def get_user_role(username: str) -> bool:
try:
cursor.execute(
"SELECT role FROM users WHERE name = %s",
(username,)
)
result = cursor.fetchone()
if result is None:
logger.warning("Permission check for unknown user: %s", username)
return False
is_admin = result[0] == "admin"
logger.info("Permission check: user=%s admin=%s", username, is_admin)
return is_admin
except Exception:
logger.exception("Permission check failed for user: %s", username)
return False # Fail closed

Implementation Notes

Pattern design

The sixty-plus patterns are organized as (regex, human-readable description) tuples grouped by category. The regex handles detection; the description is what the user sees. Severity is determined at the category level, not the pattern level — all SQL injection patterns are Critical regardless of which specific form they take.
One deliberate design choice: patterns are kept simple enough to have low false-positive rates even at the cost of some false negatives. A pattern that fires on legitimate code trains developers to ignore findings. Better to catch eight real issues reliably than twelve issues where four are noise.

Graceful degradation

The AWS Documentation MCP connection is attempted at startup, but the tool functions fully without it. When the MCP server is unavailable, cerberus_best_practices() serves the built-in remediation guides. The agent's system prompt makes this transparent to the user without making it a point of failure.

Cross-session memory

The mem0_memory integration allows Cerberus to track findings across sessions. In practice this means a developer can return after a week and ask "have the critical issues I found last session been resolved?" — and get an answer grounded in what was actually found, not reconstructed from the current scan alone.

Limitations and Honest Assessment

Regex-based static analysis has known limitations that are worth naming directly.
False negatives on dynamic patterns. Multi-line SQL construction, dynamic exec calls built from variables, or credentials passed through several layers of indirection will not be caught. The patterns match what is visible on a single line.
False positives in test code. Hardcoded secrets in test fixtures, eval() in a REPL utility, or shell=True in a developer script will all trigger findings that may not be actionable. The directory scanner does not currently exclude test directories.
Language coverage is uneven. Python patterns are the most complete. JavaScript, Java, and Go patterns exist but are less comprehensive, because Python string formatting and resource management patterns do not map directly.
No data flow tracking. If a user-controlled string passes through three functions before reaching a SQL call, Cerberus will not connect them. This is the canonical limitation of line-level regex analysis versus AST traversal or taint analysis.
These are the right problems to solve in a future iteration, not weaknesses that undermine the current tool's value for its intended use case.

What Comes Next

The immediate next step is replacing regex with AST-based analysis for Python, using the ast module to parse the syntax tree rather than matching text. This would eliminate an entire class of false positives (patterns inside comments and strings) and enable cross-line detection.
Beyond that, the three-head framework maps cleanly onto additional MCP integrations:
  • GitHub MCP: Surface similar patterns found and fixed in public repositories
  • CVE database MCP: Cross-reference findings against known vulnerability records
  • OWASP MCP: Link each finding to the relevant OWASP category with its full context
The conversational interface also opens a path toward automated remediation — not code that silently edits your files, but a workflow where Cerberus proposes a fix, explains the trade-offs, and waits for the developer to approve it. The goal is a tool that develops the engineer, not one that replaces their judgment.

Conclusion

The most important thing Cerberus does is not finding bugs. Most experienced developers can find the bugs in the examples above if they look carefully enough. The value is in looking at every line, every time, across all three dimensions simultaneously, and being explicit about what assumption each finding violates.
Software defects are expensive precisely because they are invisible. A race condition does not announce itself. A hardcoded credential sits quietly until it is extracted from a public repository. An O(n²) algorithm works perfectly until the dataset grows.
Cerberus does not eliminate these problems. It makes them visible earlier and explains them in terms that help developers recognize the same pattern the next time they are about to write it.
That is what a guardian is supposed to do.

Technical Reference

Stack
  • Strands Agents SDK — agent orchestration
  • Amazon Nova Pro (us.amazon.nova-pro-v1:0) — reasoning and response generation
  • AWS Documentation MCP Server — real-time AWS best practices
  • mem0 — cross-session memory
  • Python standard library — re, pathlib, os
Detection coverage
  • Head 1 (Security): 25 patterns across 8 categories
  • Head 2 (Logic): 20 patterns across 8 categories
  • Head 3 (Architecture): 15 patterns across 5 categories
  • Supported file types: .py, .js, .ts, .java, .go, .rb, .php
Resources
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article