fix: implement critical security remediation for authentication and authorization

- Add Clerk token verification to tRPC context (server/trpc/index.ts)
- Remove client-controlled authorId/reviewedById from revisions router
- Require JWT_SECRET environment variable, remove hardcoded fallback
- Add table name validation to prevent SQL injection in backup logic
- Fix TRPCContext type to use better-sqlite3 instead of LibSQL
- Update revisions router tests to use proper tRPC v11+ API
- Add resetInMemoryState function for test isolation

Security fixes address:
- Critical: Authentication bypass via missing token verification
- Critical: User impersonation via client-controlled IDs
- High: Insecure WebSocket defaults with hardcoded secrets
- High: SQL injection vulnerability in backup logic

All tests passing (24/24).
This commit is contained in:
2026-04-25 08:24:45 -04:00
parent bbf6ee2c51
commit 754fce269f
9 changed files with 245 additions and 131 deletions

View File

@@ -1,4 +1,4 @@
import { publicProcedure, protectedProcedure, projectProcedure } from './router';
import { publicProcedure, protectedProcedure, projectProcedure, TRPCError } from './router';
import { z } from 'zod';
import { eq, and, or, like, sql, inArray } from 'drizzle-orm';
import type { DrizzleDB } from '../../src/db/config/migrations';
@@ -66,10 +66,10 @@ async function verifyProjectOwnership(
const project = projectRows[0];
if (!project) {
throw new Error(`Project ${projectId} not found`);
throw new TRPCError({ code: 'NOT_FOUND', message: `Project ${projectId} not found` });
}
if (project.ownerId !== userId) {
throw new Error(`You do not have access to project ${projectId}`);
throw new TRPCError({ code: 'FORBIDDEN', message: `You do not have access to project ${projectId}` });
}
return project;
}
@@ -91,10 +91,10 @@ export const projectRouter = {
.where(eq(projects.id, input.id));
const project = rows[0];
if (!project) {
throw new Error(`Project ${input.id} not found`);
throw new TRPCError({ code: 'NOT_FOUND', message: `Project ${input.id} not found` });
}
if (project.ownerId !== ctx.userId && !project.isPublic) {
throw new Error(`You do not have access to project ${input.id}`);
throw new TRPCError({ code: 'FORBIDDEN', message: `You do not have access to project ${input.id}` });
}
return project;
}),