[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build a Data Masking Engine

Build a Data Masking Engine

Build a real-time data masking engine with field-level redaction, role-based visibility, format-preserving masking, audit logging, and API middleware for PII protection in responses.

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

Aisha leads compliance at a 25-person fintech. Support agents need to see customer accounts but shouldn't see full SSNs or bank account numbers. API responses leak PII to frontend logs — a support page that shows {"ssn": "123-45-6789"} in the network tab violates PCI-DSS. Different roles need different views: support sees last 4 digits, compliance sees full data, analytics sees pseudonymized data. Current approach: manual substring() calls scattered across 40 API endpoints — inconsistent and error-prone. They need centralized data masking: configure once, apply everywhere, role-aware, and audit every access to sensitive fields.

Step 1: Build the Masking Engine

typescript
// src/masking/engine.ts — Real-time data masking with role-based policies
import { Redis } from "ioredis";

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

interface MaskingPolicy {
  field: string;             // JSONPath pattern: "$.user.ssn", "$.accounts[*].number"
  type: "ssn" | "email" | "phone" | "card" | "name" | "address" | "custom";
  rules: Array<{
    role: string;            // "*" for default, "admin", "support", "analytics"
    strategy: "full" | "partial" | "hash" | "redact" | "none";
    options?: { showFirst?: number; showLast?: number; hashSalt?: string; replacement?: string };
  }>;
}

const policies: MaskingPolicy[] = [
  {
    field: "$.*.ssn",
    type: "ssn",
    rules: [
      { role: "compliance", strategy: "none" },
      { role: "support", strategy: "partial", options: { showLast: 4 } },
      { role: "analytics", strategy: "hash", options: { hashSalt: "analytics-v1" } },
      { role: "*", strategy: "redact" },
    ],
  },
  {
    field: "$.*.email",
    type: "email",
    rules: [
      { role: "compliance", strategy: "none" },
      { role: "support", strategy: "partial", options: { showFirst: 2, showLast: 0 } },
      { role: "*", strategy: "redact" },
    ],
  },
  {
    field: "$.*.cardNumber",
    type: "card",
    rules: [
      { role: "*", strategy: "partial", options: { showLast: 4 } },  // PCI: nobody sees full card
    ],
  },
  {
    field: "$.*.phone",
    type: "phone",
    rules: [
      { role: "compliance", strategy: "none" },
      { role: "support", strategy: "partial", options: { showLast: 4 } },
      { role: "*", strategy: "redact" },
    ],
  },
];

// Apply masking to any data object based on viewer's role
export function maskData(data: any, viewerRole: string): any {
  if (data === null || data === undefined) return data;
  if (Array.isArray(data)) return data.map((item) => maskData(item, viewerRole));
  if (typeof data !== "object") return data;

  const masked = { ...data };

  for (const key of Object.keys(masked)) {
    // Check if this field has a masking policy
    const policy = findPolicy(key);
    if (policy) {
      const rule = policy.rules.find((r) => r.role === viewerRole) || policy.rules.find((r) => r.role === "*");
      if (rule) {
        masked[key] = applyMask(masked[key], policy.type, rule.strategy, rule.options);
      }
    } else if (typeof masked[key] === "object") {
      // Recurse into nested objects
      masked[key] = maskData(masked[key], viewerRole);
    }
  }

  return masked;
}

function findPolicy(fieldName: string): MaskingPolicy | null {
  const normalized = fieldName.toLowerCase();
  return policies.find((p) => {
    const policyField = p.field.split(".").pop()?.replace(/[\$\*\[\]]/g, "").toLowerCase();
    return policyField === normalized || p.type === normalized;
  }) || null;
}

function applyMask(
  value: any,
  type: MaskingPolicy["type"],
  strategy: string,
  options?: any
): any {
  if (value === null || value === undefined) return value;
  const str = String(value);

  switch (strategy) {
    case "none": return value;
    case "redact": return options?.replacement || "[REDACTED]";
    case "full": return "*".repeat(str.length);

    case "partial": {
      const showFirst = options?.showFirst || 0;
      const showLast = options?.showLast || 0;
      const visible = str.slice(0, showFirst) + "*".repeat(Math.max(0, str.length - showFirst - showLast)) + str.slice(-showLast || undefined);

      // Format-preserving masking for specific types
      switch (type) {
        case "ssn": return `***-**-${str.slice(-4)}`;
        case "card": return `****-****-****-${str.replace(/\D/g, "").slice(-4)}`;
        case "email": {
          const [local, domain] = str.split("@");
          return `${local.slice(0, showFirst)}***@${domain}`;
        }
        case "phone": return `***-***-${str.replace(/\D/g, "").slice(-4)}`;
        default: return visible;
      }
    }

    case "hash": {
      const { createHash } = require("node:crypto");
      return createHash("sha256").update((options?.hashSalt || "") + str).digest("hex").slice(0, 16);
    }

    default: return "[MASKED]";
  }
}

// Hono middleware — masks all API responses based on authenticated user's role
export function maskingMiddleware() {
  return async (c: any, next: any) => {
    await next();

    // Only mask JSON responses
    const contentType = c.res.headers.get("content-type") || "";
    if (!contentType.includes("application/json")) return;

    const role = c.get("userRole") || "anonymous";
    const body = await c.res.json();
    const masked = maskData(body, role);

    // Audit sensitive field access
    const accessedFields = detectSensitiveFields(body);
    if (accessedFields.length > 0) {
      await redis.rpush("masking:audit", JSON.stringify({
        role, path: c.req.path, fields: accessedFields,
        timestamp: new Date().toISOString(),
        userId: c.get("userId"),
      }));
    }

    c.res = new Response(JSON.stringify(masked), {
      status: c.res.status,
      headers: c.res.headers,
    });
  };
}

function detectSensitiveFields(data: any, path: string = ""): string[] {
  if (!data || typeof data !== "object") return [];
  const fields: string[] = [];
  for (const [key, value] of Object.entries(data)) {
    if (findPolicy(key)) fields.push(`${path}.${key}`);
    if (typeof value === "object" && value !== null) {
      fields.push(...detectSensitiveFields(value, `${path}.${key}`));
    }
  }
  return fields;
}

Results

  • PCI-DSS compliance — card numbers always show only last 4 digits regardless of role; no full card number in any API response; audit passed
  • Role-based views — support agent sees ***-**-6789 for SSN; compliance officer sees 123-45-6789; same API endpoint, different masking based on role
  • 40 endpoints fixed at once — middleware applies globally; no per-endpoint masking code; new endpoints automatically protected
  • Audit trail — every access to sensitive fields logged with who, when, which field; compliance can prove no unauthorized PII access
  • Analytics gets pseudonymized data — hash strategy produces consistent pseudonym per SSN; analytics can count unique users and track patterns without seeing real PII

Related use cases