MAQPNADocs

Protect data with DLP profiles

Configure MAQPNA's data loss prevention (DLP) profiles to deny or redact card numbers, IBANs, national IDs and secrets in tool arguments, tool results, prompts and completions.

flowchart LR
  A[Tool call or model call] --> B[Gateway picks a DLP profile<br/>server, model route, namespace, default]
  B --> C[Scan request<br/>arguments or prompt]
  C -- deny --> D[Call denied<br/>reason dlp:detector]
  C -- redact --> E[Replace match with<br/>REDACTED:detector]
  C -- clean --> F[Policy, approval, budget]
  E --> F
  F --> G[MCP server or model]
  G --> H[Scan response<br/>result or completion]
  H --> I[Agent]
  D --> J[Audit ledger:<br/>detector ids and counts only]
  H --> J

Goal#

Stop sensitive data from leaving through an agent's tool and model calls. You will define two profiles, one that denies a request carrying an IBAN and one that redacts a card number and an email address, bind them to traffic, and read what the audit ledger records.

Data loss prevention (DLP) in MAQPNA runs inside the gateway. Its detectors are deterministic and work offline: matches are validated with checksums, structure or context keywords, so false positives stay low and no data goes to an outside classifier.

Prerequisites#

  • A local MAQPNA (maqpna dev up), or a cluster with MAQPNA installed.
  • jq, to read the configuration and the audit ledger.

What DLP scans#

Direction Scanned
Request Tool-call arguments (string values, object keys, long numbers) and model request bodies, before policy is evaluated and before anything is forwarded
Response Every MCP response (result, error and params, JSON or each server-sent event), exec output and files, and model completions, including streamed deltas

Detectors#

A profile with no detectors list uses the default set: every detector marked "default" below.

Detector Class Default Validation
iban financial yes Country length and ISO 7064 mod-97
pan financial yes 13–19 digits, card grouping, issuer prefix and Luhn
email personal data no Dot-atom local part, dotted domain, alphabetic TLD
phone_e164 personal data no + and 8–15 digits (E.164)
de_tax_id personal data yes 11 digits, ISO 7064 MOD 11,10 and repeat rule
fr_nir personal data yes 15 characters, 97 − (n mod 97) key
nl_bsn personal data yes 9 digits, 11-proof, context keyword required
it_codice_fiscale personal data yes 16-character structure and check character
es_dni personal data yes DNI/NIE, mod-23 control letter
uk_nino personal data yes HMRC prefix and suffix rules
us_ssn personal data yes SSA area, group and serial rules
ipv4_private infrastructure no RFC 1918 dotted quad
aws_access_key secret yes AKIA/ASIA/ABIA/ACCA and 16 base32 characters
gcp_sa_key secret yes "private_key_id" with 40 hex characters
gcp_api_key secret yes AIza and 35 characters
github_token secret yes gh[pousr]_ and 36 characters, github_pat_ and 82 characters
slack_token secret yes xox[abposre]-, xapp-, Slack webhook URLs
openai_api_key secret yes sk-proj-, sk-svcacct-, sk-admin- or the legacy marker
anthropic_api_key secret yes sk-ant- and 80 or more characters
jwt secret yes Three base64url segments, JSON header with alg
pem_private_key secret yes A -----BEGIN ... PRIVATE KEY----- block
high_entropy_secret secret no (opt-in) Shannon entropy over tokens of 32 or more characters

Add your own with custom: {id, regex, secret}. The id must match [a-z0-9_]{1,40} and must not collide with a built-in detector.

Profile settings#

Key Values Default
detectors Detector IDs The default set
custom [{id, regex, secret}] —
requestAction off, audit, redact, deny redact
responseAction off, audit, redact, deny redact
onSecret An action for secret-class detectors in both directions, for example deny while personal data is only redacted —
maxScanBytes Bytes scanned per message 1 MiB
onOversize audit or deny for a message over maxScanBytes deny where the direction redacts or denies, else audit
streamCarryBytes Hold-back window for matches split across streamed chunks (-1 disables) 256
entropyMinLength, entropyThreshold Tuning for high_entropy_secret 32, 4.3

audit records the hit and forwards the data unchanged. redact replaces each match with [REDACTED:<detector>] and keeps the JSON structure. deny blocks the message.

Steps#

1. Define profiles and bind them#

The gateway reads two configuration keys: dlpProfiles (named profiles) and dlp (which profile applies to which traffic).

{
  "dlpProfiles": {
    "payments": {
      "detectors": ["iban", "pan", "github_token", "aws_access_key"],
      "requestAction": "deny",
      "responseAction": "redact"
    },
    "support": {
      "detectors": ["email", "iban", "pan"],
      "requestAction": "redact",
      "responseAction": "redact",
      "onSecret": "deny"
    }
  },
  "dlp": {
    "defaultProfile": "payments",
    "namespaces": {"support": "support"}
  }
}

Bindings, from most to least specific:

  1. For an MCP server: dlp.servers[<server>]. For a model route: the route's own dlpProfile (Agent.spec.model.dlpProfile in a cluster), then dlp.models[<route>].
  2. dlp.namespaces[<namespace>].
  3. dlp.defaultProfile.

The profile name none turns DLP off for that binding. Profile names match [a-z0-9][a-z0-9-]{0,62}.

2a. In a cluster: set Helm values#

The chart maps the same keys under dlp:

dlp:
  profiles:
    eu-strict:
      detectors: [iban, pan, email, de_tax_id, fr_nir, nl_bsn, pem_private_key, github_token, jwt]
      requestAction: redact
      responseAction: redact
      onSecret: deny
  defaultProfile: eu-strict
  namespaces: {}
  servers: {}
  models: {}

Per MCP server, set spec.dlpProfile on the MCPServer; per agent model route, set spec.model.dlpProfile on the Agent. Apply the values with maqpna upgrade -f values.yaml (see Upgrade and rollback).

2b. On a local MAQPNA: edit the gateway configuration#

maqpna dev up has no DLP flag. To try DLP locally, add the two keys to .maqpna/gateway.json, check the file, and restart the gateway with the same configuration and admin token:

maqpna-gateway -check-config -config .maqpna/gateway.json
Config OK: .maqpna/gateway.json
pkill -f "maqpna-gateway -config $PWD/.maqpna"
(set -a; . .maqpna/dev.env; nohup maqpna-gateway -config "$PWD/.maqpna/gateway.json" \
  > .maqpna/logs/maqpna-gateway.log 2>&1 &)

3. Send data the profile denies#

maqpna dev run -- maqpna call --server echo --tool echo \
  --arg text="Pay invoice 4411 to DE89370400440532013000"

Expected output (exit status 3):

maqpna dev: session dev-aa2e3c7f (agent coder, namespace dev, on behalf of you@localhost)
error -32001: denied by DLP profile payments: dlp:iban
data: {"domain":"maqpna.com","policy":"dlp","reason":"dlp:iban","rule":"payments"}
maqpna dev: dev-aa2e3c7f: 1 deny, cost $0.0000 (maqpna dev timeline --last)

The reason is dlp:<detector>; the rule is the profile name. A denied model call answers HTTP 403 with the error type maqpna_dlp_blocked.

4. Send data the profile redacts#

Sessions in namespace support use the support profile, which redacts:

maqpna dev run --namespace support -- maqpna call --server echo --tool echo \
  --arg text="Refund card 4111 1111 1111 1111 for alice@example.com"
{
  "content": [
    {
      "text": "Refund card [REDACTED:pan] for [REDACTED:email]",
      "type": "text"
    }
  ],
  "isError": false
}

The MCP server received the redacted text, so its echo shows the markers.

DLP denies an IBAN and redacts a card number and an email.cast

Verify#

The audit ledger records detector IDs and counts, never the matched values:

tail -1 .maqpna/audit.jsonl | jq '{decision, ext}'
{
  "decision": "allow",
  "ext": {
    "argsRedactedSha256": "e6929dc2c9aaa2c5e02c4cf28f14414f8312cb2dd2eb88664a1b253ac15c6e9f",
    "dlp": "email:1,pan:1",
    "dlpAction": "redact",
    "dlpProfile": "support"
  }
}

argsSha256 stays the hash of the original arguments; argsRedactedSha256 is the hash of what was forwarded. A denial shows in the timeline with its reason:

maqpna dev timeline --session dev-aa2e3c7f
TIME      KIND       SERVER/TOOL  DECISION  DETAIL
23:48:38  tool_call  echo/echo    deny      dlp:iban

Troubleshooting#

Symptom Cause Fix
The gateway does not start: dlpProfiles.<name>: ... An unknown detector, an invalid action or a bad custom regex Run maqpna-gateway -check-config -config FILE and fix the named key
unknown DLP profile "x" A binding names a profile that is not defined Define it under dlpProfiles (Helm dlp.profiles)
A namespace binding has no effect A server binding wins over a namespace binding Check dlp.servers, or set the server's binding to the profile you want
Large requests are denied The message is over maxScanBytes and the direction enforces, so onOversize is deny Raise maxScanBytes, or set onOversize: audit knowingly
A national ID is not found The detector is not in the profile, or it needs a context keyword (nl_bsn, unformatted us_ssn) List it in detectors; include the keyword in test data
Locally, the change has no effect The old gateway is still running pgrep -fl maqpna-gateway, stop the old one, start it again

Next steps#