Skip to main content

DLP Rules Editor

Supported modes: Hosted Available in: Solo Teams -- View pricing

DLP rules are the core admin lever for blocking sensitive content from leaving developer machines, the LLM gateway, and (soon) the browser extension. This guide walks through the dashboard workflow end-to-end. For the API contract see the /api/orgs/{id}/dlp/rules family of endpoints documented in the backend OpenAPI spec.

What a DLP rule is

A DLP rule has six fields:

FieldTypeNotes
namestringHuman-readable label, shown in audit logs. Required.
patternstring (RE2)Compiled at insert time. PCRE features rejected. Required.
categoryenumpii / secret / ip / financial / compliance / custom. Required.
actionenumdetect / mask / block. Required.
scopesstring[]Any combination of sdk, gateway, browser_ext, scout. Required.
enabledboolDefaults to true. A disabled rule is invisible to enforcement.

A rule lives in a lifecycle state machine:

   draft -> tested -> impact_previewed -> [pending_approval] -> live
|
+-> disabled

A new rule starts in draft. You can move it to live directly, or route through the optional approval queue for high-impact rules. Only live rules are enforced by the SDK / gateway / browser extension.

Writing your first rule

Open Governance → DLP Rules in the dashboard. Click New rule.

Pattern syntax

Patterns must be RE2-compatible. Most regular regexes work, but the following PCRE features are NOT supported:

  • Lookaheads: (?=foo), (?!foo)
  • Lookbehinds: (?<=foo), (?<!foo)
  • Backreferences: \1, \2
  • Possessive quantifiers: *+, ++

If you paste a PCRE pattern, the test affordance immediately shows the compile error inline. Fix it before saving.

Common patterns

US Social Security Number:

\b\d{3}-\d{2}-\d{4}\b

Email address (loose):

[\w._%+-]+@[\w.-]+\.[A-Za-z]{2,}

AWS access key:

AKIA[0-9A-Z]{16}

Credit card (Visa):

\b4\d{3}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b

Internal project code names:

\b(Manhattan|Apollo|Voyager|Falcon)\b

Test before saving

The form has a Test pattern affordance. Type a sample input that contains the thing you want to catch:

Customer record:
name: Alice Johnson
ssn: 123-45-6789
cc: 4111-1111-1111-1111

Click Test pattern. The backend compiles your regex and reports:

  • compiles: true | false — if false, the error message points to the exact column.
  • matched: true | false — whether the sample triggered.
  • matches: [...] — up to 10 matched substrings, in order.

Iterate until the matches look right. Remember:

  • Greedy is the default. Use ? after a quantifier (.*?) for non-greedy.
  • \b is your friend. Word boundaries prevent 4111 from matching inside 4111111111 (which would be a 16-digit card).
  • Anchor when you can. ^ and $ are cheap and prevent false positives.

Action

Pick one:

  • detect — Log the match in audit. Don't change anything. Use this when you want visibility but the rule is too noisy or too new to enforce.
  • mask — Replace the matched span with [REDACTED:CATEGORY:LEN] before forwarding to the LLM. The user's original prompt is NOT changed in their UI; only the network payload is masked.
  • block — Reject the entire request with a 403 (gateway) or a PolicyDeniedError (SDK). The reason includes the rule name so the user knows what triggered.

Scopes

A rule with scopes: ["sdk"] only fires inside the on-machine SDK guard call. A rule with scopes: ["sdk", "gateway"] fires both on-machine AND in front of the LLM provider.

For maximum coverage, set ["sdk", "gateway", "browser_ext"]. The scout scope is reserved for the discovery agent (Phase 11).

Publishing

Click Publish on a draft rule. The rule transitions to live and the org's policy_version counter bumps by 1. Within 5 minutes (the next SDK policy poll) every enrolled machine sees the new rule.

The gateway picks it up immediately because the gateway has its own custom rules cache that hot-reloads from /etc/controlzero/custom_dlp_rules.json on file change. See Phase 9 gateway integration for the deploy mechanism.

Two-person approval (high-impact rules)

If a rule could break production (e.g., a block action with the gateway scope and a permissive pattern), you may want a second admin to sign off. The flow:

  1. Admin A creates the rule, tests it, then clicks Request approval instead of Publish.
  2. The rule transitions to pending_approval state. Admin A's user ID is recorded as pending_approval_by.
  3. Admin B (a different user) opens the rule and clicks Approve. The rule transitions to live and policy_version bumps.

If admin A tries to approve their own request, the API returns 403 SAME_ACTOR. The two-person check is enforced server-side so a malicious actor with a stolen session can't bypass it from the dashboard.

If 48 hours pass without approval, the rule auto-rejects and falls back to draft (background expiry job, runs nightly).

Editing a live rule

Live rules are immutable by direct edit. To change a live rule:

  1. Click Disable to take it out of enforcement.
  2. Click Edit (the rule is now back in draft).
  3. Change the pattern / action / scopes.
  4. Click Test pattern to verify.
  5. Click Publish to put it back into enforcement.

This dance forces you to think about the change rather than silently mutating something developers depend on.

The edit reason field is required on every patch and surfaces in the version history.

Version history

GET /api/orgs/{id}/dlp/rules/{rid}/versions returns every prior state of a rule (newest first). The dashboard shows this as a sidebar drawer when you click a rule's "Versions" button.

Each version row has:

  • version (monotonic per rule)
  • pattern, category, action, scopes at that point in time
  • edit_reason and edited_by admin
  • edited_at timestamp

Versions are immutable — they are append-only snapshots written by the storage layer's UpdateDLPRule transaction. You cannot delete a version, even by deleting the rule.

Rollback

Find the version number you want to restore (from the sidebar drawer or the API). Click Rollback to v3 (or call POST /dlp/rules/{rid}/rollback with target_version: 3).

The rule's pattern, category, action, and scopes are restored to the v3 values. Two important behaviors:

  1. Rollback creates a NEW version. History is never deleted. The new version's edit_reason is auto-filled with "ROLLBACK to v3: {your reason}".
  2. The result lands in draft, not live. Even if you're rolling back from a regression, you have to explicitly re-publish. This avoids accidentally pushing a half-tested rollback into production.

Soft delete

Click Delete on a rule to soft-delete it. The rule stays in the dlp_rules table with deleted_at set, but is filtered out of every list query and the SDK / gateway never sees it.

Soft-deleted rules can still be queried via the version history endpoint for audit purposes.

If the rule was live at delete time, deletion bumps the org's policy_version so the next SDK poll picks up the removal.

Rule lifecycle audit

Every state transition is logged with:

  • The actor (admin user ID)
  • The from-state and to-state
  • A timestamp
  • For approve/reject: the requester AND the resolver

You can query this log via the version history endpoint, or pull the audit feed from the analytical store for org-wide DLP activity.

Rate limiting

The DLP rules CRUD endpoints are rate-limited to 100 req/min per org. The DLP test endpoint (POST /dlp/rules/test) is rate-limited to 30 req/min because it's the most expensive call (regex compile + sample matching). The cooperative MCP guard call adds another 30 req/min on top.

If you hit a rate limit, the response is 429 Too Many Requests with a Retry-After header. Back off and retry.