Threat model, hardening checklist, and production deployment best practices
GIF CAPTCHA is designed to distinguish humans from bots by testing visual comprehension of animated GIF content. Understanding the attack surface helps you deploy it securely.
Bots may attempt to solve CAPTCHAs using image recognition, OCR, or multimodal AI models that can describe GIF content.
Mitigations:
createAttemptTracker()requiredKeywords in validateAnswer() to require specific vocabularyAttackers relay challenges to human workers who solve them in real-time.
Mitigations:
captcha-rate-limiter.js) to limit attempts per IP/sessionUser input (CAPTCHA answers) could be injected into the DOM if not sanitized.
Mitigations:
sanitize() before inserting any user input into HTMLinnerHTML with unsanitized content โ the library handles this internallyAn attacker captures a valid challenge-response pair and replays it.
Mitigations:
The included nginx-security.conf provides production-grade security headers. At minimum, deploy these CSP directives:
# Recommended Content-Security-Policy
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' https://media.tenor.com https://i.giphy.com data:;
connect-src 'self';
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
โ ๏ธ img-src allowlist: Add only the GIF hosting domains you actually use. The example includes Tenor and Giphy โ remove any you don't need.
Never trust client-side validation alone. Always verify CAPTCHA responses server-side:
const { validateAnswer } = require("gif-captcha");
app.post("/verify-captcha", (req, res) => {
const { answer, challengeToken } = req.body;
// 1. Verify token is valid and not expired
const challenge = tokenStore.consume(challengeToken);
if (!challenge) {
return res.status(400).json({ error: "Invalid or expired token" });
}
// 2. Verify token is bound to this session
if (challenge.sessionId !== req.session.id) {
return res.status(403).json({ error: "Token/session mismatch" });
}
// 3. Validate the answer with strict keywords
const result = validateAnswer(answer, challenge.expectedAnswer, {
threshold: 0.4,
requiredKeywords: challenge.keywords
});
if (!result.passed) {
return res.status(403).json({ error: "CAPTCHA failed" });
}
// 4. Proceed with protected action
req.session.captchaVerified = true;
res.json({ success: true });
});
The built-in rate limiter prevents brute-force solving attempts:
const { createRateLimiter } = require("gif-captcha/src/captcha-rate-limiter");
const limiter = createRateLimiter({
maxAttempts: 5, // Max attempts per window
windowMs: 60000, // 1-minute window
blockDurationMs: 300000 // 5-minute block after exceeding
});
app.use("/verify-captcha", (req, res, next) => {
const clientId = req.ip;
if (limiter.isBlocked(clientId)) {
return res.status(429).json({ error: "Too many attempts" });
}
limiter.record(clientId);
next();
});
nginx-security.conf headers (CSP, X-Frame-Options, HSTS)validateAnswer()sanitize() on all user input before DOM insertionframe-ancestors 'none' to prevent clickjackingSECURITY.md for vulnerability reporting proceduresCombine CAPTCHA verification with behavioral signals for stronger bot detection:
| Signal | Human Pattern | Bot Pattern |
|---|---|---|
| Solve time | 5โ30 seconds, variable | <2s or extremely consistent |
| Mouse movement | Natural curves, micro-corrections | Linear paths or absent |
| Typing cadence | Variable inter-key timing | Uniform or instant paste |
| Session behavior | Page navigation, scrolling | Direct POST to endpoint |
| Answer style | Informal, typos, varied phrasing | "I cannot view animated images" |
Use createAttemptTracker() to collect these signals and feed them into your trust scoring pipeline.
GitHub ยท npm ยท Report a Vulnerability ยท MIT License