Skip to content

VNX-566: Authorization Bypass Through User-Controlled SQL Primary Key

Reports a Java or Python SELECT, UPDATE or DELETE whose only WHERE condition is a key column compared with a request-supplied value, with no ownership condition in the query and no ownership check nearby.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-566
Severity High
SARIF level error
Kind sast
Languages Java, Python
CWE CWE-566
CAPEC CAPEC-66
ATT&CK T1190
CWSS vector CWSS:1.0/TI:H/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H
Tags sql-auth-bypass idor cwe-566
Source internal/sast/rules/vnx-566.rego

Overview

VNX-566 maps to CWE-566: Authorization Bypass Through User-Controlled SQL Primary Key. It reports a query whose only WHERE condition is a key column (id, uuid, <name>_id, <name>Id) compared with a value the request supplied. Nothing in the query ties the row to the caller, so changing the key in the URL or form reads or changes somebody else’s record (an insecure direct object reference).

What Gets Flagged

The key counts as request-supplied when it comes from request.getParameter/getHeader, a @PathVariable/@RequestParam/@PathParam/@QueryParam parameter, Flask request.args/form/values/json, Django request.GET/POST or a Django view argument, or a Flask route placeholder, and it reaches the statement: concatenated, interpolated, passed as a bind argument, or bound with ps.setX(n, key) on the next lines.

String invoiceId = request.getParameter("invoiceId");
PreparedStatement ps = c.prepareStatement("SELECT amount, customer FROM invoices WHERE id = ?");
ps.setString(1, invoiceId);
@app.route("/profile/<int:profile_id>")
def show_profile(profile_id):
    row = query(f"SELECT name, email, phone FROM profiles WHERE profile_id = {profile_id}")
def document_detail(request, doc_id):
    with connection.cursor() as cursor:
        cursor.execute("SELECT title, body FROM documents WHERE id = %s", [doc_id])

What Is Not Flagged

  • A query with a further condition: WHERE id = ? AND customer_id = ?, also when the AND is on the next concatenated line.
  • A key from the session or another server-side value, and constant keys (WHERE id = 1).
  • A nearby ownership or authorization check: owner, current_user, request.user, getUserPrincipal, @PreAuthorize, @Secured, @RolesAllowed, permission_required.
  • A key already passed to a lookup function a few lines above (get_post(id)), which commonly loads the record and checks its owner.
  • SQL in comments and in strings that are not queries.

Ownership enforced in another function the rule cannot see, a database row-level security policy and ORM lookups are out of scope.

Remediation

  1. Scope the query to the caller: SELECT ... FROM invoices WHERE id = ? AND customer_id = ?, binding the customer from the authenticated session, not from the request.
  2. Or load the row and compare its owner with the authenticated user before returning or changing it, answering 404 when they differ.
  3. Prefer unguessable identifiers (UUIDs) as defence in depth; they do not replace the check.

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-566
// nosec vnx-566

Or record an ignore rule: vulnetix ignore add --rule vnx-566 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-566.rego, cli/internal/sast/docs/vnx-566.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.