Skip to content

Privacy

Consent decides whether a record is collected; the privacy rules decide what is in it. They run before a record is queued - in the browser script and in the .NET pipeline alike - so secrets in URLs, e-mail addresses in error messages and card numbers in logs are gone before anything leaves the device or the process. The defaults are on; most apps only ever add to them.

See it happen

Each button below makes a real request, or throws a real error, carrying something that must not be recorded. Open a record in the feed to see what was actually exported: the server received the original URL, the telemetry did not.

Livethe privacy rules
The page usedThe telemetry carries
/api/demo/echo?token=secret&[email protected]&q=ok.../api/demo/echo?token=REDACTED&email=REDACTED&q=ok
/api/demo/[email protected]&from=203.0.113.7.../api/demo/echo?q=[email]&from=[ip]
/api/demo/echo#access_token=abc123&token_type=bearer.../api/demo/echo
Payment failed for [email protected] with card 4111 1111 1111 1111Payment failed for [email] with card [card]
The live feed starts once the page is interactive.

Nothing yet - press a button, then open the record and read its url.full or exception.message.

Query parameters

The values of the parameters in RedactQueryParameters are replaced with REDACTED in every URL attribute (url.full, page.referrer, ...), matched case-insensitively and after URL decoding. The parameter stays, so a dashboard can still tell the requests apart. The default list:

tokencodepasswordpwdsecretkeyapikeyapi_keyauthauthorizationsessionsidsigsignatureemailaccess_tokenid_tokenrefresh_token

StripQueryString (off) drops the query entirely, for apps whose query strings are mostly personal. StripFragment (on) drops the #... part, because OAuth implicit and hybrid flows return tokens there; with it off, parameters in the fragment are redacted like the query's.

builder.Services.AddBitBelemetryServices(options =>
{
    // Add your own parameter names to the defaults.
    options.Privacy.RedactQueryParameters.Add("customerId");
    options.Privacy.RedactQueryParameters.Add("invite");

    options.Privacy.StripQueryString = false;   // the default; true drops every query string
    options.Privacy.StripFragment = true;       // the default
});

Pattern scrubbing

With ScrubPatterns (on) these patterns are replaced wherever free text can carry them - log bodies, exception messages and stack traces, span status messages and URLs. In the browser every string attribute is scrubbed (the session and user ids excepted); on the .NET side, the URL attributes, exception.message, exception.stacktrace, url.query and the log body.

PatternBecomesNotes
E-mail addresses[email]name@domain.tld
JSON Web Tokens[jwt]Three base64url segments starting with eyJ
Bearer tokensBearer [token]Bearer followed by a token, case-insensitive
Payment card numbers[card]13 to 19 digits, optionally grouped with spaces or dashes, and only when the Luhn checksum holds - so order numbers and timestamps survive
IPv4 addresses[ip]Except after Version=, so .NET assembly versions in stack traces stay readable, and except the host of a URL and server.address: they name the server that was called, not the user

Scrubbing is a safety net, not a design: a value you know is personal should never be logged in the first place, and structured attributes you control are better removed by name (below) than by pattern.

Blocked attributes and the user agent

BlockedAttributes removes attributes by exact key from every record and span event, on both sides: the browser script gets the list in its configuration, and the .NET pipeline applies it to its own logs and spans, IBelemetry calls, and browser records relayed to it. Use a processor on the .NET side for anything more selective.

CollectUserAgent (on) puts the browser's user agent string in the browser resource as user_agent.original. Turn it off to keep only the coarse browser.* attributes (language, platform, mobile, brands). On Azure Monitor, MaskIp (on) keeps the ingestion service from deriving a location from the client IP; Belemetry never records the client IP itself.

builder.Services.AddBitBelemetryServices(options =>
{
    options.Privacy.ScrubPatterns = true;                    // the default
    options.Privacy.BlockedAttributes.Add("client.address"); // exact keys, browser and .NET
    options.Privacy.CollectUserAgent = false;                // drop user_agent.original
});

API

BelemetryPrivacyOptions Class

Data-minimization rules applied to every record, on both the browser and the .NET side, before it is queued.

Name Type and default Description
RedactQueryParameters List<string>= ["token", "code", "password", "pwd", "secret", "key", "apikey", "api_key", "auth", "authorization", "session", "sid", "sig", "signature", "email", "access_token", "id_token", "refresh_token"] Query-string parameters (case-insensitive) whose values are replaced with REDACTED in every URL attribute.
StripQueryString bool= false Whether to drop query strings from URLs entirely. Defaults to false.
StripFragment bool= true Whether to drop URL fragments (which may carry OAuth tokens). Defaults to true.
ScrubPatterns bool= true Whether messages, stack traces and URLs are scrubbed of e-mail addresses, JWTs, bearer tokens, card numbers (Luhn-checked) and IPv4 addresses. Defaults to true.
CollectUserAgent bool= true Whether the browser's user agent string is part of the browser resource. Defaults to true.
BlockedAttributes List<string>= [] Attribute keys removed from every record (exact match).
An unhandled error has occurred. Reload 🗙