Blog

How DarkMoon investigates a target: signal, specialist agents, and a governed 142-tool boundary

From a target string to qualified findings: fingerprinting technology signals, dispatching 50 specialist agents by target class, the adversarial EXPLOITED/CONFIRMED/UNCONFIRMED rubric, and the single build-enforced allow-list of 142 security tools that bounds what every agent can run.

· 9 min read

A vulnerability scanner runs one pass of signatures and hands you a list. A penetration test does something a list cannot: it reasons about the target, follows the thread from one artifact to the next, and only calls something a finding once it can say exactly how sure it is. Darkmoon is built to run the second thing autonomously. This article walks the path a target actually takes through the platform, from the launch string to a qualified finding, and is honest about which steps are machine-checked and which are the agent's own judgement.

From a target string to a dispatch

Everything starts with a single pentest orchestrator. It does not attack. Its job is to fingerprint the target and decide who should. On the way in it reads roughly fourteen technology signals off the target, the stack, the exposed services, the identity surface, the presence of a CI system or an inference API, and turns that reading into a dispatch by target class. A web application draws the web specialist; a domain controller draws the Active Directory specialist; a live /v1/models endpoint draws the llm specialist. The routing is not a keyword match, it is a decision the orchestrator makes from the artifacts it actually saw.

There is an implementation detail worth stating plainly, because it is easy to imagine a fancier architecture than the one that exists. Dispatch is emitted inline, as a task() tool call inside the same per-turn model stream that is doing the reasoning. There is no separate router model sitting in front deciding who runs in order to save money. The orchestrator reasons and dispatches in one place.

# orchestrator fingerprints the target, then dispatches by class
recon           ->  14 signals: http/tls, wordpress, api/graphql, postgres, k8s-api
task(agent="web",   scope="URL_001", reason="wordpress + graphql surface")
task(agent="infra", scope="IP_PRIVATE_001", reason="postgres 5432 answered")
# no infra/cloud agent is dispatched on a guess — only on a positive artifact

Two guardrails matter here. Cloud and infrastructure agents dispatch only on a positive artifact, an endpoint that answered, a service that banner-grabbed, never on inference from the target's name. And cascade depth is capped, so a specialist that spawns a follow-up cannot spiral into runaway recursion. The result is a campaign that fans out where there is real surface and stays quiet where there is not.

The fleet: one orchestrator, 50 specialists

Behind the orchestrator sit 50 specialist agents. Each one is a focused playbook for a single target class rather than a generalist trying to remember everything: web and API, cloud (AWS, Azure, GCP, Entra ID), Active Directory, Kubernetes, databases, IoT and firmware, CI/CD, and infrastructure-as-code, among others. One of them is the llm agent, which assesses an exposed AI or LLM inference endpoint against the OWASP LLM Top-10. It is dispatched only when a real inference API answers, and it runs a mandatory, bounded garak pass first before any manual exploitation, so the automated coverage happens before the agent starts improvising.

Counting the agents honestly

The community engine ships the orchestrator plus these 50 specialists. Say "50 specialists" when you mean what the open-source engine does. The number becomes "52 agents" only when the paid Pro tier is in scope, because Pro adds one more, the remediation agent (1 orchestrator + 50 specialists + 1 Pro remediation). This post is about the investigation, which is the community fleet.

How a finding earns its status

This is the part most worth understanding, because it is where Darkmoon is deliberately conservative. Every finding an agent produces is forced through an adversarial STATUS QUALIFICATION rubric written into that agent's own prompt. The rubric makes the agent argue against itself and assign one of three tiers, and it defines exactly what each tier requires:

StatusWhat the agent must haveWhat it does not claim
EXPLOITEDThe impact was executed end to end (data pulled, access gained, command run)Not merely reachable
CONFIRMEDThe payload plus the raw response plus the extracted data are all presentImpact not yet executed
UNCONFIRMEDA lead only: something looks wrong but the evidence chain is incompleteNot presented as proven

So a SQL injection where the agent read a row out of the database is CONFIRMED and carries the payload and the raw response; a login page that merely looks like it might be injectable stays UNCONFIRMED and is labelled as a lead, not a win. Exploited findings are counted separately, so a lead is never dressed up as a proven exploit in the totals.

What the rubric is, and what it is not

The qualification is a prompt-enforced self-review, not a machine check. The status is asserted by the agent and then normalized and counted; there is no automated engine that independently re-runs the exploit to prove the agent right at pentest time. The one place in the whole product where an exploit is machine-re-verified is the paid Pro remediation retest, described in the closed remediation loop. We say the platform qualifies findings; we do not say it automatically proves them.

The toolbox is a boundary, not a bag of scripts

Autonomous agents that run offensive tools are only as safe as the set of tools they can reach. Darkmoon exposes 142 security tools to its agents through a single executor allow-list that acts as a hard security boundary. The number is not the point. The point is the gate: a build-time drift-guard test asserts that every tool wrapper installed in the image is present in the allow-list, and it fails the build if anything drifts.

  • An agent cannot silently gain an unvetted binary, because a tool that is installed but not allow-listed breaks the build.
  • An agent cannot silently lose a capability across releases, because a tool that is allow-listed but missing is caught the same way.
  • The agents' capability surface is therefore a reviewed list in the repository, not an emergent property of whatever happened to land in the container.

That is a real safety property for an autonomous offensive system, and it is the kind of thing a governed toolbox gives you that a pile of ad-hoc scripts never can.

What a finding carries, and what leaves the host

When an agent qualifies a finding, the record is rich: severity, a CVSS score and vector, MITRE ATT&CK mapping, the relevant ISO 27001 control, and the full local evidence, the payload, the raw request and response, the extracted data. That full record lives on your host. What crosses the wire to any integration is deliberately thin. The API, the webhooks and the event stream emit only safe fields: counts, booleans, ids, MITRE tags and timestamps. Evidence bodies, secrets and raw request/response pairs never leave the host over those surfaces.

A couple of honest boundaries on the finding model: Darkmoon maps findings to MITRE ATT&CK and ISO 27001, but it does not write a CWE id, and there is no per-finding confidence score at pentest time (a confidence number exists only later, on Pro fix validation). And the read API, the webhooks and the SSE stream are Pro surfaces; the community engine gives you the same structured findings as a local JSON report and a Markdown write-up.

Evidence, limitations, and the two editions

The mechanisms above are structural and readable: the orchestrator and every specialist are prompt files you can open, the qualification rubric is a block inside each of them, and the tool allow-list is a literal in the executor with a test that guards it. The honest limits are the ones already stated: qualification is the agent's own adversarial judgement rather than an independent proof, the marquee counts from any single run depend on the target and the model, and machine re-verification of an exploit happens only in the Pro remediation retest, not during the pentest itself.

  • Community (open source, GPLv3): the orchestrator and all 50 specialists, the qualification rubric, the 142-tool governed allow-list and its drift-guard, and a local JSON + Markdown report. Everything runs on your own infrastructure from the CLI and MCP.
  • Pro: the REST API, the live SSE dashboard, HMAC-signed webhooks, and the remediation agent with its exploit-retested fix pull requests. These are never open source.

Read on

The fleet-level design is covered in the multi-agent penetration testing architecture, why qualification beats a raw score is argued in proof of exploitation vs AI vulnerability scores, and the llm specialist's methodology is in the OWASP LLM Top-10 write-up. For the whole path end to end see how Darkmoon works. The engine is open source (GPL-3.0): github.com/ASCIT31/Dark-Moon, docs.

Run it against your own lab

Darkmoon is open source (GPL-3.0) and self hosted. Clone it, point it at a target you own, and read every line.