Skip to main content

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:
  1. On startup it authenticates with the backend (via Kong) using the SCANNER_TOKEN and fetches its configuration record from the scanners collection.
  2. 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).
  3. As a fallback, it also polls for new scans on a 10-second interval in case the realtime connection is interrupted.
  4. 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:
ArtifactStored as
Full-page screenshotPNG file, saved to PocketBase file storage (screenshots field)
Rendered HTMLHTML file, first element of the html field array
Network traceJSON 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:
MethodPathAuth
GET/api/v1/scansUser or API token
GET/api/v1/scans/:idUser or API token
POST/api/v1/scansUser or API token
GET/api/v1/scans/:id/screenshotUser or API token
GET/api/v1/scans/:id/htmlUser or API token
GET/api/v1/scans/:id/traceUser 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:
NetworkMembersPurpose
frontendKong, Core, BackendAllows Kong to route UI requests to Core and API requests to Backend; allows Core to call the Backend directly
restKong, Scanner, CoreAllows 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.

Build docs developers (and LLMs) love