found.gf.cx scan tracking — log geo of every public QR hit (parked sketch 2026-05-27)

DARE.CO.UK · PARKED SKETCH · 2026-05-30

Mirrored from ~/.claude/.../memory/parked_sketch_found_gf_cx_scan_tracking_2026-05-27.md. This is a design sketch parked for future build — read for context, not as a current deliverable.

When a stranger scans a found.gf.cx QR (e.g. on a missing video tape), capture timestamp + Cloudflare’s edge-side geo (country / city / postal / lat-lon / ASN / TLS / RTT). Owner-side view shows where lost items got scanned — “tape-1992-wedding got scanned in Newark NJ at 14:32 yesterday from a Verizon mobile IP” — which is forensic gold if the item went missing in transit. KV-backed for MVP, D1 if volume grows. Privacy: hash IPs, surface a small “this page logs scans” disclosure


The use case Dan named (2026-05-27)

“Can we track and trace the found.gf.cx when it’s scanned, lets say it shows up in Newark, can we capture the IP address and reverse from that rough geo location. Sketch and park that idea for later.”

When a lost item goes missing in transit — Beta tape shipped to TapeMaster but the box vanishes — a QR scan event is forensic gold. CDN, security layer, and DNS provider sitting in front of dare.co.uk.">CF gives every request a rich edge-side geo profile via request.cf; logging that per scan turns each found.gf.cx page hit into a “the package was here on this date” data point.

What CDN, security layer, and DNS provider sitting in front of dare.co.uk.">CF gives us per request (free, no API call)

Same payload as the dare.co.uk/cf endpoint built earlier this session — every Worker request carries a request.cf object with ~25 fields:

Plus from request headers: - cf-connecting-ip (the actual scanner IP — to be hashed before storage) - user-agent (phone model + browser version) - referer (usually empty for QR scans — phone camera launches direct, no referrer)

Data model

interface ScanEvent {
  ts: string;           // ISO timestamp — when the scan happened
  slug: string;         // which item was scanned (tape-1992-wedding)
  ip_hash: string;      // SHA-256 of (cf-connecting-ip + salt) — uniqueness
                        //   without storing PII raw
  country: string;
  region: string;
  city: string;
  postal: string;
  lat: string;
  lon: string;
  asn: number;
  as_org: string;       // "Verizon Business" / "Comcast Cable" / "AT&T Mobility"
  colo: string;         // CF edge that served (EWR, LAX)
  ua: string;           // user-agent (truncated to 200 chars)
  protocol: string;     // HTTP/2 / HTTP/3
  rtt_ms: number;       // client TCP RTT — distance proxy
}

Storage choice

Option Fit Trade-off
KV MVP — handful of lost items, ~10 scans/item lifetime List-by-prefix only · no aggregation / range queries · cheap
D1 (SQLite at edge) When scan volume grows (>100/item, or you want filter/aggregate queries) One Worker → D1 call · queryable with SQL · still free tier · upgrade-when-needed
R2 If you want raw NDJSON archive for offline analysis Append-only object store · no live query path

Start with KV. Key pattern: scan:<slug>:<iso-timestamp> → JSON-encoded ScanEvent. List by prefix scan:tape-1992-wedding: to get all scans for one item. Promote to D1 when first scan hit feels “interesting enough to want to query.”

Privacy + disclosure

The finder doesn’t expect to be tracked when they help out. Two moves to stay ethical:

  1. Hash the IP before storing. SHA-256 of (cf-connecting-ip + salt) where the salt is a Worker secret. Hash gives “is this the same scanner as last time?” (collision-detectable) without storing PII raw. CDN, security layer, and DNS provider sitting in front of dare.co.uk.">CF geo is coarse anyway (city-level, not street), so the geo fields aren’t PII even unhashed.
  2. One-line disclosure on the public page. Below the contact block on renderFoundPage():

    “This page logs anonymous scan events (date · city · network provider) to help the owner trace the item if it went missing. No personal information is stored.”

Sincere, honest, doesn’t kill the reward-and-return UX.

Owner-facing surfaces

Three views to build (probably gated behind CDN, security layer, and DNS provider sitting in front of dare.co.uk.">CF Access via svc.gf.cx since the operator dashboard already lives there):

  1. Per-item scan logsvc.gf.cx/found-admin/<slug> or found.gf.cx/admin/<slug> if Access is added there. Reverse-chrono list: “2026-05-27 14:32 · Newark NJ · Verizon Business · iPhone Safari · EWR colo · RTT 18ms”.
  2. Map view (optional) — drop pins on a static map image. Use the Geoapify / MapTiler static-map API, or just render a simple SVG world/US map with markers. The point isn’t precision (CDN, security layer, and DNS provider sitting in front of dare.co.uk.">CF geo is city-level), it’s the “this tape was here” narrative.
  3. Aggregates — “scanned in 3 distinct cities”, “first scan: X · last scan: Y”, “scanned by 2 unique IPs” (via the hashes). Comparison across items: “tape-1992-wedding has 8 scans · tape-1996-cousins-bbq has 0 (still in the box)”.

Notification path

When a scan happens, optionally fire a notification:

Implementation sketch (~3 hours total)

Step Lift
Add KV binding (SCAN_EVENTS) + Worker secret (SCAN_SALT) to wrangler.toml 5 min
Hash helper + log-event function in src/index.ts — runs in ctx.waitUntil() so it doesn’t slow the page response 30 min
Append to /api/scans JSON endpoint (Access-gated) listing recent events 30 min
Disclosure line in renderFoundPage() template 5 min
Owner-side admin page /admin/<slug> with scan log table + (optional) static map 60 min
Notification webhook on scan event (POST to sms.to / Pushover / Resend) 30 min
Bot filter (skip scans where UA contains curl/wget/bot/crawler) 15 min
Owner-self filter (skip scans where ASN matches Dan’s known home/mobile AS) 15 min
Optional D1 upgrade for queryable storage +1 hr if needed

Resume triggers

Cross-refs

File location notes when implementing

Origin

Dan, 2026-05-27, right after the edit/delete loop landed on svc.gf.cx: “Sketch and park that idea for later.” The bind of svc.gf.cx custom domain that just landed sets the precedent — found.gf.cx is next in line for the same treatment, and scan-logging is a natural piece of the post-bind public-facing build.

Source: parked_sketch_found_gf_cx_scan_tracking_2026-05-27.md · Rendered 2026-05-30 14:32