
anti-rea-mcp
io.github.dankezo/anti-rea-mcp
antiRea advisory bot-abuse signals as stateful MCP sessions (evaluation and demos).
Documentation
antiRea
Build it. Ship it. Keep it yours.
antiRea is a server-side TypeScript library that meters dataset access per identity and flags coordinated bot abuse - bulk extraction crews, scheduler-driven herds, seat squatting. Advisory only: it flags, you decide.
Website - Threat model - Limitations - Changelog
English - Tiếng Việt - 中文 - Español
The problem, in 30 seconds
Cloning tools can watch your app's pages, bundles and traffic, then rebuild what they saw. You cannot hide code a browser has already received - so antiRea defends the other side: the data itself.
Unprotected. Anyone with an account can take your data home. In our tests, one account walked away with the entire 10,000-record dataset in under 200 requests. Quietly. Nothing unusual in the logs.
Protected. Real users notice nothing. Each identity can only ever receive a capped slice - 20% of the dataset in our test setup - so copying stops being an export and becomes an identity problem. Split the work across many accounts, and the herd detector flags the coordination.
- The run that took everything with 1 account was stopped at 5%.
- A scripted crew that insisted on finishing needed 25 identities and about 19 minutes - and was flagged on the way.
Unprotected, your data leaves whole, free and invisible. Protected, it leaves in slices, at a cost, and on the record.
Full numbers: Measured results.
Install
npm install @dannyphan190/anti-rea
Node.js 20+. Zero runtime dependencies. Also available via GitHub Release.
Quick start
Request quotas cap speed. A record budget caps how much of the dataset one identity can ever receive, however politely it asks.
import { createRecordBudget, createHerdDetector } from "@dannyphan190/anti-rea";
// One identity receives at most 2,000 distinct records per day.
const budget = createRecordBudget({ maxDistinctPerWindow: 2_000 });
const herd = createHerdDetector({
onFlag: snap => { /* snap.band: "budget_cut_supported" | "graduated_only" */ },
});
// Check before the handler runs:
if (!budget.check("account:123", 1).allowed) { /* return 429 */ }
// Then record what the response actually served:
budget.consume("account:123", recordIdsServed);
herd.observe({ type: "list", identity: "account:123", ip: serverSeenIp, recordIds: recordIdsServed });
Use the same identity string (account:<id>) in both, and a herd
flag can feed straight into a budget cut via applyHerdLadder().
What's inside
Four groups. Start at the top; each layer is independent.
| Group | What it does, in plain words | Main pieces | Honest limit |
|---|---|---|---|
| Meter | Caps how much data any one identity can ever receive, and how fast | Record budget (createRecordBudget), quota and pagination guard (createQuotaGuard), priced exports (createExportGuard), session cursors (mintCursor / verifyCursor), flow scoring (createFlowScorer) | Attackers can rotate identities - pair with real signup cost on your side |
| Watch | Spots coordination: many identities quietly tiling your dataset, probing decoys, or crowding a room | Herd detector (createHerdDetector + applyHerdLadder), phase signals (createHerdPhaseSignal, createActivityPhaseSignal), room guard (createRoomGuard), canary traps and decoys (handleCanaryTrapRequest, createCanaryMaze, createDecoyMirage), automation telemetry (detectAutomationSignals), source-map hygiene (verify-build.py / run-audit.cjs) | Detection is not enforcement - low-tiling herds get a graduated response, and one adaptive herd was flagged yet still finished |
| Prove | Pins down what a verdict means: which config produced it, who actually signed, what may ground a consequence | Config provenance (createConfigProvenance), Web Bot Auth verification (createWebBotAuthVerifier), step-up proofs (issueStepUpProof, createStepUpProofVerifier), evidence roles (evidentiaryRoleOf, auditEvidencePack, decorateSignalResult) | A verified signature is identity, not authorization - your auth checks stay exactly as they are |
| Trace | Optional client-side aids for leaks and replay | Context-bound envelopes (encryptPayload / decryptPayload, createInMemoryReplayGuard), forensic watermark (mountForensicWatermark) | Traceability, not access control - overlays can be cropped, and the browser holds its own context |
Recommended order: quota guard first (see below), then the record budget, then herd detection.
Does it work? Measured results
Method, in short. We test against Ledgerly, a synthetic SaaS
holding 10,000 records behind 120 accounts, run twice: unprotected,
and the same app with antiRea wired in. Attack runs are driven by
REA 6.8.0 (rea-agents) and by general-purpose agents following the
standard clone workflow; polite-user runs measure the cost to
legitimate traffic. An independent benchmark was published in
issue #1 - most claims
held up, and the gaps it found are fixed and in the changelog.
Full method and per-layer table: docs/EVIDENCE.md.
| Layer | Headline result |
|---|---|
| No protection (baseline) | The full 10,000-record dataset leaves in 160-193 requests |
| Record budget | One identity is capped at exactly 20% of the dataset |
| Herd detector + CUSUM evidence | REA-style identity rotation stopped at 5%; an adaptive crew needed 25 identities and ~19 minutes |
| Declared cohort ceiling | The cohort union stopped at exactly 600 records, zero served past the cap |
| Legitimate users | Declared team 1,200/1,200 with zero friction; polite users zero consequences |
| Action budget (lab) | Feature-abuse herd completed 16/80 operations (20%); ordinary users 48/48, zero consequences |
| Advisory signals (lab) | Five slow-shape signals: 0-1.67% false flags on legitimate traffic - per-signal numbers in the full table |
What this adds up to. The casual one-shot clone is gone. Bulk extraction becomes an identity problem: every extra slice costs another account and more visibility. It does not stop a determined, well-resourced rebuilder - and the real price of an identity (signup friction, verified accounts, payment) is set by you, which is why budgets are per identity and herd flags distinguish "cut the budget" from "step up scrutiny".
Also speaks MCP
antiRea ships an official MCP server for AI agents:
@dannyphan190/anti-rea-mcp
(source), built on the
official @modelcontextprotocol/sdk, speaking MCP over stdio.
It exposes the library's eight advisory signal kinds as six tools -
list_signals, create_signal, observe, observe_reference,
evaluate, close_signal - so an agent can open a signal session,
feed it events and read the verdict.
{
"mcpServers": {
"anti-rea": {
"command": "npx",
"args": ["-y", "@dannyphan190/anti-rea-mcp"]
}
}
}
It is an evaluation and demo channel. Production protection still belongs in-process, in your own app.
Latest release
v2.8.1 - the span gate fix
- Problem: inside a fraction of one grid period (900 s by default), the herd-phase and activity-phase signals could confirm a group that was only briefly in sync. On this project's own test range, 12 legitimate readers were confirmed over a 40 s window.
- Fix: a member now counts only when its event span covers at
least one full grid period; only eligible members are named, and
results carry the new
spanEligibleMembersfield. - Write-up: v2.8.1 deep dive.
v2.8.0 - the room guard, plus an evidence layer
createRoomGuardwatches a shared room on 30-second ticks: join bursts, churn, and seats held by fresh, silent accounts. It recommends step-up for new joins only - seated members are never named or touched. One hole is declared openly: a squatter who chats even 1-2 messages leaves the occupancy definition.- An evidence layer of four small modules pins down what every verdict means: the exact configuration behind it (CS-02), what identity verification does and does not prove (BS-01), single-use proof that a step-up was passed (CAP-01), and which evidence may ground a consequence (KT-01).
- In the lab pin, flood flagged at +60 s, churn at +30 s, seat squatting at +690 s - and pressure never fired on five legitimate cells, including an audience where 60% of seated sessions never say a word.
- Everything is additive and advisory; defaults are unchanged.
- Write-up: v2.8.0 deep dive - previous: v2.7.4 deep dive.
First step: the quota guard
Server-side quotas and pagination bounds, enforced per account,
API key and IP. Returns HTTP 429 with Retry-After, and logs
structured events for operators.
import { createExpressQuotaMiddleware } from "@dannyphan190/anti-rea";
app.use("/api/", createExpressQuotaMiddleware({
trustedIpHeader: "cf-connecting-ip",
tiers: {
anonymous: { maxRequests: 30, windowMs: 60_000, maxPageSize: 25, maxPage: 5 },
free: { maxRequests: 60, windowMs: 60_000, maxPageSize: 50, maxPage: 20 },
pro: { maxRequests: 300, windowMs: 60_000, maxPageSize: 200, maxPage: 100 },
},
resolveIdentity: req => ({
accountId: req.headers.get("x-verified-account") ?? undefined, // from your server auth
apiKeyId: req.headers.get("x-verified-key-id") ?? undefined,
tier: "free",
}),
onQuotaExceeded: event => {
console.warn("[quota-exceeded]", event);
},
}));
More patterns
- Canary endpoints - trap routes that log abnormal probing, with path canonicalization so variants cannot slip past. Runnable example: examples/node-canary-server.cjs.
- Signed agents - verify Web Bot Auth (RFC 9421) signatures and key quota policy on a stable identity. Runnable end-to-end demo: examples/web-bot-auth-verify.cjs.
- Python / FastAPI - the quota guard ported with identical
tiers,
429semantics and pagination bounds, held to the same test contract: examples/python-fastapi. - Shared cohort ceilings and the CUSUM evidence engine - transactional reservation across a whole declared cohort, with protected cohorts whose telemetry accrues but never draws a consequence: semantics in docs/LIMITATIONS.md.
- Watermark and envelopes - see the Trace group above; never embed a person's email, IP or raw user ID in watermark pixels.
Honest limits
- It cannot hide code delivered to a browser, or prevent anyone from inspecting what their own browser downloaded.
- It cannot stop a determined, well-resourced rebuilder. It makes bulk copying slow, costly and visible instead.
- The evidence engine's built-in tables were fitted on lab traces,
not a real user population, and are labelled
uncalibrated: truewherever they are mentioned. See the calibration pilot below. - Client-side pieces are traceability aids, not access control.
- No client-side bot-blocking tricks and no prompt injections: they do not hold up, and they are not ethical controls. See docs/REFERENCES.md.
Full list: docs/LIMITATIONS.md.
Reproduce the numbers
The repo ships a Playwright harness that runs a synthetic SaaS
(Northstar: dashboard, customer search, lead creation, CSV
export, chart views) in baseline (:4173) and protected (:4174)
modes, across Chromium, Firefox and WebKit.
npm install
npm run build && npm run benchmark:build-sdk
python -m pip install playwright
python -m playwright install chromium firefox webkit
python benchmark/run_all.py --browsers chromium,firefox,webkit --iterations 5
Working on the repo itself: npm run check compiles, runs 817
automated tests (815 passing, 2 skipped RSA-PSS vectors) and
performs static checks. Scan a public build before deploying with
node scripts/run-audit.cjs /path/to/dist or
python3 scripts/verify-build.py /path/to/dist.
Help calibrate it
The one thing this project cannot generate for itself is real
traffic. If you run antiRea (or are evaluating it), run the CUSUM
tripwire in mode: "observe" - crossings are logged, nothing is
restricted - and share the anonymised aggregate snapshots it
emits: cohort sizes, scores, union-record counts.
No raw user data, no record contents, no identifiers. Review and
strip a snapshot before sending; if a field looks identifying,
leave it out and say so. The first real snapshot set is how the
uncalibrated label comes off.
Interested, or want the snapshot format first? Email dannyphan190@gmail.com.
Documentation
- Threat model - what antiRea assumes about attackers
- Limitations - what it does not do
- Evidence - full measured results and method
- Legal and privacy notes
- Deep dives: v2.8.1 - v2.8.0
- Changelog
Contributing and security
Read CONTRIBUTING.md and SECURITY.md before opening a pull request or a vulnerability report.
Project funding: Support the maintainer (optional; never necessary to use).
@dannyphan190/anti-rea-mcpnpm install @dannyphan190/anti-rea-mcp