fix(SEC-API-27): Scope RLS context to transaction boundary
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
createAuthMiddleware was calling SET LOCAL on the raw PrismaClient outside of any transaction. In PostgreSQL, SET LOCAL without a transaction acts as a session-level SET, which can leak RLS context to subsequent requests sharing the same pooled connection, enabling cross-tenant data access. Wrapped the setCurrentUser call and downstream handler execution inside a $transaction block so SET LOCAL is automatically reverted when the transaction ends (on both success and failure). Added comprehensive test suite for db-context module verifying: - RLS context is set on the transaction client, not the raw client - next() executes inside the transaction boundary - Authentication errors prevent any transaction from starting - Errors in downstream handlers propagate correctly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -349,12 +349,18 @@ export function createAuthMiddleware(client: PrismaClient) {
|
||||
ctx: { userId?: string };
|
||||
next: () => Promise<unknown>;
|
||||
}): Promise<unknown> {
|
||||
if (!opts.ctx.userId) {
|
||||
const { userId } = opts.ctx;
|
||||
if (!userId) {
|
||||
throw new Error("User not authenticated");
|
||||
}
|
||||
|
||||
await setCurrentUser(opts.ctx.userId, client);
|
||||
return opts.next();
|
||||
// SEC-API-27: SET LOCAL must be called inside a transaction boundary.
|
||||
// Without a transaction, SET LOCAL behaves as a session-level SET,
|
||||
// which can leak RLS context to other requests via connection pooling.
|
||||
return client.$transaction(async (tx) => {
|
||||
await setCurrentUser(userId, tx as PrismaClient);
|
||||
return opts.next();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user