OTel PII & Secrets Leak Detector
Pricing
from $250.00 / 1,000 telemetry batch scanneds
OTel PII & Secrets Leak Detector
Scan OpenTelemetry traces, metrics and logs for leaked secrets and PII before they reach your observability backend. Get a risk report, a Collector redaction config and sanitized telemetry. Standby HTTP API included.
Pricing
from $250.00 / 1,000 telemetry batch scanneds
Rating
0.0
(0)
Developer
Justin Hagerty
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
12 days ago
Last modified
Categories
Share
Scan OpenTelemetry traces, logs and metrics for API keys, tokens, passwords, emails, card numbers and other personal data before they reach your observability backend. You get a risk report, a ready-to-review OpenTelemetry Collector redaction config, and a sanitized copy of your telemetry.
Telemetry leaks quietly. An HTTP instrumentation captures the Authorization header. An exception message echoes a database URL with its password. A developer adds user.email to a span "just for debugging", or a log line prints a whole request body containing a card number. A few weeks later that data has been copied to every observability backend, retained for 30 to 400 days and made searchable by hundreds of engineers and third-party vendors.
This Actor reads OTLP/JSON (the format the Collector file exporter, otlphttp with JSON encoding and SDK debug exporters write) and runs 66 rules against every value: resource and scope attributes, span names and attributes, span events (exception.message, exception.stacktrace), links, status messages, log bodies (including nested structured bodies), log attributes, metric names and descriptions, data-point attributes and exemplars. For each leak it tells you which service, scope and attribute key leaks, how often, with what confidence and with which potential regulatory exposure. It then generates the Collector processors that remove the leak, and checks its own output by re-scanning.
The analysis is deterministic. It uses no LLMs and makes no network calls, and raw values are never stored: findings carry redacted previews and salted fingerprints only.
Why use it
- Find leaks before your vendor does. Secrets in telemetry are a common incident source, and a quiet one: logs and traces are rarely access-controlled like databases.
- Reduce compliance exposure. Personal data in telemetry brings your observability stack into scope for GDPR, CCPA, HIPAA or PCI DSS. The report labels potential exposure per regime (this is not legal advice).
- Fix it, not just find it. You get an
attributes+transform(OTTL) +redactionprocessor config wired into a full pipeline, plus SDK-side hints to stop the leak at the source. - Prove the fix. The sanitized copy of your telemetry is re-scanned (self-check), and the generated Collector config is emulated against your data, so the report lists exactly which leaks the config would still miss (for example, keys nested inside structured log bodies).
- Low noise. Checksums (Luhn, IBAN mod-97, ABA, Spanish DNI, Dutch BSN, Brazilian CPF, Aadhaar Verhoeff, PESEL), SSA invalid ranges, numbering-plan rules and context requirements filter out look-alikes such as order IDs, timestamps, trace IDs, UUIDs, pagination tokens and part numbers. On a ~200,000-value synthetic corpus of realistic benign telemetry, no high-confidence finding was a false positive (see Limitations). Well-known test values (
4111 1111 1111 1111,AKIAIOSFODNN7EXAMPLE,example.com,555-01xx, documentation IP ranges) are labelled as test values.
Who it's for
- Platform / observability teams rolling out OpenTelemetry who need a pre-production gate for new instrumentation.
- Security and privacy engineers auditing what actually ends up in Datadog, Grafana, Honeycomb, New Relic, Elastic, Splunk and similar backends.
- Teams in regulated industries (health, fintech, e-commerce) who must keep PHI and cardholder data out of logs.
- Pipelines and AI agents that call it over HTTP (Standby mode) to check a telemetry sample before shipping a change.
What it detects
Group (detectors) | Rules | Examples |
|---|---|---|
secrets | LEAK-001 – LEAK-021, LEAK-062 – LEAK-066 | AWS access/secret keys, GCP service-account keys, Google API keys, Azure account keys and SAS signatures, GitHub/GitLab/Slack/Stripe/Twilio/SendGrid/OpenAI/Anthropic tokens, npm/PyPI tokens, New Relic/Grafana/Datadog keys, Shopify tokens, HashiCorp Vault (hvs./hvb./hvr.) and Terraform Cloud tokens, Hugging Face tokens, Databricks PATs, Mailgun keys (with a Mailgun label), PEM private keys, JWTs (decoded structurally to report the algorithm and which PII claims they carry — never verified), high-entropy values in secret-named attributes, password=/api_key: assignments in free text |
credentials | LEAK-022 – LEAK-031 | Captured Authorization, Cookie/Set-Cookie, x-api-key-style headers; Bearer/Basic credentials in messages (Basic is decoded to confirm user:password); session IDs; passwords in URLs (scheme://user:pass@host) and connection strings; tokens in URL query strings (url.full, http.url, http.target); IDENTIFIED BY '…' / PASSWORD '…' / Redis AUTH literals in db.statement / db.query.text; password-named attributes |
pii-contact | LEAK-032 – LEAK-035 | Emails (also URL-encoded %40), phone numbers (E.164, international, North-American with numbering-plan checks), street addresses (heuristic), names in name-labelled attributes |
pii-identifiers | LEAK-036 – LEAK-042, LEAK-059 – LEAK-061 | US SSNs (SSA rules), checksum-verified national IDs (ES DNI/NIE, NL BSN, UK NINO when labelled or printed AB 12 34 56 C, Brazilian CPF, Indian Aadhaar, Polish PESEL), passport / driver's licence / tax IDs, dates of birth, user.* / enduser.* identity attributes, IMEI (Luhn), advertising IDs |
network | LEAK-043 – LEAK-045 | End-user IPs in client.address, source.address, X-Forwarded-For and similar (public vs. private told apart; personal data under GDPR), public IPs elsewhere, MAC addresses |
financial | LEAK-046 – LEAK-049 | Card numbers (Luhn + issuer prefix/length + printed grouping), CVV/expiry attributes, IBANs (mod-97), bank account / ABA routing numbers |
health | LEAK-050 – LEAK-052 | ICD-10 codes in diagnosis context, medical record / patient / health-plan IDs, health terms near patient context (PHI heuristic) |
location | LEAK-053 | Precise coordinates (≥ 3 decimals) |
free-text (off by default) | LEAK-054 – LEAK-055 | Names in prose (honorifics, customer: First Last), request/response/message bodies captured as attributes |
| — | LEAK-056 | Your own customPatterns |
| — | LEAK-057 – LEAK-058 | Placement: sensitive values in span/metric names (can't be deleted, explode cardinality) or in resource attributes (copied onto every signal) |
Every rule, with severity, rationale and references, is listed in RULES.md.
How to use
In Apify Console
- Open the Actor and paste an OTLP/JSON export into Telemetry (the prefilled demo uses obviously fake values). You can also paste JSON Lines from the Collector
fileexporter into Telemetry as text. - Optionally pick detector groups, regimes, allowlists and custom patterns.
- Run. The Findings view lists each leak by
signal/service/attribute. The Risk report view shows category counts, top leaking keys and potential regulatory exposure. Collector config & sanitized telemetry has the YAML and the cleaned copy.
Capture a sample from your Collector
exporters:file/sample:path: /tmp/otel-sample.jsonl # OTLP/JSON, one export request per lineservice:pipelines:traces: { receivers: [otlp], processors: [batch], exporters: [file/sample, otlp/backend] }logs: { receivers: [otlp], processors: [batch], exporters: [file/sample, otlp/backend] }
Prefer a staging environment with synthetic traffic (see Data handling).
Via the API (curl)
curl -s -X POST "https://api.apify.com/v2/acts/michiganpublictech~otel-pii-leak-detector/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d @- <<'EOF'{"telemetry": {"resourceLogs": [{"resource": {"attributes": [{"key": "service.name", "value": {"stringValue": "checkout"}}]},"scopeLogs": [{"logRecords": [{"body": {"stringValue": "payment failed for jane.doe@example.com card 4111 1111 1111 1111"},"attributes": [{"key": "http.request.header.authorization", "value": {"stringValue": "Basic ZGVtbzpkZW1v"}}]}]}]}]},"redactionStrategy": "balanced"}EOF
Python (apify-client)
import jsonfrom apify_client import ApifyClientclient = ApifyClient("<APIFY_TOKEN>")batches = [json.loads(line) for line in open("/tmp/otel-sample.jsonl")]run = client.actor("michiganpublictech/otel-pii-leak-detector").call(run_input={"telemetryBatches": batches[:20], "failOnSeverity": "high"})for report in client.dataset(run["defaultDatasetId"]).iterate_items():print(report["target"], report["risk_level"], report["summary"])for f in report["findings"]:print(" ", f["severity"], f["rule_id"], f["location"])
Standby mode (HTTP API for pipelines and AI agents)
The Actor supports Actor Standby. Apify keeps a warm instance behind an HTTPS endpoint, so calls skip container start-up. Charging is identical to normal runs.
BASE=https://michiganpublictech--otel-pii-leak-detector.apify.actor# Free: help, rule catalog, detector groupscurl -s "$BASE/" -H "Authorization: Bearer $APIFY_TOKEN"curl -s "$BASE/rules" -H "Authorization: Bearer $APIFY_TOKEN"# Scan: POST a raw OTLP/JSON document...curl -s -X POST "$BASE/scan" -H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \--data @otlp-export.json# ...or an input-shaped object with optionscurl -s -X POST "$BASE/scan" -H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \-d '{"telemetry": {"spans": [{"name": "GET /x", "attributes": {"url.full": "https://a.example.com/?token=abc123def456"}}]},"detectors": ["secrets", "credentials"], "emitSanitizedTelemetry": false}'# → {"items": [<report>], "charged": {"telemetry-scanned": 1, "sanitized-output": 1}}
Endpoints: GET / (health/help; also answers the readiness probe), GET /rules, GET /detectors, POST /scan. Request bodies are limited to 8 MB. Options you don't send fall back to the Standby run's input. In Standby, results are returned in the HTTP response only and nothing is written to a dataset. The server never logs request bodies.
CI: block leaky instrumentation in pull requests
Run your integration tests with the Collector file exporter, then:
- name: Scan telemetry sample for PII & secretsrun: |jq -Rs '{telemetryText: ., failOnSeverity: "high", sampleRetention: "none"}' otel-sample.jsonl > input.jsoncurl -sf -X POST "https://api.apify.com/v2/acts/michiganpublictech~otel-pii-leak-detector/runs?token=${{ secrets.APIFY_TOKEN }}&waitForFinish=300" \-H "Content-Type: application/json" -d @input.json | jq -e '.data.status == "SUCCEEDED"'
failOnSeverity marks the run as FAILED when any finding reaches that severity. REPORT.sarif can be uploaded to GitHub code scanning.
Input
| Field | Type | Default | Description |
|---|---|---|---|
telemetry | object (or list) | demo | One OTLP/JSON export request (resourceSpans / resourceLogs / resourceMetrics; snake_case and legacy instrumentationLibrary* keys accepted), or a simplified {resource, scope, spans, logs, metrics} object with plain attribute maps. A list counts as several batches. |
telemetryBatches | array | — | Several batches; each an OTLP/JSON document or {"name": "...", "telemetry": {...}}. One report per batch. |
telemetryText | string | — | OTLP/JSON text, a JSON array, or JSON Lines (one batch per line). Invalid lines become free failed reports. |
detectors | array | all except free-text | Detector groups to run (see the table above). |
regimes | array | all | GDPR, CCPA, HIPAA, PCI_DSS — only used to label potential exposure. |
minConfidence | low/medium/high | low | Drop detections below this confidence. |
testValues | downgrade/report/ignore | downgrade | Known test values are reported with LOW severity by default. |
allowlistAttributes | array | — | Keys never reported; exact or glob (app.public_*), matched against the full key and its last segment. |
allowlistPatterns | array | — | Regexes; a detection whose text (or whole value) fully matches is ignored. ReDoS-screened. |
customPatterns | array | — | Up to 20 {name, pattern, severity, action} detectors. ≤ 300 chars; nested quantifiers (a+)+, overlapping repeated alternations `(a |
emitSanitizedTelemetry | boolean | true | Return a sanitized copy plus a re-scan self-check. |
generateCollectorConfig | boolean | true | Generate the Collector redaction config, SDK hints and the coverage emulation. |
redactionStrategy | balanced/mask/hash/drop | balanced | balanced: delete credential headers, hash identifiers (user.email, client.address…), mask the rest. hash: hash whole values where possible (keeps joinability). drop: delete leaking attributes and emit a redaction allowed_keys allowlist. |
collectorFlavor | contrib/contrib-legacy | contrib | Target otelcol-contrib version family: contrib for current releases (verified 0.148, 0.150), contrib-legacy for 0.115-era Collectors (see Collector version notes). |
hashSalt | secret string | random per run | Salts fingerprints and [HASH:…] tokens so they're comparable across runs. |
sampleRetention | redacted/none | redacted | none removes previews and fingerprints from findings (counts and locations only). |
minSeverity | severity | info | Hide findings below this severity (risk counts still include them). |
failOnSeverity | none/critical/high/medium/low | none | CI gate. |
disabledRules | array | — | Rule IDs to skip (Pro plan and above). |
licenseKey | secret string | — | Enterprise / prepaid license key. Store users don't need one. |
Output
One dataset item per batch, using the same report shape as all our Actors. The key-value store also gets OUTPUT (run summary), REPORT.md and REPORT.sarif. Abridged example from the prefilled demo:
{"product": "otel-pii-leak-detector","target": "telemetry","score": 26,"risk_level": "critical","summary": {"critical": 2, "high": 6, "medium": 6, "low": 10, "info": 0},"findings": [{"rule_id": "LEAK-022","title": "Authorization header captured as an attribute","severity": "critical","kind": "observation","category": "credential","location": "traces/checkout/span.attributes[\"http.request.header.authorization\"]","confidence": 0.95,"evidence": {"occurrences": 1, "distinct_values": 1, "confidence": "high", "detector": "authorization-header","scopes": ["io.opentelemetry.http"],"example_path": "$.resourceSpans[0].scopeSpans[0].spans[0].attributes[2].value.arrayValue.values[0].stringValue","potential_regulatory_exposure": ["CCPA"],"redacted_previews": ["Bear…Ww (187 chars)"], "value_fingerprints": ["5d1c0a9e44b2"],"details": {"scheme": ["bearer"]}},"recommendation": "Remove Authorization from captured request headers and delete the attribute in the Collector; …"},{"rule_id": "LEAK-017", "title": "JWT payload carries personal data claims", "severity": "high","evidence": {"details": {"pii_claims": ["email", "name"]}, "redacted_previews": ["eyJ…"], "...": "..."}},{"rule_id": "LEAK-046", "title": "Payment card number (PAN) in telemetry (known test value)", "severity": "low","location": "logs/payments/log.attributes[\"payment.card_number\"]","evidence": {"redacted_previews": ["************1111"], "known_test_value": true, "...": "..."}}],"data": {"signals": ["traces", "logs", "metrics"],"scanned": {"spans": 2, "span_events": 1, "log_records": 2, "data_points": 1, "records": 5, "values_scanned": 34,"bytes": 3459, "billable_units": 1},"risk": {"total_occurrences": 24,"by_category": {"credential": {"findings": 2, "occurrences": 2, "max_severity": "critical"}, "...": {}},"by_service": [{"service": "checkout", "occurrences": 15, "categories": ["credential", "secret", "..."],"max_severity": "critical"}],"top_leaking_keys": [{"signal": "traces", "location": "span.attributes[\"http.request.header.authorization\"]","occurrences": 3, "rules": ["LEAK-016", "LEAK-017", "LEAK-022"], "max_severity": "critical"}],"regulatory_exposure": {"GDPR": {"potential_exposure": true, "occurrences": 18, "categories": ["..."]},"PCI_DSS": {"potential_exposure": true, "...": "..."}},"legal_notice": "Regulatory labels … indicate POTENTIAL exposure … not legal advice …"},"collector_config": "# Generated by OTel PII & Secrets Leak Detector — REVIEW BEFORE DEPLOYING.\n…","collector_config_coverage": {"residual_high_confidence": 3, "residual_locations": ["logs:payments:log.body.patient.dob", "..."]},"sdk_hints": ["Don't capture credential-bearing HTTP headers: …"],"sanitized_telemetry": {"resourceSpans": ["… same structure, values masked / hashed / deleted …"]},"sanitization": {"values_changed": 18, "masked_spans": 16, "hashed_values": 5, "deleted_attributes": 1},"self_check": {"residual_high_confidence": 0, "shared_secret_scanner_hits": 0, "passed": true}},"disclaimer": "Automated pattern-based scan of the telemetry you supplied; …"}
Generated Collector config (excerpt)
processors:memory_limiter: {check_interval: 1s, limit_percentage: 80, spike_limit_percentage: 25}attributes/leak_keys:actions:- key: http.request.header.authorizationaction: deletetransform/leak:error_mode: ignoretrace_statements:- context: spanstatements:- set(attributes["user.id"], SHA256(Concat([attributes["user.id"], "${env:PII_HASH_SALT}"], ""))) where attributes["user.id"] != nil- replace_all_patterns(attributes, "value", "eyJ[A-Za-z0-9_-]{8,}[.]eyJ[A-Za-z0-9_-]{8,}[.][A-Za-z0-9_-]*", "[REDACTED:jwt]")- replace_all_patterns(attributes, "value", "([?&;](access_token|...|api_key|...)=)[^&# ]+", "$${1}[REDACTED]")- replace_pattern(name, "[A-Za-z0-9._%+-]+(@|%40)...", "[REDACTED:email]")log_statements:- context: logstatements:- replace_pattern(body, "(AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}", "[REDACTED:aws_key]") where IsString(body)redaction/leak:allow_all_keys: trueignored_keys: [user.id] # hashed keys: keep the hash instead of masking it to ****blocked_values: ["(AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}", "…"]blocked_key_patterns: ["(?i).*(authorization|password|passwd|pwd|passphrase).*"]summary: infobatch: {send_batch_size: 8192, timeout: 5s}service:pipelines:traces:receivers: [otlp]processors: [memory_limiter, attributes/leak_keys, transform/leak, redaction/leak, batch]exporters: [otlp_grpc/backend] # contrib-legacy: otlp/backend
How it is built:
attributes/leak_keysdeletes credential-bearing keys observed on spans, logs and data points.transform/leak(OTTL) hashes identifiers with a saltedSHA256(Concat([…, "${env:PII_HASH_SALT}"], ""))instead of deleting them, so you can still count distinct users. It deletes or masks keys where the attributes processor can't reach them (resource, scope, span events) and rewrites values withreplace_all_patterns/replace_pattern(attributes, span names, status messages, string log bodies, metric descriptions).redaction/leakis a safety net. Itsblocked_valuesare the patterns above plus a baseline of high-impact secret patterns. It also getsblocked_key_patterns(contrib) andignored_keysfor the keys the transform hashed — without that, a blocked key pattern would overwrite the hash with****.- With
redactionStrategy: dropthe redaction processor runs withallow_all_keys: falseand anallowed_keyslist of every key observed in your sample that didn't leak (resource and scope attributes, and log-body map keys forcontrib, included;service.nameand the otherservice.*identity keys always). Any key that is not on the list is removed, including resource attributes, so add the keys your services emit that the sample didn't contain. - All patterns are written in the RE2 ∩ Python subset without quotes or backslashes, so they survive YAML and OTTL quoting unchanged.
collector_config_coverageis a Python emulation of the config on your data, followed by a re-scan. It lists what the config would not catch (typically values inside array or map attributes, which neither OTTL'sreplace_all_patternsnor the redaction processor rewrite, and — oncontrib-legacy— nested log-body maps). Fix those with the SDK hints. The emulation was checked against real Collectors (see below) and predicted their output exactly, apart from the hash format ([HASH:…]vs SHA-256 hex) and hashed arrays (the Collector hashes the whole stringified array).
Collector version notes
Generated configs were checked with otelcol-contrib validate on 0.115.1, 0.148.0 and 0.150.1, and run end to end (an otlpjsonfile receiver feeding the generated processors into a file exporter) on 0.115.1 and 0.150.1 with every redaction strategy. What we found:
0.115.1 (contrib-legacy) | 0.148.0 / 0.150.1 (contrib) | |
|---|---|---|
redaction processor in traces, logs and metrics pipelines | yes | yes |
blocked_key_patterns | rejected ('' has invalid keys) | yes |
ignored_keys, allowed_keys, allow_all_keys, summary | yes | yes |
| Redaction masks resource, span, log-record and data-point attributes | yes | yes |
| … also scope and span-event attributes, and log bodies (strings and nested maps/arrays) | no | yes |
allow_all_keys: false removes non-listed keys from resource attributes | yes | yes (also scope, span-event, log-body keys) |
| Exporter type | otlp (no otlp_grpc) | otlp_grpc (otlp logs a deprecation warning on 0.150.1) |
Behaviour common to all three versions:
- The OTTL used here parses in every version:
set,delete_key,replace_pattern,replace_all_patterns(attributes, "value", …),SHA256(Concat([…], "")),IsString(body)and thecontext:+statements:block form (no deprecation warning on 0.150.1). $$escapes$in Collector config files, so$${1}reaches OTTL as a${1}capture-group reference. That worked as intended on all versions. Hashing needs thePII_HASH_SALTenv var (alphanumeric);SHA256produces 64 hex characters.- Neither the transform nor the redaction processor rewrites values inside array or map attributes, and the redaction processor never touches span names, status messages, links or metric descriptions (the transform covers those that leaked).
- The redaction processor runs after the transform, so it can re-mask what the transform already replaced: on 0.150.1,
password=[REDACTED]in a log body becomes****because a blocked value matches it. The data stays hidden either way.
Pick contrib-legacy for 0.115-era Collectors and contrib for current releases. Versions between 0.116 and 0.147 were not tested: if validate rejects blocked_key_patterns or otlp_grpc, use contrib-legacy. Always review and run otelcol-contrib validate --config=<file> before deploying. Generated configs are a starting point, not a guarantee.
Regulatory mapping
| Regime | Labelled when we detect | Basis |
|---|---|---|
| GDPR | contact details, identifiers, IPs/MACs/device IDs, location, financial, health (Art. 9 special category), JWTs with PII claims | personal data (Art. 4) |
| CCPA / CPRA | the above plus account credentials (password, auth headers, session tokens) | personal and sensitive personal information |
| HIPAA | health detectors (ICD-10, MRN, PHI heuristics); identifiers are labelled HIPAA only when health data appears in the same batch | PHI = health information + identifiers |
| PCI DSS | card numbers (PAN) and card verification data | cardholder data / sensitive authentication data |
Not legal advice. Labels mean potential exposure based on the data types detected in your sample. They are not a compliance assessment or certification. Involve your privacy, security and compliance teams.
Data handling
- Prefer synthetic or sampled telemetry. Use staging traffic, test accounts and test cards where possible. The scanner tells you whether a field captures a kind of data even when the value is a known test value.
- What we store: findings with redacted previews (for example
AKIA…LE (20 chars),j***@***.com,************1111; passwords always****) and salted HMAC fingerprints (12 hex chars). SetsampleRetention: noneto drop previews and fingerprints entirely. The sanitized telemetry contains masked/hashed values only, and the self-check verifies that. The generated config contains patterns, never your values. - What we never store or log: raw secret or PII values. Your input itself is stored by Apify as the run's
INPUTrecord, like any Actor input. - Retention: Apify's default storage retention applies to the run's input, dataset and key-value store (unnamed storages expire automatically; see the Apify docs on data retention). Delete the run when you are done if your sample was sensitive.
hashSaltandlicenseKeyare secret inputs (encrypted by Apify) and never echoed into output.
Pricing
Pay per event, with no subscription.
| Event | Price | When |
|---|---|---|
telemetry-scanned | $0.25 | Per scanned batch. One event covers up to 5 MB of OTLP/JSON and up to 10,000 records (spans + log records + metric data points). Larger batches count max(ceil(MB / 5), ceil(records / 10,000)) events, up to 20 (bigger batches are refused for free; split them). |
sanitized-output | $0.10 | Per batch, only when a remediation pack (sanitized telemetry and/or Collector config) with at least one change was delivered. |
Examples: one 300-span trace export with leaks costs $0.25 + $0.10 = $0.35. A clean batch costs $0.25. A 25,000-span batch with remediation costs 3 × $0.25 + $0.10 = $0.85. Batches that can't be parsed, and batches we refuse, are never charged. The Actor checks your spending limit before each batch and drops the remediation pack, rather than charging partially, if only the scan fits.
Plans and licensing
Store users need nothing extra. Sanitized telemetry and the generated config are included on the Apify Store (marketplace) plan. Self-hosted/community runs produce the report only. disabledRules needs a Pro license. Enterprise and prepaid keys go in licenseKey.
Limitations
- Heuristic detection. Names, addresses and health context use heuristics (low/medium confidence), and novel token formats without a recognisable shape are only caught when the attribute name is secret-like. False positives and false negatives are possible. Check
confidenceon each finding. - Measured precision (synthetic data). On a generated corpus of ~197,000 benign values (trace IDs, UUIDs, digests, k8s names, versions, timestamps, base64 cursors, URLs, order and invoice numbers, part numbers, stack traces…) plus 603 planted leaks, every planted leak was found, no high-confidence finding was a false positive, and about 1 in 7,500 benign values raised a medium-confidence finding (mostly formatted CPF look-alikes). Real telemetry differs from synthetic data, so treat these figures as a regression baseline rather than a guarantee. Use
minConfidence: highfor the quietest output. - Only what you send. The report covers the sample, not everything your services emit. Rare code paths may leak in ways a sample doesn't show.
- OTLP/JSON only. Protobuf, Jaeger and Zipkin formats aren't parsed. Use the Collector
fileexporter (JSON) to capture.bytesValueattributes are not decoded. - Values longer than 64 KiB are scanned up to that length, and at most 500,000 values per batch.
- The Collector config can't reach everything. Nested map values and metric names need source fixes. The coverage emulation tells you which locations are affected.
- Collector patterns have no checksums or context. OTTL/RE2 can't run Luhn, mod-97, mod-11 or Verhoeff, so the generated card, IBAN, SSN, CPF and national-ID patterns may also mask look-alike values (card patterns are restricted to issuer prefixes). The Mailgun pattern can mask
key-<32 hex>cache keys. Review them against your data.
FAQ
Does it call any external service or LLM? No. Detection, JWT decoding and config generation are local and deterministic.
Why is 4111 1111 1111 1111 reported at all? It's a test value, so severity is lowered to LOW. But a card-shaped value in payment.card_number means the attribute captures card numbers, and real ones would leak the same way. Set testValues: ignore to hide them.
Will hashing user.email keep my dashboards working? Yes for counting and grouping, because the same input gives the same salted hash. Hashing is pseudonymisation, not anonymisation.
Why do I get both LEAK-022 and LEAK-016 for one header? LEAK-022 says the header is captured at all. LEAK-016/017 describe the token inside it (and whether its payload carries personal data).
Can I add my own identifiers? Yes: customPatterns (for example CUST-[0-9]{8}) with severity and action. They're ReDoS-screened and added to the generated config when RE2-compatible.
Is my data used for anything else? No. See Data handling.
Roadmap
Planned detectors, outputs and known caveats are tracked in STATUS.md.
Changelog
See CHANGELOG.md.