From 6112c891b1d00fbe5372928fc7e50eca512b2c2a Mon Sep 17 00:00:00 2001 From: anannayamustcode Date: Thu, 23 Jul 2026 18:31:51 +0530 Subject: [PATCH] fix: resolve false negative in asyncpg-sqli rule by migrating to taint mode Fixes #3027 The asyncpg-sqli rule failed to detect SQL injection when a tainted query was assigned to an intermediate variable (e.g. sql_query_copy = sql_query). This migrates the rule from AST pattern matching to mode: taint, which enables Semgrep's built-in data-flow tracking to follow taint through variable assignments. All existing test cases pass unchanged. Added a new test case (bad12) reproducing the exact scenario from #3027. --- .../lang/security/audit/sqli/asyncpg-sqli.py | 7 ++ .../security/audit/sqli/asyncpg-sqli.yaml | 96 +++++++------------ 2 files changed, 43 insertions(+), 60 deletions(-) diff --git a/python/lang/security/audit/sqli/asyncpg-sqli.py b/python/lang/security/audit/sqli/asyncpg-sqli.py index d0d0703205..ae277b0076 100644 --- a/python/lang/security/audit/sqli/asyncpg-sqli.py +++ b/python/lang/security/audit/sqli/asyncpg-sqli.py @@ -130,3 +130,10 @@ def ok11(user_input): # ok: asyncpg-sqli stmt = await con.prepare('SELECT ($1::int, $2::text)') print(stmt.get_parameters()) + +async def bad12(conn: asyncpg.Connection, user_input: str): + sql_query = 'SELECT * FROM {}'.format(user_input) + sql_query_copy = sql_query + # ruleid: asyncpg-sqli + cur = await conn.cursor(sql_query_copy) + diff --git a/python/lang/security/audit/sqli/asyncpg-sqli.yaml b/python/lang/security/audit/sqli/asyncpg-sqli.yaml index b28e305d43..3a6aae89ca 100644 --- a/python/lang/security/audit/sqli/asyncpg-sqli.yaml +++ b/python/lang/security/audit/sqli/asyncpg-sqli.yaml @@ -31,66 +31,42 @@ rules: likelihood: LOW impact: HIGH confidence: LOW - patterns: - - pattern-either: - - patterns: - - pattern: $CONN.$METHOD(...,$QUERY,...) - - pattern-either: - - pattern-inside: | - $QUERY = $X + $Y - ... - - pattern-inside: | - $QUERY += $X - ... - - pattern-inside: | - $QUERY = '...'.format(...) - ... - - pattern-inside: | - $QUERY = '...' % (...) - ... - - pattern-inside: | - $QUERY = f'...{$USERINPUT}...' - ... - - pattern-not-inside: | - $QUERY += "..." + mode: taint + pattern-sources: + - patterns: + - pattern-either: + - pattern: $X + $Y + - pattern: $X += $Y + - pattern: $Y.format(...) + - pattern: '"..." % $ANYTHING' + - pattern: f"...{$ANYTHING}..." + - pattern-not: '"..." + "..."' + - pattern-not: $X += "..." + - pattern-not: '"...".format()' + - pattern-not: '"..." % ()' + pattern-sinks: + - patterns: + - pattern: $CONN.$METHOD(..., $QUERY, ...) + - pattern-either: + - pattern-inside: | + $CONN = await asyncpg.connect(...) ... - - pattern-not-inside: | - $QUERY = "..." + "..." + - pattern-inside: | + async with asyncpg.create_pool(...) as $CONN: + ... + - pattern-inside: | + async with $POOL.acquire(...) as $CONN: + ... + - pattern-inside: | + $CONN = await $POOL.acquire(...) ... - - pattern-not-inside: | - $QUERY = '...'.format() - ... - - pattern-not-inside: | - $QUERY = '...' % () - ... - - pattern: $CONN.$METHOD(..., $X + $Y, ...) - - pattern: $CONN.$METHOD(..., $Y.format(...), ...) - - pattern: $CONN.$METHOD(..., '...'.format(...), ...) - - pattern: $CONN.$METHOD(..., '...' % (...), ...) - - pattern: $CONN.$METHOD(..., f'...{$USERINPUT}...', ...) - - pattern-either: - - pattern-inside: | - $CONN = await asyncpg.connect(...) - ... - - pattern-inside: | - async with asyncpg.create_pool(...) as $CONN: - ... - - pattern-inside: | - async with $POOL.acquire(...) as $CONN: - ... - - pattern-inside: | - $CONN = await $POOL.acquire(...) - ... - - pattern-inside: | - def $FUNCNAME(..., $CONN: Connection, ...): - ... - - pattern-inside: | - def $FUNCNAME(..., $CONN: asyncpg.Connection, ...): - ... - - pattern-not: $CONN.$METHOD(..., "..." + "...", ...) - - pattern-not: $CONN.$METHOD(..., '...'.format(), ...) - - pattern-not: $CONN.$METHOD(..., '...'%(), ...) - - metavariable-regex: - metavariable: $METHOD - regex: ^(fetch|fetchrow|fetchval|execute|executemany|prepare|cursor|copyfromquery)$ + - pattern-inside: | + def $FUNCNAME(..., $CONN: Connection, ...): + ... + - pattern-inside: | + def $FUNCNAME(..., $CONN: asyncpg.Connection, ...): + ... + - metavariable-regex: + metavariable: $METHOD + regex: ^(fetch|fetchrow|fetchval|execute|executemany|prepare|cursor|copyfromquery)$ severity: WARNING