-
-
Notifications
You must be signed in to change notification settings - Fork 134
Description
Description
$queryRawUnsafe, $executeRawUnsafe, $queryRaw, and $executeRaw fail with "non-CRUD queries are not allowed" when PolicyPlugin is active.
Reproduction
const client = new ZenStackClient(schema, {
dialect: new PostgresDialect({ pool }),
plugins: [new PolicyPlugin()],
});
// This throws: "non-CRUD queries are not allowed"
await client.$queryRawUnsafe('SELECT 1');Root cause
In client-impl.ts, all four raw methods execute through this.kysely, which routes queries through ZenStackQueryExecutor and the plugin pipeline. PolicyPlugin's handle() (policy-handler.ts:78-84) rejects any node that isn't Select/Insert/Update/Delete:
if (!this.isCrudQueryNode(node)) {
throw createRejectedByPolicyError(
undefined,
RejectedByPolicyReason.OTHER,
'non-CRUD queries are not allowed',
);
}Raw SQL produces a RawNode, not a CRUD node, so it's always rejected.
Why this should be fixed
Raw SQL has no model-level semantics — there's no table/row context for PolicyPlugin to enforce access control on. The caller is fully responsible for the SQL they write. Blocking raw SQL forces users to either:
- Maintain a separate non-policy client just for raw queries
- Call
$unuseAll()before every raw query (which also has a transaction isolation bug, see $unuseAll() and $unuse() break transaction isolation #2494) - Mutate
$options.pluginsat runtime to temporarily remove PolicyPlugin
All three workarounds are fragile and error-prone.
Suggested fix
Route raw SQL methods through kyselyRaw instead of this.kysely:
$queryRawUnsafe<T = unknown>(query: string, ...values: any[]) {
return createZenStackPromise(async () => {
const compiledQuery = this.createRawCompiledQuery(query, values);
const result = await this.kyselyRaw.executeQuery(compiledQuery);
return result.rows as T;
});
}Note: kyselyRaw would also need to be updated inside interactiveTransaction to preserve transaction isolation (same pattern as this.kysely = tx).
Alternatively, PolicyPlugin could pass through non-CRUD nodes instead of rejecting them.
Version
@zenstackhq/orm 3.4.6, @zenstackhq/plugin-policy 3.4.6