๐Ÿ”’ Security Guide

Threat model, hardening checklist, and production deployment best practices

Threat Model

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.

Automated Solving HIGH

Bots may attempt to solve CAPTCHAs using image recognition, OCR, or multimodal AI models that can describe GIF content.

Mitigations:

CAPTCHA Farming MEDIUM

Attackers relay challenges to human workers who solve them in real-time.

Mitigations:

Cross-Site Scripting (XSS) HIGH

User input (CAPTCHA answers) could be injected into the DOM if not sanitized.

Mitigations:

Replay Attacks MEDIUM

An attacker captures a valid challenge-response pair and replays it.

Mitigations:

Content Security Policy

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.

Server-Side Validation

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 });
});

Rate Limiting

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();
});

Production Deployment Checklist

Behavioral Analysis

Combine CAPTCHA verification with behavioral signals for stronger bot detection:

SignalHuman PatternBot Pattern
Solve time5โ€“30 seconds, variable<2s or extremely consistent
Mouse movementNatural curves, micro-correctionsLinear paths or absent
Typing cadenceVariable inter-key timingUniform or instant paste
Session behaviorPage navigation, scrollingDirect POST to endpoint
Answer styleInformal, 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