From c8f932344f012891e9b3762ea3c51c4d426ab611 Mon Sep 17 00:00:00 2001 From: Nyk <0xnykcd@googlemail.com> Date: Fri, 27 Feb 2026 21:57:50 +0700 Subject: [PATCH] fix: patch command injection, missing rate limit, Docker build, logger crash - Sanitize session ID in control route to prevent command injection via unsanitized URL params interpolated into shell commands - Add mutationLimiter and structured logging to session control endpoint - Install python3/make/g++ in Dockerfile deps stage for better-sqlite3 native addon compilation - Handle missing public/ directory in Docker COPY with glob pattern - Guard pino-pretty transport against missing devDependency at runtime --- Dockerfile | 5 ++++- src/app/api/sessions/[id]/control/route.ts | 16 ++++++++++++++++ src/lib/logger.ts | 13 ++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0beefce..3e1296c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,8 @@ WORKDIR /app FROM base AS deps COPY package.json pnpm-lock.yaml ./ +# better-sqlite3 requires native compilation tools +RUN apt-get update && apt-get install -y python3 make g++ --no-install-recommends && rm -rf /var/lib/apt/lists/* RUN pnpm install --frozen-lockfile FROM base AS build @@ -17,7 +19,8 @@ ENV NODE_ENV=production RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs COPY --from=build /app/.next/standalone ./ COPY --from=build /app/.next/static ./.next/static -COPY --from=build /app/public ./public +# Copy public directory if it exists (may not exist in all setups) +COPY --from=build /app/public* ./public/ USER nextjs EXPOSE 3000 CMD ["node", "server.js"] diff --git a/src/app/api/sessions/[id]/control/route.ts b/src/app/api/sessions/[id]/control/route.ts index 573c9d5..2500fb8 100644 --- a/src/app/api/sessions/[id]/control/route.ts +++ b/src/app/api/sessions/[id]/control/route.ts @@ -2,6 +2,11 @@ import { NextRequest, NextResponse } from 'next/server' import { requireRole } from '@/lib/auth' import { runClawdbot } from '@/lib/command' import { db_helpers } from '@/lib/db' +import { mutationLimiter } from '@/lib/rate-limit' +import { logger } from '@/lib/logger' + +// Only allow alphanumeric, hyphens, and underscores in session IDs +const SESSION_ID_RE = /^[a-zA-Z0-9_-]+$/ export async function POST( request: NextRequest, @@ -10,10 +15,20 @@ export async function POST( const auth = requireRole(request, 'operator') if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + const rateCheck = mutationLimiter(request) + if (rateCheck) return rateCheck + try { const { id } = await params const { action } = await request.json() + if (!SESSION_ID_RE.test(id)) { + return NextResponse.json( + { error: 'Invalid session ID format' }, + { status: 400 } + ) + } + if (!['monitor', 'pause', 'terminate'].includes(action)) { return NextResponse.json( { error: 'Invalid action. Must be: monitor, pause, terminate' }, @@ -53,6 +68,7 @@ export async function POST( stdout: result.stdout.trim(), }) } catch (error: any) { + logger.error({ err: error }, 'Session control error') return NextResponse.json( { error: error.message || 'Session control failed' }, { status: 500 } diff --git a/src/lib/logger.ts b/src/lib/logger.ts index b9f1117..1dd7d13 100644 --- a/src/lib/logger.ts +++ b/src/lib/logger.ts @@ -1,8 +1,19 @@ import pino from 'pino' +function hasPinoPretty(): boolean { + try { + require.resolve('pino-pretty') + return true + } catch { + return false + } +} + +const usePretty = process.env.NODE_ENV !== 'production' && hasPinoPretty() + export const logger = pino({ level: process.env.LOG_LEVEL || 'info', - ...(process.env.NODE_ENV !== 'production' && { + ...(usePretty && { transport: { target: 'pino-pretty', options: { colorize: true },