VNX-1060: Excessive Number of Inefficient Server-Side Data Accesses
Python database queries (cursor.execute, session.query, Model.objects.get/filter, asyncpg fetch) issued inside a loop or comprehension with the loop variable, or a name derived from it, as an argument: an N+1 query pattern. Chunked IN batches are not reported.
| Field | Value |
|---|---|
| Rule ID | VNX-1060 |
| Severity | Low |
| SARIF level | note |
| Kind | sast |
| Languages | Python |
| CWE | CWE-1060 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:L/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:L/DI:H/EX:L/EC:N/P:L |
| Tags | inefficient-server-side-access n-plus-one performance python cwe-1060 |
| Source | internal/sast/rules/vnx-1060.rego |
Overview
VNX-1060 maps to CWE-1060: Excessive Number of Inefficient Server-Side Data Accesses. Python N+1 query: a database query (DB-API cursor/connection .execute(), SQLAlchemy session.query()/Model.query.get(), Django Model.objects.get()/filter(), asyncpg conn.fetch()/fetchrow()/fetchval()) issued inside the body of a for/async for loop, or inside a list/dict/set comprehension, with the loop variable, or a name the loop body assigns from it, in the query’s arguments. Each iteration makes one more round trip to the database where one batched query (WHERE id IN (…), select_related/joinedload, executemany) would do. Not reported: a query inside a loop that does not use the loop variable (a retry loop), a loop that hands each chunk to one IN/ANY/_in/in() query, a loop whose variable is the statement being executed (a schema or migration runner), executemany(), a query after the loop has ended, queries built and run once from a list the loop collected, and the pattern in strings or comments. Loops are recognised by indentation within 25 lines above the query; queries reached through helper functions are not followed.
Why This Matters
This weakness class (Excessive Number of Inefficient Server-Side Data Accesses) creates a concrete exploit surface: the rule searches for the concrete source-level patterns most commonly associated with CWE-1060 and surfaces them for review. Each finding should be evaluated in context, the rule catches the pattern, not the context.
What Gets Flagged
# FLAGGED: one query per row
for user_id in user_ids:
cursor.execute("SELECT * FROM orders WHERE user_id = %s", (user_id,))
# FLAGGED: ORM lookup in a comprehension
users = [User.query.get(i) for i in ids]
# FLAGGED: a name derived from the loop variable, asyncpg
for inv in invoices:
invoice_id = inv["id"]
cursor.execute("SELECT sku FROM invoice_line WHERE invoice_id = %s", [invoice_id])
Not flagged: cursor.executemany(...), one WHERE id IN (...) query built from the collected ids, a chunked loop that runs WHERE id IN %s once per batch, for statement in SCHEMA: cur.execute(statement), or a retry loop whose query does not use the loop variable.
Remediation
- Review each flagged line and determine whether the pattern represents a real instance of CWE-1060 or a false positive.
- Replace the flagged construct with a documented safe alternative appropriate to your language and framework.
- For confirmed false positives, add a
# vulnetix-ignore: VNX-1060comment on the line. - Ensure equivalent test coverage exists to prevent regression.
References
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-1060
// nosec vnx-1060
Or record an ignore rule: vulnetix ignore add --rule vnx-1060 --reason "...". See Suppressing findings.