Files
rengine/docker-compose.yml
T
Yogesh OjhaandClaude Fable 5.1 db030d733c feat(bounty-hub): watch a program
Scope-true continuous discovery for a bounty program, bug bounty mode only
(capability `program_watches`).

Watching a program imports its in-scope targets under the program's
organization, writes out-of-scope entries as exclusions and the rate ceiling
on a `Watch · <program>` scan context, schedules an optional baseline, and
streams certificate transparency for the watched apexes. A new in-scope host
is resolved, written into the covering scan with source `ct_log`, probed by a
focused run with a screenshot, evaluated against the watch's alert query and
alerted once per fingerprint through the chosen channels.

- ct-stream compose service: certspotter 0.24.2 under a supervisor that
  regenerates the watchlist from active watches, spools events through a
  hook script that emits only certspotter fields, and reports over Redis
- worker tasks: certificate, settle, recheck, reconcile; finalize dispatches
  settle for any probe run; reconcile after every program or feed sync
- notifier: named channels bypass per-channel preferences; attachments;
  new `watch` notification type
- API under /bounty-programs/watches with preview, ledger, events, marks
- Bounty Hub: Watching tab, Watch dialog, watch sheet, program badge,
  certificate stream card in Settings
- schedules carry an intensity; scan_schedules.intensity

Two review rounds folded in: compare-and-set probe claim, resolver-down
handling, spool kept on failed dispatch, schedule cap, mute keeps prior
state, ledger-derived totals, event filters, mode lockout of worker and
stream in corporate mode.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ba2f7gwu2GXiETCKqRztyB
2026-09-12 21:51:18 +05:30

266 lines
7.2 KiB
YAML

services:
db:
image: postgres:16-alpine
shm_size: ${POSTGRES_SHM_SIZE:-1gb}
env_file:
- .env
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
command: >
postgres
-c shared_buffers=${POSTGRES_SHARED_BUFFERS:-2GB}
-c effective_cache_size=${POSTGRES_EFFECTIVE_CACHE_SIZE:-6GB}
-c work_mem=${POSTGRES_WORK_MEM:-32MB}
-c maintenance_work_mem=${POSTGRES_MAINTENANCE_WORK_MEM:-512MB}
-c random_page_cost=1.1
-c effective_io_concurrency=200
-c max_parallel_workers_per_gather=4
-c jit=off
-c max_connections=${POSTGRES_MAX_CONNECTIONS:-150}
-c shared_preload_libraries=pg_stat_statements
-c pg_stat_statements.max=5000
-c pg_stat_statements.track=top
-c track_io_timing=on
-c log_min_duration_statement=${POSTGRES_LOG_MIN_DURATION:-2000}
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
- ./connectors:/app/connectors
- ./toolbox:/app/toolbox
- ./binaries:/app/binaries:ro
- ./alembic:/app/alembic
- ./alembic.ini:/app/alembic.ini
- ./tests:/app/tests
- ./pyproject.toml:/app/pyproject.toml
# read-only, so the tests can check a frontend mirror has not drifted
- ./frontend/src/lib/config:/app/frontend-config:ro
- ./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:
init: true
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
- ./toolbox:/app/toolbox
- ./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
# one scan now holds up to 5 slots at its widest — 3 gating stages plus the 2 that
# run beside them — so 8 covered a single scan and queued the moment two overlapped
command: >
uv run celery -A app.celery worker
--loglevel=DEBUG
--concurrency=${CELERY_SCAN_CONCURRENCY:-12}
--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:
init: true
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
- ./toolbox:/app/toolbox
- ./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
# certificate transparency stream for watched programs
ct-stream:
init: true
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
- ./toolbox:/app/toolbox
- ct_state:/app/ct-state
- /app/shared/build
- /app/shared/rengine_shared.egg-info
- /app/tools/build
- /app/tools/rengine_tools.egg-info
env_file:
- .env
command: uv run python -m app.ct_stream
restart: unless-stopped
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
- ./toolbox:/app/toolbox
- ./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:
ct_state:
postgres_data:
redis_data:
celery_beat_data:
vuln_templates:
wordlists:
reports_out:
report_fonts: