Documentation Index
Fetch the complete documentation index at: https://mintlify.com/webhood-io/webhood/llms.txt
Use this file to discover all available pages before exploring further.
Webhood is composed of four Docker containers that each handle a distinct responsibility: a Kong API gateway that is the single entry point for all external traffic, a headless Chrome scanner that visits URLs and captures evidence, a Next.js UI that analysts use to submit scans and review results, and a PocketBase backend that provides authentication, a SQLite database, file storage, and a REST API. Together they form a self-contained stack that can run on any host with Docker Engine installed.
Traffic Flow
┌─────────────────────────────────┐
│ Docker host │
│ │
│ ┌──────────────────────────┐ │
Browser ──── :8000 ────────▶ Kong (webhood-proxy) │ │
│ └──────────┬───────────────┘ │
│ │ │
│ /api/ ───┼──▶ Backend (8090) │
│ / ───┼──▶ Core UI (3000) │
│ │ │
│ ▲ │
│ Scanner polls & │
│ subscribes via Kong │
│ │
└─────────────────────────────────┘
│
Scanner ──────▶ Public Internet
The browser never communicates with the backend or the scanner directly. All traffic enters through Kong on port 8000 (HTTP) or 8443 (HTTPS). The scanner initiates outbound connections both to Kong (to authenticate and poll for jobs) and to the public internet (to visit scan target URLs). It never receives inbound connections.
kong.yml is the single routing configuration file for the entire Webhood deployment. It uses Kong’s DB-less declarative mode, meaning no separate Kong database is required. The file is mounted read-only into the Kong container at /usr/local/kong/kong.yml.
Services
Kong — webhood-proxy
Kong is the API gateway that sits in front of all other services. It listens on port 8000 (HTTP) and 8443 (HTTPS) on the Docker host and applies routing, CORS, and authentication rules defined in kong.yml.
Routing logic (from kong.yml):
_format_version: "1.1"
services:
- name: webhood-core-v1
url: http://core:3000
routes:
- name: webhood-beta-all
paths:
- /
plugins:
- name: cors
- name: webhood-backend-v1
url: http://backend:8090
routes:
- name: api
strip_path: false
paths:
- /api/
Any request whose path begins with /api/ is forwarded to the PocketBase backend on port 8090. All other requests — including the Next.js pages, static assets, and the scan UI — are forwarded to the Core container on port 3000.
Key configuration details:
- Runs in DB-less mode (
KONG_DATABASE: "off") — no separate Postgres or Cassandra instance is needed
- Enabled plugins:
request-transformer, cors, key-auth, acl
- TLS termination is supported by supplying
WEBHOOD_TLS_CERT and WEBHOOD_TLS_KEY in .env; the certificate and key files are mounted into the container
- Nginx proxy buffer settings are tuned (
160k buffers) to handle large HTML and screenshot payloads without truncation
- Connected to both the
frontend and rest Docker networks so it can reach Core and the Backend
Scanner — webhood-scanner
The scanner is a Node.js/TypeScript service that drives a headless Chromium browser using Puppeteer Core. It is the only service that makes outbound connections to the public internet.
How it discovers and runs scans:
- On startup it authenticates with the backend (via Kong) using the
SCANNER_TOKEN and fetches its configuration record from the scanners collection.
- It opens two PocketBase realtime subscriptions — one on the
scans collection (to receive new scan jobs immediately) and one on its own scanners record (to pick up configuration changes without restarting).
- As a fallback, it also polls for new scans on a 10-second interval in case the realtime connection is interrupted.
- A semaphore limits how many scans run simultaneously. The concurrency limit respects both the
simultaneousScans setting configured in the UI and available container memory (approximately 150 MB per simultaneous scan).
What the scanner captures:
| Artifact | Stored as |
|---|
| Full-page screenshot | PNG file, saved to PocketBase file storage (screenshots field) |
| Rendered HTML | HTML file, first element of the html field array |
| Network trace | JSON file, second element of the html field array |
Configurable scanner options (set via Settings UI):
useStealth — enables puppeteer-extra-plugin-stealth to reduce bot-fingerprinting
useSkipCookiePrompt — loads the “I don’t care about cookies” extension to dismiss cookie consent dialogs
simultaneousScans — maximum number of concurrent browser sessions
ua / lang — custom User-Agent string and browser language overrides
- Scan speed presets: Fast, Balanced (default), Slow — control page-load timeouts
Security hardening:
The scanner container uses a custom seccomp profile (files/chrome.json) to restrict the syscalls available to the Chrome process. This allows Chrome’s built-in sandbox to function correctly without running the entire container as root.
Network membership: rest only. The scanner never joins the frontend network and has no direct path to the Core UI container.
Core UI — webhood-core
The Core container is a Next.js application served on port 3000. Kong proxies the root path (/) to this container, so end users access it at http://<host>:8000/.
Pages and features:
- Scan list — searchable list of all scans using PocketBase filter syntax with autocomplete. The search query is reflected in the URL so results can be shared.
- Scan detail — tabbed view with Screenshot, HTML, Trace, Details, and Metadata tabs. Right-click context menus on the screenshot and trace data allow copying values, re-scanning URLs, or pivoting to external services (Shodan, Whois, Talos Intelligence, Google).
- Settings — scanner management (create, delete, refresh tokens), user management, and scanner configuration (stealth mode, cookie prompts, simultaneous scans, UA/lang overrides).
Backend connectivity:
Core communicates with PocketBase using the PocketBase JavaScript client. The EXTERNAL_URL environment variable controls the base URL the client uses; it defaults to / (routed through Kong) but can be set to http://backend:8090 for environments where Core needs a direct path to the backend.
Network membership: both frontend and rest.
Backend — webhood-backend
The backend is built on PocketBase — a single Go binary that bundles authentication, a SQLite database, file storage, and a REST API. It listens on port 8090 inside the Docker network and is reachable externally only through Kong at the /api/ prefix.
API endpoints registered in apiv1.go:
| Method | Path | Auth |
|---|
GET | /api/v1/scans | User or API token |
GET | /api/v1/scans/:id | User or API token |
POST | /api/v1/scans | User or API token |
GET | /api/v1/scans/:id/screenshot | User or API token |
GET | /api/v1/scans/:id/html | User or API token |
GET | /api/v1/scans/:id/trace | User or API token |
The POST /api/v1/scans endpoint creates a scan record with status pending and a URL-derived slug, then returns HTTP 202 Accepted. The scanner picks up pending records via its realtime subscription or the 10-second poll.
Data model fields returned by the API:
id, slug, url, status, created, updated, errorMessage, html, screenshots, done_at, final_url, options, scanData
Persistence:
All data — the SQLite database, uploaded files (screenshots, HTML, traces), and PocketBase system files — is stored in the named Docker volume data mounted at /pb/pb_data. This volume persists across container restarts and upgrades.
Health check:
The backend container exposes a health check at http://localhost:8090/_/. Docker polls this every 10 seconds; the scanner container’s depends_on directive waits for a healthy status before starting.
Network membership: frontend only. The backend is not on the rest network and cannot be reached directly by the scanner — all scanner-to-backend communication goes through Kong.
Docker Networks
Webhood uses two internal Docker networks to enforce a minimal connectivity model:
| Network | Members | Purpose |
|---|
frontend | Kong, Core, Backend | Allows Kong to route UI requests to Core and API requests to Backend; allows Core to call the Backend directly |
rest | Kong, Scanner, Core | Allows the Scanner to reach Kong (and through it, the Backend API); allows Core to reach Kong |
The scanner is intentionally absent from the frontend network. It can only communicate with the backend indirectly through Kong, which enforces authentication on every request.
frontend network: Kong ←──→ Core ←──→ Backend
rest network: Kong ←──→ Scanner
Kong ←──→ Core
The public internet is reachable only from the scanner container, and only for outbound connections initiated during a scan. No service other than the scanner makes outbound internet requests.