Notes

The unit economics of deception

Deception is a rare defense where each detection burns attacker resources at near-zero defender cost. A week of data from my site, and why few programs use it.

essay 11 min read

Deception is one of the rare defensive categories where the economics structurally favor the defender. Each detection can actively consume attacker resources (connection slots, file descriptors, scheduler time) while costing the defender near-zero marginal CPU. The asymmetry holds at scale, and almost no security program has a line item for this.

Rate limiting, reputation blocking, and protocol hardening also impose asymmetric costs, so this is not a claim of uniqueness. The distinguishing property of deception is that the defender’s cost per high-confidence unwanted interaction approaches zero while the attacker’s cost is multiplied across every concurrent connection they maintain. I want to walk through the numbers from one week of traffic on a single personal website, because the unit economics are strange enough that they deserve to be looked at directly.

One week, one Worker

The site you are reading this on is a static Astro build fronted by a Cloudflare Worker that intercepts scanner probes. Any request for a .php file, a /wp-admin/ path, an .env, a .git/config, a phpMyAdmin console, or a few dozen other patterns gets handed a slow-drip fake WordPress response instead of a 404. The scanner’s connection is pinned open while the Worker streams a fake plugin-loading animation one chunk every one to three seconds.

Seven days of Cloudflare Worker logs (2026-04-13 through 2026-04-20). Paths, source IPs, wall time, and CPU time are all fields emitted per-invocation by the platform; I aggregated them with a small Python script against the Logpush JSON export:

  • 1,495 total requests to the Worker, from 306 unique source IPs across all traffic.
  • 1,216 of those (81%) matched a scanner-probe pattern. Of the matched probes, 734 went through the slow-drip tarpit: the rest hit paths served with an instant fake 200 (HEAD requests, since discarding the body makes streaming pure CPU waste) or a canary file (fake wp-config.php.bak, fake .env, etc. that are served as static responses to get attackers to exfiltrate something trackable).
  • The tarpit subset held 215 unique IPs. Source ASNs across the full scanner traffic were the usual VPS suspects: DigitalOcean, SnTHostings, Microsoft Azure, M247, with DigitalOcean and SnTHostings alone accounting for the majority of probes.
  • The top two requested paths were /wp-login.php (202 hits) and /xmlrpc.php (200 hits). Everything else was a long tail.

The bucket math falls out of how the Worker routes probes:

flowchart TD
    R[Incoming request]
    R --> P{Path matches<br/>scanner pattern?}
    P -->|No| A[Pass through<br/>to static assets]
    P -->|Yes| M{HTTP method?}
    M -->|HEAD| H[Return headers only<br/>no body drip]
    M -->|GET or POST| C{Path is a<br/>canary file?}
    C -->|Yes| K[Serve honeytoken body]
    C -->|No| T[Slow-drip tarpit<br/>byte by byte to cap]

Eighty-one percent of the traffic to a static personal site was automated reconnaissance. That number is not unusual: it is the internet’s ambient noise floor. What is unusual is what happened to the 734 requests that landed in the bottom-right branch.

The asymmetry, measured

Cloudflare Workers bill on two dimensions: invocations and CPU time. They do not bill for wall time spent waiting. This turns out to matter enormously.

For the 734 tarpitted requests, here is the wall-time distribution:

PercentileWall time
Median26 s
p7531 s
p9038 s
Mean5.7 min
p9517 min
p99100 min
Max8.4 hours

Binned by hold time, the shape is a spike at the short end with a thin, long tail:

xychart-beta
    title "Tarpit hold-time distribution (n=734)"
    x-axis ["0–30s", "30s–1m", "1–10m", "10–60m", ">1hr"]
    y-axis "Requests" 0 --> 600
    bar [540, 120, 25, 27, 22]

The shape matters more than any single percentile. Most scanners give up within 30 seconds: that is fast-scanning automation hitting its own per-host timeout and moving on, which is the expected behavior and still a win (the scanner spent 30 seconds on a dead end). The mean of 5.7 minutes is almost entirely pulled up by the heavy tail: roughly 20 requests stayed held for over an hour each, and one stretched to 8.4 hours. If you quote only the mean you are accurately summarizing a weighted-by-tail number; if you quote only the median you are accurately summarizing the behavior of typical scanners and undersell the tail. Both are true. The distributional picture is the honest one.

These numbers are from the earlier, unbounded version of the tarpit. The current Worker caps the heartbeat loop at 60 ticks, which closes the stream gracefully after a few minutes rather than letting it run until the platform’s soft timeout. The eight-hour tail goes away under the cap; the short-timeout bulk of the distribution is unaffected. The new steady state will look more like “median 30 s, tail capped at ~3 min” going forward: narrower spread, same average CPU footprint.

CPU time barely moved across the distribution. Median CPU per invocation was 1 ms, and the long-held requests at the tail did not use meaningfully more CPU than the short ones, because the overwhelming majority of each tarpit is spent inside setTimeout, which Cloudflare does not meter. The Worker’s active work is producing a few dozen bytes of HTML per chunk; everything between chunks is free.

That is the entire economic story in one sentence: the attacker’s cost scales with wall time, the defender’s cost scales with CPU time, and the Worker platform divorces the two. At the median of the tarpitted distribution, one scanner spent 26 seconds of connection-slot-time against roughly 1 millisecond of my CPU: a 26,000-to-1 ratio. At the mean, around 300,000-to-1. At the one-hour-plus tail, higher still. The ratio is bounded by the platform’s soft timeout, not by any physical limit; the point is that it stays four to five orders of magnitude across the whole distribution, without tuning.

The attacker is renting VPS compute, holding file descriptors open, burning scheduler slots that could have gone to the next target. The defender is paying for one millisecond of v8 time and then sleeping.

Insight

This is one of the rare places in security where scale helps

Most defensive layers get more expensive per unit as you catch more. More alerts mean more analyst hours. More endpoints mean more agent licenses. The tarpit’s cost per probe is effectively flat (all CPU, no wall-time billing) while the attacker’s cost per probe scales with their concurrency ceiling. Rate limiting has a similar shape, but it terminates the interaction; the tarpit extends it, which is what produces the multiplier.

What this costs at a budget line

Annualizing the held-traffic subset: 734 tarpitted requests per week times 52 weeks is roughly 38,000 high-confidence unwanted-automation events per year, from a single domain.

The signal quality varies by path family. For requests to .php files or /wp-admin/ paths on a site that does not run PHP and never has, “no legitimate client by construction” is a fair claim: there is no configuration under which a browser or well-behaved crawler would request those URLs. For the broader pattern set (.env, .git/config, generic secret JSON, backup-archive names), the claim is softer: legitimate vulnerability scanners, search-engine security probes, or a misconfigured internal monitor could plausibly touch those paths. For the WordPress-specific subset (which is the bulk of the traffic anyway), the detection is signal-pure. For the rest, it is high-confidence-but-not-impossible, and triage should reflect that.

The infrastructure line is literally zero at this volume. Cloudflare Workers’ free tier covers 100,000 invocations per day (roughly 3 million per month); my Worker serves around 6,400 requests per month, so the tarpit sits comfortably inside free-tier headroom. Workers bill for invocations and CPU time, not wall time spent waiting on I/O (pricing, limits). The worst case, if I moved to Workers Paid for unrelated reasons, would be the $5/month entry price (call it $60/year), which still works out to less than a fifth of a cent per event. The actual cost today is $0.

So the full picture: 38,000 high-confidence scanner-probe events per year, from a single domain, for zero dollars. Worst-case bound on infrastructure cost if volume pushed past free tier: $60/year, or roughly $0.0016 per event. For comparison: in many internal cost models, a manually investigated alert can easily land in the tens of dollars once analyst time is fully loaded. The exact figure depends on salary assumptions and minutes-per-triage, but the direction is consistent across the SIEM and SOC literature.

The value here is not that anyone should investigate 38,000 scanner hits. The value is that you have replaced a noisy detection source with one that is signal-pure where it counts, at a cost that rounds to zero on any security budget. You can then spend your actual detection budget on the sources where analyst judgment matters.

Why this category is underbuilt

The ideas are not new. Bill Cheswick wrote “An Evening with Berferd” in 1991, describing a deliberately slow, deceptive environment for an attacker who had broken into Bell Labs. Tom Liston shipped LaBrea in 2001, which did the same thing at the TCP layer. Thinkst Canary has been selling commercial honeypots and canary tokens for over a decade and, by all accounts, has a very good business. The underlying technique is older than the term “cybersecurity.”

And yet a majority of security programs deploy nothing like it. Three structural reasons:

It is not in any major compliance framework. SOC 2, ISO 27001, PCI DSS, HIPAA, FedRAMP: none of them require or reward deception layers. Procurement does not ask about it, auditors do not score it, and security leaders do not get promoted for deploying it. Budget follows the framework. That is what happens when compliance becomes the ceiling rather than the floor.

It has no standard vendor category. There is no large, durable Magic Quadrant-style buying motion around deception: the category exists in analyst coverage, but it is not a mainstream budget anchor the way SIEM, EDR, CNAPP, or SASE are. There is no adjacent consolidation story pulling deception into a larger platform purchase. Buyers pattern-match their stack against a reference architecture, and deception does not appear in most reference architectures.

It produces loss reduction rather than new revenue. Fundamentally, a tarpit or a canary token reduces the blast radius of attacks that would otherwise succeed. That is hard to sell to a CFO against a “prevents breach” or “detects lateral movement” pitch, even when the economics are better. The ROI is real but diffuse. The underlying bias is loss aversion plus imaginability: an unimaginable future loss gets fractional weight in a budget conversation, no matter how large its expected value. It is a hard category to build a billion-dollar vendor around, which is why Thinkst, the most successful company in this space, has stayed deliberately focused and does not try to be an everything-platform.

What the AI scraping wave changes

The calculus has started to shift, quietly, because of something adjacent: LLM training scrapers. Organizations that never cared about web reconnaissance now care about bot traffic for content-provenance reasons, for infrastructure-cost reasons, and for competitive-intelligence reasons. The same edge primitives that catch WordPress scanners (intercept at the Worker, stream back slowly, never bill wall time) can be used against LLM crawlers.

The detection problem, though, is materially different. WordPress scanner deception can key off impossible paths: no legitimate client requests /wp-login.php on a static site, so the false-positive rate is defined by the pattern list. LLM crawlers request the same pages that legitimate users and well-behaved search engines request. Deciding which ones to tarpit requires identifying the client by user agent, IP range, robots behavior, or rate pattern, and every one of those signals can be spoofed or misclassified. Bot management for content scraping is a stricter false-positive problem than scanner tarpitting, and the tooling reflects that.

What transfers cleanly is the cost model (CPU-cheap, wall-time-free detection at the edge), not the detection logic itself. This is probably how the category finally grows in the mid-market: as a bot-management capability, sold to marketing and content teams, with security inheriting the signal as a side effect.

Two questions

If you are a security buyer: the interesting question is not “should we deploy a tarpit.” The interesting question is what percentage of your current detection tooling produces a signal by construction: where the path to a detection does not depend on tuning, thresholds, or analyst judgment but on the fact that no legitimate client could ever generate that specific request. Not every deception layer clears that bar, but the WordPress-path class of probes does, trivially. If the answer is near zero across your stack, that is worth knowing.

If you are a security vendor: Thinkst owns the high end of the focused deception market. The mid-market is underbuilt. The infrastructure is commodity: Cloudflare Workers, Cloud Functions, a DNS provider. The hard parts are distribution and integration, not technology. There is a very large middle.

The unit economics will still be there when someone decides to go build it.