Skip to content

Worker

The worker is a long-running process that pulls scan jobs from a Redis queue, executes the configured security plugins against target scopes, and persists discovered assets and vulnerabilities to the database. It also runs the schedule checker that fires recurring scans.

Contents


Overview

API ──LPUSH──► Redis queue ──BRPOP──► Worker
                                        │
                        ┌───────────────┴──────────────┐
                        ▼                              ▼
                  Plugin pipeline               Scheduler (60 s tick)
                  subfinder → httpx             checks next_run_at
                  nmap → nuclei                 enqueues overdue scans
                        │
                  ┌─────┴──────┐
                  ▼            ▼
               Assets    Vulnerabilities
               (upsert)  (upsert)

The worker binary is built from cmd/worker/main.go. It shares the same config file as the API but only reads the sections it needs (database, redis, log, tools).


Architecture

Component Description
Worker.Run() Blocking loop — calls BRPOP on the scan queue with a 5-second timeout, dispatches each job as a goroutine
Worker.processScan() Resolves the plugin list for the requested profile, creates ScanJob records, runs each plugin in sequence, writes results
Worker.processResults() Separates plugin output into asset entities and vulnerability entities, upserts both
Worker.RunScheduler() Ticks every 60 seconds, queries scheduled_scans for rows where next_run_at <= NOW(), enqueues them
toolinstaller.Install() Runs at startup — checks PATH for each configured tool and installs missing ones

Startup sequence

  1. Load configuration from ./configs/config.yaml (or env vars, see Environment variables)
  2. Install tools — iterate tools config, skip tools already in PATH, install missing ones
  3. Connect to PostgreSQL
  4. Connect to Redis
  5. Start Worker.Run() goroutine — listens for scan jobs
  6. Start Worker.RunScheduler() goroutine — fires recurring scans
  7. Block until SIGINT / SIGTERM, then cancel both goroutines and exit

If any tool installation fails the worker exits immediately with a fatal error. Set install_method: skip to make a tool optional.


Tool installation

Tools are installed once on worker startup. If the binary is already present in $PATH it is skipped entirely, regardless of install_method. This means re-deploying the container with the same image will re-install tools on each restart unless you persist /usr/local/bin as a volume.

install_method: apk

Runs apk add --no-cache <apk_package> inside the container. Requires Alpine Linux (the default worker base image).

tools:
  nmap:
    enabled: true
    install_method: apk
    apk_package: nmap

install_method: binary

Downloads the file at binary_url, detects the format (.zip, .tar.gz, or raw binary), extracts the file named binary_name, and places it in install_dir with executable permissions.

tools:
  subfinder:
    enabled: true
    install_method: binary
    binary_url: https://github.com/projectdiscovery/subfinder/releases/download/v2.6.6/subfinder_2.6.6_linux_amd64.zip
    binary_name: subfinder
    install_dir: /usr/local/bin

Supported archive formats:

Extension Behaviour
.zip Extracts the entry whose basename matches binary_name
.tar.gz / .tgz Extracts the entry whose basename matches binary_name
anything else Treats the download as a raw binary and writes it directly

The download timeout is 5 minutes. Use binary_name exactly as the filename appears inside the archive (basename only; directory path is ignored).

install_method: skip

Does nothing. The worker logs a warning if the binary is not found in $PATH but continues running. Use this when the tool is installed by other means (baked into a custom image, mounted from a volume, etc.).

tools:
  amass:
    enabled: false   # completely ignored
    install_method: skip

Tool config reference

Field Type Required Description
enabled bool yes false skips the tool entirely
install_method string yes "apk", "binary", or "skip"
apk_package string if apk Package name passed to apk add
binary_url string if binary Direct download URL
binary_name string if binary Filename to extract from the archive. Defaults to the tool key name
install_dir string no Where to place the binary. Defaults to /usr/local/bin

Adding a new tool

  1. Add an entry to configs/config.yaml under tools:.
  2. Find the release URL for the Linux amd64 build on the tool's GitHub releases page.
  3. Set binary_name to the executable filename inside the archive.
  4. If the tool has a corresponding plugin in internal/plugins/, register it in cmd/worker/main.go.

Example — adding katana:

tools:
  katana:
    enabled: true
    install_method: binary
    binary_url: https://github.com/projectdiscovery/katana/releases/download/v1.1.0/katana_1.1.0_linux_amd64.zip
    binary_name: katana
    install_dir: /usr/local/bin

Scan profiles

The profile controls which plugins run for a scan.

Profile Plugins Use case
quick subfinder, dnsx, httpx Fast surface enumeration, no active scanning
default subfinder, dnsx, httpx, naabu, nuclei Balanced discovery + service mapping + vuln scan
deep subfinder, amass, dnsx, naabu, nmap, httpx, tlsx, katana, nuclei Maximum visibility scan
stealth subfinder, amass, httpx Low-noise reconnaissance

Profiles are defined in backend/configs/config.yaml under scan_profiles. API and worker validate profiles at startup; a profile that references an unregistered plugin fails startup instead of being silently skipped.


Scheduler

The scheduler goroutine wakes every 60 seconds and queries:

SELECT * FROM scheduled_scans
WHERE enabled = true AND next_run_at <= NOW()

For each due schedule it: 1. Creates a new Scan record 2. Pushes a QueueMessage to Redis (LPUSH easm:scan:queue) 3. Updates last_run_at = NOW() and next_run_at = NOW() + interval_hours

Scheduled scans are managed through the API (POST /organizations/:id/scheduled-scans).


Running the worker

Docker Compose

The standard way. The worker image is built from the worker target in backend/Dockerfile.

docker compose up -d worker

Tool config is read from ./backend/configs/config.yaml which is mounted read-only into /app/configs/.

To override a specific tool URL without rebuilding:

# Edit the config
nano backend/configs/config.yaml

# Restart the worker (re-runs tool installer)
docker compose restart worker

Standalone binary

cd backend
go build -o bin/worker ./cmd/worker
./bin/worker

The binary looks for config.yaml in . then ./configs. All settings can be overridden with EASM_* environment variables (see Environment variables).

Tools with install_method: apk require Alpine; on other Linux distributions substitute apk_package with the appropriate distro package name or use install_method: binary instead.

Test mode

Set EASM_TEST_MODE=true to make every plugin return mock data instead of running real CLI tools. Useful for development without the security tools installed.

EASM_TEST_MODE=true ./bin/worker

In Docker Compose:

environment:
  - EASM_TEST_MODE=true

Environment variables

Every config key can be set as an environment variable using the prefix EASM_ with dots replaced by _:

Variable Default Description
EASM_DATABASE_DSN postgres://easm:easm@postgres:5432/easm?sslmode=disable PostgreSQL connection string
EASM_REDIS_ADDR redis:6379 Redis address
EASM_REDIS_PASSWORD (empty) Redis password
EASM_REDIS_DB 0 Redis database index
EASM_LOG_LEVEL info Log level: debug, info, warn, error
EASM_TEST_MODE false Enable mock plugin output

Tool install settings cannot be set via individual environment variables — use the config file.