Skip to content

feat: anonymous ai-scan usage ping (opt-out) + /api/telemetry - #369

Merged
ralyodio merged 1 commit into
masterfrom
ai-scan-usage-ping
Oct 28, 2026
Merged

ralyodio merged 1 commit into
masterfrom
ai-scan-usage-ping

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Anthony approved this on 2026-10-28 ("yes" to the ai-scan usage ping). ai-scan runs on the user's machine and sent nothing home, so the nightly report (profullstack/scripts#35) couldn't count it.

CLI (apps/cli/src/core/usage-ping.ts): each ai-scan and ai-scan-run sends one count to ${THREATCRUSH_API_URL}/api/telemetry. The payload is the command, version, OS/arch, Node version, a CI flag, a random install id (~/.threatcrush/usage-ping.json), probes, findings by severity, duration and a failed flag. It never includes the target, headers, prompts, responses or finding text.

  • Off with THREATCRUSH_TELEMETRY=0|off|false|no or DO_NOT_TRACK=1. The first run prints a one-line notice on stderr.
  • Best effort: 2s timeout and it never throws.

Web:

  • POST /api/telemetry checks every field against a narrow shape (lib/usage-ping.ts) and drops everything else. No IP or user is stored, and a body over 4 KB gets a 400.
  • Rate limit is 30/min per IP in middleware.
  • Table cli_usage_pings: RLS on, no anon/authenticated grants. Already applied on dev2 by hand (no CI applies migrations), then the PostgREST schema was reloaded.

Docs: /privacy has a new "CLI usage counts" entry (last updated Oct 28). The CLI README and SELF_HOSTING.md note that the ping goes to THREATCRUSH_API_URL, so a self-hosted install sends it to its own server.

Tests: new tests for the ping (opt-out, notice once, stable id, exact payload keys, network failure) and the route (stores only documented fields, rejects junk/oversize/unknown events, clamps, insert failure gives 500). CLI 678/678, web 890 pass, CLI tsc clean, pre-commit hook passed.

🤖 Generated with Claude Code

ai-scan runs on the user's machine and sent nothing home, so there was no way
to count how often the scanner is used. Each ai-scan / ai-scan-run now sends
one count to ${THREATCRUSH_API_URL}/api/telemetry: command, version, OS,
Node, CI flag, a random install id (~/.threatcrush/usage-ping.json), probes,
findings by severity, duration. Never the target, prompts, responses or
finding text. Off with THREATCRUSH_TELEMETRY=0 or DO_NOT_TRACK=1; the first
run prints a one-line notice. 2s timeout, never throws.

Server: POST /api/telemetry validates every field against a narrow shape
(lib/usage-ping.ts), drops the rest, stores no IP or user, rate-limited
30/min per IP. Table cli_usage_pings (RLS on, no anon/authenticated grants;
applied on dev2 by hand). Privacy policy, CLI README and SELF_HOSTING
updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ralyodio
ralyodio merged commit b43a4c0 into master Oct 28, 2026
10 checks passed
const res = await (opts.fetchImpl ?? fetch)(`${apiUrl()}/api/telemetry`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(pingBody(p, id, env)),
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

22 finding(s)

HIGH/CRITICAL: 1 | MEDIUM: 12 | LOW: 9

Severity Rule Location
HIGH secret-aws-access-key prd/0003-detect-hardcoded-secrets-before-they-are-committed-or-served.md:126
MEDIUM sql-string-concatenation .github/workflows/migrate-dev2.yml:128
MEDIUM js-uninitialized-buffer apps/cli/src/core/log-tail.ts:58
MEDIUM js-open-redirect apps/web/src/app/auth/login/page.tsx:67
MEDIUM js-unescaped-html-sink apps/web/src/app/hire/page.tsx:111
MEDIUM js-unescaped-html-sink apps/web/src/app/hire/page.tsx:115
MEDIUM js-open-redirect apps/web/src/app/invite/[token]/invite-client.tsx:55
MEDIUM js-open-redirect apps/web/src/components/CheckoutPanel.tsx:99
MEDIUM js-open-redirect apps/web/src/components/funding/FundingClient.tsx:97
MEDIUM js-unescaped-html-sink apps/web/src/components/GuideReader.tsx:265
MEDIUM js-unescaped-html-sink apps/web/src/components/PitFooter.tsx:17
MEDIUM js-regex-from-input apps/web/src/lib/credit-codes.ts:567
MEDIUM js-uninitialized-buffer packages/scan/src/node-rules.ts:456
LOW secret-generic-credential apps/cli/src/daemon/workers/auto-update.ts:155
LOW secret-generic-credential apps/web/src/app/api/auth/refresh/route.ts:17
LOW secret-generic-credential apps/web/src/app/api/auth/reset-password/route.ts:26
LOW secret-generic-credential apps/web/src/app/api/auth/reset-password/route.ts:27
LOW secret-generic-credential PRD.md:269
LOW tls-verification-disabled prd/0004-find-dangerous-code-patterns-without-pretending-to-be-a-compiler.md:121
LOW tls-verification-disabled prd/0004-find-dangerous-code-patterns-without-pretending-to-be-a-compiler.md:122
LOW sh-remote-script-execution scripts/smoke-test.sh:72
LOW secret-aws-access-key scripts/smoke-test.sh:150

Snippets are redacted; ThreatCrush never prints matched credential material.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants