Files
sencho/Dockerfile
T
SaelixCode ce50db0fde security: pre-release hardening, automated testing, and production readiness
SECURITY (critical fixes):
- Add authMiddleware to /api/system/console-token (was publicly accessible)
- Validate api_url on node create/update to prevent SSRF (rejects localhost/loopback)
- Add rate limiting (5 req/15 min/IP) to /api/auth/login and /api/auth/setup
- Fix path traversal in env_file resolution — absolute/escaping paths rejected
- Add stack name validation to GET routes (was only on PUT/POST)
- Add helmet security headers middleware
- Restrict CORS to FRONTEND_URL in production

PRODUCTION READINESS:
- Add GET /api/health public endpoint + HEALTHCHECK in Dockerfile
- Add SIGTERM/SIGINT graceful shutdown handler (drains connections, closes DB)
- Run container as non-root sencho user in Dockerfile

QUALITY:
- Fix 4 silent empty catch{} blocks in EditorLayout (now show toast.error)
- Connect ErrorBoundary to root App in main.tsx
- Replace WebSocket.Server with named WebSocketServer import (ESM compat)

TESTING (new automated test suite):
- Install Vitest; 38 backend tests across 4 suites covering validation utilities,
  health endpoint, auth middleware, login flows, SSRF protection, and path traversal
- Extract isValidStackName/isValidRemoteUrl/isPathWithinBase to utils/validation.ts
- Playwright E2E scaffolding: auth, stacks, nodes specs + shared login helper
- CI: run Vitest + ESLint on every PR
2026-03-21 21:59:44 -04:00

79 lines
2.1 KiB
Docker

# Stage 1: Build Frontend
FROM node:20-alpine AS frontend-builder
WORKDIR /app/frontend
# Copy frontend package files
COPY frontend/package*.json ./
# Install dependencies
RUN npm config set fetch-retry-maxtimeout 120000 && \
npm config set fetch-retries 5 && \
npm install
# Copy frontend source
COPY frontend/ ./
# Build frontend
RUN npm run build
# Stage 2: Build Backend
FROM node:20-alpine AS backend-builder
WORKDIR /app/backend
# Install build dependencies for node-pty native modules
RUN apk add --no-cache python3 make g++
# Copy backend package files
COPY backend/package*.json ./
# Install dependencies
RUN npm config set fetch-retry-maxtimeout 120000 && \
npm config set fetch-retries 5 && \
npm install
# Copy backend source
COPY backend/ ./
# Build backend
RUN npm run build
# Stage 3: Production
FROM node:20-alpine
# Install Docker CLI, Docker Compose CLI, and Bash for Host Console
RUN apk add --no-cache docker-cli docker-cli-compose bash
WORKDIR /app
# Copy built backend and node_modules from backend-builder
COPY --from=backend-builder /app/backend/dist ./dist
COPY --from=backend-builder /app/backend/node_modules ./node_modules
COPY --from=backend-builder /app/backend/package.json ./
# Copy built frontend from frontend-builder to public folder
COPY --from=frontend-builder /app/frontend/dist ./public
# Set environment to production
ENV NODE_ENV=production
# Create a non-root user and ensure the data/compose directories are writable.
# The actual volume paths are mounted at runtime, so we only pre-create the
# default data dir here; the compose dir is user-supplied via COMPOSE_DIR.
RUN addgroup -S sencho && adduser -S -G sencho sencho \
&& mkdir -p /app/data \
&& chown -R sencho:sencho /app
USER sencho
# Expose port
EXPOSE 3000
# Health check — polls the public /api/health endpoint every 30s
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD node -e "const h=require('http');h.get('http://localhost:3000/api/health',r=>{process.exit(r.statusCode===200?0:1)}).on('error',()=>process.exit(1))"
# Start the server
CMD ["node", "dist/index.js"]