@link-foundation/sensitive-data-sanitizer 0.19.3 → 0.21.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.
- package/CHANGELOG.md +12 -0
- package/README.md +22 -2
- package/bin/sensitive-data-sanitizer.js +50 -28
- package/docs/API.md +355 -16
- package/docs/COVERAGE.md +19 -9
- package/docs/RULES.md +218 -0
- package/examples/second-pass-placeholders.mjs +28 -0
- package/package.json +12 -3
- package/src/allow-list.js +93 -0
- package/src/confidence.js +24 -9
- package/src/context.js +287 -40
- package/src/credential-spans.js +180 -0
- package/src/credentials-entry.d.ts +23 -0
- package/src/credentials-entry.js +62 -0
- package/src/credentials.js +51 -0
- package/src/crypto-addresses.js +74 -0
- package/src/detection.js +87 -41
- package/src/document-person-cues.js +1 -1
- package/src/encoded.js +137 -51
- package/src/encrypt.js +95 -0
- package/src/engines.js +33 -10
- package/src/entities.js +4 -3
- package/src/exempt.js +72 -0
- package/src/fake-memory.js +32 -0
- package/src/fake.js +33 -29
- package/src/features.js +29 -0
- package/src/files.js +57 -12
- package/src/gitleaks.js +82 -27
- package/src/html-tags.js +8 -0
- package/src/identifier-detector.js +36 -0
- package/src/identity-types.js +63 -0
- package/src/identity.js +3 -47
- package/src/index.d.ts +157 -3
- package/src/index.js +13 -0
- package/src/known.js +33 -15
- package/src/national-checks.js +209 -0
- package/src/national.js +310 -92
- package/src/pattern-detector.js +219 -0
- package/src/personal-context.js +3 -2
- package/src/personal-evidence.js +233 -0
- package/src/personal-features.js +39 -0
- package/src/personal.js +66 -10
- package/src/placeholder-types.js +154 -4
- package/src/placeholders.js +127 -45
- package/src/projection.js +78 -26
- package/src/proper-names.js +48 -5
- package/src/prose-words.js +120 -0
- package/src/record-worker.js +1 -0
- package/src/rule-catalog.js +426 -0
- package/src/rules.js +8 -0
- package/src/sanitizer.js +133 -23
- package/src/sarif.js +127 -0
- package/src/stream-worker.js +1 -0
- package/src/strict-attributes.js +48 -4
- package/src/strict.js +1 -1
- package/src/structured.js +2 -0
- package/src/token-checksum.js +49 -0
- package/src/transforms.js +146 -33
- package/src/vendor/betterleaks/LICENSE +21 -0
- package/src/vendor/betterleaks/provenance.json +11 -0
- package/src/vendor/betterleaks/rules-0.json +463 -0
- package/src/vendor/betterleaks/rules-1.json +454 -0
- package/src/vendor/betterleaks/rules-2.json +461 -0
- package/src/vendor/betterleaks/rules-3.json +455 -0
- package/src/vendor/betterleaks/rules-4.json +483 -0
- package/src/vendor/betterleaks/rules-5.json +35 -0
- package/src/vendor/entities/aws.json +1 -1
- package/src/vendor/entities/azure.json +6 -2
- package/src/vendor/entities/google.json +2 -1
- package/src/vocabulary.js +27 -1
- package/src/worker-options.js +1 -0
package/docs/RULES.md
CHANGED
|
@@ -12,16 +12,221 @@ Regenerate with `node experiments/issue-118/generate-rule-catalog.mjs`.
|
|
|
12
12
|
- `anthropic`
|
|
13
13
|
- `authorization`
|
|
14
14
|
- `aws`
|
|
15
|
+
- `betterleaks/abuseipdb-api-key.1`
|
|
16
|
+
- `betterleaks/aikido-ci-token`
|
|
17
|
+
- `betterleaks/aikido-client-secret`
|
|
18
|
+
- `betterleaks/airtable-oauth-token`
|
|
19
|
+
- `betterleaks/aiven-auth-token`
|
|
20
|
+
- `betterleaks/alibaba-sts-access-key-secret`
|
|
21
|
+
- `betterleaks/amplitude-secret-key`
|
|
22
|
+
- `betterleaks/apify-api-token`
|
|
23
|
+
- `betterleaks/apollo-api-key.1`
|
|
24
|
+
- `betterleaks/asaas-api-token`
|
|
25
|
+
- `betterleaks/assemblyai-api-key`
|
|
26
|
+
- `betterleaks/auth0-client-secret.1`
|
|
27
|
+
- `betterleaks/azure-app-configuration-connection-string`
|
|
28
|
+
- `betterleaks/azure-servicebus-connection-string`
|
|
29
|
+
- `betterleaks/azure-storage-account-key`
|
|
30
|
+
- `betterleaks/bitbucket-data-center-token`
|
|
31
|
+
- `betterleaks/bitly-access-token`
|
|
32
|
+
- `betterleaks/bitrise-access-token`
|
|
33
|
+
- `betterleaks/box-api-access-token`
|
|
34
|
+
- `betterleaks/brave-search-api-key`
|
|
35
|
+
- `betterleaks/browserstack-access-key.1`
|
|
36
|
+
- `betterleaks/buildkite-service-token`
|
|
37
|
+
- `betterleaks/buildkite-user-access-token`
|
|
38
|
+
- `betterleaks/canadian-digital-service-notify-api-key`
|
|
39
|
+
- `betterleaks/canva-client-secret`
|
|
40
|
+
- `betterleaks/cartesia-api-key.1`
|
|
41
|
+
- `betterleaks/cerebras-api-key`
|
|
42
|
+
- `betterleaks/checkout-secret-key`
|
|
43
|
+
- `betterleaks/circleci-personal-token`
|
|
44
|
+
- `betterleaks/circleci-project-token`
|
|
45
|
+
- `betterleaks/civo-api-key`
|
|
46
|
+
- `betterleaks/clerk-secret-key`
|
|
47
|
+
- `betterleaks/clickup-personal-api-token`
|
|
48
|
+
- `betterleaks/cloudflare-api-key.2`
|
|
49
|
+
- `betterleaks/cloudinary-api-secret`
|
|
50
|
+
- `betterleaks/cloudsmith-api-key`
|
|
51
|
+
- `betterleaks/cockroachlabs-cloud-api-key`
|
|
52
|
+
- `betterleaks/configcat-sdk-key`
|
|
53
|
+
- `betterleaks/configcat-sdk-key-extended`
|
|
54
|
+
- `betterleaks/couchbase-capella-api-key`
|
|
55
|
+
- `betterleaks/coveralls-personal-api-token`
|
|
56
|
+
- `betterleaks/crates-io-api-key`
|
|
57
|
+
- `betterleaks/cursor-api-key`
|
|
58
|
+
- `betterleaks/databento-api-key`
|
|
59
|
+
- `betterleaks/datadog-api-key`
|
|
60
|
+
- `betterleaks/datadog-application-key`
|
|
61
|
+
- `betterleaks/datagov-api-key`
|
|
62
|
+
- `betterleaks/datastax-astra-application-token`
|
|
63
|
+
- `betterleaks/deepgram-api-key`
|
|
64
|
+
- `betterleaks/deepseek-api-key`
|
|
65
|
+
- `betterleaks/deno-account-token`
|
|
66
|
+
- `betterleaks/devcycle-client-sdk-key`
|
|
67
|
+
- `betterleaks/devcycle-mobile-sdk-key`
|
|
68
|
+
- `betterleaks/devcycle-server-sdk-key`
|
|
69
|
+
- `betterleaks/devin-personal-api-key`
|
|
70
|
+
- `betterleaks/devin-service-api-key`
|
|
71
|
+
- `betterleaks/devin-service-user-token`
|
|
72
|
+
- `betterleaks/disqus-api-key`
|
|
73
|
+
- `betterleaks/docker-swarm-join-token`
|
|
74
|
+
- `betterleaks/docker-swarm-unlock-key`
|
|
75
|
+
- `betterleaks/dockerhub-organization-access-token`
|
|
76
|
+
- `betterleaks/dockerhub-personal-access-token`
|
|
77
|
+
- `betterleaks/ebay-client-secret`
|
|
78
|
+
- `betterleaks/elastic-cloud-api-key`
|
|
79
|
+
- `betterleaks/elevenlabs-api-key`
|
|
80
|
+
- `betterleaks/endorlabs-api-key`
|
|
81
|
+
- `betterleaks/endorlabs-api-secret`
|
|
82
|
+
- `betterleaks/etsy-open-api-key`
|
|
83
|
+
- `betterleaks/exoscale-api-key`
|
|
84
|
+
- `betterleaks/fal-api-key.1`
|
|
85
|
+
- `betterleaks/figma-personal-access-header-token`
|
|
86
|
+
- `betterleaks/figma-personal-access-token`
|
|
87
|
+
- `betterleaks/fullstory-api-key`
|
|
88
|
+
- `betterleaks/gcp-application-default-credentials`
|
|
89
|
+
- `betterleaks/gcp-gemini-api`
|
|
90
|
+
- `betterleaks/gcp-service-account`
|
|
91
|
+
- `betterleaks/gitea-access-token`
|
|
92
|
+
- `betterleaks/gitlab-incoming-mail-address-token`
|
|
93
|
+
- `betterleaks/gitlab-pat-routable-versioned`
|
|
94
|
+
- `betterleaks/greptile-api-key`
|
|
95
|
+
- `betterleaks/groq-api-key`
|
|
96
|
+
- `betterleaks/gumroad-access-token`
|
|
97
|
+
- `betterleaks/highnote-secret-live-key`
|
|
98
|
+
- `betterleaks/honeycomb-api-key`
|
|
99
|
+
- `betterleaks/hunter-api-key.1`
|
|
100
|
+
- `betterleaks/ibm-cloud-user-api-key`
|
|
101
|
+
- `betterleaks/influxdb-api-token`
|
|
102
|
+
- `betterleaks/infomaniak-api-token`
|
|
103
|
+
- `betterleaks/instantly-api-key.1`
|
|
104
|
+
- `betterleaks/ionic-personal-access-token`
|
|
105
|
+
- `betterleaks/jumpcloud-api-key`
|
|
106
|
+
- `betterleaks/kagi-api-key`
|
|
107
|
+
- `betterleaks/kimi-api-key`
|
|
108
|
+
- `betterleaks/klaviyo-api-key`
|
|
109
|
+
- `betterleaks/langchain-langsmith-personal-access-token`
|
|
110
|
+
- `betterleaks/langchain-langsmith-service-key`
|
|
111
|
+
- `betterleaks/langfuse-secret-key.1`
|
|
112
|
+
- `betterleaks/lark-app-secret`
|
|
113
|
+
- `betterleaks/lichess-personal-access-token`
|
|
114
|
+
- `betterleaks/lighton-paradigm-api-key`
|
|
115
|
+
- `betterleaks/llama-cloud-api-key.1`
|
|
116
|
+
- `betterleaks/mailersend-api-token`
|
|
117
|
+
- `betterleaks/mem0-api-key.1`
|
|
118
|
+
- `betterleaks/mercury-production-api-token`
|
|
119
|
+
- `betterleaks/mergify-application-key`
|
|
120
|
+
- `betterleaks/midtrans-production-server-client-key`
|
|
121
|
+
- `betterleaks/minimax-api-key`
|
|
122
|
+
- `betterleaks/miro-access-token`
|
|
123
|
+
- `betterleaks/miro-client-secret`
|
|
124
|
+
- `betterleaks/mistral-api-key`
|
|
125
|
+
- `betterleaks/monday-api-token.1`
|
|
126
|
+
- `betterleaks/mongodb-atlas-service-account-secret`
|
|
127
|
+
- `betterleaks/mongodb-connection-string`
|
|
128
|
+
- `betterleaks/mux-access-token-secret.1`
|
|
129
|
+
- `betterleaks/neon-api-key`
|
|
130
|
+
- `betterleaks/ngrok-api-key.1`
|
|
131
|
+
- `betterleaks/nuget-api-key`
|
|
132
|
+
- `betterleaks/nvidia-api-key`
|
|
133
|
+
- `betterleaks/nylas-api-key.1`
|
|
134
|
+
- `betterleaks/ollama-api-key`
|
|
135
|
+
- `betterleaks/onesignal-rich-authentication-token`
|
|
136
|
+
- `betterleaks/onfido-live-api-token-ca`
|
|
137
|
+
- `betterleaks/onfido-live-api-token-eu`
|
|
138
|
+
- `betterleaks/onfido-live-api-token-us`
|
|
139
|
+
- `betterleaks/openrouter-api-key`
|
|
140
|
+
- `betterleaks/openweather-api-key`
|
|
141
|
+
- `betterleaks/opsgenie-api-key.1`
|
|
142
|
+
- `betterleaks/ovh-application-secret`
|
|
143
|
+
- `betterleaks/paddle-live-api-key`
|
|
144
|
+
- `betterleaks/pagerduty-authorization-token.1`
|
|
145
|
+
- `betterleaks/paypal-client-secret.1`
|
|
146
|
+
- `betterleaks/persona-production-api-key`
|
|
147
|
+
- `betterleaks/pinecone-api-key.1`
|
|
148
|
+
- `betterleaks/pinecone-api-key.2`
|
|
149
|
+
- `betterleaks/pinterest-access-token`
|
|
150
|
+
- `betterleaks/plivo-auth-token`
|
|
151
|
+
- `betterleaks/polar-oauth-access-token`
|
|
152
|
+
- `betterleaks/polar-organization-access-token`
|
|
153
|
+
- `betterleaks/polar-personal-access-token`
|
|
154
|
+
- `betterleaks/polymarket-api-key`
|
|
155
|
+
- `betterleaks/polymarket-private-key`
|
|
156
|
+
- `betterleaks/posthog-personal-api-key`
|
|
157
|
+
- `betterleaks/posthog-project-api-key`
|
|
158
|
+
- `betterleaks/postmark-api-token.1`
|
|
159
|
+
- `betterleaks/proof-full-access-api-key`
|
|
160
|
+
- `betterleaks/rainforest-pay-production-api-key`
|
|
161
|
+
- `betterleaks/ramp-client-secret`
|
|
162
|
+
- `betterleaks/razorpay-key-secret.1`
|
|
163
|
+
- `betterleaks/redirect-pizza-api-token.1`
|
|
164
|
+
- `betterleaks/render-api-key`
|
|
165
|
+
- `betterleaks/replicate-api-token`
|
|
166
|
+
- `betterleaks/resend-api-key.1`
|
|
167
|
+
- `betterleaks/retell-api-key.1`
|
|
168
|
+
- `betterleaks/rootly-api-key.1`
|
|
169
|
+
- `betterleaks/runpod-api-key.1`
|
|
170
|
+
- `betterleaks/salesforce-access-token.1`
|
|
171
|
+
- `betterleaks/samsara-api-token.1`
|
|
172
|
+
- `betterleaks/scaleway-secret-key`
|
|
173
|
+
- `betterleaks/scalr-api-access-token.1`
|
|
174
|
+
- `betterleaks/segment-public-api-token.1`
|
|
175
|
+
- `betterleaks/slack-session-cookie`
|
|
176
|
+
- `betterleaks/slack-session-token`
|
|
177
|
+
- `betterleaks/snowflake-programmatic-access-token.1`
|
|
178
|
+
- `betterleaks/sslmate-api-key.1`
|
|
179
|
+
- `betterleaks/stability-ai-api-key`
|
|
180
|
+
- `betterleaks/supabase-management-token`
|
|
181
|
+
- `betterleaks/supabase-project-api-key`
|
|
182
|
+
- `betterleaks/tableau-personal-access-token.1`
|
|
183
|
+
- `betterleaks/tailscale-api-key.1`
|
|
184
|
+
- `betterleaks/telnyx-api-v2-key.1`
|
|
185
|
+
- `betterleaks/temporal-cloud-api-key.1`
|
|
186
|
+
- `betterleaks/thunderstore-api-token.1`
|
|
187
|
+
- `betterleaks/togetherai-api-key`
|
|
188
|
+
- `betterleaks/unkey-root-key.1`
|
|
189
|
+
- `betterleaks/upcloud-api-token`
|
|
190
|
+
- `betterleaks/upstage-api-key`
|
|
191
|
+
- `betterleaks/upstash-redis-rest-token.1`
|
|
192
|
+
- `betterleaks/val-town-api-token.1`
|
|
193
|
+
- `betterleaks/vercel-ai-gateway-key`
|
|
194
|
+
- `betterleaks/vercel-api-token`
|
|
195
|
+
- `betterleaks/vercel-app-access-token`
|
|
196
|
+
- `betterleaks/vercel-app-refresh-token`
|
|
197
|
+
- `betterleaks/vercel-integration-token`
|
|
198
|
+
- `betterleaks/vercel-personal-access-token`
|
|
199
|
+
- `betterleaks/virustotal-api-key.1`
|
|
200
|
+
- `betterleaks/voyageai-api-key`
|
|
201
|
+
- `betterleaks/vultr-api-key.1`
|
|
202
|
+
- `betterleaks/wakatime-api-key.1`
|
|
203
|
+
- `betterleaks/wakatime-api-key.2`
|
|
204
|
+
- `betterleaks/weatherstack-api-key.1`
|
|
205
|
+
- `betterleaks/weights-and-biases-api-key`
|
|
206
|
+
- `betterleaks/weights-and-biases-api-key-v1`
|
|
207
|
+
- `betterleaks/wiz-client-secret.1`
|
|
208
|
+
- `betterleaks/woocommerce-consumer-secret.1`
|
|
209
|
+
- `betterleaks/workato-developer-api-token.1`
|
|
210
|
+
- `betterleaks/workos-production-api-key.1`
|
|
211
|
+
- `betterleaks/xai-api-key`
|
|
212
|
+
- `betterleaks/xendit-production-api-key.1`
|
|
213
|
+
- `betterleaks/zai-api-key`
|
|
214
|
+
- `betterleaks/zoho-client-secret.1`
|
|
215
|
+
- `betterleaks/zoho-oauth-token.1`
|
|
216
|
+
- `betterleaks/zoho-zapi-key.1`
|
|
217
|
+
- `betterleaks/zuplo-consumer-api-key.1`
|
|
15
218
|
- `birth-context`
|
|
16
219
|
- `birth-year`
|
|
17
220
|
- `booking-context`
|
|
18
221
|
- `booking-filename`
|
|
222
|
+
- `card-context`
|
|
19
223
|
- `catalog/aws`
|
|
20
224
|
- `catalog/azure`
|
|
21
225
|
- `catalog/google`
|
|
22
226
|
- `cli-password`
|
|
23
227
|
- `context`
|
|
24
228
|
- `cookie`
|
|
229
|
+
- `credential-pair`
|
|
25
230
|
- `databricks`
|
|
26
231
|
- `detect-secrets`
|
|
27
232
|
- `digitalocean`
|
|
@@ -315,6 +520,7 @@ Regenerate with `node experiments/issue-118/generate-rule-catalog.mjs`.
|
|
|
315
520
|
- `google`
|
|
316
521
|
- `grafana`
|
|
317
522
|
- `home-path`
|
|
523
|
+
- `honorific-name`
|
|
318
524
|
- `huggingface`
|
|
319
525
|
- `iban-checksum`
|
|
320
526
|
- `identity-number`
|
|
@@ -329,9 +535,12 @@ Regenerate with `node experiments/issue-118/generate-rule-catalog.mjs`.
|
|
|
329
535
|
- `location-gazetteer`
|
|
330
536
|
- `location-label`
|
|
331
537
|
- `location-residence`
|
|
538
|
+
- `login-call`
|
|
332
539
|
- `luhn`
|
|
333
540
|
- `mac`
|
|
541
|
+
- `mail-display-name`
|
|
334
542
|
- `mailchimp`
|
|
543
|
+
- `marker-token`
|
|
335
544
|
- `messenger-handle`
|
|
336
545
|
- `messenger-label`
|
|
337
546
|
- `mrz`
|
|
@@ -342,6 +551,7 @@ Regenerate with `node experiments/issue-118/generate-rule-catalog.mjs`.
|
|
|
342
551
|
- `name-initials`
|
|
343
552
|
- `name-title`
|
|
344
553
|
- `name-variant`
|
|
554
|
+
- `named-value`
|
|
345
555
|
- `national-format`
|
|
346
556
|
- `national-phone`
|
|
347
557
|
- `ner/offline`
|
|
@@ -365,10 +575,13 @@ Regenerate with `node experiments/issue-118/generate-rule-catalog.mjs`.
|
|
|
365
575
|
- `passport-format`
|
|
366
576
|
- `passport-issuer`
|
|
367
577
|
- `passport-series`
|
|
578
|
+
- `password-hash`
|
|
368
579
|
- `personal-profile-url`
|
|
369
580
|
- `phone-context`
|
|
581
|
+
- `po-box`
|
|
370
582
|
- `postal-address`
|
|
371
583
|
- `postal-code`
|
|
584
|
+
- `postal-label`
|
|
372
585
|
- `presidio`
|
|
373
586
|
- `private-key`
|
|
374
587
|
- `profile-attribute`
|
|
@@ -378,12 +591,17 @@ Regenerate with `node experiments/issue-118/generate-rule-catalog.mjs`.
|
|
|
378
591
|
- `pypi`
|
|
379
592
|
- `quoted-entropy`
|
|
380
593
|
- `residence-prose`
|
|
594
|
+
- `salutation`
|
|
381
595
|
- `school-acronym-context`
|
|
382
596
|
- `secretlint`
|
|
597
|
+
- `self-introduction`
|
|
383
598
|
- `sendgrid`
|
|
384
599
|
- `shopify`
|
|
600
|
+
- `sign-off`
|
|
385
601
|
- `slack`
|
|
386
602
|
- `square`
|
|
603
|
+
- `ssn-label`
|
|
604
|
+
- `state-zip`
|
|
387
605
|
- `strict-age`
|
|
388
606
|
- `strict-attribute`
|
|
389
607
|
- `strict-date`
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Sanitizes a CV note into readable placeholders, then sanitizes the result
|
|
2
|
+
// again after a model added text, keeping the tokens already in it.
|
|
3
|
+
import {
|
|
4
|
+
createSyncSanitizer,
|
|
5
|
+
isPlaceholderToken,
|
|
6
|
+
} from '@link-foundation/sensitive-data-sanitizer';
|
|
7
|
+
|
|
8
|
+
const policy = (existing) => ({
|
|
9
|
+
profile: 'documents',
|
|
10
|
+
properNames: 'capitalized',
|
|
11
|
+
strict: true,
|
|
12
|
+
transformation: { mode: 'placeholder', map: false, existing },
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const first = createSyncSanitizer(policy()).sanitize(
|
|
16
|
+
'Кандидат Петров Иван Сергеевич, тел. +7 916 123-45-67. Работал в ООО «Ромашка», живу в Казани. Перенёс инсульт.'
|
|
17
|
+
).text;
|
|
18
|
+
console.log(first);
|
|
19
|
+
// Кандидат Person-1, тел. Phone-1. Работал в Organization-1, живу в Location-1. Перенёс PersonalAttribute-1.
|
|
20
|
+
|
|
21
|
+
const answer = `${first} Рекомендует Анна Смирнова.`;
|
|
22
|
+
const second = createSyncSanitizer(policy('keep')).sanitize(answer).text;
|
|
23
|
+
console.log(second);
|
|
24
|
+
// ... Перенёс PersonalAttribute-1. Рекомендует Person-2.
|
|
25
|
+
|
|
26
|
+
console.log(
|
|
27
|
+
second.split(/[\s,.]+/u).filter((word) => isPlaceholderToken(word))
|
|
28
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-foundation/sensitive-data-sanitizer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Offline sensitive-text sanitization with credential, multilingual personal-data and scanner union detection",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -27,12 +27,21 @@
|
|
|
27
27
|
"examples/reversible-placeholders.mjs",
|
|
28
28
|
"examples/prose-logs.mjs",
|
|
29
29
|
"docs/RULES.md",
|
|
30
|
-
"examples/custom-log-policy.mjs"
|
|
30
|
+
"examples/custom-log-policy.mjs",
|
|
31
|
+
"examples/second-pass-placeholders.mjs"
|
|
31
32
|
],
|
|
32
33
|
"exports": {
|
|
33
34
|
".": {
|
|
34
35
|
"types": "./src/index.d.ts",
|
|
35
|
-
"import": "./src/index.js"
|
|
36
|
+
"import": "./src/index.js",
|
|
37
|
+
"require": "./src/index.js",
|
|
38
|
+
"default": "./src/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./credentials": {
|
|
41
|
+
"types": "./src/credentials-entry.d.ts",
|
|
42
|
+
"import": "./src/credentials-entry.js",
|
|
43
|
+
"require": "./src/credentials-entry.js",
|
|
44
|
+
"default": "./src/credentials-entry.js"
|
|
36
45
|
},
|
|
37
46
|
"./eslint-plugin": {
|
|
38
47
|
"types": "./src/eslint-plugin.d.ts",
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { failure } from './detection.js';
|
|
2
|
+
|
|
3
|
+
// Caller-declared values are explicit private policy; an allow list must not
|
|
4
|
+
// silently cancel them. Schema denials (JSON_VALUE) always win as well.
|
|
5
|
+
const declaredRules = new Set([
|
|
6
|
+
'known-personal',
|
|
7
|
+
'confirmed-personal',
|
|
8
|
+
'known-secret',
|
|
9
|
+
]);
|
|
10
|
+
const anchored = new WeakMap();
|
|
11
|
+
|
|
12
|
+
function fullMatcher(pattern) {
|
|
13
|
+
let compiled = anchored.get(pattern);
|
|
14
|
+
if (!compiled) {
|
|
15
|
+
compiled = new RegExp(
|
|
16
|
+
`^(?:${pattern.source})$`,
|
|
17
|
+
pattern.flags.replace(/[gy]/g, '')
|
|
18
|
+
);
|
|
19
|
+
anchored.set(pattern, compiled);
|
|
20
|
+
}
|
|
21
|
+
return compiled;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Inline suppression comments understood by Gitleaks/Betterleaks
|
|
25
|
+
// (gitleaks:allow), detect-secrets and ripsecrets (pragma: allowlist secret),
|
|
26
|
+
// Kingfisher, TruffleHog and SonarQube. Honoured only with inlineAllow: true,
|
|
27
|
+
// because anyone who writes to a log could otherwise suppress redaction.
|
|
28
|
+
const sameLine =
|
|
29
|
+
/(?:gitleaks|betterleaks|kingfisher|trufflehog|sanitizer):(?:allow|ignore)\b|pragma:\s*allowlist(?:\s+secret)?\b(?!\s+nextline)|\bNOSONAR\b/i;
|
|
30
|
+
const nextLine = /pragma:\s*allowlist\s+nextline(?:\s+secret)?\b/i;
|
|
31
|
+
|
|
32
|
+
function lineAt(text, index) {
|
|
33
|
+
const start = text.lastIndexOf('\n', index - 1) + 1;
|
|
34
|
+
const end = text.indexOf('\n', index);
|
|
35
|
+
return { start, end: end < 0 ? text.length : end };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function inlineSuppressed(text, finding) {
|
|
39
|
+
const line = lineAt(text, finding.start);
|
|
40
|
+
if (sameLine.test(text.slice(line.start, line.end))) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (line.start === 0) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const previous = lineAt(text, line.start - 1);
|
|
47
|
+
return nextLine.test(text.slice(previous.start, previous.end));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function validateAllowList(options) {
|
|
51
|
+
if (
|
|
52
|
+
options.inlineAllow !== undefined &&
|
|
53
|
+
typeof options.inlineAllow !== 'boolean'
|
|
54
|
+
) {
|
|
55
|
+
throw failure('ERR_CONFIG');
|
|
56
|
+
}
|
|
57
|
+
const list = options.allowList;
|
|
58
|
+
if (list === undefined) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (
|
|
62
|
+
!Array.isArray(list) ||
|
|
63
|
+
list.length > 10000 ||
|
|
64
|
+
!list.every(
|
|
65
|
+
(entry) =>
|
|
66
|
+
(typeof entry === 'string' && entry.length > 0) ||
|
|
67
|
+
entry instanceof RegExp
|
|
68
|
+
)
|
|
69
|
+
) {
|
|
70
|
+
throw failure('ERR_CONFIG');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Presidio allow_list, Google exclusion rules (MATCHING_TYPE_FULL_MATCH) and
|
|
75
|
+
// DataFog allowlist_patterns: a listed string or pattern must cover the whole
|
|
76
|
+
// detected value. Partial matches never shorten a finding.
|
|
77
|
+
export function allowListed(text, finding, options) {
|
|
78
|
+
if (
|
|
79
|
+
!(options.allowList?.length || options.inlineAllow) ||
|
|
80
|
+
declaredRules.has(finding.rule) ||
|
|
81
|
+
finding.type === 'JSON_VALUE' ||
|
|
82
|
+
(finding.category === 'credential' && !options.allowCredentialDisable)
|
|
83
|
+
) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
if (options.inlineAllow && inlineSuppressed(text, finding)) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
const value = text.slice(finding.start, finding.end);
|
|
90
|
+
return (options.allowList ?? []).some((entry) =>
|
|
91
|
+
typeof entry === 'string' ? entry === value : fullMatcher(entry).test(value)
|
|
92
|
+
);
|
|
93
|
+
}
|
package/src/confidence.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
// A valid check digit is common in arbitrary log numbers. Context decides
|
|
2
2
|
// whether that evidence merits a replacement; inspect retains low scores.
|
|
3
|
-
export function threshold(options) {
|
|
4
|
-
return
|
|
3
|
+
export function threshold(options, type) {
|
|
4
|
+
return (
|
|
5
|
+
options.minConfidenceByType?.[type] ??
|
|
6
|
+
options.minConfidence ??
|
|
7
|
+
options.threshold ??
|
|
8
|
+
0.5
|
|
9
|
+
);
|
|
5
10
|
}
|
|
6
11
|
export function actionable(finding, options) {
|
|
7
12
|
return (
|
|
8
13
|
finding.kept !== 'fake' &&
|
|
9
14
|
(finding.category === 'credential' ||
|
|
10
|
-
finding.confidence >= threshold(options))
|
|
15
|
+
finding.confidence >= threshold(options, finding.type))
|
|
11
16
|
);
|
|
12
17
|
}
|
|
13
18
|
export function numericNoise(text, start) {
|
|
@@ -29,11 +34,10 @@ export function epochNumber(value) {
|
|
|
29
34
|
);
|
|
30
35
|
}
|
|
31
36
|
export function cardEvidence(text, start, value) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return identityContext || (!numericNoise(text, start) && !epochNumber(value));
|
|
37
|
+
return (
|
|
38
|
+
cardContext(text, start, value) ||
|
|
39
|
+
(!numericNoise(text, start) && !epochNumber(value))
|
|
40
|
+
);
|
|
37
41
|
}
|
|
38
42
|
export function cardNetwork(value) {
|
|
39
43
|
const d = value.replace(/\D/g, ''),
|
|
@@ -60,6 +64,17 @@ function otherCard(d, n) {
|
|
|
60
64
|
n <= 19) ||
|
|
61
65
|
(/^62/.test(d) && n >= 16 && n <= 19) ||
|
|
62
66
|
(/^220[0-4]/.test(d) && n >= 16 && n <= 19) ||
|
|
63
|
-
(/^3(?:0[0-5]|[68])/.test(d) && n === 14)
|
|
67
|
+
(/^3(?:0[0-5]|[68])/.test(d) && n === 14) ||
|
|
68
|
+
// Maestro and Dankort IINs listed by Presidio and the card networks.
|
|
69
|
+
(/^(?:50(?:1[89]|20|38)|5893|6304|6759|676[1-3])/.test(d) &&
|
|
70
|
+
n >= 12 &&
|
|
71
|
+
n <= 19)
|
|
64
72
|
);
|
|
65
73
|
}
|
|
74
|
+
// UATP cards (prefix 1, 15 digits) collide with numeric IDs, so they need a
|
|
75
|
+
// card word nearby.
|
|
76
|
+
export const cardContext = (text, start, value) =>
|
|
77
|
+
/(?<![\p{L}\p{N}_])(?:card|visa|mastercard|amex|discover|jcb|unionpay|mir|diners|maestro|dankort|uatp|карта)(?![\p{L}\p{N}_])/iu.test(
|
|
78
|
+
text.slice(Math.max(0, start - 64), start + value.length + 32)
|
|
79
|
+
);
|
|
80
|
+
export const uatpCard = (value) => /^1\d{14}$/.test(value.replace(/\D/g, ''));
|