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
- Architecture
- Startup sequence
- Tool installation
- install_method: apk
- install_method: binary
- install_method: skip
- Config reference
- Adding a new tool
- Scan profiles
- Scheduler
- Running the worker
- Docker Compose
- Standalone binary
- Test mode
- Environment variables
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
- Load configuration from
./configs/config.yaml(or env vars, see Environment variables) - Install tools — iterate
toolsconfig, skip tools already in PATH, install missing ones - Connect to PostgreSQL
- Connect to Redis
- Start
Worker.Run()goroutine — listens for scan jobs - Start
Worker.RunScheduler()goroutine — fires recurring scans - 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
- Add an entry to
configs/config.yamlundertools:. - Find the release URL for the Linux amd64 build on the tool's GitHub releases page.
- Set
binary_nameto the executable filename inside the archive. - If the tool has a corresponding plugin in
internal/plugins/, register it incmd/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.