zuplo 6.70.70 → 6.70.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/docs/ai-gateway/getting-started.mdx +2 -1
  2. package/docs/ai-gateway/integrations/ai-sdk.mdx +17 -0
  3. package/docs/ai-gateway/introduction.mdx +5 -5
  4. package/docs/ai-gateway/providers.mdx +2 -0
  5. package/docs/analytics/access-and-entitlements.md +71 -0
  6. package/docs/analytics/overview.md +63 -0
  7. package/docs/analytics/reference/metrics-glossary.md +105 -0
  8. package/docs/analytics/reference/url-parameters.md +66 -0
  9. package/docs/analytics/shared-controls.md +121 -0
  10. package/docs/analytics/tabs/agents.md +88 -0
  11. package/docs/analytics/tabs/consumers.md +73 -0
  12. package/docs/analytics/tabs/graphql.md +77 -0
  13. package/docs/analytics/tabs/mcp.md +80 -0
  14. package/docs/analytics/tabs/origins.md +82 -0
  15. package/docs/analytics/tabs/requests.md +96 -0
  16. package/docs/articles/ci-cd-github/basic-deployment.mdx +10 -1
  17. package/docs/articles/ci-cd-github/deploy-and-test.mdx +14 -1
  18. package/docs/articles/ci-cd-github/local-testing.mdx +3 -1
  19. package/docs/articles/ci-cd-github/pr-preview-environments.mdx +36 -4
  20. package/docs/articles/custom-ci-cd-github.mdx +11 -2
  21. package/docs/articles/monetization/api-access.mdx +184 -0
  22. package/docs/articles/monetization/meters.mdx +4 -4
  23. package/docs/articles/monetization/monetization-policy.md +4 -1
  24. package/docs/articles/monetization/private-plans.md +3 -4
  25. package/docs/articles/monetization/stripe-integration.md +9 -0
  26. package/docs/articles/monetization/subscription-lifecycle.md +12 -11
  27. package/docs/articles/monorepo-deployment.mdx +20 -2
  28. package/docs/cli/deploy.mdx +32 -0
  29. package/docs/cli/deploy.partial.mdx +32 -0
  30. package/docs/concepts/api-keys.md +2 -2
  31. package/docs/dev-portal/zudoku/components/callout.mdx +11 -18
  32. package/docs/dev-portal/zudoku/configuration/search.md +36 -0
  33. package/docs/dev-portal/zudoku/configuration/site.md +38 -0
  34. package/docs/dev-portal/zudoku/customization/colors-theme.mdx +51 -40
  35. package/docs/errors/rate-limit-exceeded.mdx +30 -3
  36. package/docs/policies/_index.md +2 -0
  37. package/docs/policies/data-loss-prevention-inbound/doc.md +116 -0
  38. package/docs/policies/data-loss-prevention-inbound/intro.md +15 -0
  39. package/docs/policies/data-loss-prevention-inbound/schema.json +220 -0
  40. package/docs/policies/data-loss-prevention-outbound/doc.md +116 -0
  41. package/docs/policies/data-loss-prevention-outbound/intro.md +18 -0
  42. package/docs/policies/data-loss-prevention-outbound/schema.json +220 -0
  43. package/docs/programmable-api/background-dispatcher.mdx +6 -8
  44. package/docs/programmable-api/zone-cache.mdx +1 -1
  45. package/docs/rate-limiting/combining-policies.mdx +293 -0
  46. package/docs/rate-limiting/dynamic-rate-limiting.mdx +240 -0
  47. package/docs/rate-limiting/getting-started.mdx +339 -0
  48. package/docs/rate-limiting/how-it-works.md +225 -0
  49. package/docs/rate-limiting/monitoring-and-troubleshooting.mdx +243 -0
  50. package/docs/{articles → rate-limiting}/per-user-rate-limits-using-db.mdx +39 -27
  51. package/package.json +4 -4
  52. package/docs/concepts/rate-limiting.md +0 -246
@@ -164,29 +164,55 @@ const config = {
164
164
  };
165
165
  ```
166
166
 
167
- Alternatively, you can copy the CSS code and paste it into your `customCss` configuration:
167
+ Alternatively, paste the copied CSS into a stylesheet and import it from your config:
168
+
169
+ ```css title=styles.css
170
+ /* Copied CSS code */
171
+ ```
168
172
 
169
173
  ```ts title=zudoku.config.ts
170
- const config = {
171
- theme: {
172
- customCss: `
173
- /* Copied CSS code */
174
- `,
175
- },
176
- };
174
+ import "./styles.css";
177
175
  ```
178
176
 
179
177
  ## Custom CSS
180
178
 
181
- For advanced styling, you can add custom CSS either as a string or structured object:
179
+ The recommended way to add custom styles is to write a `.css` file alongside your config and import
180
+ it. This gives you HMR during development, syntax highlighting, autocompletion, and lets you split
181
+ styles across files as your site grows.
182
182
 
183
- :::note
183
+ ```css title=styles.css
184
+ .custom {
185
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
186
+ }
187
+ ```
184
188
 
185
- Changes to `customCss` require restarting the development server to take effect.
189
+ ```ts title=zudoku.config.ts
190
+ import "./styles.css";
186
191
 
187
- :::
192
+ const config = {
193
+ // ...
194
+ };
195
+ ```
196
+
197
+ If TypeScript reports `Cannot find module './styles.css'`, add `zudoku/client` to your tsconfig
198
+ types so CSS side-effect imports are recognized:
199
+
200
+ ```json title=tsconfig.json
201
+ {
202
+ "compilerOptions": {
203
+ "types": ["zudoku/client"]
204
+ }
205
+ }
206
+ ```
207
+
208
+ Projects created with `create-zudoku` include this by default.
209
+
210
+ ### Inline alternatives (deprecated)
188
211
 
189
- ### CSS String
212
+ The `theme.customCss` option is deprecated and will be removed in a future release. It still accepts
213
+ a CSS string or object for backwards compatibility, but every change requires restarting the dev
214
+ server and you lose syntax highlighting, autocompletion, and HMR. Migrate to an imported `.css`
215
+ file.
190
216
 
191
217
  ```ts title=zudoku.config.ts
192
218
  const config = {
@@ -200,37 +226,22 @@ const config = {
200
226
  };
201
227
  ```
202
228
 
203
- ### CSS Object
204
-
205
- ```ts title=zudoku.config.ts
206
- const config = {
207
- theme: {
208
- customCss: {
209
- ".custom": {
210
- background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
211
- },
212
- },
213
- },
214
- };
215
- ```
216
-
217
229
  ### Enabling Code Ligatures
218
230
 
219
231
  Dev Portal disables ligatures in code blocks by default to avoid unwanted glyph joining in fonts like
220
232
  Geist Mono (e.g. `---`, `###`, `|--|`). If you're using a coding font designed around ligatures
221
- (Fira Code, JetBrains Mono, etc.), re-enable them via `customCss`:
222
-
223
- ```ts title=zudoku.config.ts
224
- const config = {
225
- theme: {
226
- customCss: `
227
- code, pre, kbd, samp, .shiki, .shiki span {
228
- font-variant-ligatures: normal;
229
- font-feature-settings: normal;
230
- }
231
- `,
232
- },
233
- };
233
+ (Fira Code, JetBrains Mono, etc.), re-enable them in your CSS file:
234
+
235
+ ```css title=styles.css
236
+ code,
237
+ pre,
238
+ kbd,
239
+ samp,
240
+ .shiki,
241
+ .shiki span {
242
+ font-variant-ligatures: normal;
243
+ font-feature-settings: normal;
244
+ }
234
245
  ```
235
246
 
236
247
  ## Default Theme
@@ -4,6 +4,29 @@ title: Rate Limit Exceeded (RATE_LIMIT_EXCEEDED)
4
4
 
5
5
  The request was rejected because the client exceeded the configured rate limit.
6
6
 
7
+ ## Response format
8
+
9
+ The 429 response uses the
10
+ [Problem Details](../programmable-api/http-problems.mdx) format:
11
+
12
+ ```json
13
+ {
14
+ "type": "https://httpproblems.com/http-status/429",
15
+ "title": "Too Many Requests",
16
+ "status": 429,
17
+ "detail": "Rate limit exceeded",
18
+ "instance": "/your-route",
19
+ "trace": {
20
+ "requestId": "4d54e4ee-c003-4d75-aba9-e09a6d707b08",
21
+ "timestamp": "2026-04-14T12:00:00.000Z",
22
+ "buildId": "ec44e831-3a02-467e-a26c-7e401e4473bf"
23
+ }
24
+ }
25
+ ```
26
+
27
+ If `headerMode` is set to `"retry-after"` (the default), the response includes a
28
+ `Retry-After` header with the number of seconds to wait before retrying.
29
+
7
30
  ## Common Causes
8
31
 
9
32
  - **Too many requests** — The client sent more requests than the rate limit
@@ -24,8 +47,12 @@ The request was rejected because the client exceeded the configured rate limit.
24
47
 
25
48
  ## For API Operators
26
49
 
27
- - Review the rate limiting policy configuration in the route settings.
50
+ - Review the rate limiting policy configuration in the route settings. Check the
51
+ `requestsAllowed` and `timeWindowMinutes` values and verify that the
52
+ `rateLimitBy` identifier is resolving correctly.
28
53
  - Consider using
29
- [dynamic rate limiting](../articles/step-5-dynamic-rate-limiting.mdx) to set
54
+ [dynamic rate limiting](../rate-limiting/dynamic-rate-limiting.mdx) to set
30
55
  different limits per customer tier.
31
- - Check the rate limit metrics to determine if limits need adjustment.
56
+ - Use your [logging integration](../articles/logging.mdx) to filter for 429
57
+ responses and identify which consumers are being throttled. Break down by user
58
+ or IP to spot noisy neighbors.
@@ -37,6 +37,8 @@
37
37
  | curity-phantom-token-inbound | Curity Phantom Token Auth | Authenticate users using the Curity Phantom Token Pattern. | api-gateway |
38
38
  | custom-code-inbound | Custom Code Inbound | Enables a custom code policy written in TypeScript. Change YOUR_MODULE to the name of your module (without .ts extension) | api-gateway |
39
39
  | custom-code-outbound | Custom Code Outbound | A custom outbound response policy. | api-gateway |
40
+ | data-loss-prevention-outbound | Data Loss Prevention | Scans the upstream response body for sensitive data — PII, secrets, and financial identifiers — using an extensible catalog of built-in recognizers plus any custom patterns, and takes a configurable action when a match is found. The action is one of `mask` (redact matches before returning the response), `block` (replace the response with a `422` listing the detected entity names only), or `log` (record a warning and return unchanged). Only text content types are inspected; binary bodies pass through untouched, and the body is read from a clone so the client still receives the original stream. | api-gateway |
41
+ | data-loss-prevention-inbound | Data Loss Prevention | Scans the incoming request body for sensitive data — PII, secrets, and financial identifiers — using an extensible catalog of built-in recognizers plus any custom patterns, and takes a configurable action when a match is found. The action is one of `mask` (redact matches before forwarding the request), `block` (reject with a `422` listing the detected entity names only), or `log` (record a warning and forward unchanged). Only text content types are inspected; binary bodies pass through untouched, and the body is read from a clone so the upstream still receives the original stream. | api-gateway |
40
42
  | firebase-jwt-inbound | Firebase JWT Auth | Authenticate users using Firebase issued JWT tokens. | api-gateway |
41
43
  | formdata-to-json-inbound | Form Data to JSON | Converts form data in the incoming request to JSON. | api-gateway |
42
44
  | galileo-tracing-inbound | Galileo Tracing | Galileo Tracing Inbound Policy | ai-gateway |
@@ -0,0 +1,116 @@
1
+ This policy inspects the body of each incoming request for sensitive data and
2
+ applies a configurable action. It is the inbound counterpart to the
3
+ [Data Loss Prevention - Outbound](/docs/policies/data-loss-prevention-outbound)
4
+ policy, which inspects upstream responses.
5
+
6
+ Detection happens entirely inside the gateway isolate — request bodies are never
7
+ sent to a third-party service.
8
+
9
+ ## Actions
10
+
11
+ - **`mask`** (default) — every detected value is replaced with the `mask` string
12
+ and the modified body is forwarded upstream. Overlapping matches are merged
13
+ and masked once.
14
+ - **`block`** — the request is rejected with a `422 Unprocessable Content`. The
15
+ problem detail lists only the names of the detected entities, never the
16
+ matched values, so the policy never leaks the data it caught.
17
+ - **`log`** — a structured warning is written (entity ids and counts only) and
18
+ the request is forwarded unchanged.
19
+
20
+ ## Built-in recognizers
21
+
22
+ Enable entities individually or by **group selector** in the `entities`
23
+ option, or omit it to use the full catalog. Entity ids follow a
24
+ `{category}-{scope}-{name}` taxonomy, and any dash-aligned prefix of an id is a
25
+ valid selector: `secret` enables every secret, `id-au` enables Australia's
26
+ identifiers, `secret-aws` enables both AWS entities. Two named groups (`pii`,
27
+ `region-eu`) bundle entities across categories.
28
+
29
+ | Group | Entities |
30
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
31
+ | `secret` | `secret-private-key`, `secret-jwt`, `secret-aws-access-key`, `secret-aws-bedrock`, `secret-github`, `secret-gitlab`, `secret-zuplo`, `secret-openai`, `secret-anthropic`, `secret-google-api-key`, `secret-stripe`, `secret-slack`, `secret-discord-webhook`, `secret-npm`, `secret-pypi`, `secret-sendgrid`, `secret-twilio`, `secret-hugging-face`, `secret-databricks`, `secret-shopify`, `secret-square`, `secret-mailchimp`, `secret-mailgun`, `secret-postman`, `secret-terraform`, `secret-sentry`, `secret-digitalocean`, `secret-heroku`, `secret-perplexity`, `secret-azure-client`, `secret-telegram-bot` |
32
+ | `finance` | `finance-credit-card` (Luhn), `finance-iban` (per-country length + mod-97), `finance-crypto-wallet`, `finance-us-aba-routing` (checksum), `finance-swift-bic`, `finance-us-bank-account`, `finance-cvv` |
33
+ | `id` | `id-us-ssn`, `id-us-itin`, `id-us-passport`, `id-uk-nino`, `id-uk-nhs` (mod-11), `id-ca-sin` (Luhn), `id-au-abn`, `id-au-acn`, `id-au-tfn`, `id-au-medicare` (all checksummed), `id-in-aadhaar` (Verhoeff), `id-in-pan`, `id-sg-nric` (checksum), `id-es-nif` (checksum), `id-it-fiscal-code` (checksum), `id-pl-pesel` (checksum), `id-nl-bsn` (11-proef), `id-br-cpf` (checksum), `id-fr-nir` (mod-97) |
34
+ | `contact` | `contact-email`, `contact-phone` |
35
+ | `network` | `network-ipv4`, `network-ipv6`, `network-mac` |
36
+ | `pii` | `contact` + `id` |
37
+ | Prefixes | `id-us`, `id-uk`, `id-au`, `id-ca`, `id-in`, `id-sg`, `id-es`, `id-it`, `id-pl`, `id-nl`, `id-br`, `id-fr`, `finance-us`, `secret-aws` — everything whose id starts with that prefix |
38
+ | `region-eu` | `id-es-nif`, `id-it-fiscal-code`, `id-pl-pesel`, `id-nl-bsn`, `id-fr-nir`, `finance-iban` |
39
+
40
+ ## Context-word scoring
41
+
42
+ Every match gets a confidence score. Recognizers whose raw pattern is just "a
43
+ run of digits" (bank accounts, routing numbers, NHS numbers, …) carry a low
44
+ base confidence and a list of **context words**; when one of those words
45
+ appears near the match — in prose, or in a JSON key, form field, or header-like
46
+ label (`nhsNumber`, `routing_number`, `cvv:`) — the confidence is boosted above
47
+ the detection threshold.
48
+
49
+ For example, with the `id-uk-nhs` entity enabled, `{"nhsNumber": "9434765919"}` is
50
+ masked while the same digits in `{"orderId": "9434765919"}` pass through
51
+ untouched.
52
+
53
+ The threshold is configurable via `minConfidence` (default `0.5`): lower it to
54
+ detect context-dependent entities everywhere, raise it to keep only prefix- and
55
+ checksum-validated matches.
56
+
57
+ ## Custom patterns
58
+
59
+ Add your own recognizers with `customPatterns`. Each entry has a `name`, a
60
+ JavaScript regular expression `pattern`, and optionally a `confidence` and
61
+ `context` words to participate in context scoring. Invalid patterns are logged
62
+ and skipped rather than failing the request. Remember to escape backslashes for
63
+ JSON (for example `\\d` to match a digit).
64
+
65
+ ## Content types
66
+
67
+ Only text-based bodies (JSON, XML, form-encoded, and `text/*`) are scanned;
68
+ binary bodies pass through untouched. Override the allow-list with the
69
+ `contentTypes` option if you need to scan a different set of content types.
70
+
71
+ ## Configuration
72
+
73
+ - `engine`: The detection engine. Only `builtin` is available today. **Default:**
74
+ `builtin`
75
+ - `entities`: Recognizer ids and/or group selectors (prefixes, `pii`,
76
+ `region-eu`) to enable. **Default:** all
77
+ recognizers
78
+ - `customPatterns`: Additional `{ name, pattern, confidence?, context? }` regex
79
+ recognizers
80
+ - `action`: `mask`, `block`, or `log`. **Default:** `mask`
81
+ - `mask`: Replacement string used when `action` is `mask`. **Default:**
82
+ `[REDACTED]`
83
+ - `minConfidence`: Detection threshold (0-1). **Default:** `0.5`
84
+ - `contentTypes`: Override the scannable content-type allow-list
85
+
86
+ ## Usage
87
+
88
+ Apply this policy to inbound requests in your route configuration:
89
+
90
+ ```json
91
+ {
92
+ "policies": [
93
+ {
94
+ "name": "data-loss-prevention-inbound",
95
+ "policyType": "data-loss-prevention-inbound",
96
+ "handler": {
97
+ "export": "DataLossPreventionInboundPolicy",
98
+ "module": "$import(@zuplo/runtime)",
99
+ "options": {
100
+ "action": "mask",
101
+ "entities": ["secret", "finance", "id-us", "contact-email"],
102
+ "mask": "[REDACTED]",
103
+ "customPatterns": [
104
+ {
105
+ "name": "employee-id",
106
+ "pattern": "EMP-\\d{6}",
107
+ "confidence": 0.3,
108
+ "context": ["employee"]
109
+ }
110
+ ]
111
+ }
112
+ }
113
+ }
114
+ ]
115
+ }
116
+ ```
@@ -0,0 +1,15 @@
1
+ The Data Loss Prevention (DLP) policy scans incoming request bodies for
2
+ sensitive data — personally identifiable information (PII), secrets and API
3
+ keys for dozens of vendors, payment and bank identifiers, and national IDs for
4
+ many countries — using a catalog of 60+ built-in recognizers plus any custom
5
+ patterns you add. When a match is found it takes a configurable action: mask
6
+ the matches, block the request, or log a warning and let it through.
7
+
8
+ Recognizers are selected individually or via entity groups (`secret`,
9
+ `finance`, `pii`, `id-us`, `id-uk`, `region-eu`, …). Detection runs entirely in the
10
+ gateway isolate using regular expressions, checksums (Luhn, mod-97, Verhoeff,
11
+ and friends), and context-word scoring — no request data leaves the gateway.
12
+
13
+ Pair with the
14
+ [Data Loss Prevention - Outbound](/docs/policies/data-loss-prevention-outbound)
15
+ policy to also scan upstream responses before they're returned to the client.
@@ -0,0 +1,220 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft-07/schema",
3
+ "$id": "https://cdn.zuplo.com/policies/runtime/schemas/data-loss-prevention-inbound.json",
4
+ "type": "object",
5
+ "title": "Data Loss Prevention",
6
+ "isDeprecated": false,
7
+ "isPaidAddOn": false,
8
+ "isEnterprise": false,
9
+ "isInternal": false,
10
+ "isBeta": false,
11
+ "isHidden": false,
12
+ "requiresAI": false,
13
+ "products": ["api-gateway"],
14
+ "description": "Scans the incoming request body for sensitive data — PII, secrets, and financial identifiers — using an extensible catalog of built-in recognizers plus any custom patterns, and takes a configurable action when a match is found.\n\nThe action is one of `mask` (redact matches before forwarding the request), `block` (reject with a `422` listing the detected entity names only), or `log` (record a warning and forward unchanged). Only text content types are inspected; binary bodies pass through untouched, and the body is read from a clone so the upstream still receives the original stream.",
15
+ "deprecatedMessage": "",
16
+ "required": ["handler"],
17
+ "properties": {
18
+ "handler": {
19
+ "type": "object",
20
+ "default": {},
21
+ "required": ["export", "module", "options"],
22
+ "properties": {
23
+ "export": {
24
+ "const": "DataLossPreventionInboundPolicy",
25
+ "description": "The name of the exported type"
26
+ },
27
+ "module": {
28
+ "const": "$import(@zuplo/runtime)",
29
+ "description": "The module containing the policy"
30
+ },
31
+ "options": {
32
+ "title": "DataLossPreventionInboundPolicyOptions",
33
+ "type": "object",
34
+ "description": "The options for the Data Loss Prevention inbound policy. Scans the incoming request body for sensitive data and applies the configured action (mask, block, or log).",
35
+ "additionalProperties": false,
36
+ "required": [],
37
+ "examples": [
38
+ {
39
+ "action": "mask",
40
+ "entities": ["secret", "finance", "contact-email", "id-us-ssn"],
41
+ "mask": "[REDACTED]"
42
+ }
43
+ ],
44
+ "properties": {
45
+ "engine": {
46
+ "type": "string",
47
+ "enum": ["builtin"],
48
+ "default": "builtin",
49
+ "description": "The detection engine. Only `builtin` (in-isolate regex + checksum detection with context-word scoring) is available today. This is the extension point for a future hosted `presidio-service` mode; declaring it now keeps adding that mode an additive, non-breaking change."
50
+ },
51
+ "entities": {
52
+ "type": "array",
53
+ "description": "Built-in recognizer ids and/or group selectors to enable. Entity ids follow a {category}-{scope}-{name} taxonomy, and any dash-aligned id prefix acts as a selector (for example `secret` is every secret, `id-au` is Australia's identifiers, `secret-aws` is both AWS entities), plus the named groups `pii` and `region-eu`. Available selectors: `contact`, `finance`, `finance-us`, `id`, `id-au`, `id-br`, `id-ca`, `id-es`, `id-fr`, `id-in`, `id-it`, `id-nl`, `id-pl`, `id-sg`, `id-uk`, `id-us`, `network`, `pii`, `region-eu`, `secret`, `secret-aws`. When omitted, the full built-in catalog is used.",
54
+ "items": {
55
+ "type": "string",
56
+ "enum": [
57
+ "contact",
58
+ "finance",
59
+ "finance-us",
60
+ "id",
61
+ "id-au",
62
+ "id-br",
63
+ "id-ca",
64
+ "id-es",
65
+ "id-fr",
66
+ "id-in",
67
+ "id-it",
68
+ "id-nl",
69
+ "id-pl",
70
+ "id-sg",
71
+ "id-uk",
72
+ "id-us",
73
+ "network",
74
+ "pii",
75
+ "region-eu",
76
+ "secret",
77
+ "secret-aws",
78
+ "contact-email",
79
+ "contact-phone",
80
+ "finance-credit-card",
81
+ "finance-crypto-wallet",
82
+ "finance-cvv",
83
+ "finance-iban",
84
+ "finance-swift-bic",
85
+ "finance-us-aba-routing",
86
+ "finance-us-bank-account",
87
+ "id-au-abn",
88
+ "id-au-acn",
89
+ "id-au-medicare",
90
+ "id-au-tfn",
91
+ "id-br-cpf",
92
+ "id-ca-sin",
93
+ "id-es-nif",
94
+ "id-fr-nir",
95
+ "id-in-aadhaar",
96
+ "id-in-pan",
97
+ "id-it-fiscal-code",
98
+ "id-nl-bsn",
99
+ "id-pl-pesel",
100
+ "id-sg-nric",
101
+ "id-uk-nhs",
102
+ "id-uk-nino",
103
+ "id-us-itin",
104
+ "id-us-passport",
105
+ "id-us-ssn",
106
+ "network-ipv4",
107
+ "network-ipv6",
108
+ "network-mac",
109
+ "secret-anthropic",
110
+ "secret-aws-access-key",
111
+ "secret-aws-bedrock",
112
+ "secret-azure-client",
113
+ "secret-databricks",
114
+ "secret-digitalocean",
115
+ "secret-discord-webhook",
116
+ "secret-github",
117
+ "secret-gitlab",
118
+ "secret-google-api-key",
119
+ "secret-heroku",
120
+ "secret-hugging-face",
121
+ "secret-jwt",
122
+ "secret-mailchimp",
123
+ "secret-mailgun",
124
+ "secret-npm",
125
+ "secret-openai",
126
+ "secret-perplexity",
127
+ "secret-postman",
128
+ "secret-private-key",
129
+ "secret-pypi",
130
+ "secret-sendgrid",
131
+ "secret-sentry",
132
+ "secret-shopify",
133
+ "secret-slack",
134
+ "secret-square",
135
+ "secret-stripe",
136
+ "secret-telegram-bot",
137
+ "secret-terraform",
138
+ "secret-twilio",
139
+ "secret-zuplo"
140
+ ]
141
+ }
142
+ },
143
+ "customPatterns": {
144
+ "type": "array",
145
+ "description": "Additional customer-defined regex recognizers. Invalid patterns are logged and skipped rather than failing the request.",
146
+ "items": {
147
+ "type": "object",
148
+ "title": "DlpCustomPattern",
149
+ "additionalProperties": false,
150
+ "required": ["name", "pattern"],
151
+ "properties": {
152
+ "name": {
153
+ "type": "string",
154
+ "description": "Identifier reported in findings and block details for this pattern."
155
+ },
156
+ "pattern": {
157
+ "type": "string",
158
+ "description": "A JavaScript regular expression source string. Remember to escape backslashes for JSON (for example `\\\\d` for a digit)."
159
+ },
160
+ "confidence": {
161
+ "type": "number",
162
+ "minimum": 0,
163
+ "maximum": 1,
164
+ "default": 0.85,
165
+ "description": "Base confidence (0-1) for matches of this pattern. The default of 0.85 is above the default detection threshold; combine a low value with `context` words for patterns that are only sensitive in context."
166
+ },
167
+ "context": {
168
+ "type": "array",
169
+ "items": {
170
+ "type": "string"
171
+ },
172
+ "description": "Context words that boost a match's confidence by 0.45 when one appears near the match (in the surrounding field, label, or key)."
173
+ }
174
+ }
175
+ }
176
+ },
177
+ "action": {
178
+ "type": "string",
179
+ "enum": ["mask", "block", "log"],
180
+ "default": "mask",
181
+ "description": "What to do when sensitive data is detected. `mask` redacts matches before forwarding the request, `block` rejects with a 422 listing only the detected entity names, and `log` records a warning and forwards the request unchanged."
182
+ },
183
+ "mask": {
184
+ "type": "string",
185
+ "default": "[REDACTED]",
186
+ "examples": ["[REDACTED]"],
187
+ "description": "The string that replaces detected values when `action` is `mask`."
188
+ },
189
+ "minConfidence": {
190
+ "type": "number",
191
+ "minimum": 0,
192
+ "maximum": 1,
193
+ "default": 0.5,
194
+ "x-show-example": false,
195
+ "description": "Minimum confidence (0-1) a match must reach to count as a finding. Context-dependent recognizers (for example `finance-us-bank-account` or `finance-us-aba-routing`) sit below the default threshold of 0.5 until a context word near the match boosts them above it. Lower the threshold to surface them everywhere; raise it to keep only prefix- or checksum-validated matches."
196
+ },
197
+ "contentTypes": {
198
+ "type": "array",
199
+ "description": "Override the set of scannable content-type prefixes. When omitted, the built-in text content-type allow-list (JSON, XML, form-encoded, text/\\*) is used.",
200
+ "items": {
201
+ "type": "string"
202
+ }
203
+ }
204
+ }
205
+ }
206
+ },
207
+ "examples": [
208
+ {
209
+ "export": "DataLossPreventionInboundPolicy",
210
+ "module": "$import(@zuplo/runtime)",
211
+ "options": {
212
+ "action": "mask",
213
+ "entities": ["secret", "finance", "contact-email", "id-us-ssn"],
214
+ "mask": "[REDACTED]"
215
+ }
216
+ }
217
+ ]
218
+ }
219
+ }
220
+ }
@@ -0,0 +1,116 @@
1
+ This policy inspects the body of each upstream response for sensitive data and
2
+ applies a configurable action. It is the outbound counterpart to the
3
+ [Data Loss Prevention - Inbound](/docs/policies/data-loss-prevention-inbound)
4
+ policy, which inspects incoming requests.
5
+
6
+ Detection happens entirely inside the gateway isolate — response bodies are
7
+ never sent to a third-party service.
8
+
9
+ ## Actions
10
+
11
+ - **`mask`** (default) — every detected value is replaced with the `mask` string
12
+ and the modified response is returned to the client. Overlapping matches are merged
13
+ and masked once.
14
+ - **`block`** — the response is replaced with a `422 Unprocessable Content`. The
15
+ problem detail lists only the names of the detected entities, never the
16
+ matched values, so the policy never leaks the data it caught.
17
+ - **`log`** — a structured warning is written (entity ids and counts only) and
18
+ the response is returned unchanged.
19
+
20
+ ## Built-in recognizers
21
+
22
+ Enable entities individually or by **group selector** in the `entities`
23
+ option, or omit it to use the full catalog. Entity ids follow a
24
+ `{category}-{scope}-{name}` taxonomy, and any dash-aligned prefix of an id is a
25
+ valid selector: `secret` enables every secret, `id-au` enables Australia's
26
+ identifiers, `secret-aws` enables both AWS entities. Two named groups (`pii`,
27
+ `region-eu`) bundle entities across categories.
28
+
29
+ | Group | Entities |
30
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
31
+ | `secret` | `secret-private-key`, `secret-jwt`, `secret-aws-access-key`, `secret-aws-bedrock`, `secret-github`, `secret-gitlab`, `secret-zuplo`, `secret-openai`, `secret-anthropic`, `secret-google-api-key`, `secret-stripe`, `secret-slack`, `secret-discord-webhook`, `secret-npm`, `secret-pypi`, `secret-sendgrid`, `secret-twilio`, `secret-hugging-face`, `secret-databricks`, `secret-shopify`, `secret-square`, `secret-mailchimp`, `secret-mailgun`, `secret-postman`, `secret-terraform`, `secret-sentry`, `secret-digitalocean`, `secret-heroku`, `secret-perplexity`, `secret-azure-client`, `secret-telegram-bot` |
32
+ | `finance` | `finance-credit-card` (Luhn), `finance-iban` (per-country length + mod-97), `finance-crypto-wallet`, `finance-us-aba-routing` (checksum), `finance-swift-bic`, `finance-us-bank-account`, `finance-cvv` |
33
+ | `id` | `id-us-ssn`, `id-us-itin`, `id-us-passport`, `id-uk-nino`, `id-uk-nhs` (mod-11), `id-ca-sin` (Luhn), `id-au-abn`, `id-au-acn`, `id-au-tfn`, `id-au-medicare` (all checksummed), `id-in-aadhaar` (Verhoeff), `id-in-pan`, `id-sg-nric` (checksum), `id-es-nif` (checksum), `id-it-fiscal-code` (checksum), `id-pl-pesel` (checksum), `id-nl-bsn` (11-proef), `id-br-cpf` (checksum), `id-fr-nir` (mod-97) |
34
+ | `contact` | `contact-email`, `contact-phone` |
35
+ | `network` | `network-ipv4`, `network-ipv6`, `network-mac` |
36
+ | `pii` | `contact` + `id` |
37
+ | Prefixes | `id-us`, `id-uk`, `id-au`, `id-ca`, `id-in`, `id-sg`, `id-es`, `id-it`, `id-pl`, `id-nl`, `id-br`, `id-fr`, `finance-us`, `secret-aws` — everything whose id starts with that prefix |
38
+ | `region-eu` | `id-es-nif`, `id-it-fiscal-code`, `id-pl-pesel`, `id-nl-bsn`, `id-fr-nir`, `finance-iban` |
39
+
40
+ ## Context-word scoring
41
+
42
+ Every match gets a confidence score. Recognizers whose raw pattern is just "a
43
+ run of digits" (bank accounts, routing numbers, NHS numbers, …) carry a low
44
+ base confidence and a list of **context words**; when one of those words
45
+ appears near the match — in prose, or in a JSON key, form field, or header-like
46
+ label (`nhsNumber`, `routing_number`, `cvv:`) — the confidence is boosted above
47
+ the detection threshold.
48
+
49
+ For example, with the `id-uk-nhs` entity enabled, `{"nhsNumber": "9434765919"}` is
50
+ masked while the same digits in `{"orderId": "9434765919"}` pass through
51
+ untouched.
52
+
53
+ The threshold is configurable via `minConfidence` (default `0.5`): lower it to
54
+ detect context-dependent entities everywhere, raise it to keep only prefix- and
55
+ checksum-validated matches.
56
+
57
+ ## Custom patterns
58
+
59
+ Add your own recognizers with `customPatterns`. Each entry has a `name`, a
60
+ JavaScript regular expression `pattern`, and optionally a `confidence` and
61
+ `context` words to participate in context scoring. Invalid patterns are logged
62
+ and skipped rather than failing the response. Remember to escape backslashes for
63
+ JSON (for example `\\d` to match a digit).
64
+
65
+ ## Content types
66
+
67
+ Only text-based bodies (JSON, XML, form-encoded, and `text/*`) are scanned;
68
+ binary bodies pass through untouched. Override the allow-list with the
69
+ `contentTypes` option if you need to scan a different set of content types.
70
+
71
+ ## Configuration
72
+
73
+ - `engine`: The detection engine. Only `builtin` is available today. **Default:**
74
+ `builtin`
75
+ - `entities`: Recognizer ids and/or group selectors (prefixes, `pii`,
76
+ `region-eu`) to enable. **Default:** all
77
+ recognizers
78
+ - `customPatterns`: Additional `{ name, pattern, confidence?, context? }` regex
79
+ recognizers
80
+ - `action`: `mask`, `block`, or `log`. **Default:** `mask`
81
+ - `mask`: Replacement string used when `action` is `mask`. **Default:**
82
+ `[REDACTED]`
83
+ - `minConfidence`: Detection threshold (0-1). **Default:** `0.5`
84
+ - `contentTypes`: Override the scannable content-type allow-list
85
+
86
+ ## Usage
87
+
88
+ Apply this policy to outbound responses in your route configuration:
89
+
90
+ ```json
91
+ {
92
+ "policies": [
93
+ {
94
+ "name": "data-loss-prevention-outbound",
95
+ "policyType": "data-loss-prevention-outbound",
96
+ "handler": {
97
+ "export": "DataLossPreventionOutboundPolicy",
98
+ "module": "$import(@zuplo/runtime)",
99
+ "options": {
100
+ "action": "mask",
101
+ "entities": ["secret", "finance", "id-us", "contact-email"],
102
+ "mask": "[REDACTED]",
103
+ "customPatterns": [
104
+ {
105
+ "name": "employee-id",
106
+ "pattern": "EMP-\\d{6}",
107
+ "confidence": 0.3,
108
+ "context": ["employee"]
109
+ }
110
+ ]
111
+ }
112
+ }
113
+ }
114
+ ]
115
+ }
116
+ ```