> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.hoop.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Live Data Masking

> Automatically detect and mask sensitive data in query results without writing rules

export const DataMaskingDemo = () => {
  const FONTS_URL = "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Inter:wght@400;500;600;700;800&display=swap";
  const C = {
    espresso: "var(--gradient-dark-mid)",
    warmGold: "var(--warm-gold)",
    sand100: "var(--sand-100)",
    sand300: "var(--sand-300)",
    sand500: "var(--sand-500)"
  };
  const QUERY = "SELECT name, email, ssn, card FROM customers LIMIT 4;";
  const TYPING_CHAR_MS = 22;
  const ROWS = [{
    raw: ["Sarah Chen", "sarah.chen@acme.io", "284-19-7653", "4532-8821"],
    masked: ["[PERSON]", "[EMAIL]", "[SSN]", "[PCI]"]
  }, {
    raw: ["Marcus Webb", "m.webb@globex.com", "531-77-0294", "4916-3350"],
    masked: ["[PERSON]", "[EMAIL]", "[SSN]", "[PCI]"]
  }, {
    raw: ["Elena Ruiz", "eruiz@initech.co", "719-42-8106", "5425-1247"],
    masked: ["[PERSON]", "[EMAIL]", "[SSN]", "[PCI]"]
  }, {
    raw: ["James Okafor", "j.okafor@stark.dev", "603-88-1542", "4111-9063"],
    masked: ["[PERSON]", "[EMAIL]", "[SSN]", "[PCI]"]
  }];
  const ROW_DELAY = 800;
  const CROSS_DURATION = 600;
  const HOLD = 3000;
  function ShieldIcon({active, done}) {
    return <svg width="18" height="18" viewBox="0 0 20 20" fill="none" style={{
      animation: done ? "shieldIn 0.5s ease-out" : undefined,
      opacity: active ? 1 : 0.3,
      transition: "opacity 0.4s ease"
    }}>
        <path d="M10 2L3 5.5V10C3 14.5 6 17.5 10 19C14 17.5 17 14.5 17 10V5.5L10 2Z" fill={done ? "rgba(var(--warm-gold-rgb),0.15)" : "rgba(var(--sand-100-rgb),0.05)"} stroke={done ? "var(--warm-gold)" : "rgba(var(--sand-100-rgb),0.15)"} strokeWidth="1.2" />
        {done && <path d="M7 10.5L9 12.5L13 8" stroke="var(--warm-gold)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />}
      </svg>;
  }
  const [typedLen, setTypedLen] = useState(0);
  const [typingDone, setTypingDone] = useState(false);
  const [rowPhases, setRowPhases] = useState(ROWS.map(() => "hidden"));
  const [allDone, setAllDone] = useState(false);
  const [membraneActive, setMembraneActive] = useState(false);
  const [membraneGlow, setMembraneGlow] = useState(false);
  const timeouts = useRef([]);
  const frameRef = useRef(null);
  const clearAll = useCallback(() => {
    timeouts.current.forEach(clearTimeout);
    timeouts.current = [];
    if (frameRef.current) cancelAnimationFrame(frameRef.current);
  }, []);
  const later = useCallback((fn, ms) => {
    const id = setTimeout(fn, ms);
    timeouts.current.push(id);
  }, []);
  useEffect(() => {
    let cancelled = false;
    const cycle = () => {
      if (cancelled) return;
      setTypedLen(0);
      setTypingDone(false);
      setRowPhases(ROWS.map(() => "hidden"));
      setAllDone(false);
      setMembraneActive(false);
      setMembraneGlow(false);
      const typingStart = performance.now();
      function tick(now) {
        if (cancelled) return;
        const elapsed = now - typingStart;
        const chars = Math.min(Math.floor(elapsed / TYPING_CHAR_MS), QUERY.length);
        setTypedLen(chars);
        if (chars < QUERY.length) {
          frameRef.current = requestAnimationFrame(tick);
        } else {
          setTypingDone(true);
        }
      }
      frameRef.current = requestAnimationFrame(tick);
      const typingTotal = QUERY.length * TYPING_CHAR_MS;
      const baseDelay = typingTotal + 400;
      later(() => {
        if (cancelled) return;
        setMembraneActive(true);
      }, baseDelay);
      ROWS.forEach((_, i) => {
        const t0 = baseDelay + 300 + i * ROW_DELAY;
        later(() => {
          if (cancelled) return;
          setRowPhases(p => {
            const n = [...p];
            n[i] = "approaching";
            return n;
          });
        }, t0);
        later(() => {
          if (cancelled) return;
          setRowPhases(p => {
            const n = [...p];
            n[i] = "crossing";
            return n;
          });
          setMembraneGlow(true);
          later(() => {
            if (!cancelled) setMembraneGlow(false);
          }, 300);
        }, t0 + 400);
        later(() => {
          if (cancelled) return;
          setRowPhases(p => {
            const n = [...p];
            n[i] = "through";
            return n;
          });
        }, t0 + 400 + CROSS_DURATION);
      });
      const total = baseDelay + 300 + (ROWS.length - 1) * ROW_DELAY + 400 + CROSS_DURATION + 300;
      later(() => {
        if (!cancelled) setAllDone(true);
      }, total);
      later(() => {
        if (!cancelled) cycle();
      }, total + HOLD);
    };
    cycle();
    return () => {
      cancelled = true;
      clearAll();
    };
  }, [later, clearAll]);
  return <>
      <link rel="stylesheet" href={FONTS_URL} />
      <style>{`
        @keyframes approachSlide {
          from { opacity: 0; transform: translateX(-30px); }
          to { opacity: 1; transform: translateX(0); }
        }
        @keyframes crossFlash {
          0% { opacity: 1; filter: blur(0); }
          30% { opacity: 0.3; filter: blur(4px); transform: scaleY(0.8); }
          60% { opacity: 0.6; filter: blur(2px); transform: scaleY(1.05); }
          100% { opacity: 1; filter: blur(0); transform: scaleY(1); }
        }
        @keyframes throughSlide {
          from { opacity: 0; transform: translateX(0); }
          to { opacity: 1; transform: translateX(0); }
        }
        @keyframes membranePulse {
          0%, 100% { box-shadow: 0 0 8px 0 rgba(var(--warm-gold-rgb),0.1); }
          50% { box-shadow: 0 0 24px 4px rgba(var(--warm-gold-rgb),0.3); }
        }
        @keyframes membraneFlash {
          0% { background: rgba(var(--warm-gold-rgb),0.15); }
          100% { background: rgba(var(--warm-gold-rgb),0.06); }
        }
        @keyframes shieldIn {
          0% { opacity:0; transform:scale(0.6); }
          60% { opacity:1; transform:scale(1.08); }
          100% { opacity:1; transform:scale(1); }
        }
        @keyframes cursorBlink {
          0%, 100% { opacity: 1; }
          50% { opacity: 0; }
        }
      `}</style>

      <div style={{
    background: "linear-gradient(135deg, var(--gradient-dark-start) 0%, var(--gradient-dark-mid) 35%, var(--gradient-dark-end) 70%, var(--bronze) 100%)",
    borderRadius: 14,
    padding: "28px 28px 24px",
    fontFamily: "'Inter', system-ui, sans-serif",
    position: "relative",
    overflow: "hidden",
    maxWidth: 760,
    margin: "0 auto",
    height: 420
  }}>
        {}
        <div style={{
    position: "absolute",
    top: "-30%",
    right: "-10%",
    width: "60%",
    height: "160%",
    background: "radial-gradient(ellipse at center, rgba(var(--warm-gold-rgb),0.12) 0%, transparent 70%)",
    pointerEvents: "none"
  }} />

        {}
        <div style={{
    display: "flex",
    alignItems: "center",
    gap: 10,
    marginBottom: 16,
    position: "relative",
    zIndex: 1
  }}>
          <ShieldIcon done={allDone} active={membraneActive} />
          <span style={{
    fontFamily: "'Inter', sans-serif",
    fontSize: 14,
    fontWeight: 600,
    color: C.sand100,
    letterSpacing: "-0.01em"
  }}>Claude Code Gateway</span>
          <span style={{
    fontFamily: "'Inter', sans-serif",
    fontSize: 10,
    fontWeight: 600,
    textTransform: "uppercase",
    letterSpacing: "0.06em",
    color: allDone ? C.warmGold : "rgba(var(--sand-100-rgb),0.18)",
    background: allDone ? "rgba(var(--warm-gold-rgb),0.10)" : "rgba(var(--sand-100-rgb),0.04)",
    padding: "2px 8px",
    borderRadius: 99,
    transition: "all 0.3s ease"
  }}>{allDone ? "Protected" : membraneActive ? "Active" : "Ready"}</span>
        </div>

        {}
        <div style={{
    background: "rgba(var(--sand-100-rgb),0.03)",
    border: "1px solid rgba(var(--sand-100-rgb),0.06)",
    borderRadius: 8,
    padding: "8px 12px",
    marginBottom: 14,
    position: "relative",
    zIndex: 1,
    display: "flex",
    alignItems: "center",
    gap: 8,
    minHeight: 34
  }}>
          <span style={{
    fontFamily: "'JetBrains Mono', monospace",
    fontSize: 11,
    fontWeight: 500,
    color: C.warmGold,
    opacity: 0.7,
    flexShrink: 0
  }}>claude $</span>
          <span style={{
    fontFamily: "'JetBrains Mono', monospace",
    fontSize: 11,
    color: "rgba(var(--sand-100-rgb),0.75)",
    whiteSpace: "nowrap",
    overflow: "hidden"
  }}>
            {QUERY.slice(0, typedLen)}
            {!typingDone && <span style={{
    color: C.warmGold,
    fontWeight: 600,
    animation: "cursorBlink 0.6s step-end infinite"
  }}>|</span>}
          </span>
        </div>

        {}
        <div style={{
    display: "grid",
    gridTemplateColumns: "1fr 48px 1fr",
    gap: 0,
    position: "relative",
    zIndex: 1,
    minHeight: 220
  }}>
          {}
          <div style={{
    position: "relative"
  }}>
            <div style={{
    fontFamily: "'JetBrains Mono', monospace",
    fontSize: 9,
    fontWeight: 500,
    color: "rgba(var(--sand-100-rgb),0.20)",
    textTransform: "uppercase",
    letterSpacing: "0.1em",
    marginBottom: 10,
    paddingLeft: 2
  }}>Claude Code query</div>

            <div style={{
    background: "rgba(var(--sand-100-rgb),0.03)",
    border: "1px solid rgba(var(--sand-100-rgb),0.06)",
    borderRadius: 8,
    overflow: "hidden"
  }}>
              {ROWS.map((row, i) => {
    const phase = rowPhases[i];
    const show = phase === "approaching" || phase === "crossing";
    return <div key={i} style={{
      padding: "7px 12px",
      borderBottom: i < ROWS.length - 1 ? "1px solid rgba(var(--sand-100-rgb),0.04)" : "none",
      opacity: show ? 1 : phase === "through" ? 0.15 : 0,
      transform: show ? "translateX(0)" : "translateX(-10px)",
      transition: phase === "through" ? "opacity 0.4s ease" : "opacity 0.3s ease, transform 0.3s ease",
      animation: phase === "crossing" ? "crossFlash 0.6s ease-out" : undefined,
      minHeight: 34,
      display: "flex",
      flexDirection: "column",
      justifyContent: "center",
      gap: 1
    }}>
                    <div style={{
      fontFamily: "'JetBrains Mono', monospace",
      fontSize: 11,
      color: "rgba(var(--sand-100-rgb),0.50)",
      whiteSpace: "nowrap",
      overflow: "hidden",
      textOverflow: "ellipsis"
    }}>{row.raw[0]} , {row.raw[1]}</div>
                    <div style={{
      fontFamily: "'JetBrains Mono', monospace",
      fontSize: 10,
      color: "rgba(var(--sand-100-rgb),0.30)",
      whiteSpace: "nowrap"
    }}>{row.raw[2]} , {row.raw[3]}</div>
                  </div>;
  })}
            </div>
          </div>

          {}
          <div style={{
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    position: "relative"
  }}>
            <div style={{
    width: 3,
    height: "calc(100% - 20px)",
    marginTop: 20,
    background: membraneActive ? `linear-gradient(180deg, transparent 0%, ${C.warmGold} 20%, ${C.warmGold} 80%, transparent 100%)` : "rgba(var(--sand-100-rgb),0.08)",
    borderRadius: 2,
    position: "relative",
    transition: "background 0.6s ease",
    animation: membraneGlow ? "membranePulse 0.4s ease-out" : undefined,
    boxShadow: membraneActive ? "0 0 12px 2px rgba(var(--warm-gold-rgb),0.15)" : "none"
  }}>
              {}
              <div style={{
    position: "absolute",
    top: "50%",
    left: "50%",
    transform: "translate(-50%, -50%)",
    width: 28,
    height: 28,
    borderRadius: "50%",
    background: "rgba(var(--espresso-rgb),0.95)",
    border: `1.5px solid ${membraneActive ? C.warmGold : "rgba(var(--sand-100-rgb),0.12)"}`,
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    transition: "border-color 0.4s ease",
    zIndex: 5
  }}>
                <svg width="14" height="14" viewBox="0 0 20 20" fill="none">
                  <path d="M10 2L3 5.5V10C3 14.5 6 17.5 10 19C14 17.5 17 14.5 17 10V5.5L10 2Z" fill={membraneActive ? "rgba(var(--warm-gold-rgb),0.2)" : "rgba(var(--sand-100-rgb),0.05)"} stroke={membraneActive ? C.warmGold : "rgba(var(--sand-100-rgb),0.15)"} strokeWidth="1.2" />
                </svg>
              </div>
            </div>
          </div>

          {}
          <div style={{
    position: "relative"
  }}>
            <div style={{
    fontFamily: "'JetBrains Mono', monospace",
    fontSize: 9,
    fontWeight: 500,
    color: "rgba(var(--sand-100-rgb),0.20)",
    textTransform: "uppercase",
    letterSpacing: "0.1em",
    marginBottom: 10,
    paddingLeft: 2
  }}>Delivered to Claude</div>

            <div style={{
    background: "rgba(var(--sand-100-rgb),0.03)",
    border: "1px solid rgba(var(--sand-100-rgb),0.06)",
    borderRadius: 8,
    overflow: "hidden"
  }}>
              {ROWS.map((row, i) => {
    const phase = rowPhases[i];
    const show = phase === "through";
    return <div key={i} style={{
      padding: "7px 12px",
      borderBottom: i < ROWS.length - 1 ? "1px solid rgba(var(--sand-100-rgb),0.04)" : "none",
      opacity: show ? 1 : 0,
      transform: show ? "translateX(0)" : "translateX(10px)",
      transition: "opacity 0.4s ease, transform 0.4s ease",
      minHeight: 34,
      display: "flex",
      flexDirection: "column",
      justifyContent: "center",
      gap: 1
    }}>
                    <div style={{
      display: "flex",
      gap: 8
    }}>
                      {row.masked.slice(0, 2).map((m, j) => <span key={j} style={{
      fontFamily: "'JetBrains Mono', monospace",
      fontSize: 11,
      fontWeight: 600,
      color: C.warmGold
    }}>{m}</span>)}
                    </div>
                    <div style={{
      display: "flex",
      gap: 8
    }}>
                      {row.masked.slice(2).map((m, j) => <span key={j} style={{
      fontFamily: "'JetBrains Mono', monospace",
      fontSize: 10,
      fontWeight: 600,
      color: C.warmGold,
      opacity: 0.7
    }}>{m}</span>)}
                    </div>
                  </div>;
  })}
            </div>
          </div>
        </div>

        {}
        <div style={{
    display: "flex",
    gap: 8,
    marginTop: 14,
    position: "relative",
    zIndex: 1,
    opacity: allDone ? 1 : 0,
    transform: allDone ? "translateY(0)" : "translateY(4px)",
    transition: "opacity 0.5s ease, transform 0.5s ease",
    height: 20
  }}>
          {["Zero-config proxy", "GDPR", "PCI-DSS", "Real-time"].map(tag => <span key={tag} style={{
    fontFamily: "'Inter', sans-serif",
    fontSize: 10,
    fontWeight: 600,
    textTransform: "uppercase",
    color: "rgba(var(--sand-100-rgb),0.25)",
    background: "rgba(var(--sand-100-rgb),0.04)",
    padding: "2px 8px",
    borderRadius: 99,
    letterSpacing: "0.04em"
  }}>{tag}</span>)}
        </div>
      </div>
    </>;
};

<div style={{height: 400, overflow: 'hidden', borderRadius: 14}}>
  <DataMaskingDemo />
</div>

## What You'll Accomplish

Live Data Masking automatically detects and redacts sensitive data in your query results. Unlike traditional DLP solutions that require complex rule configuration, Hoop's data masking works out of the box:

* Automatically detect PII (names, emails, phone numbers, SSNs)
* Mask credit card numbers and financial data
* Redact passwords, API keys, and secrets
* Protect health information (HIPAA compliance)
* No regex patterns to write or maintain

***

## How It Works

Live Data Masking operates at the protocol layer. When someone queries a resource role that has masking enabled, hoop.dev intercepts the response as it streams back, sends the content to your configured DLP provider for inspection, and redacts any sensitive values before the results reach the user. This happens **in memory and in real time** — the original data is never stored or exposed.

<Steps>
  <Step title="Query Executed">
    A user runs a query or command through hoop.dev
  </Step>

  <Step title="Response Intercepted">
    hoop.dev intercepts the result stream at the protocol layer
  </Step>

  <Step title="Data Inspected">
    The configured DLP provider scans the content for sensitive data
  </Step>

  <Step title="Results Masked">
    Sensitive values are redacted in memory; the user sees masked results
  </Step>
</Steps>

### Before and After

**Original query result:**

```
| name         | email              | ssn         | phone        |
|--------------|--------------------| ------------|--------------|
| John Smith   | john@example.com   | 123-45-6789 | 555-123-4567 |
| Jane Doe     | jane@company.org   | 987-65-4321 | 555-987-6543 |
```

**With Live Data Masking enabled:**

```
| name         | email              | ssn         | phone        |
|--------------|--------------------| ------------|--------------|
| [REDACTED]   | [REDACTED]         | [REDACTED]  | [REDACTED]   |
| [REDACTED]   | [REDACTED]         | [REDACTED]  | [REDACTED]   |
```

***

## Supported Data Types

Live Data Masking relies on your DLP provider's detection engine to recognize a wide range of sensitive data out of the box, grouped into categories such as:

* **Personal information** — names, email addresses, phone numbers, physical addresses
* **Government & national IDs** — SSNs, passport numbers, driver's licenses
* **Financial data** — credit card numbers, bank accounts, IBANs
* **Credentials & secrets** — API keys, passwords, access keys
* **Health information** — medical record numbers, health plan IDs

A default set of the most common fields is enabled automatically, and you can add or remove fields per resource role. For the complete, provider-specific catalog, see the [full list of supported fields](/setup/configuration/live-data-masking/fields).

***

## Use Cases

### 1. Developer Access to Production

Developers need to debug production issues but shouldn't see customer PII:

* Enable Live Data Masking on production resource roles
* Developers can run diagnostic queries
* Customer data is automatically protected

### 2. Analytics Without Exposure

Data analysts need aggregate insights but not individual records:

* Masking protects individual-level PII
* Aggregations (COUNT, SUM, AVG) work normally
* Compliance requirements are met

### 3. Support Team Access

Support teams need to look up customer records:

* Enable masking on support-facing resource roles
* They can verify account status without seeing SSNs
* Audit trail shows who accessed what

### 4. Third-Party Contractor Access

External contractors need database access:

* Create a resource role with masking enabled
* Grant access to contractors
* Sensitive data is never exposed

***

## Compliance

Live Data Masking helps meet requirements for:

* **GDPR** - Protect EU citizen personal data
* **HIPAA** - Mask protected health information
* **PCI DSS** - Redact credit card numbers
* **SOC 2** - Demonstrate data protection controls
* **CCPA** - Protect California consumer data

<Info>
  Live Data Masking is one layer of a defense-in-depth strategy. Combine with [Access Control](/learn/features/access-control) and [Guardrails](/learn/features/guardrails) for comprehensive protection.
</Info>

***

<Note>
  Ready to turn it on? The [Live Data Masking configuration guide](/setup/configuration/live-data-masking/get-started) walks through setting up a DLP provider, choosing a redact mode, and enabling masking on your resource roles.
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Configuration Guide" icon="gear" href="/setup/configuration/live-data-masking/get-started">
    Set up Microsoft Presidio or GCP DLP
  </Card>

  <Card title="Supported Fields" icon="list" href="/setup/configuration/live-data-masking/fields">
    See all detectable data types
  </Card>

  <Card title="Guardrails" icon="shield" href="/learn/features/guardrails">
    Block queries before they execute
  </Card>

  <Card title="Access Control" icon="lock" href="/learn/features/access-control">
    Control who can access resource roles
  </Card>
</CardGroup>
