Skip to content

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.

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.

In the dashboard: Security → Attack surface.

  1. “Import domains from agents”: your agent vhosts arrive, verified via their certificate.
  2. Enable the policy with TOTP. Scanning is off by default.
  3. Choose a maintenance window; scheduled scans run only within it.
  4. “Scan now” (TOTP) for an immediate run. The kill-switch sits next to it and stops a running scan within seconds.

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.

  • One chokepoint. Every scanner gets its targets only through the internal ResolveTargets function, which (a) checks scope membership with a tenant_id filter, (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; the docker run itself lives in the wrapper.
  • Kill-switch actually stops. Each container has a name the hub can docker kill through the wrapper’s kill verb; 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 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.

The hub needs:

  • Docker on the host and the wrapper /usr/local/sbin/monsys-scan + /etc/sudoers.d/monsys-scan (shipped in hub/packaging/).
  • A writable /var/lib/monsys/scan-targets (in the hub unit’s systemd ReadWritePaths) 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.

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.