Nox: A Modular Go Framework for Recon and Vulnerability Scanning
Nox is a Go-based attack surface management and vulnerability scanning framework. Its README describes it as shipping with 300 built-in modules across OSINT, subdomain enumeration, DNS, port scanning, web fingerprinting, and active vulnerability testing.
That is a lot of surface area for one CLI. The interesting Go lesson is not just that Nox has a long module list. It is how the repository tries to keep that list from turning into a pile of special cases.
The shape of the project
Nox follows a familiar Go layout:
cmd/nox/main.gois the small executable entrypoint.internal/cli/contains Cobra commands such asfull,scan,probe,report,modules,wordlists, andhealth-check.internal/app/wires configuration, logging, storage, the registry, and the engine together.internal/engine/handles scan execution, concurrency, events, rate limiting, output, Interactsh setup, and optional distributed execution.internal/modules/contains the recon, OSINT, fuzzing, and vulnerability modules.pkg/module/defines the common module contract used by the engine and registry.
That split matters. A tool like this can easily become one huge command handler that shells out to other tools and writes files wherever it happens to be convenient. Nox instead puts command parsing, orchestration, module implementation, persistence, and output writing behind separate packages.
This is the kind of structure that pays off when a project grows from “run a few checks” into “run a full scan pipeline”.
Modules are the core abstraction
The most important design choice is the module interface. Nox’s architecture docs describe every module as exposing the same basic operations: name, category, description, tags, validation, and execution.
That gives the engine a simple job. It does not need to know whether it is running a WHOIS lookup, a DNS resolver, an HTTP prober, a nuclei runner, or an authorization check. It can validate the module, pass in a target and scan ID, then collect findings and assets from the result.
You can see the benefit in the CLI:
nox modules listcan list registered modules consistently.nox modules run <name>can execute one module without a bespoke code path for every check.nox full <domain>can run the registered modules in a fixed, 24-phase order.nox scan --workflow <file>can run a user-defined workflow that references modules by name.
For Go developers, this is a good reminder that interfaces do not need to be clever to be useful. A small contract becomes powerful when it lets the rest of the system stop caring about concrete module types.
The full scan is explicit, not magical
The full command is not hidden behind a vague “do everything” function. In internal/cli/full.go, Nox defines a phase list in code. The early phases handle OSINT, subdomains, DNS, ports, and HTTP fingerprinting. Later phases move into core vulnerability checks, session and auth testing, authorization, client-side issues, cloud checks, injection, API security, content discovery, secrets, mobile API security, business logic, AI/LLM security, and reporting enrichment.
That explicit phase list is useful for two reasons.
First, operators can understand the order of work. Discovery runs before probing. Probing runs before deeper active checks. Reporting comes at the end.
Second, contributors can see where a new module belongs. A scanner with hundreds of checks needs that kind of map, otherwise “add a module” becomes “guess where this should be called from”.
Concurrency is centralized in the engine
Recon and vulnerability scanning involve a lot of I/O: DNS lookups, HTTP requests, port checks, subprocesses, API calls, and file writes. The temptation in Go is to launch goroutines wherever they are needed. That works for small tools. It becomes hard to reason about once every module starts doing its own scheduling.
Nox keeps the main scan scheduling in the engine. internal/engine/worker_pool.go defines a worker pool with:
- a fixed worker count, with a floor of one worker
- buffered job and result channels
- context-aware workers
- a
sync.WaitGroupfor shutdown - a results channel that closes after all workers exit
The engine also uses rate limiting from golang.org/x/time/rate, so concurrency and request pacing are not treated as the same problem. That is an important distinction. Worker count limits how much work runs at once. Rate limiting controls how quickly work is allowed to start.
If you are building a Go tool that touches real services, both knobs matter.
Events keep progress reporting separate
internal/engine/events.go defines scan events for scan start, module start, module completion, stage start, stage completion, scan completion, and scan failure. Events can carry counts, findings, assets, progress, output paths, duration, and errors.
The implementation is deliberately small: emit into a channel when one is present, and avoid blocking if the receiver is not ready.
That design keeps the engine from being tightly coupled to a specific UI. A CLI can use events for progress output. A future API server could use the same stream for live status. Tests can inspect events without parsing human-readable logs.
Output writing has its own package boundary
Scan results are not just printed and forgotten. internal/engine/output_writer.go writes findings and assets under a scan-specific output directory. It groups findings and assets by category, supports text and newline-delimited JSON output, tracks per-file counts, and writes a summary.
That is not glamorous code, but it is the kind of code that makes a scanner usable after the terminal scrollback is gone. It also keeps formatting and file handling out of the module implementations. Modules return structured findings and assets; the writer decides how those results land on disk.
Configuration is layered
Nox’s README documents a layered configuration model: built-in defaults, then an optional YAML config file, then NOX_ environment variables, then command-line flags. The repo includes configs/nox.yaml, configs/passive.yaml, and detailed docs under docs/configuration/.
That is a sensible approach for a security CLI. Local experimentation wants flags. Repeatable scans want config files. CI, containers, and scheduled jobs often want environment variables. The important part is that precedence is explicit.
Distributed scanning is behind a provider interface
Nox also has a fleet package for distributed execution. internal/fleet/provider.go defines a provider interface for instance creation, destruction, listing, job submission, job status, and health checks. The Axiom implementation lives in internal/fleet/axiom/provider.go.
This is a clean place to use an interface. The scan manager can distribute work without embedding Axiom-specific HTTP details everywhere. The provider handles the remote API. The manager handles jobs and cleanup.
There is still a practical warning here: distributed scanning does not remove the need for scope control or authorization. Nox’s own README is explicit that active scans perform real network requests and should only be run against systems you own or have written permission to test.
Interactsh support is lifecycle-managed
Out-of-band testing is another area where lifecycle matters. internal/interactsh/client.go wraps registration, payload generation, polling, and close operations for an Interactsh-compatible server. The engine registers the client at scan start when configured and closes it when the scan ends.
That is the right ownership model. The engine owns the scan lifecycle, so it should also own setup and teardown of services that exist for the duration of that scan.
What Go developers can take from Nox
Nox is a useful codebase to read because it deals with problems that show up in many non-security systems too:
- many plugins or modules with one common contract
- ordered pipelines with optional custom workflows
- controlled concurrency
- progress events
- structured output
- layered configuration
- provider-backed integrations
The security domain makes those problems obvious, but the patterns are broadly useful. If you are building a Go CLI that has grown past a couple of commands, spend time with the Nox source tree. The best ideas to borrow are not the individual vulnerability checks. They are the boring boundaries around the checks: modules, engine, events, output, config, and providers.