[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build an IP Blocking Firewall

Build an IP Blocking Firewall

Build an application-level IP firewall with allowlists, blocklists, CIDR range support, country blocking, automatic threat detection, rate limiting tiers, and real-time analytics.

Development#typescript#tsconfig#type-checking#type-safety#javascript-migration
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 4 skills

Avg quality 95/100·All SAFE
>

typescript

v1.0.0

Sets up and repairs TypeScript projects: picks tsconfig.json settings for a Node.js service, an npm library or a bundled web app, runs type-checking in CI, and replaces `any` and unsafe casts with types the compiler can verify. Use when someone asks to "set up TypeScript", "fix this tsconfig", "fix a TypeScript error", "upgrade to TypeScript 6 or 7", "migrate JavaScript to TypeScript", "publish types for a package", "run .ts files in Node" or "make this type-safe". Covers the changed defaults in TypeScript 6.0 and 7.0, module and moduleResolution, strict mode, project references, declaration output, narrowing, satisfies and common compiler errors.

93/100 quality
7.30× impact
SAFE· stale score
View skill
>

redis

v1.1.0

Redis is an in-memory data store used as a cache, session store, message broker and counter service. Use when adding a cache layer, storing sessions, rate limiting an API, running pub/sub or Streams with consumer groups, taking a distributed lock, building a leaderboard, or configuring Redis for production (persistence, maxmemory, eviction).

93/100 quality
1.79× impact
SAFE· stale score
View skill
>

hono

v1.1.0

Hono is a small web framework built on Web Standards (Request/Response) that runs unchanged on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda and Vercel. Use it when the user asks to build a Hono API, add Zod validation, JWT auth, CORS or middleware, use the type-safe RPC client (hc), render JSX, stream responses, or deploy one codebase to several runtimes.

100/100 quality
6.20× impact
SAFE· stale score
View skill
>

zod

v1.1.0

Zod is a TypeScript-first schema library that validates data at runtime and infers static types from the same schema. Use when validating API request bodies, form input, environment variables or config files, parsing untrusted JSON, or migrating from Zod 3 to Zod 4. Covers z.object, safeParse, z.infer, coercion, discriminated unions, refinements, JSON Schema output and the v4 API changes.

93/100 quality
3.87× impact
SAFE· stale score
View skill
$

The Problem

Luca leads security at a 25-person API company. They get hit by credential stuffing attacks from rotating IP ranges, scrapers from data center IPs, and DDoS attempts from botnets. Their cloud WAF costs $500/month and still lets through targeted attacks because rules are too generic. They can't block entire countries during attacks without calling support. They need an application-level firewall: real-time IP blocking, CIDR range support, country-level rules, automatic threat detection with auto-blocking, and tiered rate limiting for different API consumers.

Step 1: Build the IP Firewall

typescript
// src/security/firewall.ts — Application-level IP firewall with auto-blocking and analytics
import { Redis } from "ioredis";
import { pool } from "../db";

const redis = new Redis(process.env.REDIS_URL!);

interface FirewallRule {
  id: string;
  type: "ip" | "cidr" | "country" | "asn";
  value: string;
  action: "allow" | "block" | "challenge" | "rate_limit";
  rateLimitConfig?: { requests: number; windowSeconds: number };
  reason: string;
  expiresAt: string | null;
  autoGenerated: boolean;
  createdAt: string;
}

interface FirewallDecision {
  action: "allow" | "block" | "challenge" | "rate_limit";
  rule: string | null;
  reason: string;
}

// Check IP against firewall rules
export async function checkIP(ip: string, country?: string, asn?: number): Promise<FirewallDecision> {
  // 1. Check allowlist first (always takes priority)
  const allowed = await redis.sismember("fw:allowlist", ip);
  if (allowed) return { action: "allow", rule: "allowlist", reason: "Allowlisted IP" };

  // 2. Check exact IP blocklist
  const blocked = await redis.sismember("fw:blocklist", ip);
  if (blocked) return { action: "block", rule: "blocklist", reason: "Blocked IP" };

  // 3. Check temporary blocks (auto-generated)
  const tempBlock = await redis.get(`fw:temp:${ip}`);
  if (tempBlock) {
    const data = JSON.parse(tempBlock);
    return { action: "block", rule: "auto-block", reason: data.reason };
  }

  // 4. Check CIDR ranges
  const cidrBlock = await checkCIDR(ip);
  if (cidrBlock) return cidrBlock;

  // 5. Check country blocks
  if (country) {
    const countryBlocked = await redis.sismember("fw:blocked_countries", country);
    if (countryBlocked) return { action: "block", rule: `country:${country}`, reason: `Country blocked: ${country}` };
  }

  // 6. Check ASN blocks
  if (asn) {
    const asnBlocked = await redis.sismember("fw:blocked_asns", String(asn));
    if (asnBlocked) return { action: "block", rule: `asn:${asn}`, reason: `ASN blocked: ${asn}` };
  }

  // 7. Rate limiting
  const rateResult = await checkRateLimit(ip);
  if (rateResult) return rateResult;

  return { action: "allow", rule: null, reason: "No matching rules" };
}

// Add rule
export async function addRule(rule: Omit<FirewallRule, "id" | "createdAt">): Promise<void> {
  switch (rule.type) {
    case "ip":
      if (rule.action === "allow") await redis.sadd("fw:allowlist", rule.value);
      else if (rule.action === "block") await redis.sadd("fw:blocklist", rule.value);
      break;
    case "cidr":
      await redis.sadd(`fw:cidr:${rule.action}`, rule.value);
      break;
    case "country":
      if (rule.action === "block") await redis.sadd("fw:blocked_countries", rule.value);
      break;
    case "asn":
      if (rule.action === "block") await redis.sadd("fw:blocked_asns", rule.value);
      break;
  }

  if (rule.expiresAt) {
    const ttl = Math.ceil((new Date(rule.expiresAt).getTime() - Date.now()) / 1000);
    if (ttl > 0 && rule.type === "ip") {
      await redis.setex(`fw:temp:${rule.value}`, ttl, JSON.stringify({ reason: rule.reason }));
    }
  }

  await pool.query(
    `INSERT INTO firewall_rules (type, value, action, reason, expires_at, auto_generated, created_at)
     VALUES ($1, $2, $3, $4, $5, $6, NOW())`,
    [rule.type, rule.value, rule.action, rule.reason, rule.expiresAt, rule.autoGenerated]
  );
}

// Auto-detect and block threats
export async function detectThreats(ip: string, context: {
  path: string;
  method: string;
  statusCode: number;
  userAgent: string;
}): Promise<void> {
  const now = Date.now();
  const minute = Math.floor(now / 60000);

  // Track failed auth attempts
  if (context.statusCode === 401 || context.statusCode === 403) {
    const failKey = `fw:fails:${ip}:${minute}`;
    const fails = await redis.incr(failKey);
    await redis.expire(failKey, 300);

    if (fails > 20) {
      await autoBlock(ip, 3600, "Credential stuffing: 20+ auth failures/minute");
      return;
    }
  }

  // Track 4xx errors (scanning/fuzzing)
  if (context.statusCode >= 400 && context.statusCode < 500) {
    const errorKey = `fw:errors:${ip}:${minute}`;
    const errors = await redis.incr(errorKey);
    await redis.expire(errorKey, 300);

    if (errors > 50) {
      await autoBlock(ip, 1800, "Path scanning: 50+ 4xx errors/minute");
      return;
    }
  }

  // Track request volume (DDoS)
  const volumeKey = `fw:volume:${ip}:${minute}`;
  const volume = await redis.incr(volumeKey);
  await redis.expire(volumeKey, 120);

  if (volume > 500) {
    await autoBlock(ip, 7200, "DDoS: 500+ requests/minute");
  }

  // Detect SQL injection / XSS attempts
  const suspicious = [
    "UNION SELECT", "DROP TABLE", "<script>", "../../", "%00",
    "' OR '1'='1", "eval(", "exec(", ".env", "wp-admin",
  ];
  const fullPath = context.path.toLowerCase();
  if (suspicious.some((s) => fullPath.includes(s.toLowerCase()))) {
    await autoBlock(ip, 86400, `Attack pattern detected in path: ${context.path.slice(0, 100)}`);
  }
}

async function autoBlock(ip: string, durationSeconds: number, reason: string): Promise<void> {
  await redis.setex(`fw:temp:${ip}`, durationSeconds, JSON.stringify({ reason, blockedAt: Date.now() }));

  await pool.query(
    `INSERT INTO firewall_rules (type, value, action, reason, expires_at, auto_generated, created_at)
     VALUES ('ip', $1, 'block', $2, $3, true, NOW())`,
    [ip, reason, new Date(Date.now() + durationSeconds * 1000).toISOString()]
  );

  await redis.rpush("notification:queue", JSON.stringify({
    type: "firewall_block", ip, reason, duration: durationSeconds,
  }));
}

// CIDR range check
async function checkCIDR(ip: string): Promise<FirewallDecision | null> {
  const blockedCIDRs = await redis.smembers("fw:cidr:block");

  for (const cidr of blockedCIDRs) {
    if (ipInCIDR(ip, cidr)) {
      return { action: "block", rule: `cidr:${cidr}`, reason: `IP in blocked CIDR range ${cidr}` };
    }
  }
  return null;
}

function ipInCIDR(ip: string, cidr: string): boolean {
  const [range, bits] = cidr.split("/");
  const mask = ~(2 ** (32 - parseInt(bits)) - 1);
  const ipNum = ipToNum(ip);
  const rangeNum = ipToNum(range);
  return (ipNum & mask) === (rangeNum & mask);
}

function ipToNum(ip: string): number {
  return ip.split(".").reduce((acc, octet) => (acc << 8) + parseInt(octet), 0) >>> 0;
}

// Tiered rate limiting
async function checkRateLimit(ip: string): Promise<FirewallDecision | null> {
  const minute = Math.floor(Date.now() / 60000);
  const key = `fw:rl:${ip}:${minute}`;

  const count = await redis.incr(key);
  await redis.expire(key, 120);

  // Default: 100 req/min
  if (count > 100) {
    return { action: "rate_limit", rule: "default_rate_limit", reason: `Rate limited: ${count} req/min` };
  }
  return null;
}

// Analytics
export async function getFirewallStats(): Promise<{
  totalBlocked24h: number;
  topBlockedIPs: Array<{ ip: string; count: number; reason: string }>;
  blockedCountries: string[];
  autoBlocksActive: number;
}> {
  const { rows: [{ count: blocked24h }] } = await pool.query(
    "SELECT COUNT(*) as count FROM firewall_rules WHERE auto_generated = true AND created_at > NOW() - INTERVAL '24 hours'"
  );

  const { rows: topIPs } = await pool.query(
    `SELECT value as ip, COUNT(*) as count, reason FROM firewall_rules
     WHERE auto_generated = true AND created_at > NOW() - INTERVAL '24 hours'
     GROUP BY value, reason ORDER BY count DESC LIMIT 10`
  );

  const blockedCountries = await redis.smembers("fw:blocked_countries");

  // Count active auto-blocks
  const keys = await redis.keys("fw:temp:*");

  return {
    totalBlocked24h: parseInt(blocked24h),
    topBlockedIPs: topIPs,
    blockedCountries,
    autoBlocksActive: keys.length,
  };
}

// Middleware
export async function firewallMiddleware(c: any, next: any): Promise<void> {
  const ip = c.req.header("CF-Connecting-IP") || c.req.header("X-Forwarded-For")?.split(",")[0]?.trim() || "unknown";
  const decision = await checkIP(ip);

  if (decision.action === "block") {
    return c.json({ error: "Access denied" }, 403);
  }

  if (decision.action === "rate_limit") {
    c.header("Retry-After", "60");
    return c.json({ error: "Too many requests" }, 429);
  }

  await next();

  // Post-response threat detection
  detectThreats(ip, {
    path: c.req.path,
    method: c.req.method,
    statusCode: c.res.status,
    userAgent: c.req.header("User-Agent") || "",
  }).catch(() => {});
}

Results

  • WAF cost: $500/month → $0 — application-level firewall handles what the cloud WAF couldn't; custom rules for their specific threat profile
  • Credential stuffing auto-blocked — 20+ auth failures/minute → IP blocked for 1 hour; attack surface reduced without manual intervention
  • Country blocking in 1 command — during attacks from specific regions, block entire countries in Redis set operation; takes effect in milliseconds
  • CIDR range blocking — data center IP ranges (AWS, DigitalOcean) blocked as CIDR; stops proxy rotation attacks that change individual IPs
  • SQL injection/XSS caught at firewall — suspicious patterns in URLs blocked before reaching application code; auto-blocked for 24 hours with evidence logged

Related use cases