> ## Documentation Index
> Fetch the complete documentation index at: https://hoopdev-feat-new-runbook-parameters.mintlify.site/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=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;0,9..40,800&family=JetBrains+Mono:wght@400;500&family=Sora:wght@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: "'DM Sans', 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: "'Sora', sans-serif",
    fontSize: 14,
    fontWeight: 600,
    color: C.sand100,
    letterSpacing: "-0.01em"
  }}>Claude Code Gateway</span>
          <span style={{
    fontFamily: "'DM Sans', 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: "'DM Sans', 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

<Steps>
  <Step title="Query Executed">
    User runs a query through Hoop
  </Step>

  <Step title="Results Analyzed">
    AI scans the query results for sensitive data patterns
  </Step>

  <Step title="Data Masked">
    Sensitive values are replaced with redacted placeholders
  </Step>

  <Step title="Results Returned">
    User sees masked results; original data never exposed
  </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]   |
```

***

## Quick Start

## Prerequisites

To get the most out of this guide, you will need to:

* Either [create an account in our managed instance](https://use.hoop.dev) or [deploy your own hoop.dev instance](/setup/deployment/overview)
* You must be your account administrator to perform the following actions

- A DLP provider configured (Microsoft Presidio or GCP DLP)

### Step 1: Set Up a DLP Provider

Choose and deploy one of the supported providers:

<CardGroup cols={2}>
  <Card title="Microsoft Presidio" icon="microsoft" href="https://microsoft.github.io/presidio/">
    Open-source, self-hosted PII detection
  </Card>

  <Card title="Google Cloud DLP" icon="google" href="https://cloud.google.com/dlp">
    Managed service with advanced detection
  </Card>
</CardGroup>

See [Live Data Masking Configuration](/setup/configuration/live-data-masking/get-started) for detailed setup instructions.

### Step 2: Configure the Gateway

Set the required environment variables:

**For Microsoft Presidio:**

```bash theme={null}
DLP_PROVIDER=mspresidio
DLP_MODE=best-effort
MSPRESIDIO_ANALYZER_URL=http://presidio-analyzer:5001
MSPRESIDIO_ANONYMIZER_URL=http://presidio-anonymizer:5002
```

**For Google Cloud DLP:**

```bash theme={null}
DLP_PROVIDER=gcp
DLP_MODE=best-effort
GCP_PROJECT_ID=your-project-id
```

### Step 3: Enable on a Connection

1. Go to **Connections** in the Web App
2. Select a connection and click **Configure**
3. Enable **Live Data Masking**
4. Click **Save**

### Step 4: Test It

Run a query that returns sensitive data:

```sql theme={null}
SELECT name, email, phone FROM customers LIMIT 5;
```

You should see masked values in the results.

***

## Supported Data Types

Live Data Masking detects these sensitive data types by default:

### Personal Information

| Type             | Example                                     | Masked As  |
| ---------------- | ------------------------------------------- | ---------- |
| Person Name      | John Smith                                  | \[PERSON]  |
| Email Address    | [john@example.com](mailto:john@example.com) | \[EMAIL]   |
| Phone Number     | 555-123-4567                                | \[PHONE]   |
| Physical Address | 123 Main St                                 | \[ADDRESS] |

### Government IDs

| Type             | Example     | Masked As   |
| ---------------- | ----------- | ----------- |
| SSN (US)         | 123-45-6789 | \[SSN]      |
| Passport Number  | AB1234567   | \[PASSPORT] |
| Driver's License | D1234567    | \[LICENSE]  |

### Financial Data

| Type         | Example                | Masked As        |
| ------------ | ---------------------- | ---------------- |
| Credit Card  | 4111-1111-1111-1111    | \[CREDIT\_CARD]  |
| Bank Account | 123456789012           | \[BANK\_ACCOUNT] |
| IBAN         | GB82WEST12345698765432 | \[IBAN]          |

### Credentials

| Type     | Example             | Masked As   |
| -------- | ------------------- | ----------- |
| API Key  | sk\_live\_abc123... | \[API\_KEY] |
| Password | password123         | \[PASSWORD] |
| AWS Key  | AKIA...             | \[AWS\_KEY] |

### Health Information

| Type           | Example   | Masked As          |
| -------------- | --------- | ------------------ |
| Medical Record | MRN-12345 | \[MEDICAL\_RECORD] |
| Health Plan ID | HPL-98765 | \[HEALTH\_ID]      |

***

## Configuration Options

### DLP Mode

| Mode          | Behavior                                     |
| ------------- | -------------------------------------------- |
| `best-effort` | Mask detected fields, continue if some fail  |
| `strict`      | Block the entire result if any masking fails |

**Recommendation:** Start with `best-effort` to avoid blocking legitimate queries.

### Custom Fields

Add or remove fields from detection. See [Supported Fields](/setup/configuration/live-data-masking/fields) for the complete list.

### Per-Connection Settings

Enable or disable masking on individual connections:

* Enable on production databases with real customer data
* Disable on development databases with synthetic data

***

## 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 connections
* 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 connections
* 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 connection with masking enabled
* Grant access to contractors
* Sensitive data is never exposed

***

## Troubleshooting

### Data Not Being Masked

**Check:**

1. Live Data Masking is enabled on the connection
2. DLP provider is running and accessible
3. Gateway environment variables are set correctly
4. The data type is in the [supported fields list](/setup/configuration/live-data-masking/fields)

**Test the DLP provider directly:**

```bash theme={null}
curl -X POST http://presidio-analyzer:5001/analyze \
  -H "Content-Type: application/json" \
  -d '{"text": "John Smith, SSN 123-45-6789", "language": "en"}'
```

### Too Much Data Being Masked

If legitimate data is being masked incorrectly:

1. Check which field type is triggering
2. Disable that specific field type in configuration
3. Or use [Guardrails](/learn/features/guardrails) for more precise control

### Performance Impact

Live Data Masking adds latency to query results:

| Result Size   | Typical Latency |
| ------------- | --------------- |
| \< 100 rows   | 50-100ms        |
| 100-1000 rows | 100-500ms       |
| > 1000 rows   | 500ms+          |

**To reduce latency:**

* Use `LIMIT` clauses in queries
* Select only needed columns (avoid `SELECT *`)
* Consider disabling masking for high-volume analytics

***

## 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

<Note>
  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.
</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 connections
  </Card>
</CardGroup>
