You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(fetch): Add redactUrl option and sanitizeUrl helper
Allows callers to rewrite `url.full` before it is recorded as a span
attribute, stripping secrets from query strings or paths while keeping
the span. Unlike `ignore`, the request still goes through.
`sanitizeUrl()` follows OTel URL semconv: sensitive query-parameter
values and `user:pass@` credentials are replaced with `REDACTED`, with
parameter keys preserved. A built-in default list covers common AWS/GCP
signing parameters.
On Node, requesting `redactUrl` forces the `globalThis.fetch` wrap
instead of the native undici instrumentation, which has no hook to
rewrite `url.full`.
On Node, `setupOtel()` uses the native `@opentelemetry/instrumentation-undici` by default; on Bun it wraps `globalThis.fetch`. Force the wrap on both runtimes with `mode: "global"`:
208
222
209
223
```ts
@@ -236,4 +250,4 @@ The package always excludes its own OTLP trace and log exporter endpoints from f
236
250
- Set `serviceVersion` from your release artifact when possible.
237
251
- Prefer stable resource attributes over high-cardinality request values.
238
252
- Use `LOG_LEVEL=debug` temporarily when debugging production incidents, then return to `info` or higher.
239
-
- Avoid putting secrets in URL query strings because fetch spans include `url.full`.
253
+
- Avoid putting secrets in URL query strings (fetch spans include `url.full`); when unavoidable, strip them with the `redactUrl` option and the `sanitizeUrl()` helper.
Copy file name to clipboardExpand all lines: docs/guides/fetch-instrumentation.mdx
+23-5Lines changed: 23 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -107,7 +107,25 @@ When a URL is ignored:
107
107
- no span is emitted
108
108
- trace headers are not injected by this wrapper
109
109
110
-
Use this for health checks, high-volume polling, or URLs with secrets in the query string.
110
+
Use this for health checks or high-volume polling. To keep the span but hide a secret in the URL, reach for `redactUrl` instead (below).
111
+
112
+
## Redacting URLs
113
+
114
+
`ignore` drops the whole span. When you want to keep the span but strip a secret from the URL, use `redactUrl` — it rewrites the value stored as `url.full` while leaving `server.address` / `server.port` (derived from the original URL) and the rest of the span intact.
`sanitizeUrl()` follows the OpenTelemetry URL semantic conventions: it replaces sensitive query-parameter values and `user:pass@` credentials with the literal `REDACTED`, keeping the parameter key (`token=REDACTED`). It also redacts a built-in default list (`X-Amz-Signature`, `X-Amz-Credential`, `X-Amz-Security-Token`, `sig`, `X-Goog-Signature`). Pass your own names in `params`, or write any `(url) => string` function for full control — including stripping tokens from path segments.
125
+
126
+
The redactor only changes what telemetry records; the real request still uses the original URL.
127
+
128
+
On Node, setting `redactUrl` forces the `globalThis.fetch` wrap instead of the native undici instrumentation (which has no hook to rewrite `url.full`), trading undici's richer attributes for the redaction — the same tradeoff as static `attributes`.
111
129
112
130
## Automatic OTLP endpoint exclusion
113
131
@@ -172,14 +190,14 @@ Native fetch tracing requires Node 20.6 or newer (the undici instrumentation's f
172
190
173
191
Fetch spans include `url.full`, which includes the query string.
174
192
175
-
Do not put secrets, tokens, emails, or phone numbers in query strings. If you must call an endpoint with sensitive query parameters, use`ignore` to skip that URL or sanitize upstream.
193
+
Prefer keeping secrets, tokens, emails, and phone numbers out of query strings. When you can't, use [`redactUrl`](#redacting-urls) to strip them while keeping the span, or`ignore` to skip the URL entirely.
176
194
177
-
Thrown fetch error messages are sanitized before being used as span status messages by the `globalThis.fetch` wrap (Bun, or `mode: "global"`). The native undici instrumentation on Node does not scrub status messages — use `mode: "global"` if you need that scrubbing on Node. URL attributes are not sanitized on either path.
195
+
Thrown fetch error messages are sanitized before being used as span status messages by the `globalThis.fetch` wrap (Bun, or `mode: "global"`). The native undici instrumentation on Node does not scrub status messages — use `mode: "global"` if you need that scrubbing on Node. Unless you set `redactUrl`, URL attributes are recorded as-is on both paths.
178
196
179
197
## Best practices
180
198
181
199
- Prefer automatic setup-managed instrumentation for services.
182
-
- Use `ignore` for health checks and sensitive URLs.
183
-
- Keep secrets out of query strings.
200
+
- Use `ignore` for health checks; use `redactUrl` to strip secrets from traced URLs.
201
+
- Keep secrets out of query strings where you can.
184
202
- Call `shutdown()` or `unpatch()` in tests to restore global fetch.
185
203
- Install setup before code starts making outbound requests.
Copy file name to clipboardExpand all lines: docs/guides/pii-scrubbing.mdx
+23-10Lines changed: 23 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,11 +3,12 @@ title: "PII scrubbing"
3
3
description: "Use the sanitize helpers and understand where automatic scrubbing does and does not happen."
4
4
---
5
5
6
-
The package exports three helpers for masking common personally identifiable information:
6
+
The package exports helpers for masking common personally identifiable information and URL secrets:
7
7
8
8
-`sanitizeEmail(input)`
9
9
-`sanitizePhone(input)`
10
10
-`sanitizeErrorMessage(input)`
11
+
-`sanitizeUrl(input, options?)`
11
12
12
13
They are intentionally small and predictable. They are not a full data-loss-prevention system.
13
14
@@ -79,7 +80,7 @@ The package does not sanitize:
79
80
- log attributes
80
81
- logger exception attributes
81
82
- span attributes
82
-
- fetch `url.full`
83
+
- fetch `url.full` (opt in with the `redactUrl` option — see [URL safety](#url-safety))
83
84
- response bodies
84
85
- request bodies
85
86
- headers
@@ -123,21 +124,33 @@ Prefer stable internal identifiers over emails or phone numbers.
123
124
124
125
## URL safety
125
126
126
-
Fetch spans include `url.full`, including query parameters.
127
+
Fetch spans include `url.full`, including query parameters. When a URL carries a token, API key, or other secret, redact it with the `redactUrl` fetch option so the span keeps everything except the secret. The `sanitizeUrl()` helper implements semantic-convention redaction — sensitive query-parameter values and `user:pass@` credentials are replaced with the literal `REDACTED`, with the key preserved:
with the sensitive value in a request body or header that your telemetry pipeline does not record. If you cannot avoid sensitive query strings, configure fetch instrumentation to ignore those URLs.
151
+
`redactUrl` keeps the span — unlike `ignore`, which drops it entirely. On Node it forces the `globalThis.fetch` wrap, because the native undici instrumentation has no hook to rewrite `url.full`.
152
+
153
+
Even with redaction available, prefer keeping secrets out of URLs in the first place — put the sensitive value in a request body or header your telemetry pipeline does not record. Reserve `ignore` for URLs you do not want traced at all.
Copy file name to clipboardExpand all lines: docs/reference/api.mdx
+42Lines changed: 42 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,6 +15,7 @@ import {
15
15
sanitizeEmail,
16
16
sanitizeErrorMessage,
17
17
sanitizePhone,
18
+
sanitizeUrl,
18
19
setLogLevel,
19
20
setupOtel,
20
21
withSpan,
@@ -185,11 +186,14 @@ function instrumentFetch(options?: InstrumentFetchOptions): FetchInstrumentation
185
186
interfaceFetchSpanOptions {
186
187
ignore?: (url:string) =>boolean;
187
188
attributes?:Attributes;
189
+
redactUrl?: (url:string) =>string;
188
190
}
189
191
```
190
192
191
193
`ignore` skips spans for matching URLs. `attributes` (the OpenTelemetry `Attributes` type) merges static attributes into every span this instrumentation produces — useful for tagging an SDK's traffic, such as `{ "peer.service": "openai" }`.
192
194
195
+
`redactUrl` rewrites the URL before it is stored as `url.full`, so you can strip secrets from the query string or path while keeping the span (unlike `ignore`, which drops the span entirely). `server.address` / `server.port` are still derived from the original URL. Pair it with [`sanitizeUrl`](#sanitizeurl-url-options). On Node, setting `redactUrl` forces the `globalThis.fetch` wrap, because the native undici instrumentation has no hook to rewrite `url.full`.
196
+
193
197
### `InstrumentFetchOptions`
194
198
195
199
```ts
@@ -237,6 +241,7 @@ function createInstrumentedFetch(
237
241
238
242
- Wraps the given fetch so each non-ignored request creates a client span with HTTP attributes and W3C trace-context injection.
239
243
- Merges `options.attributes` into every span.
244
+
- Applies `options.redactUrl` to the stored `url.full`, if provided.
240
245
- Returns the fetch function directly — there is no global lifecycle and no `unpatch()`.
241
246
- Is idempotent: passing an already-instrumented fetch returns it unchanged.
242
247
- Always uses the wrapper technique, so it works identically on Bun and Node.
@@ -288,6 +293,43 @@ sanitizeErrorMessage("contact foo.bar@example.com or +13315553374");
288
293
// "contact fo***@e***.com or +133xxxx3374"
289
294
```
290
295
296
+
## `sanitizeUrl(url, options)`
297
+
298
+
Redacts secrets from a URL before it is recorded as a span attribute, following the OpenTelemetry URL semantic conventions: sensitive query-parameter values and `user:pass@` credentials are replaced with the literal `REDACTED`, with the key preserved (`sig=REDACTED`). Non-sensitive parameters and the path are left intact.
299
+
300
+
```ts
301
+
function sanitizeUrl(url:string, options?:SanitizeUrlOptions):string;
302
+
303
+
interfaceSanitizeUrlOptions {
304
+
params?:string[];
305
+
redactDefaults?:boolean;
306
+
}
307
+
```
308
+
309
+
-`params` — additional query-parameter names to redact, on top of the built-in list. Case-sensitive, matching the semantic conventions.
310
+
-`redactDefaults` — redact the built-in semconv list (`X-Amz-Signature`, `X-Amz-Credential`, `X-Amz-Security-Token`, `sig`, `X-Goog-Signature`) and `user:pass@` credentials. Defaults to `true`.
311
+
312
+
Unparseable input — and input with nothing to redact — is returned unchanged.
313
+
314
+
It is designed to pair with the `redactUrl` fetch option:
0 commit comments