Automated pentest (attack surface)
The monsys inventory knows what runs on your hosts (ports, certificates, packages, runtimes). The automated-pentest layer looks from the outside and answers what the inventory cannot: what is actually reachable from the internet, and is a detected CVE exploitable?
It is opt-in, runs only against proven-owned scope, and is detect-only: never exploitation, never brute force, nothing written to a target.
Scope = proven ownership
Section titled “Scope = proven ownership”A scanner only touches an asset that exists as a verified scope row. There is no free-form field that starts a scan. Ownership is proven three ways:
- agent_vhost: an agent found the domain in nginx/Caddy/certbot on its host. Auto-verified only when a public-CA certificate on that host covers the name (you cannot get a public cert for a domain you do not control). Otherwise it is a candidate, not scannable, until DNS-TXT or manual confirmation.
- dns_txt: place a
_monsys-challenge.<domain>TXT record with the shown token. For domains without an agent. - manual_admin: a tenant owner adds a domain/IP with TOTP.
Enabling
Section titled “Enabling”In the dashboard: Security → Attack surface.
- “Import domains from agents”: your agent vhosts arrive, verified via their certificate.
- Enable the policy with TOTP. Scanning is off by default.
- Choose a maintenance window; scheduled scans run only within it.
- “Scan now” (TOTP) for an immediate run. The kill-switch sits next to it and stops a running scan within seconds.
Scanners and phases
Section titled “Scanners and phases”All scanners run as open source in pinned Docker containers on the hub.
| Phase | Scanner | What it detects |
|---|---|---|
| 1 | nmap | open ports and services (connect scan, no OS probe) |
| 1 | testssl.sh | weak/expired certificates, old TLS versions, weak ciphers |
| 1 | nuclei | misconfigurations, exposed files (.git, .env), missing security headers, default logins |
| 2 | nuclei (CVE) | confirms a CVE from the inventory actually responds → exploitable confirmed |
| 3 | OWASP ZAP | passive baseline per web application |
nuclei runs with the tags misconfig,exposure,ssl,default-login; everything
intrusive (DoS, brute force, fuzzing, active injection) is excluded and
out-of-band interaction is off.
Safety architecture
Section titled “Safety architecture”- One chokepoint. Every scanner gets its targets only through the internal
ResolveTargetsfunction, which (a) checks scope membership with atenant_idfilter, (b) repeats the SSRF/private-range guard on the resolved IP (no RFC1918, loopback, link-local, CGNAT, 169.254, NAT64/6to4/Teredo, cloud metadata), (c) enforces the kill-switch, opt-in and window. - Hardened, pinned containers. Scanners run through a sudo wrapper
(
/usr/local/sbin/monsys-scan) with digest-pinned images,--read-only,--cap-drop ALL,--security-opt no-new-privileges, pids/memory/cpu limits, non-root where the tool allows, and DNS pinned to public resolvers. The hostname is bound to the vetted IP via--add-host, making DNS rebinding impossible. The unprivileged hub only picks a scanner name and supplies a validated target file; thedocker runitself lives in the wrapper. - Kill-switch actually stops. Each container has a name the hub can
docker killthrough the wrapper’skillverb; the worker checks the flag every few seconds. - Redaction. Evidence is recursively redacted (tokens, JWTs, basic-auth in URLs, private keys) before storage. Raw scanner output is never kept.
Findings and audit evidence
Section titled “Findings and audit evidence”Findings appear with category, severity, status (open / fixed / accepted / false positive) and are compared week over week (“new since last week”). A confirmed CVE lifts the matching exposure to “exploitable confirmed” with an alert.
Every scan run is Ed25519-signed and enters the monthly audit pack: who, when, which scope, which findings. That is reproducible proof that you assess the effectiveness of your measures: directly usable for a NIS2 audit.
Self-hosting
Section titled “Self-hosting”The hub needs:
- Docker on the host and the wrapper
/usr/local/sbin/monsys-scan+/etc/sudoers.d/monsys-scan(shipped inhub/packaging/). - A writable
/var/lib/monsys/scan-targets(in the hub unit’s systemdReadWritePaths) and a cached template set in/var/lib/monsys/nuclei-templates(mounted read-only). - Scanner images are digest-pinned; refresh the template set periodically out of band.
Limits
Section titled “Limits”Detect-only is not a replacement for a manual pentest with exploitation; it covers the reachable, known and misconfigured surface, continuously. nuclei templates are cached and not downloaded per run. ZAP runs one container per domain.