Interactive Playground
Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.
pathfinder scan --ruleset python/PYTHON-LAMBDA-SEC-014 --project .About This Rule
Understanding the vulnerability and how it is detected
This rule detects SQL injection vulnerabilities in AWS Lambda functions where untrusted event data flows into SQLAlchemy session.execute() or connection.execute() calls without bound parameters, enabling SQL injection against RDS MySQL, PostgreSQL, SQL Server, or any SQLAlchemy-supported backend.
SQLAlchemy is widely used in Lambda functions for its ORM capabilities and support for multiple database backends. When used correctly with the ORM or with text() and bound parameters, SQLAlchemy prevents injection. However, two unsafe patterns are common in Lambda handlers: passing a raw f-string SQL query to text() without bound parameters, and passing event data directly to execute() as an unparameterized string.
Lambda functions receive event data from API Gateway, SQS, SNS, S3, and other triggers (event.get("body"), event.get("queryStringParameters"), event["Records"]). This data is attacker-controllable. When it is embedded in the SQL string argument to text() or execute() via f-strings or concatenation, the injection bypasses all SQLAlchemy protections since SQLAlchemy only escapes values that are passed through its bound parameter mechanism (text() with :parameter syntax and bindparams()).
Security Implications
Potential attack scenarios if this vulnerability is exploited
False Safety from SQLAlchemy's Reputation
Developers often assume that using SQLAlchemy automatically prevents SQL injection. This is true for ORM operations (query(), filter(), Session.add()) but not for raw SQL via execute(). When event data is embedded in a string passed to text() or execute() directly, SQLAlchemy provides no injection protection. The false sense of security from using SQLAlchemy can cause security reviews to overlook these vulnerable call sites.
Multi-Database Backend Exposure
SQLAlchemy supports MySQL, PostgreSQL, SQL Server, Oracle, and SQLite. A Lambda function using SQLAlchemy with any of these backends is equally vulnerable to injection through unparameterized execute() calls. The attacker's injection payload adapts to the specific SQL dialect, enabling database-specific attack techniques on each backend.
Full Database Access via Session Context
SQLAlchemy sessions are often configured with broad database permissions for ORM convenience. An injected SQL string through session.execute() executes with all permissions of the session's database user, which may include read/write access to all tables in the schema.
ORM Bypass via Raw execute()
The presence of SQLAlchemy ORM models and safe ORM queries in the same Lambda function does not protect against injection in raw execute() calls. Both safe and unsafe call sites can coexist in the same Lambda handler, and only the unsafe raw execute() calls with event data need to be fixed.
How to Fix
Recommended remediation steps
- 1Always use SQLAlchemy's text() with :parameter_name syntax and pass values as a dictionary to execute(), never embed event data in the SQL string.
- 2Prefer SQLAlchemy ORM methods (session.query(), session.get(), filter()) over raw SQL to benefit from automatic parameterization.
- 3Validate and type-convert event fields before use (e.g., int() for numeric IDs) as a defense-in-depth measure.
- 4Grant the Lambda's database user the minimum necessary privileges; avoid broad permissions even when using SQLAlchemy ORM.
- 5Initialize the SQLAlchemy engine at module level for connection reuse across warm Lambda invocations.
Detection Scope
How Code Pathfinder analyzes your code for this vulnerability
This rule performs inter-procedural taint analysis with global scope. Sources are Lambda event dictionary access calls: calls("event.get"), calls("event.__getitem__"), including event.get("body"), event.get("queryStringParameters"), event.get("pathParameters"), and event["Records"]. Sinks are calls("*.execute") matching SQLAlchemy session or connection objects with the tainted SQL string tracked via .tracks(0). The rule flags cases where event data reaches the SQL string argument without being passed through the bound parameters dictionary. Sanitizers include explicit int() or float() type conversion. The analysis follows taint through f-strings, concatenation, variable assignments, and module boundaries.
Compliance & Standards
Industry frameworks and regulations that require detection of this vulnerability
References
External resources and documentation
Similar Rules
Explore related security rules for Python
Lambda SQL Injection via psycopg2 cursor.execute()
Lambda event data flows to psycopg2 cursor.execute() without parameterization, enabling SQL injection against RDS PostgreSQL or Aurora PostgreSQL backends.
Lambda SQL Injection via pymssql cursor.execute()
Lambda event data flows to pymssql cursor.execute() without parameterization, enabling SQL injection against RDS SQL Server backends.
Lambda SQL Injection via PyMySQL cursor.execute()
Lambda event data flows to PyMySQL cursor.execute() without parameterization, enabling SQL injection against RDS MySQL or Aurora MySQL backends via the pure-Python driver.
Frequently Asked Questions
Common questions about Lambda SQL Injection via SQLAlchemy execute()
New feature
Get these findings posted directly on your GitHub pull requests
The Lambda SQL Injection via SQLAlchemy execute() rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.