security: bound password inputs, configure pino redact, patch deps (#36 #46 #58)

#36 CRITICAL: add .max(128) to all password Zod schemas to prevent
Argon2-based DoS from unbounded password strings.

#46 HIGH: configure pino redact paths so passwords/tokens/cookies/TOTP
secrets are never serialized in logs.

#58 MEDIUM: upgrade dompurify to ^3.4.0 and add pnpm overrides for
brace-expansion (>=5.0.5) and esbuild (>=0.25.0) to patch known CVEs.
Vite moderate (path traversal, dev-only) remains — requires vitest 3.x
major upgrade, deferred.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-17 08:13:25 +02:00
parent 0ef9add935
commit 534945f6e3
8 changed files with 110 additions and 304 deletions
+13 -6
View File
@@ -99,8 +99,10 @@ export const inviteRouter = createTRPCRouter({
select: { email: true, role: true, expiresAt: true, usedAt: true },
});
if (!invite) throw new TRPCError({ code: "NOT_FOUND", message: "Invite not found." });
if (invite.usedAt) throw new TRPCError({ code: "BAD_REQUEST", message: "This invite has already been used." });
if (invite.expiresAt < new Date()) throw new TRPCError({ code: "BAD_REQUEST", message: "This invite has expired." });
if (invite.usedAt)
throw new TRPCError({ code: "BAD_REQUEST", message: "This invite has already been used." });
if (invite.expiresAt < new Date())
throw new TRPCError({ code: "BAD_REQUEST", message: "This invite has expired." });
return { email: invite.email, role: invite.role };
}),
@@ -109,7 +111,7 @@ export const inviteRouter = createTRPCRouter({
.input(
z.object({
token: z.string(),
password: z.string().min(12, "Password must be at least 12 characters."),
password: z.string().min(12, "Password must be at least 12 characters.").max(128),
}),
)
.mutation(async ({ ctx, input }) => {
@@ -125,13 +127,18 @@ export const inviteRouter = createTRPCRouter({
where: { token: input.token },
});
if (!invite) throw new TRPCError({ code: "NOT_FOUND", message: "Invite not found." });
if (invite.usedAt) throw new TRPCError({ code: "BAD_REQUEST", message: "This invite has already been used." });
if (invite.expiresAt < new Date()) throw new TRPCError({ code: "BAD_REQUEST", message: "This invite has expired." });
if (invite.usedAt)
throw new TRPCError({ code: "BAD_REQUEST", message: "This invite has already been used." });
if (invite.expiresAt < new Date())
throw new TRPCError({ code: "BAD_REQUEST", message: "This invite has expired." });
// Check if user already exists
const existing = await ctx.db.user.findUnique({ where: { email: invite.email } });
if (existing) {
throw new TRPCError({ code: "CONFLICT", message: "An account with this email already exists." });
throw new TRPCError({
code: "CONFLICT",
message: "An account with this email already exists.",
});
}
const { hash } = await import("@node-rs/argon2");