# shadow-ai-report Turn DNS or web-filter logs into a one-file **shadow AI report** that a manager can open, read and print. `shadow-ai-report` reads the logs you already have, finds requests to known AI tools, and writes a single self-contained HTML page: which AI tools people reached, how often, how many users or devices, which tools carry a high or medium risk or data-sovereignty rating, and what to do next. - Free and open source (Apache-2.0). Python 3.8 or newer, standard library only, no dependencies. - Runs on your own computer. It makes **no network connections** and the report loads nothing from the internet. - Handles large and gzip-compressed log files by streaming them line by line. This is the **free edition**. It recognises the 300 AI tools of the free list. The full register (more than 11,000 AI tools, updated daily) adds training-policy flags and ready-made policy verdicts: https://www.shadowaitools.com/free-shadow-ai-report/ ## Install ```sh pip install https://www.shadowaitools.com/free-shadow-ai-report/shadow_ai_report-0.2.0-py3-none-any.whl ``` Or from a copy of this folder: ```sh pip install . ``` There is nothing else to install. Without installing, you can also run it in place with `PYTHONPATH=src python3 -m shadow_ai_report ...`. ## Quick start Try it on the synthetic example log that ships in `examples/`: ```sh shadow-ai-report examples/example-dns-log.csv -o report.html --org "Example Industries Inc." ``` The terminal shows a short summary and the path of the report: ``` Read example-dns-log.csv: csv [domain=query_name, user=user / src_ip, time=timestamp, action=action] - 3,003 lines, 3,000 used, 3 skipped Requests analysed 3,000 Distinct domains 61 Lines skipped 3 AI tools detected 25 AI-tool requests 556 (18.5% of all requests) Users/devices on AI 26 of 40 AI requests blocked 11 High risk/exposure 2 tool(s) Flagged (high/medium) 12 tool(s) ... Report written: /path/to/report.html ``` Open `report.html` in any browser. It also prints cleanly (A4) or saves to PDF from the browser's print dialog. ## Usage ``` shadow-ai-report LOGFILE [LOGFILE...] -o report.html [--org NAME] [--format auto|csv|cloudflare-dns|text] [--anonymise] [--json FILE] [--csv FILE] ``` | Option | Meaning | |---|---| | `LOGFILE ...` | One or more log files. Plain or gzip-compressed (detected from the content, not the file name). Several files are combined into one report. | | `-o`, `--output` | HTML report to write (default `shadow-ai-report.html`). | | `--org NAME` | Organisation name shown on the report (default "Your organisation"). | | `--format` | `auto` (default) detects the format of each file. Use `csv`, `cloudflare-dns` or `text` to force one. | | `--anonymise` | Replace users, e-mail addresses, device names and IP addresses with stable pseudonyms: User 1 has the most AI-tool requests, User 2 the next, and so on. The same log always gives the same pseudonyms. Applies to the HTML and the JSON. | | `--json FILE` | Also write a machine-readable summary (totals, tools, categories, users, input files). | | `--csv FILE` | Also write the tools-in-use table as CSV. | | `--version` | Print the version. | Exit code 0 means the report was written. On bad input (missing or empty file, unknown format, no usable lines, output not writable) the tool prints `shadow-ai-report: error: ...` and exits with code 2. ## What the report contains - **Header**: organisation, period covered (from the log's timestamps), date generated, source files. - **Summary tiles**: AI tools detected; users or devices that reached an AI tool (when the log has a user, device or IP column); AI-tool requests (and how many your filter blocked, when the log says so); share of all requests that went to AI tools; number of tools rated high for risk or data sovereignty. - **Tools in use**: tool, domain, category, risk level, data sovereignty, requests, users (and blocked), sorted by requests. - **AI use by category**: requests and number of tools per category, as bars. - **Tools that need a closer look**: every tool rated high or medium for risk or data sovereignty, with a plain-language explanation of both ratings. - **Top users** by AI-tool requests (top 25), optionally pseudonymised. - **Next steps**: practical recommendations (acceptable-use policy, allow / allow with controls / block per category, review of flagged tools). - **Coverage and method**: what the free list can and cannot see, how matching works, and the numbers that make the report auditable: log lines read, requests analysed, lines skipped, distinct domains seen, and per file the format, the columns used and the line counts. The HTML file has inline CSS only: no JavaScript, no web fonts, no images, nothing fetched from the network. ## Supported inputs ### 1. CSV or TSV from any DNS or web filter The first line must be a header. Columns are found by name (case, spaces, `_` and `-` are ignored): | Purpose | Recognised column names (first match wins) | |---|---| | Domain (required) | `domain`, `domain_name`, `query_name`/`QueryName`, `qname`, `query`, `fqdn`, `hostname`, `host`, `dst_host`, `dest_host`, `destination_host`, `destination_hostname`, `site`, `url`, `request_url`, `dest`, `destination` | | User (optional) | `email`, `user_email`, `user`, `username`, `src_user`, `identity`, `device_name`, `device`, `client_name`, `client`, `src_ip`, `client_ip`, `source_ip` | | Time (optional) | `timestamp` (also `@timestamp`), `datetime`, `time`, `date`, `ts`, `event_time`, `query_time`, `log_time` | | Action (optional) | `action`, `decision`, `resolver_decision`, `policy_action`, `verdict` | - URL values are reduced to their host name (`https://www.example.ai/chat` → `example.ai`); `host:port` works too. - When several user columns exist, each line uses the first one that is not empty (for example the user name, and the IP address when the user name is blank). - An action value containing *block*, *deny*, *drop*, *reject*, *refuse* or *sinkhole* counts as blocked. - Delimiters `,` `;` tab and `|` are detected from the header line. UTF-8 (with or without BOM) and UTF-16 with BOM are accepted. - Timestamps: ISO 8601 / RFC 3339 (any number of fractional digits, `Z` or offsets), `YYYY-MM-DD HH:MM:SS`, Unix time in seconds, milliseconds, microseconds or nanoseconds, and `30/Sep/2026:12:00:00 +0000`. Times with a time zone are converted to UTC; times without one are used as written. - Without a header row the tool guesses the domain column (and an obvious time or e-mail column) from the content, and says so in the terminal and in the report. - Each record must be on one line. A line that cannot be read (broken quoting, too few fields, no valid domain) is skipped and counted, never silently dropped. ### 2. Plain lists of domains A text file with one domain or URL per line. Lines starting with `#` and text after `#` are ignored. ### 3. Cloudflare Gateway DNS logs (Logpush dataset `gateway_dns`) JSON lines as written by Cloudflare Logpush, plain or gzip-compressed. Detected automatically when the lines are JSON objects with a `QueryName` field. These fields are used (names as listed in Cloudflare's public [Gateway DNS dataset reference](https://developers.cloudflare.com/logs/logpush/logpush-job/datasets/account/gateway_dns/)): | Field | Used for | |---|---| | `QueryName` | the domain (required) | | `Datetime` | the period covered; RFC 3339 strings and Unix integers (seconds or nanoseconds) are both read | | `Email` | the user. Cloudflare writes `non_identity@.cloudflareaccess.com` when no user identity was available; such lines fall back to `SrcIP` | | `SrcIP` | the user when `Email` is empty or a non-identity address | | `ResolverDecision` | blocked or allowed: `blockedByCategory`, `blockedAlwaysCategory` and `blockedRule` (numeric values 3, 6 and 9) count as blocked, as listed under *Resolver decisions* in Cloudflare's [Gateway activity logs](https://developers.cloudflare.com/cloudflare-one/insights/logs/dashboard-logs/gateway-logs/) page | Only `QueryName` is required; the report leaves out what the log does not contain. Root queries (`.`) are skipped and counted. ## Exporting logs ### From a generic DNS or web filter Most filters can export their query or traffic log as CSV. For this report the export needs: 1. a column with the queried domain, host or URL (required); 2. a column with the user, device or client IP (optional, for the user counts); 3. a timestamp column (optional, for the period covered); 4. the action or decision (optional, for the blocked counts). Export the period you want to report on (a week or a month works well), keep the header row, and pass the file (or several files) to `shadow-ai-report`. If a column has an unusual name, rename it in the header to one of the names in the table above. ### From Cloudflare Gateway (Cloudflare One) Based on Cloudflare's public documentation ([Logpush integration](https://developers.cloudflare.com/cloudflare-one/insights/logs/logpush/), [log output options](https://developers.cloudflare.com/logs/logpush/logpush-job/log-output-options/)). Cloudflare lists Logpush for Zero Trust logs as an Enterprise feature. 1. In Cloudflare One, go to **Insights > Logs** and select **Manage Logpush**. 2. Select **Create a Logpush job** and choose a destination (for example an R2 or S3 bucket). 3. Choose the **Gateway DNS** dataset. Include at least `QueryName`, `Datetime`, `Email`, `SrcIP` and `ResolverDecision`. 4. Keep the default output (one JSON object per line). Any timestamp format works. 5. Download the files for the period you want from your destination. Cloudflare writes them as gzipped objects; they can be passed to `shadow-ai-report` as they are: ```sh shadow-ai-report logs/gateway_dns/*.log.gz -o report.html --org "Example Industries Inc." --anonymise ``` If you configure Logpush to write CSV instead, include a header line (Cloudflare's `batch_prefix` output option); the CSV reader then recognises `QueryName`, `Datetime`, `Email`, `SrcIP` and `ResolverDecision` by name. ## Privacy - Everything runs locally. The package contains no network code: it does not import `socket`, `urllib.request`, `http.client` or any HTTP library, and the test suite runs a full report with sockets disabled to prove it. - No telemetry, no update checks, no licence checks. - The HTML report loads nothing: no scripts, fonts, images or style sheets from the internet. The only links in it are plain hyperlinks (the full-edition page and the CC BY 4.0 licence) that a reader may click. - Log files are read, never modified. Only the files you name with `-o`, `--json` and `--csv` are written. - Logs and the report contain personal data (user names, e-mail and IP addresses). Use `--anonymise` before sharing a report outside the team that owns the logs, and follow your own data-protection rules. ## Free edition and full edition | | Free edition (this tool) | Full register | |---|---|---| | AI tools recognised | 300 (the free list) | more than 11,000 AI tools, updated daily | | Updates | fixed list shipped with the package | daily | | Risk level and data-sovereignty rating | yes, for the 300 tools | yes | | Training-policy flags | no | yes | | Ready-made policy verdicts | no | yes | **Important:** the free list holds 300 AI tools: the most visited ones such as ChatGPT, Claude, Gemini, Microsoft Copilot, DeepSeek, Grok and Perplexity, plus a sample of smaller tools across all categories. Requests to AI tools that are not on the list count as ordinary traffic here, so a low or zero result does not mean low or zero AI use. More: https://www.shadowaitools.com/free-shadow-ai-report/ To change the upgrade text shown in the report and the terminal, edit `FULL_REGISTER_TEXT` and `FULL_EDITION_URL` in `src/shadow_ai_report/__init__.py` (`FREE_LIST_SIZE` there must match the number of rows in the bundled list; a test checks this). ## Limitations - DNS logs record look-ups, not page views or prompts. One visit can cause several look-ups and devices cache answers, so the counts measure activity rather than exact use. - A user is whatever identity the log records. One person on several devices counts more than once; several people behind one IP address count once. - JSON input is supported for Cloudflare Gateway DNS only. Other JSON logs need converting to CSV first. - Records spanning several lines (CSV fields with embedded line breaks) are not supported. ## Development ```sh PYTHONPATH=src python3 -m unittest discover -s tests -v # run the tests python3 examples/make_examples.py # regenerate the synthetic examples python3 -m build # build the wheel and sdist ``` ## Licence - Code: Apache License 2.0 (see `LICENSE` and `NOTICE`). Copyright 2026 Alpha Quantum. - Bundled AI-tool list (`src/shadow_ai_report/data/ai-tools-free-list.csv`, 300 tools): sources "AI Tools Taxonomy dataset sample and AI Tools Blocklist free sample and most visited tools, Alpha Quantum", CC BY 4.0. See `src/shadow_ai_report/data/DATA_LICENSE`. - Cloudflare is a trademark of Cloudflare, Inc. This project is not affiliated with or endorsed by Cloudflare; the Cloudflare log format support is built only from Cloudflare's public documentation.