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
This commit is contained in:
parent
0165173225
commit
c8f932344f
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
Loading…
Reference in New Issue