From d5547d97a5ffadb8b3b5c667837ef3ee50b51d69 Mon Sep 17 00:00:00 2001 From: "esprit-security-scan[bot]" <246782092+esprit-security-scan[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 00:55:48 +0000 Subject: [PATCH 1/2] fix: security fix for backend/prisma/schema.prisma --- backend/prisma/schema.prisma | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 1b580f3..1221db1 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -16,6 +16,7 @@ enum PageStatus { model Episode { id String @id @default(uuid()) + userId String // Owner of this episode seedInput Json outline Json? rendererModel String? @@ -24,12 +25,15 @@ model Episode { pages Page[] characters Character[] + @@index([userId]) @@index([createdAt]) @@index([updatedAt]) + @@index([userId, createdAt]) } model Page { id String @id @default(uuid()) + userId String // Owner of this page episodeId String pageNumber Int status PageStatus @@ -44,9 +48,11 @@ model Page { canvas Canvas? @@unique([episodeId, pageNumber]) + @@index([userId]) @@index([episodeId]) @@index([status]) @@index([episodeId, status]) + @@index([userId, episodeId]) } model Character { @@ -67,6 +73,7 @@ model Character { model Canvas { id String @id @default(uuid()) +userId String // Owner of this canvas pageId String @unique canvasData Json? // Serialized Fabric.js canvas data thumbnailUrl String? From 64d7a54a4d47618b61403900a3a0007ab55fe620 Mon Sep 17 00:00:00 2001 From: "esprit-security-scan[bot]" <246782092+esprit-security-scan[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 00:55:48 +0000 Subject: [PATCH 2/2] fix: security fix for backend/src/planner/planner.service.ts --- backend/src/planner/planner.service.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/backend/src/planner/planner.service.ts b/backend/src/planner/planner.service.ts index 90f45f4..7ed7314 100644 --- a/backend/src/planner/planner.service.ts +++ b/backend/src/planner/planner.service.ts @@ -18,6 +18,7 @@ import { import { generateStubOutline, mergeWithStub } from './planner.fallback'; import { LoggerService } from '../observability/logger.service'; import { TracingService } from '../observability/tracing.service'; +import { validateAndSanitizeSeed, containsInjectionPatterns } from './prompt-sanitizer'; /** * Error types for better error handling @@ -130,14 +131,26 @@ export class PlannerService { */ private validateInput(seed: EpisodeSeed): void { try { + // First validate structure with Zod EpisodeSeedSchema.parse(seed); - this.logger.debug('Input validation passed'); + + // Then sanitize and validate content + const sanitized = validateAndSanitizeSeed(seed); + + // Check for injection patterns in original seed (for monitoring) + const seedString = JSON.stringify(seed); + if (containsInjectionPatterns(seedString)) { + this.logger.warn('Potential prompt injection pattern detected in seed input'); + } + + this.logger.debug('Input validation and sanitization passed'); } catch (error) { if (error instanceof ZodError) { const details = formatZodError(error); this.logger.error('Input validation failed', undefined, { details }); throw new PlannerValidationError('Invalid episode seed data', details); } + this.logger.error('Input sanitization failed', error instanceof Error ? error.stack : undefined); throw error; } }