Skip to content
ADscan Docs

Telemetry

What the Community and PRO command-line tools collect, how it is sanitized, where it goes, and how to turn it off. The Enterprise appliance sends nothing.

Overview

Which deployment this page describes

The Community (LITE) and PRO command-line tools, where telemetry is on by default. The appliance is the opposite: it ships air-gapped and sends nothing. Offline mode is set before the first scan runs, and the appliance worker pins ADSCAN_OFFLINE=1 and ADSCAN_TELEMETRY=0 so a per-scan setting cannot re-enable it. On the paid Enterprise appliance that is the default posture and it holds until you deliberately change it. Nothing below applies to either.

The Community and PRO command-line tools collect usage events and pseudonymized terminal session recordings. Telemetry is enabled by default and can be turned off at any time.

The design decision, stated plainly: raw identifiers never leave your machine, but the structure of your session does. Domain names, hostnames, IP addresses, SIDs, usernames, passwords and hash material are replaced with deterministic pseudonyms before anything is uploaded, and the upload is aborted outright if sanitization fails. What reaches us is the shape of a run: which commands executed, which panels rendered, which finding types appeared and how many. That is enough to reproduce a broken workflow and fix it, without ever holding your client's data.

If that trade is wrong for a particular engagement, ADSCAN_TELEMETRY=0 stops all of it, and --offline refuses every outbound connection.

What is collected

Structured events

DataExamplePurpose
Command names, from a fixed allow-listenum_ldap, creds_showUnderstand which features are used
Install and update stages, including failure reasondocker_not_installedFix the install path
Timing metricsTime to first credential, scan durationMeasure where runs stall
Scan outcome countersPaths to Tier 0, credentials obtained, hashes extracted, domain compromised (yes/no)Measure whether the tool worked
System contextOS, distribution, architecture, Python versionEnsure compatibility
ADscan and launcher versions10.1.0Track adoption and pin regressions to a release
Approximate locationCountry and region, derived server-side from the source IPUnderstand where the tool is used

An event name that is not on the allow-list is coerced into a static bucket, so an operator-supplied token cannot become an event name.

The two optional questions

Once ever, at the end of a session, ADscan asks one short multiple-choice question: where you first heard about it, and on a later session, which kind of work you do. One question per session, never two. Both lists end in Prefer not to say, and Ctrl-C dismisses either.

What is sent is the option you picked. If you answer the first question with Something else, the one line you type is sent as well, capped at 120 characters and put through the same sanitizer as everything else, so a hostname or a path inside it is pseudonymized before upload.

Neither question appears on an unattended run (adscan ci), on an offline or air-gapped deployment, or on a machine with telemetry turned off.

Terminal session recordings

For install, check, update, upgrade, start, tui, ci, execute and doctor, ADscan exports the entire Rich console buffer at the end of the run, sanitizes it, and uploads it as HTML and text.

This is the part worth being explicit about. It is a full transcript of what you saw on screen: every command, every panel, every finding type and count, the sequence of the attack chain. The identifiers inside it are pseudonyms, not the real values. Set ADSCAN_SESSION_CAPTURE=0 to keep event telemetry and drop the recordings.

Exceptions

Sanitized exception envelopes, so a crash can be diagnosed from the stack rather than from a bug report.

What is not collected

Raw values of any of the following ever leave the host:

  • Domain names, hostnames, IP addresses, SIDs
  • Usernames, passwords, NTLM and LM hashes, Kerberos tickets, AES key material
  • Filesystem paths
  • File contents, share contents, LDAP objects, or anything read out of NTDS.dit
  • Workspace artifacts: technical_report.json, attack_graph.json, exports, reports

Read this before you write it into a security review

A pseudonymized recording of an engagement is engagement content in the sense a reviewer means it. The counts, the technique sequence and the shape of the attack chain are transmitted. The values in them are not. We would rather you assess the real thing than a claim that we send "nothing".

How sanitization works

Two layers, both applied to the whole buffer at export time, immediately before upload.

Marker-based. Sensitive values are wrapped in zero-width Unicode markers at the moment they are printed (mark_sensitive(value, category)). The sanitizer finds the marked spans and replaces each one with a deterministic, format-preserving pseudonym.

Structural nets. These run whether or not a value was marked, so a value that reaches the console through an unmarked path is still caught:

PatternNotes
Domain SIDsAnchored on S-1-5-21-. Well-known SIDs are deliberately left alone.
IPv4 and IPv6Including CIDR notation.
MAC addressesBroadcast and all-zero MACs are left verbatim.
Entra Connect sync accountsMSOL_ followed by 12 hex digits.
NTLM hashesBare 32-hex, and the 32-hex:32-hex LM:NT form.
Kerberos and AES key material40 to 128 hex characters.
--hashes argumentsThe flag and its value together.

Pseudonyms are keyed and deterministic: the same input always maps to the same output. Password reuse and shared-hash correlation therefore survive the sanitizer, while the raw value does not. Where the format allows, the pseudonym is provably fake, so you can tell at a glance that you are not looking at real data: a SID loses its S-1- prefix, an IP gets an octet above 255. Hex has no equivalent out-of-band symbol, so a pseudonymized hash still looks like a hash.

Fail-closed. If sanitization raises, or returns anything other than a string, the recording is discarded and nothing is uploaded. There is no raw fallback path.

Example

Real sanitizer output, not an illustration:

# In your terminal:
DCSync successful: [email protected] -> Passw0rd!

# What we receive:
DCSync successful: [email protected] -> Ifywo4prR

Where it goes

PurposeDestination
Usage events (production)https://n8n.adscanpro.com/webhook/posthog-prod
Usage events (development and CI builds)https://n8n.adscanpro.com/webhook/posthog-dev
Exceptionshttps://n8n.adscanpro.com/webhook/sentry
Session recordingshttps://sessions.adscanpro.com/api/sessions
Session recordings (fallback)https://n8n.adscanpro.com/webhook/vercel-sessions
Lab-tracking notificationshttps://n8n.adscanpro.com/webhook/adscan_labs

Events are proxied into PostHog in the EU region. Session recordings are stored on ADscan-controlled infrastructure. Both are outside your network, which is the reason the opt-out exists and the reason the sanitizer is fail-closed. The appliance contacts none of these hosts.

These endpoints are XOR-obfuscated in adscan_core/embedded_telemetry_config.py. That keeps ingest tokens out of plaintext in a repository whose source is published; it is not an attempt to hide where the data goes. They are listed here so nobody has to decode them to find out.

How to turn it off

MethodScope
ADSCAN_TELEMETRY=0Everything: events, session recordings, exceptions, lab webhook
ADSCAN_SESSION_CAPTURE=0Session recordings only; events still sent
ADSCAN_OFFLINE=1Everything above, plus every other outbound lookup
set telemetry off inside the shellEverything, for the current workspace only
set telemetry off global inside the shellEverything, for the operator-wide default that new workspaces inherit
adscan start --no-telemetry / adscan ci --no-telemetryThat run only
adscan start --offline / adscan ci --offlineThat run only, and no external lookups either

For one run

adscan start --no-telemetry

Or, for a fully air-gapped posture in the same run:

adscan start --offline

From inside ADscan: workspace vs global

Two scopes. Without a keyword, set telemetry changes only the workspace you are in; with global, it changes the operator-wide default that unset workspaces inherit.

(ADscan) > set telemetry off          # this workspace only (this engagement)
(ADscan) > set telemetry off global   # the default new workspaces inherit
(ADscan) > set telemetry on           # back on for this workspace
(ADscan) > set telemetry on global    # back on for the default

An explicit per-workspace choice always wins over the global default, in either direction. There is no one-way ratchet: a workspace you turn back on stays on even when the global default is off. ADscan resolves the state in this order:

  1. The workspace's own explicit choice, if you set one.
  2. Otherwise, the global default.
  3. Otherwise, the tier default (on for Community and PRO).

A new workspace has no explicit choice yet, so it follows the global default live. Turn the global default off and every new or unset workspace is off; any workspace you set by hand keeps what you set it to.

You runGlobal default off, new workspaceGlobal default off, workspace you set onGlobal default on, workspace you set off
ResultOff (inherits the default)On (explicit choice wins)Off (explicit choice wins)

When telemetry is off, the status view names the scope that turned it off — the workspace or the global default — and prints the exact command to turn it back on.

This is written to your ADscan configuration, so both the global default and each workspace's explicit choice persist across sessions until you change them.

Permanently, from your environment

Add to ~/.bashrc or ~/.zshrc:

export ADSCAN_TELEMETRY=0

To keep event telemetry but drop the terminal recordings:

export ADSCAN_SESSION_CAPTURE=0

Air-gapped and sovereignty-restricted engagements

ADSCAN_OFFLINE=1 (alias: ADSCAN_NO_EXTERNAL=1) is the absolute kill switch. It disables telemetry, session-recording uploads, breached-password lookups, wordlist downloads, and the draining of any payload queued before it was set. Use it when the engagement contract says no data leaves the client's network.

export ADSCAN_OFFLINE=1

This is exactly how the appliance ships. It sets offline mode as the default before the first scan and pins ADSCAN_OFFLINE=1 with ADSCAN_TELEMETRY=0 in the worker, so an air-gapped deployment does not depend on an operator remembering a flag.

Find this useful?
Pass it to the next pentester running an AD engagement
Running 2+ AD engagements/year?
Get PRO free — beta access·Free in exchange for feedback
Automated PDF reports. Save ≥1 day per engagement.

ADscan — AD pentest automation for security consultants

Telemetry | ADscan