From 3aa4023c8f7bdee5554136f9ce4020776adc6508 Mon Sep 17 00:00:00 2001 From: Nicolas Pastorello Date: Mon, 6 Jul 2026 17:44:20 -0300 Subject: [PATCH 1/5] feat: add Docker deployment support - Add Dockerfile with Node.js 18 alpine build - Add docker-compose.yml with environment configuration - Add docs/DOCKER.md deployment guide - Update README.md with Docker quick start section - Server binds to 0.0.0.0:3333 for external access - UI available at http://localhost:3333/ui/ - Volumes mounted for reports and evidence persistence --- Dockerfile | 36 +++++++++ README.md | 25 ++++++ docker-compose.yml | 22 +++++ docs/DOCKER.md | 195 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 278 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 docs/DOCKER.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..41eafc5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +# syntax=docker/dockerfile:1 + +FROM node:18-alpine + +# Install dependencies +RUN apk add --no-cache \ + bash \ + curl \ + git \ + openssl \ + ca-certificates + +WORKDIR /app + +# Copy package files +COPY package*.json ./ +COPY tsconfig.json ./ + +# Install dependencies +RUN npm ci && \ + npm cache clean --force + +# Copy source code +COPY . . + +# Build TypeScript +RUN npm run build + +# Ensure docs directory exists for /ui static files +RUN mkdir -p docs + +# Expose port +EXPOSE 3333 + +# Default command: run the server (not interactive CLI) +CMD ["npm", "run", "server"] diff --git a/README.md b/README.md index 33450bf..e4c0aca 100755 --- a/README.md +++ b/README.md @@ -96,6 +96,31 @@ npm run verify-claims # re-derives every headline from committed JSO Library/SDK usage, the full HTTP API, and MCP setup live in [docs/](docs/). +### Docker + +Run T3MP3ST API server in a container: + +```bash +cp .env.example .env # configure API keys +docker compose up -d # API → http://localhost:3333 +docker compose logs -f # view logs +``` + +Test the API: +```bash +curl http://localhost:3333/api/health +curl http://localhost:3333/api/bounty/platforms +``` + +Execute commands inside the container: + +```bash +docker compose exec app npm run verify-claims +docker compose exec app npm run cve:bench +``` + +Full deployment guide: [docs/DOCKER.md](docs/DOCKER.md). + ## What ships today The framework is an 8-operator kill chain, and this table won't blow smoke about it. **Recon is a live, tool-backed engine** — and the teeth are already real: 90.1% pass@1 on XBEN, 8/10 held-out post-cutoff CVEs pinned to exact file/line/CWE, and a coordinated-disclosure pipeline that's live enough to have drafts held for vendor coordination right now. What's *not* proven is the swarm. Each downstream operator — Exploiter, Infiltrator, Exfiltrator, Ghost — runs the **same real, tool-backed ReAct loop as recon** (real exploit tools, not stubs), but the headline numbers came from a single agent, not the coordinated 8-operator cell, and end-to-end swarm exploitation is unbenchmarked and still unreliable. The engine is real; the swarm is the part still earning its stripes. Loud where we've earned it, blunt about the rest. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..381fae1 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + container_name: t3mp3st + restart: unless-stopped + env_file: + - .env + environment: + - T3MP3ST_HOST=0.0.0.0 + - T3MP3ST_PORT=3333 + volumes: + - ./reports:/app/reports + - ./evidence:/app/evidence + ports: + - "3333:3333" + healthcheck: + test: ["CMD", "node", "-e", "process.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 diff --git a/docs/DOCKER.md b/docs/DOCKER.md new file mode 100644 index 0000000..069461f --- /dev/null +++ b/docs/DOCKER.md @@ -0,0 +1,195 @@ +# T3MP3ST Docker Deployment + +Run T3MP3ST in a containerized environment. The Docker setup provides a minimal Node.js runtime with persistent volumes for reports and evidence. + +## Quick Start + +```bash +# Build and run +docker compose up -d + +# View logs +docker compose logs -f + +# Stop +docker compose down +``` + +The API server is available at `http://localhost:3333`. + +Test endpoints: +```bash +curl http://localhost:3333/api/health +curl http://localhost:3333/api/bounty/platforms +``` + +## Prerequisites + +- Docker Engine 20.10+ +- Docker Compose 2.0+ + +Verify installation: +```bash +docker --version +docker compose version +``` + +## Environment Configuration + +Copy the environment template and configure API keys: + +```bash +cp .env.example .env +``` + +Edit `.env` with your credentials: +```env +ANTHROPIC_API_KEY=sk-ant-xxx +OPENAI_API_KEY=sk-xxx +OPENROUTER_API_KEY=xxx +``` + +The compose file mounts `.env` into the container automatically. + +## Volume Mounts + +| Host Path | Container Path | Purpose | +|---|---|---| +| `./reports` | `/app/reports` | Engagement reports and findings | +| `./evidence` | `/app/evidence` | Collected evidence and artifacts | + +Data persists across container restarts. + +## Running Commands + +Execute any npm script inside the container: + +```bash +# CLI +docker compose exec app npm run dev + +# Setup +docker compose exec app npm run setup + +# Doctor check +docker compose exec app npm run doctor + +# Verify claims +docker compose exec app npm run verify-claims + +# Run benchmarks +docker compose exec app npm run cve:bench +docker compose exec app npm run cloud:bench +docker compose exec app npm run mobile:bench +``` + +## Development Mode + +For live code changes, mount the source directory: + +```yaml +# docker-compose.dev.yml +services: + app: + build: . + volumes: + - ./src:/app/src + - ./scripts:/app/scripts + - ./reports:/app/reports + - ./evidence:/app/evidence + command: npm run dev +``` + +Run with: +```bash +docker compose -f docker-compose.yml -f docker-compose.dev.yml up +``` + +## Build Optimization + +The default `Dockerfile` is single-stage for simplicity. For production, consider a multi-stage build: + +```dockerfile +FROM node:18-alpine AS builder +WORKDIR /app +COPY package*.json tsconfig.json ./ +RUN npm ci +COPY . . +RUN npm run build + +FROM node:18-alpine +WORKDIR /app +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/node_modules ./node_modules +COPY package*.json ./ +CMD ["npm", "start"] +``` + +This reduces final image size from ~800MB to ~300MB. + +## Troubleshooting + +### Container exits immediately + +Check logs for startup errors: +```bash +docker compose logs app +``` + +Verify `.env` file exists and contains valid API keys. + +### Permission errors on volumes + +Ensure the host directories are writable: +```bash +mkdir -p reports evidence +chmod 755 reports evidence +``` + +On Linux, match container UID/GID if needed: +```bash +sudo chown -R $(id -u):$(id -g) reports evidence +``` + +### Port 3000 already in use + +Change the host port in `docker-compose.yml`: +```yaml +ports: + - "8080:3000" # host:container +``` + +## Cleanup + +Remove containers and volumes: +```bash +docker compose down -v +``` + +Remove the image: +```bash +docker rmi t3mp3st-app +``` + +Full cleanup (use with caution): +```bash +docker system prune -a --volumes +``` + +## Security Notes + +- Container runs as root by default. For production, add a non-root user in the Dockerfile. +- `.env` is mounted read-only in the compose file to prevent accidental modification. +- Network mode is `bridge` by default. Change to `host` if scanning from the container's network context. + +## Integration with Tools + +External security tools (nuclei, ffuf, sqlmap) can run in separate containers or be installed in a custom Dockerfile: + +```dockerfile +FROM node:18-alpine +RUN apk add --no-cache nmap nuclei ffuf +# ... rest of Dockerfile +``` + +Refer to `docs/INSTALL_MATRIX.md` for the full tool list. From a92baca1344eb082bba0c59708aab7f35be2fde3 Mon Sep 17 00:00:00 2001 From: Nicolas Pastorello Date: Mon, 6 Jul 2026 18:06:01 -0300 Subject: [PATCH 2/5] feat: add healthcheck to Dockerfile --- Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index 41eafc5..0c833db 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,5 +32,9 @@ RUN mkdir -p docs # Expose port EXPOSE 3333 +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3333/api/health || exit 1 + # Default command: run the server (not interactive CLI) CMD ["npm", "run", "server"] From aaf2b7fe60d96553dbe40712d5239ed6404273f1 Mon Sep 17 00:00:00 2001 From: Nicolas Pastorello Date: Mon, 6 Jul 2026 18:31:34 -0300 Subject: [PATCH 3/5] security: bind Docker to localhost only (127.0.0.1) --- README.md | 4 +++- docker-compose.yml | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e4c0aca..16a2f5e 100755 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ Library/SDK usage, the full HTTP API, and MCP setup live in [docs/](docs/). ### Docker -Run T3MP3ST API server in a container: +Run T3MP3ST API server in a container (localhost only, not exposed externally): ```bash cp .env.example .env # configure API keys @@ -106,6 +106,8 @@ docker compose up -d # API → http://localhost:3333 docker compose logs -f # view logs ``` +**Security Note:** Container binds to `127.0.0.1:3333` - accessible only from localhost, not exposed to network. + Test the API: ```bash curl http://localhost:3333/api/health diff --git a/docker-compose.yml b/docker-compose.yml index 381fae1..48ff696 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,13 +8,13 @@ services: env_file: - .env environment: - - T3MP3ST_HOST=0.0.0.0 + - T3MP3ST_HOST=127.0.0.1 - T3MP3ST_PORT=3333 volumes: - ./reports:/app/reports - ./evidence:/app/evidence ports: - - "3333:3333" + - "127.0.0.1:3333:3333" healthcheck: test: ["CMD", "node", "-e", "process.exit(0)"] interval: 30s From 48b27bd6f59004ac78e0f1dc42f80e9cf6305d78 Mon Sep 17 00:00:00 2001 From: Nicolas Pastorello Date: Mon, 6 Jul 2026 19:35:02 -0300 Subject: [PATCH 4/5] fix: Docker healthcheck connection refused - Bind to 0.0.0.0 inside containers (DOCKER=true env marker) - Healthcheck was failing because localhost resolved to IPv6 ::1 but server was bound to IPv4 127.0.0.1 - External exposure still protected via Coolify port mapping to 127.0.0.1 --- Dockerfile | 3 +++ src/server.ts | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0c833db..abf9194 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,9 @@ RUN npm run build # Ensure docs directory exists for /ui static files RUN mkdir -p docs +# Docker environment marker (tells server.ts to bind to 0.0.0.0) +ENV DOCKER=true + # Expose port EXPOSE 3333 diff --git a/src/server.ts b/src/server.ts index 1f16a79..2fc18e4 100755 --- a/src/server.ts +++ b/src/server.ts @@ -144,7 +144,8 @@ const PORT = process.env.T3MP3ST_PORT || 3333; // this local command-executing API (CSRF / DNS-rebinding), NOT remote attackers. // The CORS lock + origin guard below defend that vector; if this is ever exposed // beyond localhost, add real Bearer-token auth as the upgrade path. -const HOST = process.env.T3MP3ST_HOST || '127.0.0.1'; +// DOCKER: Inside containers, bind to 0.0.0.0 (env var can still override). +const HOST = process.env.T3MP3ST_HOST || (process.env.DOCKER === 'true' ? '0.0.0.0' : '127.0.0.1'); // B-02: only enforce the Host-header allow-list when bound to loopback (the // default). If the operator EXPLICITLY exposes the server (T3MP3ST_HOST set to a // non-loopback address) they've opted into network access behind their own front, From ef5c7c3f516dc3c5b0dac56350ee42ff72e10c2a Mon Sep 17 00:00:00 2001 From: Nicolas Pastorello Date: Wed, 8 Jul 2026 20:34:34 -0300 Subject: [PATCH 5/5] fix: address Docker security and stability issues from PR review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes all issues raised in PR #47 review: 1. Add .dockerignore to prevent: - Real .env secrets baked into image layers - Host node_modules (glibc) overwriting alpine (musl) → ABI mismatch - .git, reports/, evidence/ bloating the image 2. Fix compose host-binding: - Remove T3MP3ST_HOST=127.0.0.1 override (was breaking port forwarding) - Server now binds to 0.0.0.0 in-container (DOCKER=true from Dockerfile) - Host mapping stays 127.0.0.1:3333:3333 for security 3. Add critical security warning in DOCKER.md: - Explain exposure risk of 0.0.0.0 binding + disabled Host-header guard - Document safe vs unsafe deployment patterns - Warn against network_mode: host and -p 3333:3333 without 127.0.0.1 4. Update base image: - node:18-alpine → node:22-alpine (Node 18 EOL) - Pin by digest @sha256:16e22a550... (prevent floating tag drift) 5. Fix healthcheck in compose: - Was always returning success (process.exit(0)) - Now actually tests /api/health endpoint - Add start_period: 40s to align with Dockerfile 6. Fix port typos in DOCKER.md: - 3000 → 3333 in troubleshooting examples - Update all example snippets to node:22-alpine with digest --- .dockerignore | 42 ++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 2 +- docker-compose.yml | 4 ++-- docs/DOCKER.md | 31 +++++++++++++++++++++++-------- 4 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ee03553 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,42 @@ +# Environment files (may contain real secrets) +.env +.env.* +!.env.example + +# Git +.git +.gitignore + +# Node modules (host might be glibc, container is musl) +node_modules + +# Reports and evidence (mounted as volumes) +reports +evidence + +# Build artifacts +dist +*.log +npm-debug.log* + +# IDE +.vscode +.idea +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Docker +docker-compose.override.yml +Dockerfile* +.dockerignore + +# CI/CD +.github + +# Documentation (built at runtime if needed) +docs/.vitepress/cache +docs/.vitepress/dist diff --git a/Dockerfile b/Dockerfile index abf9194..05bcb29 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -FROM node:18-alpine +FROM node:22-alpine@sha256:16e22a550f3863206a3f701448c45f7912c6896a62de43add43bb9c86130c3e2 # Install dependencies RUN apk add --no-cache \ diff --git a/docker-compose.yml b/docker-compose.yml index 48ff696..60bd12c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,6 @@ services: env_file: - .env environment: - - T3MP3ST_HOST=127.0.0.1 - T3MP3ST_PORT=3333 volumes: - ./reports:/app/reports @@ -16,7 +15,8 @@ services: ports: - "127.0.0.1:3333:3333" healthcheck: - test: ["CMD", "node", "-e", "process.exit(0)"] + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3333/api/health"] interval: 30s timeout: 10s retries: 3 + start_period: 40s diff --git a/docs/DOCKER.md b/docs/DOCKER.md index 069461f..6fdca4e 100644 --- a/docs/DOCKER.md +++ b/docs/DOCKER.md @@ -110,14 +110,14 @@ docker compose -f docker-compose.yml -f docker-compose.dev.yml up The default `Dockerfile` is single-stage for simplicity. For production, consider a multi-stage build: ```dockerfile -FROM node:18-alpine AS builder +FROM node:22-alpine@sha256:16e22a550f3863206a3f701448c45f7912c6896a62de43add43bb9c86130c3e2 AS builder WORKDIR /app COPY package*.json tsconfig.json ./ RUN npm ci COPY . . RUN npm run build -FROM node:18-alpine +FROM node:22-alpine@sha256:16e22a550f3863206a3f701448c45f7912c6896a62de43add43bb9c86130c3e2 WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules @@ -151,12 +151,12 @@ On Linux, match container UID/GID if needed: sudo chown -R $(id -u):$(id -g) reports evidence ``` -### Port 3000 already in use +### Port 3333 already in use -Change the host port in `docker-compose.yml`: +Change the host port in `docker-compose.yml` (keep the `127.0.0.1:` prefix for security): ```yaml ports: - - "8080:3000" # host:container + - "127.0.0.1:8080:3333" # host:container ``` ## Cleanup @@ -178,16 +178,31 @@ docker system prune -a --volumes ## Security Notes +**⚠️ CRITICAL: External Exposure Risk** + +The Docker setup binds the server to `0.0.0.0` inside the container to allow port forwarding. This disables the Host-header/DNS-rebinding guard in `server.ts`. + +**SAFE:** The default `docker-compose.yml` publishes the port as `127.0.0.1:3333:3333`, which restricts access to localhost only. + +**UNSAFE:** Running `docker run -p 3333:3333` (without the `127.0.0.1:` prefix) or `network_mode: host` exposes the **unauthenticated command-executing API** on all network interfaces (0.0.0.0), allowing anyone on your network (or the internet if port-forwarded) to execute arbitrary system commands. + +**Deployment Rules:** +- ✅ Always bind to `127.0.0.1` on the host: `-p 127.0.0.1:3333:3333` +- ✅ Use a reverse proxy (nginx, Caddy, Coolify) with authentication for external access +- ❌ Never use `network_mode: host` in untrusted networks +- ❌ Never publish to `0.0.0.0` without authentication middleware + +**Additional Security:** - Container runs as root by default. For production, add a non-root user in the Dockerfile. -- `.env` is mounted read-only in the compose file to prevent accidental modification. -- Network mode is `bridge` by default. Change to `host` if scanning from the container's network context. +- The `.env` file is excluded from the image via `.dockerignore` to prevent secret leakage. +- Network mode is `bridge` by default (isolated from host network). ## Integration with Tools External security tools (nuclei, ffuf, sqlmap) can run in separate containers or be installed in a custom Dockerfile: ```dockerfile -FROM node:18-alpine +FROM node:22-alpine@sha256:16e22a550f3863206a3f701448c45f7912c6896a62de43add43bb9c86130c3e2 RUN apk add --no-cache nmap nuclei ffuf # ... rest of Dockerfile ```