Files
rengine/docker-compose.yml
T
Yogesh OjhaandClaude Opus 5 0bb32328fa feat(mcp): agents can interrogate an attack surface, not just start one
Recon MCP servers expose one verb: go and scan. reconFTW v5 ships seven tools
and not one of them reads a result, because a pipeline that writes files has
nothing to query. reNgine's value is the correlated corpus, so this server is
read-first — fourteen tools for asking questions, of which four can act and
only two reach a target. Wrapping the OpenAPI document instead would have cost
276 tools and 112,600 tokens of definitions, and would still not work, because
every result endpoint needs a scan id an agent has no way to choose.

Everything lives in mcp/, mounted into the api the way stages/ and reports/
are. The protocol is implemented directly rather than through an SDK: a
tools-only server is a small JSON-RPC surface, this keeps the package free of
dependencies, and protocol.py is the only module that knows the wire format.
Tools are plug-and-play like stages and report sections — one file under
mcp/tools/, a Tool subclass whose Input model is both the schema the model
plans against and the schema the server validates with. Nothing registers it.
mcp/dimensions.py is the second axis, one entry per result dimension, so
query_assets, group_assets, surface_brief and describe_query_language each stay
short and a sixth dimension is a single entry.

ToolResult carries the product's contracts structurally. Every count comes back
with the query token that reproduces it, so a number an agent reports equals
the rows a human lands on. A dimension nobody has scanned is reported as not
scanned rather than as zero, which is why resolve_target resolves the scan that
actually covered each dimension instead of taking the newest one. Rows carrying
text the scanned party wrote are flagged untrusted, so the model reports them
rather than following them.

Access is an opaque service token, hashed at rest, bound to a project and a
capability set, revocable by row. Read, plan, write and launch are separate;
launch is the only one that reaches a machine you do not own and is off at the
instance ceiling until an operator raises it. No token may exceed the ceiling,
lowering it narrows the tokens that already exist, and tools/list returns only
what a token may call. Start and stop live on the MCP page rather than in a
config file — the server is mounted in the api, so stopped means refusing
connections rather than a supervised daemon, and the dialog says so: stopping
does not cancel a scan an agent already started.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J6fcWDtvxTer9chPWAwKd2
2026-09-06 18:24:35 +05:30

204 lines
5.1 KiB
YAML

services:
db:
image: postgres:16-alpine
env_file:
- .env
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U rengine"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:8-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
command: >
redis-server
--appendonly yes
--maxmemory 256mb
--maxmemory-policy allkeys-lru
api:
build:
context: .
dockerfile: api/Dockerfile
target: development
volumes:
- ./api/app:/app/app
- ./shared:/app/shared
- ./tools:/app/tools
- ./stages:/app/stages
- ./reports:/app/reports
- ./interest:/app/interest
- ./mcp:/app/mcp
- ./alembic:/app/alembic
- ./alembic.ini:/app/alembic.ini
- ./scan_media:/app/scan_media
- vuln_templates:/app/vuln-templates
- wordlists:/app/wordlists
- reports_out:/app/reports-out
- report_fonts:/app/report-fonts
- /app/shared/build
- /app/shared/rengine_shared.egg-info
- /app/tools/build
- /app/tools/rengine_tools.egg-info
ports:
- "8000:8000"
env_file:
- .env
environment:
- WATCHFILES_FORCE_POLLING=true
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
# default workers for handling regular tasks like imports, whois etc
worker-default:
build:
context: .
dockerfile: worker/Dockerfile
target: development
volumes:
- ./worker/app:/app/app
- ./shared:/app/shared
- ./tools:/app/tools
- ./stages:/app/stages
- ./reports:/app/reports
- ./interest:/app/interest
- ./scan_media:/app/scan_media
- vuln_templates:/app/vuln-templates
- wordlists:/app/wordlists
- reports_out:/app/reports-out
- report_fonts:/app/report-fonts
- /app/shared/build
- /app/shared/rengine_shared.egg-info
- /app/tools/build
- /app/tools/rengine_tools.egg-info
env_file:
- .env
command: >
uv run celery -A app.celery worker
--loglevel=DEBUG
--concurrency=${CELERY_SCAN_CONCURRENCY:-8}
--queues=critical,default,scans
--hostname=worker-default@%h
--max-tasks-per-child=100
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "/app/.venv/bin/celery -A app.celery inspect ping -d worker-default@$$HOSTNAME || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# beat scheduler: emits the schedule tick that fires due scan schedules
worker-beat:
build:
context: .
dockerfile: worker/Dockerfile
target: development
volumes:
- ./worker/app:/app/app
- ./shared:/app/shared
- ./tools:/app/tools
- ./stages:/app/stages
- ./reports:/app/reports
- ./interest:/app/interest
- ./scan_media:/app/scan_media
- vuln_templates:/app/vuln-templates
- wordlists:/app/wordlists
- reports_out:/app/reports-out
- report_fonts:/app/report-fonts
- /app/shared/build
- /app/shared/rengine_shared.egg-info
- /app/tools/build
- /app/tools/rengine_tools.egg-info
env_file:
- .env
command: >
uv run celery -A app.celery beat
--loglevel=INFO
--schedule=/tmp/celerybeat-schedule
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
flower:
build:
context: .
dockerfile: worker/Dockerfile
target: development
volumes:
- ./worker/app:/app/app
- ./shared:/app/shared
- ./tools:/app/tools
- ./stages:/app/stages
- ./reports:/app/reports
- ./interest:/app/interest
- ./scan_media:/app/scan_media
- vuln_templates:/app/vuln-templates
- wordlists:/app/wordlists
- reports_out:/app/reports-out
- report_fonts:/app/report-fonts
- /app/shared/build
- /app/shared/rengine_shared.egg-info
- /app/tools/build
- /app/tools/rengine_tools.egg-info
ports:
- "5555:5555"
env_file:
- .env
environment:
- FLOWER_UNAUTHENTICATED_API=true
command: >
uv run celery -A app.celery flower
--port=5555
--broker=redis://${REDIS_HOST:-redis}:${REDIS_PORT:-6379}/${REDIS_DB:-0}
--basic-auth=${FLOWER_USER:-admin}:${FLOWER_PASSWORD:-admin}
depends_on:
redis:
condition: service_healthy
frontend:
build:
context: .
dockerfile: frontend/Dockerfile.dev
volumes:
- ./frontend/src:/app/src
- ./frontend/vite.config.ts:/app/vite.config.ts
ports:
- "5173:5173"
environment:
- NODE_ENV=development
depends_on:
- api
volumes:
postgres_data:
redis_data:
celery_beat_data:
vuln_templates:
wordlists:
reports_out:
report_fonts: