zonescope separates observation, judgement, and presentation. Collectors and probes return typed records; judge is the only module that turns contradictions into findings; reporters render the resulting Snapshot without re-deciding topology.
Module map
src/collect/nginx.ts: tokenizes enabled nginx configuration, parses server and location directives, createsVhostrecords, and merges blocks that represent one operational domain.sockets.ts: runsss, parses TCP listeners, classifies loopback bindings, and retains process and PID details when available.registry.ts: parses the convention range and named entries from theport_convention:block in/home/loca/AGENTS.md.units.ts: collects system and user service units and parses Docker container status and published host ports.
src/probe/tls.ts: reads the live certificate, expiry, issuer, and hostname match from the local TLS edge.http.ts: makes a non-following HTTPS request and records status, redirect, observed gate, server header, duration, and transport error.pool.ts: providesmapLimit, the order-preserving bounded-concurrency mapper used by live probes.
src/verdict.ts: joins the sources, deduplicates each code and subject pair, assigns the fixed severity, sorts findings, and exportsHOST_WIDE_CODESfor scoped scans.src/report/terminal.ts: renders the operator table and findings for a terminal.json.ts: pretty-prints the complete snapshot with a trailing newline.html.ts: renders the self-contained interactive dashboard and embeds the complete snapshot as application JSON.
src/scan.ts: collects the five host inventories in parallel, applies--only, chooses probe hosts, runs TLS and HTTP probes, invokesjudge, suppresses host-wide findings for scoped scans, and adds generation time and hostname.src/cli.ts: parses flags, invokesscan, writes requested artifacts, selects stdout rendering, and maps findings or failures to a process exit code.
Snapshot contract
Snapshot in src/types.ts is the shared contract across collection, verdicts, and reports:
generatedAt: ISO timestamp for this scan.hostname: machine hostname at scan time.vhosts: logical nginx vhosts with file, server names, listeners, TLS configuration, certificate path, root, upstreams, gate state, and redirect-only state.sockets: TCP port, bound address, loopback classification, process, and PID.units: unit name, system or user scope, ACTIVE value, and SUB value.containers: Docker identity, image and status, plus published host ports.registry: configured port range and entries, including exemptions, no-port declarations, labels, and notes.tls: oneTlsProbeper probed host, with success, certificate expiry, days remaining, identity match, issuer, and error.http: oneHttpProbeper probed host, with success, status, redirect, observed gate, server, duration, and error.findings: typed code, severity, subject, operator message, and optional structured evidence.
Reporters receive this completed value. They do not recollect the machine or independently assign severity.
Probe design
scan probes each unique, non-empty, non-wildcard server name from TLS-enabled vhosts. The placeholder _ is excluded. TLS and HTTP run together for each selected host.
Production probes connect directly to 127.0.0.1:443, so public DNS and an external network path do not decide which edge is inspected. The hostname still selects the intended virtual host:
- TLS sends a DNS hostname as SNI, reads the presented certificate with transport verification disabled, then explicitly checks whether that certificate covers the requested host.
- An IP-literal server name is sent without SNI because Node rejects an IP in
options.servername; the served certificate is still evaluated against that IP. A mismatch is thereforeCERT_MISMATCH, not a fabricatedTLS_ERROR. - HTTP sends the requested hostname in the
Hostheader, leaves redirects untouched, and recognizes an Authelia redirect or an authentication-bearing 401 as an observed gate.
Both probe functions follow a never-throw result contract for expected transport behavior: synchronous setup errors, connection errors, response errors, aborts, and timeouts resolve to typed records with ok: false and an error string. This lets one dead host coexist with useful results for every other host.
mapLimit semantics
mapLimit(items, limit, fn):
- requires a positive integer limit and otherwise throws
RangeError; - starts at most
min(limit, items.length)workers; - assigns exactly one output slot per input and preserves input order even when completion order differs;
- returns an empty array for an empty input;
- expects the callback to convert anticipated per-item failures into result values;
- rejects the whole operation when the callback unexpectedly rejects, matching
Promise.allfor programming errors.
The TLS and HTTP probes satisfy that callback contract by resolving failure records instead of rejecting.