Skip to content

Commit d65b589

Browse files
committed
ref(lint): Flag ORM lookups that may match NULL rows
Adds lookupMayMatchNullRows to the decision-model linter's security suite. An exact lookup like filter(user_id=x) with x None means user_id IS NULL, which matched pending invites and orphaned rows for callers with no user. The rule warns on changed code and says how to decide what None should mean.
1 parent 1baaaff commit d65b589

1 file changed

Lines changed: 19 additions & 0 deletions

File tree

‎decisionModelLinter.config.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,25 @@ const security = {
204204
'src/sentry/new_migrations/**',
205205
],
206206
rules: {
207+
lookupMayMatchNullRows: {
208+
prompt:
209+
'Does the code put a value that is None for some callers or rows into an exact ORM lookup keyword (in filter(), exclude(), get(), get_or_create(), update_or_create() or Q()) without checking for None first? Examples: user_id=request.user.id, which is None for API keys and anonymous callers; user_id=member.user_id, which is None for a pending invite; application=app.application or user=obj.user when that foreign key is nullable; a parameter typed int | None. It does not count when the code checks the value first (if x is None: return), adds an __isnull=False lookup for the same field, passes a literal None, writes x.id if x else None on purpose to match NULL rows, or uses a value that is always set, such as a primary key or a required field.',
210+
values: [
211+
{
212+
value: 'TRUE',
213+
description:
214+
'A lookup keyword takes request.user.id, a member or object user id, a nullable foreign key, or an optional parameter that can be None, with no None check, so None silently matches rows where the column IS NULL.',
215+
severity: 'warn',
216+
report:
217+
"`filter(field=None)` matches rows where the column is NULL. Decide what None means here: no rows (check for None first, or use `owned_by_acting_user()` for the caller's own rows), no filter (leave the lookup out), or NULL rows (pass None or use `__isnull=True`). See src/AGENTS.md, Request Principals.",
218+
},
219+
{
220+
value: 'FALSE',
221+
description:
222+
'Every lookup value is always set or checked for None first, or matching NULL rows is clearly intended.',
223+
},
224+
],
225+
},
207226
comparesSecretWithEquality: {
208227
// Raised to cut borderline false positives, accepting that some real findings score below it.
209228
threshold: 0.8,

0 commit comments

Comments
 (0)