Summary
Strict scope validation is syntactic: it checks whether the scoped column appears somewhere in the predicate, not whether the predicate is an equality to the active scope value. Runtime injection still protects data, but tests/docs should lock in and explain this behavior to avoid a false sense of semantic validation.
Evidence
Relevant code:
- SQL path:
src/drizzle-compat.ts containsColumnFilter(...) recursively searches SQL chunks for a matching column.
- RQBv2 path:
src/internal/relational/rqb-v2-object-filter.ts checks key presence under AND / OR / NOT.
Neither path verifies operator semantics or compared value.
Examples that may pass strict validation
// SQL: mentions workspace_id but is not the required equality.
where(ne(projects.workspaceId, workspaceId));
where(isNotNull(projects.workspaceId));
where(sql`${projects.workspaceId}`);
// RQBv2: key appears under OR/NOT.
where: { OR: [{ workspaceId }, { id: "project-2" }] }
where: { NOT: { workspaceId: "workspace-2" } }
The wrapper still injects the real scope predicate afterward, so this is not necessarily a tenant leak. The risk is misunderstanding what MissingScopedPredicateError proves.
Expected behavior
Either:
- document strict mode as requiring a scope-column mention, while the wrapper's injected predicate is the actual security enforcement; or
- strengthen validation to detect specific supported equality forms.
Option 1 is likely more robust because Drizzle SQL is opaque and arbitrary SQL cannot be semantically proven safely.
Suggested tests/docs
- Unit tests showing non-equality scope-column predicates pass strict validation but final executed condition still includes the injected scope equality.
- Real-driver test showing a misleading predicate such as
ne(projects.workspaceId, workspaceId) returns no cross-scope rows because the injected guard is ANDed in.
- README strict-mode wording update: strict mode checks that callers did not omit scope context; the wrapper still injects the authoritative scope predicate as defense in depth.
- RQBv2 object-filter tests for
OR / NOT behavior, documenting the current syntactic model.
Summary
Strict scope validation is syntactic: it checks whether the scoped column appears somewhere in the predicate, not whether the predicate is an equality to the active scope value. Runtime injection still protects data, but tests/docs should lock in and explain this behavior to avoid a false sense of semantic validation.
Evidence
Relevant code:
src/drizzle-compat.tscontainsColumnFilter(...)recursively searches SQL chunks for a matching column.src/internal/relational/rqb-v2-object-filter.tschecks key presence underAND/OR/NOT.Neither path verifies operator semantics or compared value.
Examples that may pass strict validation
The wrapper still injects the real scope predicate afterward, so this is not necessarily a tenant leak. The risk is misunderstanding what
MissingScopedPredicateErrorproves.Expected behavior
Either:
Option 1 is likely more robust because Drizzle SQL is opaque and arbitrary SQL cannot be semantically proven safely.
Suggested tests/docs
ne(projects.workspaceId, workspaceId)returns no cross-scope rows because the injected guard is ANDed in.OR/NOTbehavior, documenting the current syntactic model.