Passive Shadow-AI network detector. It reads a packet capture (or sniffs an interface) and reports which internal hosts are reaching AI services — ChatGPT/OpenAI, Claude, Gemini, Copilot, Perplexity and more. Detection is by DNS query name and TLS SNI, so it works even without a proxy and without decrypting anything. Output is a bilingual (EN/ES) HTML report plus CSV and JSON.
Companion to netscan and
webscan: a blue-team / governance
tool for finding unsanctioned AI use on a network you are authorized to monitor.
Detector pasivo de Shadow-AI en red. Lee una captura (o escucha una interfaz) e informa de qué hosts internos contactan con servicios de IA, a partir del nombre DNS y del SNI de TLS. No descifra nada. Salida en informe HTML bilingue (EN/ES) mas CSV y JSON.
shadowscan looks at two things that travel in clear text on most networks:
- DNS queries (UDP/53): the domain a host resolves (
chatgpt.com,api.openai.com…). - TLS ClientHello SNI (TCP/443): the destination domain, visible in the handshake even when there is no proxy.
Matches are compared against an editable catalog of AI-service domains and aggregated by internal source IP. It never inspects payloads — only domain metadata. Matching by destination IP is deliberately avoided: services like ChatGPT sit behind shared CDNs (Cloudflare), so IPs are ambiguous. Domains are not.
This is the part that decides whether you see anything. A passive sniffer only sees traffic that crosses its own interface — being able to route to other segments (L3 reachability) is not the same as seeing their traffic. On a switched, segmented network a host sees only its own traffic plus local broadcast.
To cover other segments, capture at a point the traffic actually transits:
- SPAN / mirror port on the core switch (mirror the uplink or inter-VLAN traffic).
- On the gateway / firewall / router — all inter-VLAN and egress traffic passes through it.
- On the central DNS resolver — if every segment resolves through the same DNS, capturing there (or feeding its query logs) sees the DNS of all segments even when they are isolated. This is usually the easiest win for the DNS half.
- Or just feed shadowscan PCAPs taken at any of those points.
TLS SNI is only visible inline (gateway / proxy / mirror); DNS is visible at the resolver.
git clone https://github.com/mr7security/shadowscan.git
cd shadowscan
pip install -r requirements.txt
pip install -e . # optional, for the `shadowscan` commandPython 3.9+. The only dependency is scapy.
# Offline: analyze a capture (no root needed)
sudo tcpdump -i eth0 -w capture.pcap 'udp port 53 or tcp port 443'
python -m shadowscan --pcap capture.pcap
# Live: sniff an interface at a mirror port / gateway (needs root)
sudo python -m shadowscan --interface eth0 --timeout 300
# Console in Spanish, plus CSV and JSON for the SIEM
python -m shadowscan --pcap capture.pcap --lang es --csv usage.csv --json usage.json
# Extend the AI catalog with your own domains
python -m shadowscan --pcap capture.pcap --catalog extra_ai.jsonextra_ai.json is just {"Service name": ["domain.com", "other.com"]} and is
merged into the built-in catalog.
| Flag | Description |
|---|---|
--pcap FILE |
Analyze a .pcap/.pcapng capture |
--interface, -i IFACE |
Sniff a live interface (needs root) |
-o, --output |
HTML report path (default report.html) |
--no-report |
Do not write the HTML report |
--json [PATH] |
Write JSON (stdout if PATH omitted) |
--csv PATH |
Write a flat CSV of host/service rows |
--catalog FILE |
Merge extra AI domains from a JSON file |
--lang {en,es} |
Console language (report is bilingual) |
--count N / --timeout S |
Live mode stop conditions |
Exit codes: 2 when AI traffic was detected, 0 when none, 1 on error —
handy for scheduled runs and CI gates.
- Console summary: hosts with AI traffic, total sightings, services seen.
- Bilingual HTML report with an EN/ES toggle: one card per internal host with a per-service breakdown (DNS vs TLS hit counts, domains, last seen).
- CSV (
src_ip, service, hits, dns, tls, first_seen, last_seen, domains) and JSON for SIEM / pipelines.
shadowscan/
├── shadowscan/
│ ├── cli.py # argument parsing + orchestration
│ ├── capture.py # scapy I/O: read pcap / live sniff (passive)
│ ├── parsers.py # pure DNS qname + TLS SNI byte parsers (unit-tested)
│ ├── catalog.py # editable AI-service domain catalog + matching
│ ├── aggregate.py # group sightings by internal IP and service
│ ├── models.py # Sighting dataclass, bilingual helper
│ ├── report_html.py # self-contained bilingual HTML report
│ └── report_console.py # plain-text console report
└── tests/ # offline tests for the parsers, catalog and aggregation
This is passive, metadata-only monitoring. Run it only on a corporate network you own or are explicitly authorized to monitor, and follow your local laws and your organization's privacy and employee-monitoring policies. It reads domain names from DNS and TLS SNI; it does not decrypt or store traffic content. You are solely responsible for how you use it.
Monitorizacion pasiva y solo de metadatos. Ejecutala unicamente en una red corporativa propia o con autorizacion explicita, respetando la legislacion y las politicas de privacidad y de monitorizacion de tu organizacion. Lee nombres de dominio de DNS y del SNI de TLS; no descifra ni almacena el contenido del trafico.
MIT — see LICENSE.