@dupecom/botcha 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ramin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # BOTCHA 🤖
2
+
3
+ > Prove you're a bot. Humans need not apply.
4
+
5
+ **BOTCHA** is a reverse CAPTCHA — it verifies that visitors are AI agents, not humans. Perfect for AI-only APIs, agent marketplaces, and bot networks.
6
+
7
+ [![npm version](https://badge.fury.io/js/botcha.svg)](https://www.npmjs.com/package/botcha)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ 🌐 **Demo:** [reverse-captcha.vercel.app](https://reverse-captcha.vercel.app)
11
+
12
+ ## Why?
13
+
14
+ CAPTCHAs ask "Are you human?" — **BOTCHA asks "Are you an AI?"**
15
+
16
+ Use cases:
17
+ - 🤖 Agent-only APIs
18
+ - 🔄 AI-to-AI marketplaces
19
+ - 🎫 Bot verification systems
20
+ - 🔐 Autonomous agent authentication
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ npm install botcha
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```typescript
31
+ import express from 'express';
32
+ import { botcha } from 'botcha';
33
+
34
+ const app = express();
35
+
36
+ // Protect any route - only AI agents can access
37
+ app.get('/agent-only', botcha.verify(), (req, res) => {
38
+ res.json({ message: 'Welcome, fellow AI! 🤖' });
39
+ });
40
+
41
+ app.listen(3000);
42
+ ```
43
+
44
+ ## How It Works
45
+
46
+ BOTCHA issues a **speed challenge**: solve 5 SHA256 hashes in 500ms.
47
+
48
+ - ✅ **AI agents** compute hashes instantly
49
+ - ❌ **Humans** can't copy-paste fast enough
50
+
51
+ ```
52
+ Challenge: [645234, 891023, 334521, 789012, 456789]
53
+ Task: SHA256 each number, return first 8 hex chars
54
+ Time limit: 500ms```
55
+
56
+ ## For AI Agents
57
+
58
+ If you're building an AI agent that needs to access BOTCHA-protected APIs:
59
+
60
+ ```typescript
61
+ import { botcha } from 'botcha';
62
+
63
+ // When you get a 403 with a challenge:
64
+ const challenge = response.challenge;
65
+ const answers = botcha.solve(challenge.problems);
66
+
67
+ // Retry with solution headers:
68
+ fetch('/agent-only', {
69
+ headers: {
70
+ 'X-Botcha-Id': challenge.id,
71
+ 'X-Botcha-Answers': JSON.stringify(answers),
72
+ }
73
+ });
74
+ ```
75
+
76
+ ## Options
77
+
78
+ ```typescript
79
+ botcha.verify({
80
+ // Challenge mode: 'speed' (500ms) or 'standard' (5s)
81
+ mode: 'speed',
82
+
83
+ // Allow X-Agent-Identity header for testing
84
+ allowTestHeader: true,
85
+
86
+ // Custom failure handler
87
+ onFailure: (req, res, reason) => {
88
+ res.status(403).json({ error: reason });
89
+ },
90
+ });
91
+ ```
92
+
93
+ ## Testing
94
+
95
+ For development, you can bypass BOTCHA with a header:
96
+
97
+ ```bash
98
+ curl -H "X-Agent-Identity: MyTestAgent/1.0" http://localhost:3000/agent-only
99
+ ```
100
+
101
+ ## API Reference
102
+
103
+ ### `botcha.verify(options?)`
104
+
105
+ Express middleware that protects routes from humans.
106
+
107
+ ### `botcha.solve(problems: number[])`
108
+
109
+ Helper function for AI agents to solve challenges.
110
+
111
+ ```typescript
112
+ const answers = botcha.solve([645234, 891023, 334521]);
113
+ // Returns: ['a1b2c3d4', 'e5f6g7h8', 'i9j0k1l2']
114
+ ```
115
+
116
+ ## Challenge Flow
117
+
118
+ ```
119
+ 1. Agent requests protected endpoint
120
+ 2. BOTCHA returns 403 + challenge (5 numbers)
121
+ 3. Agent computes SHA256 of each number
122
+ 4. Agent retries with X-Botcha-Id and X-Botcha-Answers headers
123
+ 5. BOTCHA verifies (must complete in <500ms)
124
+ 6. ✅ Access granted
125
+ ```
126
+
127
+ ## Philosophy
128
+
129
+ > "If a human writes a script to solve BOTCHA using an LLM... they've built an AI agent."
130
+
131
+ BOTCHA doesn't block all automation — it blocks *casual* human access while allowing *automated* AI agents. The speed challenge ensures someone had to write code, which is the point.
132
+
133
+ For cryptographic proof of agent identity, see [Web Bot Auth](https://datatracker.ietf.org/doc/html/draft-meunier-web-bot-auth-architecture).
134
+
135
+ ## License
136
+
137
+ MIT © [Ramin](https://github.com/i8ramin)
138
+
139
+ ---
140
+
141
+ Built by [@i8ramin](https://github.com/i8ramin) and Choco 🐢
@@ -0,0 +1,34 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ export interface BotchaOptions {
3
+ /** Challenge mode: 'speed' (500ms, 5 hashes) or 'standard' (5s, primes) */
4
+ mode?: 'speed' | 'standard';
5
+ /** Difficulty for standard mode */
6
+ difficulty?: 'easy' | 'medium' | 'hard';
7
+ /** Allow X-Agent-Identity header (for testing) */
8
+ allowTestHeader?: boolean;
9
+ /** Custom failure handler */
10
+ onFailure?: (req: Request, res: Response, reason: string) => void;
11
+ }
12
+ export interface ChallengeResult {
13
+ valid: boolean;
14
+ reason?: string;
15
+ solveTimeMs?: number;
16
+ }
17
+ /**
18
+ * Express middleware to verify AI agents
19
+ *
20
+ * @example
21
+ * import { botcha } from 'botcha';
22
+ * app.use('/agent-only', botcha.verify());
23
+ */
24
+ export declare function verify(options?: BotchaOptions): (req: Request, res: Response, next: NextFunction) => Promise<void>;
25
+ /**
26
+ * Solve a BOTCHA challenge (for AI agents to use)
27
+ */
28
+ export declare function solve(problems: number[]): string[];
29
+ export declare const botcha: {
30
+ verify: typeof verify;
31
+ solve: typeof solve;
32
+ };
33
+ export default botcha;
34
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI1D,MAAM,WAAW,aAAa;IAC5B,2EAA2E;IAC3E,IAAI,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IAC5B,mCAAmC;IACnC,UAAU,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;IACxC,kDAAkD;IAClD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,6BAA6B;IAC7B,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACnE;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAqED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,OAAO,GAAE,aAAkB,IAOlC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,mBA8C9D;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAIlD;AAGD,eAAO,MAAM,MAAM;;;CAAoB,CAAC;AACxC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,115 @@
1
+ import crypto from 'crypto';
2
+ const challenges = new Map();
3
+ // Cleanup expired challenges
4
+ setInterval(() => {
5
+ const now = Date.now();
6
+ for (const [id, c] of challenges) {
7
+ if (c.expiresAt < now)
8
+ challenges.delete(id);
9
+ }
10
+ }, 30000);
11
+ // ============ CHALLENGE GENERATION ============
12
+ function generateSpeedChallenge() {
13
+ const id = crypto.randomUUID();
14
+ const problems = [];
15
+ const expectedAnswers = [];
16
+ for (let i = 0; i < 5; i++) {
17
+ const num = Math.floor(Math.random() * 900000) + 100000;
18
+ problems.push(num);
19
+ expectedAnswers.push(crypto.createHash('sha256').update(num.toString()).digest('hex').substring(0, 8));
20
+ }
21
+ challenges.set(id, {
22
+ id,
23
+ expectedAnswers,
24
+ issuedAt: Date.now(),
25
+ expiresAt: Date.now() + 600, // 500ms + 100ms grace
26
+ });
27
+ return { id, problems, timeLimit: 500 };
28
+ }
29
+ function verifySpeedChallenge(id, answers) {
30
+ const challenge = challenges.get(id);
31
+ if (!challenge)
32
+ return { valid: false, reason: 'Challenge expired or not found' };
33
+ const now = Date.now();
34
+ const solveTimeMs = now - challenge.issuedAt;
35
+ challenges.delete(id);
36
+ if (now > challenge.expiresAt) {
37
+ return { valid: false, reason: `Too slow (${solveTimeMs}ms). Limit: 500ms` };
38
+ }
39
+ if (!Array.isArray(answers) || answers.length !== 5) {
40
+ return { valid: false, reason: 'Must provide 5 answers' };
41
+ }
42
+ for (let i = 0; i < 5; i++) {
43
+ if (answers[i]?.toLowerCase() !== challenge.expectedAnswers[i]) {
44
+ return { valid: false, reason: `Wrong answer #${i + 1}` };
45
+ }
46
+ }
47
+ return { valid: true, solveTimeMs };
48
+ }
49
+ // ============ MIDDLEWARE ============
50
+ /**
51
+ * Express middleware to verify AI agents
52
+ *
53
+ * @example
54
+ * import { botcha } from 'botcha';
55
+ * app.use('/agent-only', botcha.verify());
56
+ */
57
+ export function verify(options = {}) {
58
+ const opts = {
59
+ mode: 'speed',
60
+ allowTestHeader: true,
61
+ ...options,
62
+ };
63
+ return async (req, res, next) => {
64
+ // Check for test header (dev mode)
65
+ if (opts.allowTestHeader && req.headers['x-agent-identity']) {
66
+ req.botcha = { verified: true, agent: req.headers['x-agent-identity'], method: 'header' };
67
+ return next();
68
+ }
69
+ // Check for challenge solution
70
+ const challengeId = req.headers['x-botcha-id'];
71
+ const answers = req.headers['x-botcha-answers'];
72
+ if (challengeId && answers) {
73
+ try {
74
+ const answerArray = JSON.parse(answers);
75
+ const result = verifySpeedChallenge(challengeId, answerArray);
76
+ if (result.valid) {
77
+ req.botcha = { verified: true, solveTimeMs: result.solveTimeMs, method: 'challenge' };
78
+ return next();
79
+ }
80
+ }
81
+ catch { }
82
+ }
83
+ // Generate new challenge
84
+ const challenge = generateSpeedChallenge();
85
+ const failureResponse = {
86
+ error: 'BOTCHA_CHALLENGE',
87
+ message: '🤖 Prove you are an AI agent',
88
+ challenge: {
89
+ id: challenge.id,
90
+ problems: challenge.problems,
91
+ timeLimit: challenge.timeLimit,
92
+ instructions: 'SHA256 each number, return first 8 hex chars as JSON array',
93
+ },
94
+ headers: {
95
+ 'X-Botcha-Id': challenge.id,
96
+ 'X-Botcha-Answers': '["abc123...", ...]',
97
+ },
98
+ };
99
+ if (opts.onFailure) {
100
+ opts.onFailure(req, res, 'Challenge required');
101
+ }
102
+ else {
103
+ res.status(403).json(failureResponse);
104
+ }
105
+ };
106
+ }
107
+ /**
108
+ * Solve a BOTCHA challenge (for AI agents to use)
109
+ */
110
+ export function solve(problems) {
111
+ return problems.map(n => crypto.createHash('sha256').update(n.toString()).digest('hex').substring(0, 8));
112
+ }
113
+ // ============ EXPORTS ============
114
+ export const botcha = { verify, solve };
115
+ export default botcha;
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@dupecom/botcha",
3
+ "version": "0.3.0",
4
+ "description": "Prove you're a bot. Humans need not apply. Reverse CAPTCHA for AI-only APIs.",
5
+ "main": "dist/lib/index.js",
6
+ "types": "dist/lib/index.d.ts",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/lib/index.js",
11
+ "types": "./dist/lib/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist/lib",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "dev": "tsx watch src/index.ts",
22
+ "prepublishOnly": "npm run build",
23
+ "test": "echo \"Tests coming soon\""
24
+ },
25
+ "keywords": [
26
+ "captcha",
27
+ "bot",
28
+ "ai",
29
+ "agent",
30
+ "verification",
31
+ "middleware",
32
+ "express",
33
+ "api",
34
+ "authentication"
35
+ ],
36
+ "author": "Ramin <ramin@dupe.com>",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/i8ramin/reverse-captcha"
41
+ },
42
+ "homepage": "https://reverse-captcha.vercel.app",
43
+ "bugs": {
44
+ "url": "https://github.com/i8ramin/reverse-captcha/issues"
45
+ },
46
+ "peerDependencies": {
47
+ "express": "^4.0.0 || ^5.0.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/express": "^4.17.25",
51
+ "@types/node": "^20.19.30",
52
+ "express": "^4.22.1",
53
+ "tsx": "^4.21.0",
54
+ "typescript": "^5.9.3"
55
+ },
56
+ "engines": {
57
+ "node": ">=18"
58
+ }
59
+ }