security: bound Zod inputs, add SSE per-user cap and tRPC body limit (#51, PR #59)
CI / Architecture Guardrails (push) Successful in 3m38s
CI / Assistant Split Regression (push) Successful in 4m40s
CI / Lint (push) Successful in 5m17s
CI / Typecheck (push) Successful in 5m46s
CI / Build (push) Successful in 7m1s
CI / Unit Tests (push) Failing after 9m41s
CI / Release Images (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / E2E Tests (push) Has started running

Closes #51 (ESLint rule + conventions doc remain as follow-up).

Co-authored-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
Co-committed-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
This commit was merged in pull request #59.
This commit is contained in:
2026-04-18 13:53:28 +02:00
committed by Hartmut
parent f0251a654a
commit 17471af7f8
12 changed files with 254 additions and 148 deletions
@@ -12,9 +12,21 @@ type ImportExportMutationContext = ImportExportReadContext & {
type ImportRow = Record<string, string>;
const CSV_CELL_MAX = 4000;
const CSV_COLUMNS_MAX = 100;
const CSV_ROWS_MAX = 10_000;
export const importCsvInputSchema = z.object({
entityType: z.enum(["resources", "projects", "allocations"]),
rows: z.array(z.record(z.string(), z.string())),
rows: z
.array(
z
.record(z.string().max(200), z.string().max(CSV_CELL_MAX))
.refine((row) => Object.keys(row).length <= CSV_COLUMNS_MAX, {
message: `CSV row exceeds ${CSV_COLUMNS_MAX} columns`,
}),
)
.max(CSV_ROWS_MAX),
dryRun: z.boolean().default(true),
});
@@ -32,7 +44,10 @@ function resolveVisibleBlueprintFields(fieldDefs: unknown): BlueprintFieldDefini
}
function buildCsv(headers: unknown[], rows: unknown[][]) {
return [headers.map(escapeCsvValue).join(","), ...rows.map((row) => row.map(escapeCsvValue).join(","))].join("\n");
return [
headers.map(escapeCsvValue).join(","),
...rows.map((row) => row.map(escapeCsvValue).join(",")),
].join("\n");
}
export async function exportResourcesCsv(ctx: ImportExportReadContext) {
@@ -168,7 +183,10 @@ export async function importCsv(ctx: ImportExportMutationContext, input: ImportC
try {
if (input.entityType === "resources") {
const outcome = await importResourceRow({ ...ctx, db: tx as unknown as typeof ctx.db }, row);
const outcome = await importResourceRow(
{ ...ctx, db: tx as unknown as typeof ctx.db },
row,
);
if (outcome.updated) {
results.updated += 1;
} else if (outcome.error) {