From e3551fb78feee0b9c56ba3d8dd8c01c312cad688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Sat, 11 Apr 2026 08:35:02 +0200 Subject: [PATCH] fix(api): validate rolePresets with RolePresetsSchema before DB cast Replace z.array(z.unknown()) with RolePresetsSchema for blueprint role presets mutation input, ensuring structural validation before Prisma JSON cast. Also adds SECURITY.md for vulnerability disclosure. Co-Authored-By: Claude Opus 4.6 --- .github/SECURITY.md | 27 +++++++++++++++++++ .../src/router/blueprint-procedure-support.ts | 9 +++++-- packages/api/src/router/blueprint-support.ts | 14 +++++----- 3 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 .github/SECURITY.md diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..4ae8a61 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,27 @@ +# Security Policy + +## Reporting a Vulnerability + +If you discover a security vulnerability in CapaKraken, please report it responsibly. + +**Do not** open a public GitHub issue for security vulnerabilities. + +Instead, please email the maintainer directly with: + +1. A description of the vulnerability +2. Steps to reproduce +3. Potential impact assessment + +We will acknowledge receipt within 48 hours and provide a timeline for resolution. + +## Supported Versions + +Only the latest version on the `main` branch receives security updates. + +## Security Practices + +- Dependencies are audited nightly via `pnpm audit` and on every CI run +- Authentication uses Argon2-based password hashing via Auth.js v5 +- Rate limiting is enforced on all API endpoints with Redis-backed counters +- All database mutations use parameterized queries via Prisma (no raw SQL) +- Session tokens are rotated on password change diff --git a/packages/api/src/router/blueprint-procedure-support.ts b/packages/api/src/router/blueprint-procedure-support.ts index 3a7ef43..e819d93 100644 --- a/packages/api/src/router/blueprint-procedure-support.ts +++ b/packages/api/src/router/blueprint-procedure-support.ts @@ -1,4 +1,9 @@ -import { BlueprintTarget, CreateBlueprintSchema, UpdateBlueprintSchema } from "@capakraken/shared"; +import { + BlueprintTarget, + CreateBlueprintSchema, + RolePresetsSchema, + UpdateBlueprintSchema, +} from "@capakraken/shared"; import { z } from "zod"; import { findUniqueOrThrow } from "../db/helpers.js"; import { makeAuditLogger } from "../lib/audit-helpers.js"; @@ -54,7 +59,7 @@ export const blueprintUpdateInputSchema = z.object({ export const blueprintRolePresetsInputSchema = z.object({ id: z.string(), - rolePresets: z.array(z.unknown()).max(100), + rolePresets: RolePresetsSchema.max(100), }); export const blueprintBatchDeleteInputSchema = z.object({ diff --git a/packages/api/src/router/blueprint-support.ts b/packages/api/src/router/blueprint-support.ts index ed9f592..c8abb41 100644 --- a/packages/api/src/router/blueprint-support.ts +++ b/packages/api/src/router/blueprint-support.ts @@ -30,23 +30,23 @@ export async function findBlueprintByIdentifier( ): Promise { const normalizedIdentifier = identifier.trim(); - let blueprint = await db.blueprint.findUnique({ + let blueprint = (await db.blueprint.findUnique({ where: { id: normalizedIdentifier }, ...extraArgs, - }) as TBlueprint | null; + })) as TBlueprint | null; if (!blueprint) { - blueprint = await db.blueprint.findFirst({ + blueprint = (await db.blueprint.findFirst({ where: { name: { equals: normalizedIdentifier, mode: "insensitive" } }, ...extraArgs, - }) as TBlueprint | null; + })) as TBlueprint | null; } if (!blueprint) { - blueprint = await db.blueprint.findFirst({ + blueprint = (await db.blueprint.findFirst({ where: { name: { contains: normalizedIdentifier, mode: "insensitive" } }, ...extraArgs, - }) as TBlueprint | null; + })) as TBlueprint | null; } if (!blueprint) { @@ -91,7 +91,7 @@ export function buildBlueprintUpdateData( } export function buildBlueprintRolePresetsUpdateData( - rolePresets: unknown[], + rolePresets: readonly Record[], ): Prisma.BlueprintUncheckedUpdateInput { return { rolePresets: rolePresets as unknown as Prisma.InputJsonValue,