honeycrawlpot · Documentation
Deception middleware for AI crawlers
honeycrawlpot answers scanner-style probes (/.env, AWS credentials, SSH keys, openapi.json…) with convincing decoys containing fabricated secrets, canary tokens and prompt-injection payloads aimed at LLM-driven agents.
On this page
Install
bun add honeycrawlpot
# or
npm install honeycrawlpot
No runtime dependencies. Express and Hono are optional peers. The package is ESM-only.
Node support
Fixed in 0.2.1: the 0.2.0 tarball kept extensionless relative imports, so a native Node ESM import failed with ERR_MODULE_NOT_FOUND. The build now emits explicit .js specifiers and CI runs a Node import smoke test. Bun and bundlers were never affected.
Quick start
Hono
import { Hono } from 'hono';
import { honoHoneypot } from 'honeycrawlpot';
const app = new Hono();
app.get('/health', (c) => c.json({ ok: true }));
// after your real routes, before the 404 handler
app.use('*', honoHoneypot({
onHit: (hit) => console.log('probe', hit.path, hit.category, hit.canary),
}));
app.notFound((c) => c.json({ error: 'not found' }, 404));
Express
import { expressHoneypot } from 'honeycrawlpot';
app.get('/api/health', (_req, res) => res.json({ ok: true }));
app.use(expressHoneypot());
app.use((_req, res) => res.status(404).json({ error: 'not found' }));
Framework-agnostic
import { handleProbe } from 'honeycrawlpot';
const outcome = handleProbe({ method: 'GET', path: '/.env' });
// { status: 200, headers: {...}, body: '…', category: 'env', canary: 'LF-HONEYPOT-…' }
handleProbe only uses the path; method and headers are accepted but ignored. It always answers 200 with X-Honeypot: true, X-Robots-Tag: noindex and the decoy content type.
API
| Symbol | Purpose |
|---|---|
createHoneypot(config) | Build an isolated instance with its own canary, brand, domains and secrets. |
resolveDecoy(path, options) | Return a Decoy (category, contentType, content) or null. |
handleProbe(ctx, config?) | Decoy plus HTTP status and headers. |
honoHoneypot(options) / expressHoneypot(options) | Middleware adapters with onHit and extra excludePaths. |
HONEYPOT_CANARY | Canary of the default instance. |
Decoy catalogue
Thirteen categories: env, firebase, aws, sshkey, openapi, graphql, jsonconfig, yamlconfig, pyconfig, jsconfig, php, csv and a generic fallback.
Classification is substring-based on the decoded path, in a fixed order. It covers realistic scanner traffic (/.env.bak, /@fs/.env, /.ssh/id_rsa, /serviceAccountKey.json, /.aws/credentials, /appsettings.Production.json, /wp-config.php, /dashboard, /metrics, /__debug__, /proc/self/environ) but not everything:
- Not handled:
/server-status,/.git/HEAD,/backup.sql,/dump.zip, AWS IMDS paths. - Substring matches can produce false positives:
/drawsand/clawsclassify asaws;/envoyasenv. - Dot segments are not resolved:
/health/../metricsis treated asgeneric.
Canary tokens
Every decoy body contains a canary such as LF-HONEYPOT-AB12CD34. Search your logs, tickets, model outputs and incident reports for the LF-HONEYPOT- prefix; a hit proves the decoy was ingested.
Pin the value for stable alerting:
export HONEYPOT_CANARY="LF-HONEYPOT-MYSITE-01"
# or per instance
const hp = createHoneypot({ canary: "LF-HONEYPOT-MYSITE-01" });
Without a pinned value, each instance — and each handleProbe call that builds one implicitly — gets a fresh random canary.
Configuration
| Key | Default | Notes |
|---|---|---|
canary | random | Overrides HONEYPOT_CANARY |
excludePaths | ["health", "api/v1/health"] | Exact match, no leading slash, no globs |
brand, deployment | Acme, 2.4.1 | Appear in decoy bodies |
domains | *.example.com | app, api, pgbouncer, redis, minio, smtp, Firebase project |
secrets | static fake values | AWS, Stripe, PostgreSQL, JWT, SMTP; inert |
database | app/app_user | Used in connection strings |
extraKeywords | [] | Lowercase substrings routed to the generic decoy |
extraInjections | [] | Extra prompt-injection blocks |
Limitations
- Fingerprintable.
X-Honeypot: true,X-Robots-Tagand theLF-HONEYPOT-string let a careful scanner recognise the decoy. - Some JSON decoys are invalid. The injections are appended as
/* … */after the JSON body foropenapi,jsonconfigandjsconfig; strict parsers reject them. - Static credentials. The default fake secrets are identical across deployments and can be signatured.
- No persistence or rate limiting. Only the
onHitcallback observes hits; there is no log, counter or scanner identification. - Efficacy is unmeasured. The injected instructions are best-effort; no test proves an agent obeys them.
Links
- GitHub · npm · project page