Skip to content

stats API: say where the traffic came from - #279

Merged
ralyodio merged 1 commit into
masterfrom
worktree-stats-geo
Sep 24, 2026
Merged

ralyodio merged 1 commit into
masterfrom
worktree-stats-geo

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

crawlproof stats could show what a caller says it is — its source, its referrer, the pages it asked for. All three are set by the client. Where the packets entered from is not, and that panel rendered only in the dashboard, so the terminal could not settle the one question that separates an audience from an operator.

This came out of chasing a suspected scrape on another property: the referrer panel showed traffic that looked scattered, and the geography showed it was essentially one city. Nothing in the CLI could show that.

Change

countries and cities were already in PANEL_KEYS with their RPCs (tracker_top_countries / tracker_top_cities and the _recent_ variants) — they simply were not in STATS_PANELS, so the API never asked for them.

  • lib/tracker/apiStats.ts — both panels added to STATS_PANELS and to StatsAnswer.
  • lib/dashboard/stats-text.ts — Countries and Cities sections. Both CLIs (in-repo and the published @profullstack/crawlproof) import this renderer, so that is one change rather than two.
  • Empty panels print nothing, matching every other section.

Cost is two extra queries per call. The panel comment now says why that is worth paying rather than leaving it to be rediscovered — the existing comment justified excluding panels, and this is the exception to it.

Testing

  • tests/dashboard-package-cli.test.ts — 13 pass, including two new: geo sections render, and empty panels stay silent.
  • tracker-api-stats, tracker-stats-failure, ads-stats-box, tracker-visitor-rollup — 52 pass.
  • tsc --noEmit clean.

🤖 Generated with Claude Code

`crawlproof stats` could show what a caller SAYS it is — its source, its
referrer, the pages it asked for — and every one of those is set by the
client. Where the packets entered from is not, and that panel rendered only
in the dashboard, so the terminal could not settle the one question that
distinguishes an audience from an operator.

It matters more than it sounds. Traffic that reads as a broad readership in
Sources and resolves to a single city here is one machine, and a referrer
panel full of your own hostname will never say so. `countries` and `cities`
already existed in PANEL_KEYS and already had their RPCs; they were simply
not in STATS_PANELS, so the API never asked for them.

Two extra queries per call, which the panel comment now justifies rather
than leaving to be rediscovered.

Both CLIs share lib/dashboard/stats-text, so rendering is one change. Empty
panels print nothing, like every other section.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

48 finding(s)

HIGH/CRITICAL: 2 | MEDIUM: 31 | LOW: 15

Severity Rule Location
HIGH tls-verification-disabled lib/onion.ts:48
HIGH secret-generic-credential lib/sp/platforms/facebook.ts:32
MEDIUM js-unescaped-html-sink app/(app)/dashboard/admin/email-broadcast/EmailBroadcastForm.tsx:125
MEDIUM js-unescaped-html-sink app/(app)/dashboard/projects/[id]/autoblog/articles/[articleId]/page.tsx:214
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:67
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:97
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:104
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:110
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:186
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:190
MEDIUM js-unescaped-html-sink app/c/[project]/[slug]/page.tsx:77
MEDIUM js-unescaped-html-sink app/c/[project]/page.tsx:57
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:228
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:285
MEDIUM js-unescaped-html-sink app/layout.tsx:129
MEDIUM js-open-redirect app/login/form.tsx:39
MEDIUM js-unescaped-html-sink app/r/[token]/page.tsx:176
MEDIUM js-open-redirect app/signup/form.tsx:43
MEDIUM js-open-redirect components/billing/buy-credits-modal.tsx:98
MEDIUM js-unescaped-html-sink components/json-ld.tsx:8
MEDIUM js-unescaped-html-sink components/report/markdown-view.tsx:15
MEDIUM js-unescaped-html-sink lib/careers/page-templates.ts:198
MEDIUM js-dynamic-code-execution lib/crawl-limits.ts:67
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:41
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:324
MEDIUM redos-nested-quantifier lib/lx/articleGen.ts:99
MEDIUM redos-nested-quantifier lib/tracker/agent-gate.ts:61
MEDIUM sh-remote-script-execution prober/deploy/provision.sh:30
MEDIUM sql-template-interpolation scripts/detect-slot-themes.ts:31
MEDIUM sql-template-interpolation scripts/purge-constructed-keywords.ts:163
MEDIUM sql-template-interpolation scripts/purge-offniche-keywords.ts:124
MEDIUM js-dynamic-code-execution scripts/test-crawl-limits.mjs:14
MEDIUM js-dynamic-code-execution scripts/test-crawl-limits.mjs:24
LOW secret-generic-credential app/(marketing)/docs/autoblog-webhook/page.tsx:145
LOW secret-generic-credential lib/sp/platforms/linkedin.ts:25
LOW js-dynamic-code-execution tests/careers-page-templates.test.ts:21
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:19
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:69
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:51
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:52
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:20
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:24
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:25
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:26
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:31
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:35
LOW secret-generic-credential tests/contract/posthog-integration.test.ts:13
LOW secret-generic-credential tests/lead-campaign.test.ts:16

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 64ba690 into master Sep 24, 2026
10 checks passed
@ralyodio
ralyodio deleted the worktree-stats-geo branch September 24, 2026 14:33
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.

1 participant