Static-first honeypot plugin for VitePress and other Vite-powered static sites.
VitePot emits realistic-looking sensitive files like /.env, /wp-config.php, /backup.sql, /.git/config,
/vercel.json, and settings.py so noisy crawlers, path probers, and low-effort scanners spend time on bait instead
of your real surface area.
- Serves traps from memory in
vitepress dev - Emits real trap files during
vitepress build - Reuses the same trap responses in preview (except for dotfiles)
- Generates syntax-aware fake content for env, PHP, SQL, Git, JSON, JS, YAML, Python, INI, logs, and plain text
- Uses reserved
.testdomains and RFC 5737 test-net IPs so generated data never points to real infrastructure - Supports custom traps with sync content, async content, binary content, and per-trap directory placement
bun add @itznotabug/vitepot// .vitepress/config.mts
import { defineConfig } from 'vitepress';
import { vitepot } from '@itznotabug/vitepot';
export default defineConfig({
vite: {
plugins: [
vitepot()
],
},
});That enables the built-in trap set at the site root.
import { defineConfig } from 'vitepress';
import { vitepot } from '@itznotabug/vitepot';
export default defineConfig({
vite: {
plugins: [
vitepot({
variants: ['cms-roots', 'archive-roots'],
dirs: ['/legacy'],
custom: [
{
path: '/private.env'
},
{
path: '/secrets.ini', kind: 'ini'
},
{
path: '/credentials.txt',
content: 'admin=disabled\nroot=disabled\n',
},
{
path: '/ai-keys.json',
contentType: 'application/json',
content: async ({ helpers }) =>
JSON.stringify(
{
openai: helpers.fakeOpenAIKey(),
anthropic: helpers.fakeAnthropicKey(),
google: helpers.fakeGoogleAIKey(),
},
null,
2,
),
},
{
path: '/archive.bin',
contentType: 'application/octet-stream',
content: new Uint8Array([0xde, 0xad, 0xbe, 0xef]),
},
],
}),
],
},
});During vitepress dev, VitePot registers middleware and serves trap responses directly from memory. No files are
written.
During vitepress build, VitePot generates the trap set and emits static assets into the output bundle. If a trap path
collided with an existing output file, VitePot skips it and logs a warning.
During preview, the same trap middleware is mounted so local preview behavior stays aligned with the built output. Note: Dotfiles are not served due to Vite/Vitepress limitations.
The default set includes 43 file traps across:
- leaked env and credential files
- Git and source-control metadata
- WordPress and PHP config files
- SQL dumps and backups
- server config and auth files
- framework and deployment config files
- application logs
Examples:
/.env/.aws/credentials/.git/config/wp-config.php/config.php/backup.sql/web.config/wp-login.php/xmlrpc.php/vercel.json/next.config.js/config/database.yml/settings.py/connectionstrings.config
Variants mirror compatible built-in traps into common subpaths.
vitepot({
variants: ['cms-roots'],
});Available presets:
cms-roots→/blog,/site,/wordpressapp-roots→/public,/apiarchive-roots→/backup,/backups,/old
Variant expansion is filtered by trap compatibility. Example: cms-roots expands /wp-config.php, but not /.env.
You can also mirror traps into directories you choose:
vitepot({
dirs: ['/legacy', '/staging'],
});interface VitePotOptions {
enabled?: boolean;
variants?: VariantPreset[];
dirs?: string[];
custom?: CustomTrap[];
}Turns the plugin on or off. Defaults to true.
Adds built-in preset directories for compatible built-in traps.
Adds explicit extra directories for expansion. Root is always included automatically.
Adds user-defined file traps.
type CustomTrap = {
path: string;
kind?: TrapKind;
content?: string | Uint8Array | Promise<string | Uint8Array> |
((ctx: CustomTrapContext) => string | Uint8Array | Promise<string | Uint8Array>);
contentType?: string;
dirs?: string[];
};Rules:
pathmust start with/- file paths must not end with
/ contentoverrides the built-in generatordirson a custom trap applies only to that trap
Built-in generator families map file paths to realistic content shapes:
envwordpressphpsqlgitserverjsonjsyamlpythoninilogtext
If kind is omitted on a custom trap, VitePot infers it from the path.
Custom content factories receive deterministic helpers:
helpers.fakeDomain() // calmrouter.test
helpers.fakeEmail() // admin@calmrouter.test
helpers.fakeHostname() // db-3.calmrouter.test
helpers.fakeTestNetIPv4() // 192.0.2.15
helpers.fakeMysqlPassword() // strong fake password
helpers.fakeApiToken() // generic token
helpers.fakePhpSecret() // 64-char hex secret
helpers.fakeJwtLikeSecret() // JWT-like secret
helpers.fakeCloudKey() // AWS-style access key
helpers.fakeTimestamp() // ISO timestamp
helpers.fakeOpenAIKey() // sk-proj-...
helpers.fakeAnthropicKey() // sk-ant-api...
helpers.fakeGoogleAIKey() // AIzaSy...
helpers.fakeHuggingFaceToken() // hf_...
helpers.fakeStripeKey() // sk_live_...
helpers.fakeSupabaseKey() // JWT-like Supabase key
helpers.fakeClerkKey() // sk_live_...
helpers.fakeVercelToken() // Vercel-style token
helpers.fakeSentryDSN() // https://key@host.test/123All generated domains use the reserved .test TLD, and all generated IPs use RFC 5737 test-net ranges.
If your production server can execute certain file types (PHP, Python, Ruby, etc.), configure it to serve honeypot files
as static text instead of executing them. Use .htaccess (Apache), nginx.conf (Nginx), or equivalent configuration
for your web server to disable execution and force text/plain content-type for trap files.
VitePress Preview: Dotfiles (.env, .git/config, etc.) are blocked by VitePress's preview server. They work in
dev. mode via middleware and are emitted during build, but won't be served in vitepress preview.
Production Hosting: Some managed hosting providers might block dotfiles for security. Non-dotfile traps
(wp-config.php, backup.sql, etc.) work everywhere. Dotfiles should work on servers where you have full control (VPS,
dedicated servers with custom web server configuration).
bun install
bun run check
bun test
bun run typecheck
bun run build