zuplo 6.70.71 → 6.71.0

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 (43) hide show
  1. package/docs/analytics/access-and-entitlements.md +1 -1
  2. package/docs/analytics/overview.md +12 -8
  3. package/docs/analytics/reference/metrics-glossary.md +3 -3
  4. package/docs/analytics/shared-controls.md +12 -11
  5. package/docs/analytics/tabs/agents.md +14 -14
  6. package/docs/analytics/tabs/consumers.md +6 -6
  7. package/docs/analytics/tabs/graphql.md +5 -4
  8. package/docs/analytics/tabs/mcp.md +7 -7
  9. package/docs/analytics/tabs/origins.md +11 -10
  10. package/docs/analytics/tabs/requests.md +6 -5
  11. package/docs/articles/accounts/enterprise-sso.mdx +8 -6
  12. package/docs/articles/api-key-administration.mdx +4 -0
  13. package/docs/articles/bypass-policy-for-testing.mdx +4 -0
  14. package/docs/articles/environment-variables.mdx +5 -1
  15. package/docs/articles/environments.mdx +2 -2
  16. package/docs/articles/graphql.mdx +23 -39
  17. package/docs/articles/mcp-quickstart-local.mdx +2 -1
  18. package/docs/articles/mcp-quickstart.mdx +6 -1
  19. package/docs/articles/multiple-auth-policies.mdx +2 -2
  20. package/docs/articles/openapi.mdx +6 -1
  21. package/docs/articles/rename-or-move-project.mdx +4 -0
  22. package/docs/articles/securing-your-backend.mdx +11 -3
  23. package/docs/articles/source-control-setup-github.mdx +4 -0
  24. package/docs/articles/troubleshooting-slow-responses.mdx +2 -2
  25. package/docs/articles/troubleshooting.md +3 -3
  26. package/docs/dedicated/akamai/architecture.mdx +9 -9
  27. package/docs/dev-portal/zudoku/components/browser-window.mdx +94 -0
  28. package/docs/dev-portal/zudoku/components/landing-page.mdx +283 -0
  29. package/docs/handlers/system-handlers.mdx +2 -1
  30. package/docs/mcp-gateway/how-to/connect-upstream-api-key.mdx +2 -2
  31. package/docs/mcp-gateway/observability/analytics.mdx +17 -13
  32. package/docs/mcp-gateway/quickstart.mdx +4 -3
  33. package/docs/mcp-gateway/troubleshooting.mdx +4 -4
  34. package/docs/policies/_index.md +1 -0
  35. package/docs/policies/data-loss-prevention-inbound/doc.md +23 -24
  36. package/docs/policies/data-loss-prevention-inbound/intro.md +9 -9
  37. package/docs/policies/data-loss-prevention-outbound/doc.md +25 -26
  38. package/docs/policies/data-loss-prevention-outbound/intro.md +10 -10
  39. package/docs/policies/graphql-analytics-outbound/doc.md +93 -0
  40. package/docs/policies/graphql-analytics-outbound/intro.md +12 -0
  41. package/docs/policies/graphql-analytics-outbound/schema.json +93 -0
  42. package/docs/programmable-api/zuplo-context.mdx +3 -2
  43. package/package.json +4 -4
@@ -19,35 +19,35 @@ sent to a third-party service.
19
19
 
20
20
  ## Built-in recognizers
21
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
22
+ Enable entities individually or by **group selector** in the `entities` option,
23
+ or omit it to use the full catalog. Entity ids follow a
24
24
  `{category}-{scope}-{name}` taxonomy, and any dash-aligned prefix of an id is a
25
25
  valid selector: `secret` enables every secret, `id-au` enables Australia's
26
26
  identifiers, `secret-aws` enables both AWS entities. Two named groups (`pii`,
27
27
  `region-eu`) bundle entities across categories.
28
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` |
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
39
 
40
40
  ## Context-word scoring
41
41
 
42
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
43
+ run of digits" (bank accounts, routing numbers, NHS numbers, …) carry a low base
44
+ confidence and a list of **context words**; when one of those words appears near
45
+ the match — in prose, or in a JSON key, form field, or header-like label
46
+ (`nhsNumber`, `routing_number`, `cvv:`) — the confidence is boosted above the
47
+ detection threshold.
48
+
49
+ For example, with the `id-uk-nhs` entity enabled, `{"nhsNumber": "9434765919"}`
50
+ is masked while the same digits in `{"orderId": "9434765919"}` pass through
51
51
  untouched.
52
52
 
53
53
  The threshold is configurable via `minConfidence` (default `0.5`): lower it to
@@ -70,11 +70,10 @@ binary bodies pass through untouched. Override the allow-list with the
70
70
 
71
71
  ## Configuration
72
72
 
73
- - `engine`: The detection engine. Only `builtin` is available today. **Default:**
74
- `builtin`
73
+ - `engine`: The detection engine. Only `builtin` is available today.
74
+ **Default:** `builtin`
75
75
  - `entities`: Recognizer ids and/or group selectors (prefixes, `pii`,
76
- `region-eu`) to enable. **Default:** all
77
- recognizers
76
+ `region-eu`) to enable. **Default:** all recognizers
78
77
  - `customPatterns`: Additional `{ name, pattern, confidence?, context? }` regex
79
78
  recognizers
80
79
  - `action`: `mask`, `block`, or `log`. **Default:** `mask`
@@ -1,14 +1,14 @@
1
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.
2
+ sensitive data — personally identifiable information (PII), secrets and API keys
3
+ for dozens of vendors, payment and bank identifiers, and national IDs for many
4
+ countries — using a catalog of 60+ built-in recognizers plus any custom patterns
5
+ you add. When a match is found it takes a configurable action: mask the matches,
6
+ block the request, or log a warning and let it through.
7
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.
8
+ Recognizers are selected individually or via entity groups (`secret`, `finance`,
9
+ `pii`, `id-us`, `id-uk`, `region-eu`, …). Detection runs entirely in the gateway
10
+ isolate using regular expressions, checksums (Luhn, mod-97, Verhoeff, and
11
+ friends), and context-word scoring — no request data leaves the gateway.
12
12
 
13
13
  Pair with the
14
14
  [Data Loss Prevention - Outbound](/docs/policies/data-loss-prevention-outbound)
@@ -9,8 +9,8 @@ never sent to a third-party service.
9
9
  ## Actions
10
10
 
11
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.
12
+ and the modified response is returned to the client. Overlapping matches are
13
+ merged and masked once.
14
14
  - **`block`** — the response is replaced with a `422 Unprocessable Content`. The
15
15
  problem detail lists only the names of the detected entities, never the
16
16
  matched values, so the policy never leaks the data it caught.
@@ -19,35 +19,35 @@ never sent to a third-party service.
19
19
 
20
20
  ## Built-in recognizers
21
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
22
+ Enable entities individually or by **group selector** in the `entities` option,
23
+ or omit it to use the full catalog. Entity ids follow a
24
24
  `{category}-{scope}-{name}` taxonomy, and any dash-aligned prefix of an id is a
25
25
  valid selector: `secret` enables every secret, `id-au` enables Australia's
26
26
  identifiers, `secret-aws` enables both AWS entities. Two named groups (`pii`,
27
27
  `region-eu`) bundle entities across categories.
28
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` |
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
39
 
40
40
  ## Context-word scoring
41
41
 
42
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
43
+ run of digits" (bank accounts, routing numbers, NHS numbers, …) carry a low base
44
+ confidence and a list of **context words**; when one of those words appears near
45
+ the match — in prose, or in a JSON key, form field, or header-like label
46
+ (`nhsNumber`, `routing_number`, `cvv:`) — the confidence is boosted above the
47
+ detection threshold.
48
+
49
+ For example, with the `id-uk-nhs` entity enabled, `{"nhsNumber": "9434765919"}`
50
+ is masked while the same digits in `{"orderId": "9434765919"}` pass through
51
51
  untouched.
52
52
 
53
53
  The threshold is configurable via `minConfidence` (default `0.5`): lower it to
@@ -70,11 +70,10 @@ binary bodies pass through untouched. Override the allow-list with the
70
70
 
71
71
  ## Configuration
72
72
 
73
- - `engine`: The detection engine. Only `builtin` is available today. **Default:**
74
- `builtin`
73
+ - `engine`: The detection engine. Only `builtin` is available today.
74
+ **Default:** `builtin`
75
75
  - `entities`: Recognizer ids and/or group selectors (prefixes, `pii`,
76
- `region-eu`) to enable. **Default:** all
77
- recognizers
76
+ `region-eu`) to enable. **Default:** all recognizers
78
77
  - `customPatterns`: Additional `{ name, pattern, confidence?, context? }` regex
79
78
  recognizers
80
79
  - `action`: `mask`, `block`, or `log`. **Default:** `mask`
@@ -1,15 +1,15 @@
1
1
  The Data Loss Prevention (DLP) policy scans upstream response 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 response, or log a warning and let it through.
2
+ sensitive data — personally identifiable information (PII), secrets and API keys
3
+ for dozens of vendors, payment and bank identifiers, and national IDs for many
4
+ countries — using a catalog of 60+ built-in recognizers plus any custom patterns
5
+ you add. When a match is found it takes a configurable action: mask the matches,
6
+ block the response, or log a warning and let it through.
7
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 response data leaves the gateway.
12
- This is especially useful in front of APIs that interface with user-generated
8
+ Recognizers are selected individually or via entity groups (`secret`, `finance`,
9
+ `pii`, `id-us`, `id-uk`, `region-eu`, …). Detection runs entirely in the gateway
10
+ isolate using regular expressions, checksums (Luhn, mod-97, Verhoeff, and
11
+ friends), and context-word scoring — no response data leaves the gateway. This
12
+ is especially useful in front of APIs that interface with user-generated
13
13
  content, MCP servers, and AI consumers, where a response might otherwise leak
14
14
  data the client should never see.
15
15
 
@@ -0,0 +1,93 @@
1
+ This policy reads GraphQL `errors[]` from response bodies and reports them to
2
+ Zuplo's GraphQL analytics, so operations that fail with the standard "`200 OK`
3
+ with errors" pattern (Apollo Server, graphql-yoga, and most other GraphQL
4
+ servers) show up as failures on the GraphQL dashboard instead of successes.
5
+
6
+ The response always passes through to the client unchanged — the body is read
7
+ from a clone, and any internal failure inside the policy is swallowed so error
8
+ reporting can never break a request.
9
+
10
+ ## Requirements
11
+
12
+ The route must be marked with `"x-graphql": true` in `routes.oas.json`. That
13
+ marker is what enables GraphQL analytics for the route (one `graphql_operation`
14
+ event per request); this policy enriches that event with the errors it finds in
15
+ the response body. On a route without the marker the policy logs a warning and
16
+ does nothing.
17
+
18
+ ## Error classification
19
+
20
+ Each entry in the response's `errors[]` array counts as one error toward the
21
+ operation's `errorCount`, and is classified into one of five classes from its
22
+ `extensions.code`, following the Apollo Server conventions:
23
+
24
+ | `extensions.code` | Class |
25
+ | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
26
+ | `GRAPHQL_PARSE_FAILED` | `syntax` |
27
+ | `GRAPHQL_VALIDATION_FAILED`, `BAD_USER_INPUT`, `PERSISTED_QUERY_NOT_FOUND`, `PERSISTED_QUERY_NOT_SUPPORTED`, `OPERATION_RESOLUTION_FAILURE` | `validation` |
28
+ | `UNAUTHENTICATED`, `FORBIDDEN` | `auth` |
29
+ | `REQUEST_TIMEOUT`, `TIMEOUT`, `GATEWAY_TIMEOUT` | `timeout` |
30
+ | `INTERNAL_SERVER_ERROR`, `DOWNSTREAM_SERVICE_ERROR` | `resolver` |
31
+
32
+ An error whose code is missing or unrecognized is classified as
33
+ `defaultErrorClass` (`resolver` unless configured otherwise). Servers that emit
34
+ their own codes can extend or override the table with `errorCodeClassification`;
35
+ those entries are matched case-sensitively and win against the built-ins, which
36
+ are matched case-insensitively.
37
+
38
+ Batched (array) responses are supported — errors are collected across every
39
+ result in the batch.
40
+
41
+ ## What is inspected
42
+
43
+ Only responses with a JSON content type (`application/json` or any `+json` type
44
+ such as `application/graphql-response+json`) are read, and bodies larger than
45
+ `maxResponseBytes` (5 MiB by default) are skipped — by `Content-Length` when the
46
+ header is present, or measured while reading when it is absent. Everything else
47
+ passes through without the body being touched.
48
+
49
+ ## Logging
50
+
51
+ Set `logErrors` to `true` to also write a structured warning to the request log
52
+ whenever a response contains GraphQL errors. The entry carries the message,
53
+ `extensions.code`, and path of each error (capped at the first 10) under a
54
+ consistent `"GraphQL response contained errors"` message, so you can search or
55
+ alert on it.
56
+
57
+ ## Configuration
58
+
59
+ - `errorCodeClassification`: Additional `extensions.code` → class mappings,
60
+ merged over the built-in table. **Default:** none
61
+ - `defaultErrorClass`: Class for errors with a missing or unrecognized code —
62
+ one of `syntax`, `validation`, `auth`, `timeout`, `resolver`. **Default:**
63
+ `resolver`
64
+ - `logErrors`: Also log a structured warning per errored response. **Default:**
65
+ `false`
66
+ - `maxResponseBytes`: Maximum response body size in bytes to inspect.
67
+ **Default:** `5242880` (5 MiB)
68
+
69
+ ## Usage
70
+
71
+ Apply this policy to outbound responses on your GraphQL route:
72
+
73
+ ```json
74
+ {
75
+ "policies": [
76
+ {
77
+ "name": "graphql-analytics",
78
+ "policyType": "graphql-analytics-outbound",
79
+ "handler": {
80
+ "export": "GraphqlAnalyticsOutboundPolicy",
81
+ "module": "$import(@zuplo/runtime)",
82
+ "options": {
83
+ "errorCodeClassification": {
84
+ "RATE_LIMITED": "resolver",
85
+ "NOT_LOGGED_IN": "auth"
86
+ },
87
+ "logErrors": true
88
+ }
89
+ }
90
+ }
91
+ ]
92
+ }
93
+ ```
@@ -0,0 +1,12 @@
1
+ The GraphQL Analytics policy makes failed GraphQL operations visible on Zuplo's
2
+ GraphQL analytics dashboard. GraphQL servers following the standard Apollo /
3
+ graphql-yoga pattern return `200 OK` with an `errors[]` array in the response
4
+ body when an operation fails — invisible to HTTP-level analytics, which would
5
+ report every such operation as a success.
6
+
7
+ Add this policy to a GraphQL route (marked `x-graphql: true`) and the gateway
8
+ reads the response body, counts the GraphQL errors, and classifies each one by
9
+ its `extensions.code` (syntax, validation, auth, timeout, or resolver) — no
10
+ changes to your GraphQL server or client code required. The classification map
11
+ is configurable for servers that emit custom error codes, and errors can
12
+ optionally be written to the request log.
@@ -0,0 +1,93 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft-07/schema",
3
+ "$id": "https://cdn.zuplo.com/policies/runtime/schemas/graphql-analytics-outbound.json",
4
+ "type": "object",
5
+ "title": "GraphQL Analytics",
6
+ "isDeprecated": false,
7
+ "isPaidAddOn": false,
8
+ "isEnterprise": false,
9
+ "isInternal": false,
10
+ "isBeta": true,
11
+ "isHidden": false,
12
+ "requiresAI": false,
13
+ "products": ["api-gateway"],
14
+ "description": "Reports GraphQL errors returned in response bodies to Zuplo's GraphQL analytics. GraphQL servers following the standard Apollo / graphql-yoga pattern return `200 OK` with an `errors[]` array in the body when an operation fails, which HTTP-level analytics alone report as a success — add this policy to a GraphQL route and failed operations show up as failures on the GraphQL dashboard, classified by error type.\n\nEach error in `errors[]` is classified from its `extensions.code` following the Apollo Server conventions (`GRAPHQL_PARSE_FAILED` → `syntax`, `GRAPHQL_VALIDATION_FAILED` → `validation`, `UNAUTHENTICATED` / `FORBIDDEN` → `auth`, timeout codes → `timeout`); custom codes can be mapped with `errorCodeClassification`, and anything unrecognized falls back to `defaultErrorClass` (`resolver`). Optionally set `logErrors` to also write a structured warning per errored response. Bodies larger than `maxResponseBytes` (default 5 MiB) are not inspected.\n\nThe response always passes through unchanged — the body is read from a clone, and any internal failure is swallowed so reporting can never break the request. The route must be marked `x-graphql: true` in `routes.oas.json` (which enables GraphQL analytics for the route); without the marker the policy logs a warning and does nothing.",
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": "GraphqlAnalyticsOutboundPolicy",
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": "GraphqlAnalyticsOutboundPolicyOptions",
33
+ "type": "object",
34
+ "description": "The options for the GraphQL Analytics outbound policy. Reads GraphQL `errors[]` from the response body and reports them to Zuplo's GraphQL analytics.",
35
+ "additionalProperties": false,
36
+ "required": [],
37
+ "examples": [
38
+ {
39
+ "errorCodeClassification": {
40
+ "RATE_LIMITED": "resolver",
41
+ "NOT_LOGGED_IN": "auth"
42
+ },
43
+ "defaultErrorClass": "resolver",
44
+ "logErrors": false
45
+ }
46
+ ],
47
+ "properties": {
48
+ "errorCodeClassification": {
49
+ "type": "object",
50
+ "description": "Additional `extensions.code` → error-class mappings for codes your GraphQL server emits. Entries are merged over (and win against) the built-in Apollo-convention map (`GRAPHQL_PARSE_FAILED` → `syntax`, `GRAPHQL_VALIDATION_FAILED` / `BAD_USER_INPUT` → `validation`, `UNAUTHENTICATED` / `FORBIDDEN` → `auth`, timeout codes → `timeout`, `INTERNAL_SERVER_ERROR` → `resolver`). Keys are matched case-sensitively; built-in codes are matched case-insensitively.",
51
+ "additionalProperties": {
52
+ "type": "string",
53
+ "enum": ["syntax", "validation", "auth", "timeout", "resolver"]
54
+ }
55
+ },
56
+ "defaultErrorClass": {
57
+ "type": "string",
58
+ "enum": ["syntax", "validation", "auth", "timeout", "resolver"],
59
+ "default": "resolver",
60
+ "description": "The error class reported for a GraphQL error whose `extensions.code` is missing or not in the classification map."
61
+ },
62
+ "logErrors": {
63
+ "type": "boolean",
64
+ "default": false,
65
+ "description": "When `true`, also write a structured warning to the request log (message, `extensions.code`, and path of each error — capped at the first 10) whenever a response contains GraphQL errors."
66
+ },
67
+ "maxResponseBytes": {
68
+ "type": "integer",
69
+ "minimum": 1,
70
+ "default": 5242880,
71
+ "x-show-example": false,
72
+ "description": "Maximum response body size in bytes the policy will inspect. Larger bodies — by `Content-Length`, or measured while reading when the header is absent — pass through without being scanned, so their GraphQL errors (if any) go unreported. The default is 5 MiB."
73
+ }
74
+ }
75
+ }
76
+ },
77
+ "examples": [
78
+ {
79
+ "export": "GraphqlAnalyticsOutboundPolicy",
80
+ "module": "$import(@zuplo/runtime)",
81
+ "options": {
82
+ "errorCodeClassification": {
83
+ "RATE_LIMITED": "resolver",
84
+ "NOT_LOGGED_IN": "auth"
85
+ },
86
+ "defaultErrorClass": "resolver",
87
+ "logErrors": false
88
+ }
89
+ }
90
+ ]
91
+ }
92
+ }
93
+ }
@@ -88,8 +88,9 @@ context.log.info({
88
88
 
89
89
  ### `log`
90
90
 
91
- A logger instance for debugging and monitoring. Logs appear in your log tail in
92
- the [Zuplo Portal](https://portal.zuplo.com/+/account/project/) and in your
91
+ A logger instance for debugging and monitoring. Logs appear in the
92
+ **Observability Logs** view of your project in the
93
+ [Zuplo Portal](https://portal.zuplo.com/+/account/project/) and in your
93
94
  integrated log solution (for example Datadog). Pre-production environments are
94
95
  typically set to **Info** log level, while production is set to **Error**.
95
96
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuplo",
3
- "version": "6.70.71",
3
+ "version": "6.71.0",
4
4
  "type": "module",
5
5
  "description": "The programmable API Gateway",
6
6
  "author": "Zuplo, Inc.",
@@ -19,9 +19,9 @@
19
19
  "zuplo": "zuplo.js"
20
20
  },
21
21
  "dependencies": {
22
- "@zuplo/cli": "6.70.71",
23
- "@zuplo/core": "6.70.71",
24
- "@zuplo/runtime": "6.70.71",
22
+ "@zuplo/cli": "6.71.0",
23
+ "@zuplo/core": "6.71.0",
24
+ "@zuplo/runtime": "6.71.0",
25
25
  "@zuplo/test": "1.4.0"
26
26
  }
27
27
  }