Blog

Inside the Privacy Gateway: the model never sees your real infrastructure

How Darkmoon tokenizes every sensitive value locally before it reaches the model — the 819-line command gateway, the per-session vault, the fail-closed prompt socket that tokenizes even the launch prompt, and the honest limits of deterministic placeholders.

· 10 min read

Point an autonomous AI pentester at your estate and you inherit a problem the marketing rarely mentions: to reason well, the agent wants a strong model, and the strongest models are hosted by someone else. The moment the agent sends a real IP, hostname, credential or internal path to that hosted model, sensitive facts about your infrastructure have left your perimeter and landed in a third party's logs. You have not run a private pentest. You have exported a map of your network to a vendor. For regulated, defence and sovereignty-constrained teams that single fact rules the whole approach out.

Darkmoon's answer is not "trust us with your data." It is an architecture where the model provider structurally cannot receive it. This is the Privacy Gateway: sensitive values are tokenized locally, before anything reaches the model, and rehydrated only on your own host, at the instant a tool runs and again when the local report is written. The reasoning model works the whole engagement on deterministic placeholders. This post is the architecture behind that claim, one measured run that tests it, and the limits we would rather state than have you discover.

The approach: reversible local tokenization

Every sensitive entity is replaced by a deterministic placeholder before it can reach the LLM: IP_PRIVATE_001, HOST_INTERNAL_002, URL_001, EMAIL_001, CRED_001. Deterministic is the load-bearing word. The same real value always maps to the same placeholder within a run, so the model can still reason about relationships — this host talks to that host, this credential works there — without ever seeing a real value. The mapping lives in a per-session vault, keyed by the session id, Fernet-encrypted in memory and never written to a log. The vault classifies what it sees through a Category enum — IP_PRIVATE, IP_PUBLIC, HOST_INTERNAL, DOMAIN, URL, EMAIL, PATH, USER, CRED — so an IPv6 address or an LDAP distinguished name is caught, not just the obvious IPv4.

The invariant the code states about itself is the one that matters: there is no MCP tool that maps a placeholder back to its value. Rehydration is not a capability exposed to the agent. It is a thing that happens on the host, outside the model's reach, at two moments and no others.

The architecture: two boundary crossings, and only two

The whole design collapses to a swimlane with a host lane and a model-provider lane, and exactly two arrows between them:

  • Tokenize (host → model). Everything the model is about to read — the launch prompt, tool inputs, tool outputs — passes through the gateway and comes out as placeholders. Tool output is re-tokenized by a sanitizer middleware on every tool result, with ANSI escape codes stripped before matching so a coloured banner can't smuggle a raw value past the detectors.
  • Rehydrate (host → host). Real values are restored only on your machine, at the instant a tool actually executes against the real target, and again when the report is finalized to disk. This arrow never crosses into the model lane. The model provider is on the far side of it the entire time.

The gateway itself is a single command-inspection component of roughly 819 lines, byte-identical across the community, production and API trees — the same code protects the free CLI and the paid platform. Around it sits the vault, the sanitizer middleware, and the piece that closed the last gap: the launch prompt.

Even the launch prompt is tokenized first

The very first thing a reasoning model reads in a campaign is the operator's launch prompt, and that prompt routinely carries the target — a URL, an IP, a scope. Tokenizing tool inputs and outputs is not enough if that first message reaches the model raw. So the pre-model path runs the prompt through the same vault before the first model call: an opencode plugin (darkmoon-privacy.ts) hands the prompt over a 0600 unix socket to prompt_socket.py and on to the server's tokenizer, sharing the session vault so the report can still rehydrate at the end.

This path is fail-closed by default. If the tokenizer cannot be reached, the raw prompt is withheld rather than sent through unprotected. It fails open only if an operator explicitly sets DARKMOON_PROMPT_TOKENIZE_FALLBACK=open — an opt-in, not the default.

# operator types a real target; the model receives placeholders
tokenize_prompt  ->  "TARGET: URL_001 PROGRAM=\"web app\" SCOPE=URL_001"

# the campaign is initialized on placeholders too
init_campaign    ->  target_host: URL_001   target_ip: IP_PRIVATE_001

The command gateway: a policy layer against exfiltration

Tokenization alone is not enough, because an agent can always try to be clever — encode a placeholder, stuff it in a URL, and ship the mapping itself off-box. The command gateway inspects what the agent attempts to run and blocks the exfiltration patterns: a placeholder inside a URL, a connection to an external host, echo/print of the mapping, a POST body carrying it, /dev/tcp, nc, telnet. It is explicitly a policy layer, not a sandbox. It shrinks the exfiltration surface; it does not make a genuinely hostile model safe, which is why tools also run inside containers on top of it.

One measured run that tests it

We ran the offensive llm agent end to end against a deliberately vulnerable OpenAI-compatible target on loopback, with the gateway active the whole time, and captured the model-facing traffic through a logging proxy so we could count exactly what the model received. An LLM engagement is the hardest case, because the whole point is to move large volumes of adversarial text through the reasoning model.

ControlResult on the validation run
Real target address seen by the model0 times, across ~3.5 MB of model-facing traffic
Target seen as the placeholder URL_00135 times — so the model could still reason about it
Report rehydrated on diskreal target restored 37 times, 0 residual placeholders
Exploited secrets restored to the reportcanary DMK-CANARY-7731 and the admin key, real values

Both directions matter equally. The client's own infrastructure — the address of the endpoint under test — must never reach Darkmoon's reasoning model, and it did not: zero real occurrences across the whole engagement. Meanwhile the target model's leaked secrets are the findings, so they have to end up in the report in full — and they did, rehydrated only at write time, on the host. The placeholder carried the reasoning; the real values never left your machine to get there.

What this run is, and is not

These figures come from a single end-to-end run against a deliberately vulnerable teaching endpoint on the sealed build. They show the gateway does not blind the agent and does not leak the target on that run. They are not a detector-accuracy score, not a "percentage of secrets protected," and not a guarantee about every possible input. We have not measured those, so we do not publish a number for them.

The honest limits

We would rather state these than have you find them:

  • Deterministic placeholders leak structure by design. The cardinality of distinct hosts and the co-occurrence of entities remain visible to the model even when the values are not. For most infrastructure data that is an acceptable trade, but it is a real information channel and we treat it as one.
  • The command gateway is a policy layer, not a sandbox. It reduces the exfiltration surface; it does not make a truly adversarial model safe. Containerized tool execution sits underneath it for that reason.
  • A DEGRADE policy can transmit a still-tokenized placeholder. When a value cannot be confidently classified and rehydrated, the gateway can degrade — tokenize and continue — rather than abort. That means a placeholder such as IP_PRIVATE_001 may transit even though the real value does not. We do not claim "nothing tokenized is ever transmitted"; we claim the real value is not.
  • There is no user-facing per-session privacy toggle. Privacy is on; the internal enabled flag is read-only and has no runtime setter, so we do not advertise a control that does not exist.

Community or Pro

The two components that make the guarantee — the reversible local tokenization and the anti-exfiltration command gateway — ship in the open-source engine, with the same 819-line gateway code as the paid platform. You can read every line and run it yourself. The Pro tier adds the operational layer on top: the sealed vault, the rehydration audit trail and the compliance evidence a regulated buyer needs to show that the boundary held, not just that it exists.

FAQ

Does my target reach a hosted model when Darkmoon tests it? No. The target is tokenized to a deterministic placeholder before the reasoning model sees anything, including your launch prompt. On the validation run the real address reached the model zero times across the whole engagement.

If the model only sees placeholders, how does the report contain real values? The real values live in the local session vault and the report is rehydrated from them on the host at write time. On the validation run the report was written with the real target and the real exploited secrets, with zero residual placeholders left behind.

Can I remove the third party entirely? Yes — run a local model. The gateway is what makes a hosted model safe to use; a local model on your own hardware removes the provider from the picture altogether. That path is covered in the local, self-hosted AI pentester.

The prompt-tokenization release and the offensive llm agent are covered in pentesting an LLM without leaking your own infrastructure and the threat model in how to run an AI pentest without sending your data to the LLM. See where this sits in the whole engagement in how Darkmoon works, or the sovereignty story in Darkmoon for self-hosted and sovereign teams. Darkmoon 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.