redac---n 0.0.0-stage → 0.0.1
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/LICENSE +21 -0
- package/README.md +323 -2
- package/THIRD_PARTY_NOTICES.md +59 -0
- package/dist/aho.d.ts +25 -0
- package/dist/aho.js +116 -0
- package/dist/base64.d.ts +10 -0
- package/dist/base64.js +29 -0
- package/dist/bip39-data.d.ts +2 -0
- package/dist/bip39-data.js +2 -0
- package/dist/cache.d.ts +14 -0
- package/dist/cache.js +40 -0
- package/dist/calls.d.ts +64 -0
- package/dist/calls.js +269 -0
- package/dist/charlm-data.d.ts +2 -0
- package/dist/charlm-data.js +3 -0
- package/dist/charlm.d.ts +7 -0
- package/dist/charlm.js +65 -0
- package/dist/checksums.d.ts +10 -0
- package/dist/checksums.js +98 -0
- package/dist/commands.d.ts +72 -0
- package/dist/commands.js +784 -0
- package/dist/context.d.ts +98 -0
- package/dist/context.js +469 -0
- package/dist/encoded.d.ts +11 -0
- package/dist/encoded.js +336 -0
- package/dist/environment.d.ts +10 -0
- package/dist/environment.js +62 -0
- package/dist/formats-data.d.ts +2 -0
- package/dist/formats-data.js +405 -0
- package/dist/formats.d.ts +5 -0
- package/dist/formats.js +470 -0
- package/dist/hmac.d.ts +3 -0
- package/dist/hmac.js +87 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +484 -0
- package/dist/json.d.ts +82 -0
- package/dist/json.js +392 -0
- package/dist/known.d.ts +34 -0
- package/dist/known.js +340 -0
- package/dist/lines.d.ts +28 -0
- package/dist/lines.js +69 -0
- package/dist/lookup.d.ts +5 -0
- package/dist/lookup.js +7 -0
- package/dist/model-data.d.ts +3 -0
- package/dist/model-data.js +4 -0
- package/dist/model.d.ts +2 -0
- package/dist/model.js +19 -0
- package/dist/named.d.ts +20 -0
- package/dist/named.js +3257 -0
- package/dist/names.d.ts +94 -0
- package/dist/names.js +1449 -0
- package/dist/normalize.d.ts +25 -0
- package/dist/normalize.js +529 -0
- package/dist/pem.d.ts +32 -0
- package/dist/pem.js +269 -0
- package/dist/policy.d.ts +3 -0
- package/dist/policy.js +470 -0
- package/dist/public.d.ts +44 -0
- package/dist/public.js +362 -0
- package/dist/publickeys.d.ts +41 -0
- package/dist/publickeys.js +232 -0
- package/dist/random.d.ts +29 -0
- package/dist/random.js +165 -0
- package/dist/repeats.d.ts +21 -0
- package/dist/repeats.js +76 -0
- package/dist/rules.d.ts +42 -0
- package/dist/rules.js +58 -0
- package/dist/runs.d.ts +10 -0
- package/dist/runs.js +36 -0
- package/dist/secretfile.d.ts +52 -0
- package/dist/secretfile.js +676 -0
- package/dist/shell.d.ts +127 -0
- package/dist/shell.js +703 -0
- package/dist/spans.d.ts +53 -0
- package/dist/spans.js +272 -0
- package/dist/sql.d.ts +20 -0
- package/dist/sql.js +66 -0
- package/dist/stream.d.ts +46 -0
- package/dist/stream.js +150 -0
- package/dist/structure.d.ts +24 -0
- package/dist/structure.js +1614 -0
- package/dist/syntax.d.ts +156 -0
- package/dist/syntax.js +446 -0
- package/dist/transcript.d.ts +30 -0
- package/dist/transcript.js +709 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.js +1 -0
- package/dist/unnamed.d.ts +59 -0
- package/dist/unnamed.js +939 -0
- package/dist/urls.d.ts +70 -0
- package/dist/urls.js +303 -0
- package/dist/values.d.ts +178 -0
- package/dist/values.js +1621 -0
- package/dist/yaml.d.ts +40 -0
- package/dist/yaml.js +174 -0
- package/docs/creddata.svg +318 -0
- package/docs/guessing.md +139 -0
- package/docs/holdout.svg +85 -0
- package/docs/layers.md +80 -0
- package/docs/models.svg +81 -0
- package/docs/real-text.svg +112 -0
- package/package.json +57 -5
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Finding } from "./types.ts";
|
|
2
|
+
/** The core rules: a name, a regex whose one capture group is the secret, and keywords one of which a text must hold for the rule to run (any case). */
|
|
3
|
+
export declare const CORE_FORMATS: [name: string, rule: RegExp, keywords: string[]][];
|
|
4
|
+
/** Every credential in `text` recognizable by format. */
|
|
5
|
+
export declare function findFormats(text: string): Finding[];
|
package/dist/formats.js
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credentials recognizable by format alone: a vendor prefix (`ghp_`, `sk-ant-`, `xoxb-`), a fixed
|
|
3
|
+
* structure (AWS key IDs, JWTs), or a vendor name right before an assignment of the right shape.
|
|
4
|
+
* These are the most certain findings, so they're redacted wherever they appear.
|
|
5
|
+
*
|
|
6
|
+
* The core list is maintained here; the long tail is generated from Betterleaks (formats-data.ts).
|
|
7
|
+
* A rule runs only on text that contains one of its keywords (one Aho-Corasick pass finds which), which
|
|
8
|
+
* keeps over 500 rules fast; the one rule with no keyword (Mailgun's) always runs. A match that is a stand-in
|
|
9
|
+
* (`sk-your-key-here`) is dropped here; which are public by design (publishable keys, public JWTs, client-side
|
|
10
|
+
* Google keys) is decided in policy.ts, from what public.ts knows.
|
|
11
|
+
*/
|
|
12
|
+
import { lookup } from "./lookup.js";
|
|
13
|
+
import { KeywordSet } from "./aho.js";
|
|
14
|
+
import { naturalness } from "./charlm.js";
|
|
15
|
+
import { isPlaceholder } from "./public.js";
|
|
16
|
+
import { entropy } from "./random.js";
|
|
17
|
+
import { isCredentialName, nameClass } from "./names.js";
|
|
18
|
+
import { IMPORTED_FORMATS } from "./formats-data.js";
|
|
19
|
+
import { backslashesBefore } from "./syntax.js";
|
|
20
|
+
import { leadingUrl } from "./urls.js";
|
|
21
|
+
/**
|
|
22
|
+
* For the rules whose keywords are common (`.`, `--`) or none: what any match holds, led by a literal, which the regex engine
|
|
23
|
+
* finds by skipping ahead (a class-led rule like the Mailgun signing key's `[0-9a-f]{32}-…` is tried at every character). The
|
|
24
|
+
* rule runs only on text that has it.
|
|
25
|
+
*/
|
|
26
|
+
const GATES = lookup({
|
|
27
|
+
"Rails session cookie": /--[0-9a-f]{40}/,
|
|
28
|
+
"Zhipu API key": /\.[A-Za-z0-9]{16}/,
|
|
29
|
+
"Mailgun signing-style API key": /-[0-9a-f]{8}-[0-9a-f]{8}\b/,
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* Rules' own false matches, by rule name: a link's signed payload (Cursor's "open in Cursor" buttons,
|
|
33
|
+
* `cursor.com/open?data=<JWT>`), Airtable's object IDs (`tbl…`, `app…`, `rec…`) under a vendor word, a Kubernetes pod
|
|
34
|
+
* name with its ReplicaSet hash (`sk-api-worker-7d9f8b6c5-x2x9q`) for the generic `sk-` rule.
|
|
35
|
+
*/
|
|
36
|
+
const REJECTS = lookup({
|
|
37
|
+
"Bearer token in JSON Web Token form": (_, before) => /cursor\.com\/[\w/-]*\?data=$/.test(before.slice(-60)),
|
|
38
|
+
"airtable API key": (value) => /^(?:tbl|app|rec|viw|fld|sel|usr|wsp)[A-Za-z0-9]{14}$/.test(value),
|
|
39
|
+
"airtable OAUTH token": (value) => /^(?:tbl|app|rec|viw|fld|sel|usr|wsp)[A-Za-z0-9]{14}$/.test(value),
|
|
40
|
+
"API key": (value) => /^[a-z0-9]+(?:-[a-z0-9]+)*-[a-z0-9]{8,10}-[a-z0-9]{5}$/.test(value),
|
|
41
|
+
});
|
|
42
|
+
let keywordSet;
|
|
43
|
+
/** For each keyword index, the formats that need it. */
|
|
44
|
+
let byKeyword = [];
|
|
45
|
+
/** Formats with no keyword, which always run. */
|
|
46
|
+
let always = [];
|
|
47
|
+
/** The core rules: a name, a regex whose one capture group is the secret, and keywords one of which a text must hold for the rule to run (any case). */
|
|
48
|
+
export const CORE_FORMATS = [
|
|
49
|
+
[
|
|
50
|
+
"AWS access key ID",
|
|
51
|
+
/\b((?:AKIA|ASIA|ABIA|ACCA|A3T[A-Z0-9])[A-Z0-9]{16})\b/,
|
|
52
|
+
["akia", "asia", "abia", "acca", "a3t"],
|
|
53
|
+
],
|
|
54
|
+
// The secret key printed next to its key ID (`--output text`, CSV exports, credentials files, `export-credentials`),
|
|
55
|
+
// in an argument list or a row (`s3('…', 'AKIA…', '<secret>', …)`) or named. (Base64 of random bytes, so both cases:
|
|
56
|
+
// a 40-hex SHA-1 in a scanner's report row isn't one.)
|
|
57
|
+
[
|
|
58
|
+
"AWS secret access key",
|
|
59
|
+
/(?:\b(?:AKIA|ASIA)[A-Z0-9]{16}["'`]?[ \t,;|:=]{1,10}["'`]?|(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY|secret_access_key|SecretAccessKey|secretAccessKey)["']?[ \t]{0,5}[:=][ \t]{0,5}["']?)((?=[A-Za-z0-9/+]{0,39}[a-z])(?=[A-Za-z0-9/+]{0,39}[A-Z])[A-Za-z0-9/+]{40})(?![A-Za-z0-9/+=])/,
|
|
60
|
+
["akia", "asia", "secret_access_key", "secretaccesskey"],
|
|
61
|
+
],
|
|
62
|
+
[
|
|
63
|
+
"AWS session token",
|
|
64
|
+
/\b((?:IQoJb3JpZ2luX2Vj|FwoGZXIvYXdz|FQoGZXIvYXdz|FQoDYXdz|IQoJb3Jp)[A-Za-z0-9/+]{80,4096}={0,2})(?![A-Za-z0-9/+=])/,
|
|
65
|
+
["iqojb3jp", "fwogzxivyxdz", "fqogzxivyxdz", "fqodyxdz"],
|
|
66
|
+
],
|
|
67
|
+
[
|
|
68
|
+
"GitHub token",
|
|
69
|
+
/\b(gh[pousr]_[A-Za-z0-9]{36,255}|github_pat_[A-Za-z0-9_]{60,255})\b/,
|
|
70
|
+
["ghp_", "gho_", "ghu_", "ghs_", "ghr_", "github_pat_"],
|
|
71
|
+
],
|
|
72
|
+
["GitLab token", /\b(gl(?:pat|dt|ptt|rt|soat|cbt|imt|ft|agent|oas|ffct|wt)-[A-Za-z0-9_-]{20,255})/, ["gl"]],
|
|
73
|
+
["OpenAI API key", /\b(sk-(?:proj|svcacct|admin|None)-[A-Za-z0-9_-]{20,255})/, ["sk-"]],
|
|
74
|
+
["Anthropic API key", /\b(sk-ant-[a-z]{2,10}\d{0,3}-[A-Za-z0-9_-]{20,255})/, ["sk-ant-"]],
|
|
75
|
+
["OpenRouter API key", /\b(sk-or-v1-[a-f0-9]{64})\b/, ["sk-or-"]],
|
|
76
|
+
// Gateway and older keys: `sk-` then 20+ token characters; dots inside are allowed (`sk-llm-…`).
|
|
77
|
+
["API key", /\b(sk-(?:[A-Za-z0-9_-]|\.(?=[A-Za-z0-9_-])){20,255})/, ["sk-"]],
|
|
78
|
+
["Stripe key", /\b((?:sk|rk)_(?:live|test)_[A-Za-z0-9]{20,255})\b/, ["sk_", "rk_"]],
|
|
79
|
+
["secret key", /\b(sk_(?:live|test)_[A-Za-z0-9]{8,}_[A-Za-z0-9_]{40,250})\b/, ["sk_live_", "sk_test_"]],
|
|
80
|
+
// The `<vendor>_sk_` convention (`aria_sk_…`, ElevenLabs' `sk_…`): a secret key by name, with a random body. (Not
|
|
81
|
+
// OpenSSL's stack functions, a type's words in capitals then the operation's: `sk_X509_NAME_ENTRY_pop_free`.)
|
|
82
|
+
[
|
|
83
|
+
"API secret key",
|
|
84
|
+
/\b((?:[a-z][a-z0-9]{1,15}_)?sk_(?!live_|test_)(?!(?:[A-Z][A-Z0-9]*_)+[a-z]+(?:_[a-z]+)*\b)(?=[A-Za-z_-]{0,40}\d)[A-Za-z0-9_-]{24,255})/,
|
|
85
|
+
["sk_"],
|
|
86
|
+
],
|
|
87
|
+
["Twitter bearer token", /\b(A{18,24}[A-Za-z0-9%]{60,160})/, ["aaaaaaaaaaaaaaaaaa"]],
|
|
88
|
+
["Stripe webhook secret", /\b(whsec_[A-Za-z0-9+/=]{20,255})/, ["whsec_"]],
|
|
89
|
+
["Slack token", /\b(xox[abposre]-[A-Za-z0-9-]{10,255}|xapp-\d-[A-Za-z0-9-]{20,255})/, ["xox", "xapp-"]],
|
|
90
|
+
[
|
|
91
|
+
"Slack webhook",
|
|
92
|
+
// (A services webhook's secret is its third segment: `T…/B…/<24 characters>`; one cut to `T…/B…/...` has none.)
|
|
93
|
+
/hooks\.slack\.com\/(?:services|workflows|triggers)\/((?<=services\/)T[A-Z0-9]{6,}\/B[A-Z0-9]{6,}\/[A-Za-z0-9]{16,64}|(?<!services\/)[A-Za-z0-9/_-]{20,255})/,
|
|
94
|
+
["hooks.slack.com"],
|
|
95
|
+
],
|
|
96
|
+
[
|
|
97
|
+
"Microsoft Teams webhook",
|
|
98
|
+
/(?:(?<![\w-])[\w-]+\.webhook\.office\.com\/webhookb2|outlook\.office(?:365)?\.com\/webhook)\/[\w-]{36}@[\w-]{36}\/IncomingWebhook\/([a-f0-9]{32}\/[\w-]{36}(?:\/[A-Za-z0-9_-]{20,})?)/i,
|
|
99
|
+
["incomingwebhook"],
|
|
100
|
+
],
|
|
101
|
+
[
|
|
102
|
+
"Microsoft Teams workflow webhook",
|
|
103
|
+
/\.webhook\.office\.com\/[\w/-]{0,200}?\/workflows\/[a-f0-9]{32}\/triggers\/manual\/paths\/invoke\?[^\s"']*?sig=([A-Za-z0-9_%-]{20,})/i,
|
|
104
|
+
["workflows"],
|
|
105
|
+
],
|
|
106
|
+
["Zapier webhook", /hooks\.zapier\.com\/hooks\/catch\/\d{3,12}\/([a-z0-9]{5,40})\b/i, ["hooks.zapier.com"]],
|
|
107
|
+
[
|
|
108
|
+
"IFTTT webhook key",
|
|
109
|
+
/maker\.ifttt\.com\/(?:trigger\/[\w-]+\/(?:json\/)?with\/key|use)\/([A-Za-z0-9_-]{16,64})/i,
|
|
110
|
+
["maker.ifttt.com"],
|
|
111
|
+
],
|
|
112
|
+
// (The `key` is one public constant every Chat webhook carries; the `token` is the secret.)
|
|
113
|
+
[
|
|
114
|
+
"Google Chat webhook",
|
|
115
|
+
/chat\.googleapis\.com\/v1\/spaces\/[\w-]+\/messages\?key=[\w-]{20,}&token=([\w%=-]{20,})/i,
|
|
116
|
+
["chat.googleapis.com"],
|
|
117
|
+
],
|
|
118
|
+
[
|
|
119
|
+
"Mattermost or Rocket.Chat webhook",
|
|
120
|
+
/\/hooks\/([a-z0-9]{26}|[A-Za-z0-9]{17}\/[A-Za-z0-9]{40,50})(?![\w-])/,
|
|
121
|
+
["/hooks/"],
|
|
122
|
+
],
|
|
123
|
+
["Discord webhook", /discord(?:app)?\.com\/api\/webhooks\/\d{5,30}\/([A-Za-z0-9_-]{40,255})/, ["discord"]],
|
|
124
|
+
["Google API key", /\b(AIza[A-Za-z0-9_-]{35})(?![A-Za-z0-9_-])/, ["aiza"]],
|
|
125
|
+
["Google OAuth token", /\b(ya29\.[A-Za-z0-9_.-]{20,2048}|1\/\/0[A-Za-z0-9_-]{30,512})/, ["ya29.", "1//0"]],
|
|
126
|
+
["Google OAuth client secret", /\b(GOCSPX-[A-Za-z0-9_-]{28})(?![A-Za-z0-9_-])/, ["gocspx-"]],
|
|
127
|
+
["npm token", /\b(npm_[A-Za-z0-9]{36,255})\b/, ["npm_"]],
|
|
128
|
+
["PyPI token", /\b(pypi-AgE[A-Za-z0-9_-]{50,1024})/, ["pypi-"]],
|
|
129
|
+
["Docker Hub token", /\b(dckr_(?:pat|oat)_[A-Za-z0-9_-]{20,255})/, ["dckr_"]],
|
|
130
|
+
["DigitalOcean token", /\b(do[opr]_v1_[a-f0-9]{64})\b/, ["_v1_"]],
|
|
131
|
+
["Hugging Face token", /\b(hf_[A-Za-z0-9]{30,255})\b/, ["hf_"]],
|
|
132
|
+
["Shopify token", /\b(shp(?:at|ss|ca|pa)_[a-fA-F0-9]{32})\b/, ["shp"]],
|
|
133
|
+
["Grafana token", /\b(gl(?:sa|c)_[A-Za-z0-9+/=_]{32,255})/, ["glsa_", "glc_"]],
|
|
134
|
+
["Vault token", /\b(hv[sbr]\.[A-Za-z0-9_-]{20,512})/, ["hvs.", "hvb.", "hvr."]],
|
|
135
|
+
["age secret key", /\b(AGE-SECRET-KEY-1[0-9A-Z]{58})\b/, ["age-secret-key-"]],
|
|
136
|
+
["Supabase key", /\b(sb(?:p|_secret)_[A-Za-z0-9_-]{20,255})/, ["sbp_", "sb_secret_"]],
|
|
137
|
+
["Doppler token", /\b(dp\.(?:st|ct|sa|scim|audit|pt)\.[A-Za-z0-9._-]{30,255})/, ["dp."]],
|
|
138
|
+
["Tailscale key", /\b(tskey-(?:auth|api|client|scim|webhook|secret)-[A-Za-z0-9-]{20,255})/, ["tskey-"]],
|
|
139
|
+
["Linear API key", /\b(lin_(?:api|oauth)_[A-Za-z0-9]{30,255})/, ["lin_"]],
|
|
140
|
+
["Postman API key", /\b(PMAK-[a-f0-9]{24}-[a-f0-9]{34})\b/, ["pmak-"]],
|
|
141
|
+
["Atlassian API token", /\b(ATATT3[A-Za-z0-9_=-]{100,512})/, ["atatt3"]],
|
|
142
|
+
["1Password token", /\b(ops_eyJ[A-Za-z0-9_+/=-]{50,4096})/, ["ops_eyj"]],
|
|
143
|
+
[
|
|
144
|
+
"Fly.io token",
|
|
145
|
+
/\b(fm[12]_[A-Za-z0-9+/=_-]{50,4096}|FlyV1 fm\d_[A-Za-z0-9+/=_,-]{50,4096})/,
|
|
146
|
+
["fm1_", "fm2_", "flyv1"],
|
|
147
|
+
],
|
|
148
|
+
["Mailgun API key", /\b(key-[0-9a-f]{32})\b/, ["key-"]],
|
|
149
|
+
["SendGrid key", /\b(SG\.[A-Za-z0-9_-]{16,32}\.[A-Za-z0-9_-]{32,64})/, ["sg."]],
|
|
150
|
+
["Telegram bot token", /(?:\b|(?<=bot))(\d{8,10}:AA[A-Za-z0-9_-]{32,33})(?![\w-])/, [":aa"]],
|
|
151
|
+
["Firebase Cloud Messaging key", /\b(AAAA[A-Za-z0-9_-]{7}:APA91b[A-Za-z0-9_-]{134,})/, ["apa91b"]],
|
|
152
|
+
["Okta token", /(?<=okta[\s\S]{0,40})\b(00[A-Za-z0-9_-]{40})\b/i, ["okta"]],
|
|
153
|
+
["Discord bot token", /\b([MNO][A-Za-z0-9_-]{23,25}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27,38})(?![\w-])/, ["."]],
|
|
154
|
+
["Notion token", /\b(ntn_[A-Za-z0-9]{40,255}|secret_[A-Za-z0-9]{43})\b/, ["ntn_", "secret_"]],
|
|
155
|
+
["Groq API key", /\b(gsk_[A-Za-z0-9]{48,64})\b/, ["gsk_"]],
|
|
156
|
+
["Replicate token", /\b(r8_[A-Za-z0-9]{37,40})\b/, ["r8_"]],
|
|
157
|
+
["Perplexity API key", /\b(pplx-[A-Za-z0-9]{40,64})\b/, ["pplx-"]],
|
|
158
|
+
["xAI API key", /\b(xai-[A-Za-z0-9]{70,90})\b/, ["xai-"]],
|
|
159
|
+
["Tavily API key", /\b(tvly-(?:dev-|prod-)?[A-Za-z0-9]{24,48})\b/, ["tvly-"]],
|
|
160
|
+
["Fireworks API key", /\b(fw_[A-Za-z0-9]{24,48})\b/, ["fw_"]],
|
|
161
|
+
["NVIDIA API key", /\b(nvapi-[A-Za-z0-9_-]{60,72})/, ["nvapi-"]],
|
|
162
|
+
["Pinecone key", /\b(pcsk_[A-Za-z0-9_]{50,90})\b/, ["pcsk_"]],
|
|
163
|
+
["Sentry token", /\b(sntry[su]_[A-Za-z0-9+/=_-]{40,512})/, ["sntry"]],
|
|
164
|
+
["RubyGems key", /\b(rubygems_[a-f0-9]{48})\b/, ["rubygems_"]],
|
|
165
|
+
["Mapbox secret token", /\b(sk\.eyJ[A-Za-z0-9_-]{20,512}\.[A-Za-z0-9_-]{20,128})/, ["sk.eyj"]],
|
|
166
|
+
["Alibaba access key", /\b(LTAI[A-Za-z0-9]{12,24})\b/, ["ltai"]],
|
|
167
|
+
["Tencent secret ID", /\b(AKID[A-Za-z0-9]{32})\b/, ["akid"]],
|
|
168
|
+
["Azure DevOps PAT", /\b([a-zA-Z0-9]{52}AZDO[a-zA-Z0-9]{4})\b/, ["azdo"]],
|
|
169
|
+
// Microsoft's common annotated security key format: 52 random characters, then `JQQJ9` and a date and provider tag.
|
|
170
|
+
["Azure key", /\b([A-Za-z0-9]{52}JQQJ9(?:9|D|H)[A-Za-z0-9]{4,40})/, ["jqqj9"]],
|
|
171
|
+
[
|
|
172
|
+
"Azure identifiable key",
|
|
173
|
+
/\b([A-Za-z0-9+/]{33}(?:AZEY|\+ASb|\+AEh|\+ARm|AzCa|AzFu|AzSe|APIM|ACDb|\+ABa|\+AMC|AzSc)[A-Za-z0-9+/]{5}[AQgw]==|[A-Za-z0-9+/]{76}(?:\+ASt|AzSe)[A-Za-z0-9+/]{5}[AQgw]==|[A-Za-z0-9+/]{42}AzCa[A-P][A-Za-z0-9+/]{5}=)/,
|
|
174
|
+
["azey", "+asb", "+aeh", "+arm", "azca", "azfu", "azse", "apim", "acdb", "+aba", "+amc", "azsc", "+ast"],
|
|
175
|
+
],
|
|
176
|
+
[
|
|
177
|
+
"Azure AD client secret",
|
|
178
|
+
/(?<![a-zA-Z0-9_~.-])([a-zA-Z0-9_~.]{3}\dQ~[a-zA-Z0-9_~.-]{31,34})(?![a-zA-Z0-9_~-]|\.[a-zA-Z0-9_~])/,
|
|
179
|
+
["q~"],
|
|
180
|
+
],
|
|
181
|
+
["Databricks token", /\b(dapi[a-f0-9]{32}(?:-\d)?)\b/, ["dapi"]],
|
|
182
|
+
["Figma token", /\b(figd_[A-Za-z0-9_-]{40,})/, ["figd_"]],
|
|
183
|
+
// PostHog personal API keys (`phc_` is the public project key).
|
|
184
|
+
["PostHog personal key", /\b(phx_[A-Za-z0-9]{40,50})\b/, ["phx_"]],
|
|
185
|
+
["New Relic key", /\b(NRAK-[A-Z0-9]{27}|NRII-[A-Za-z0-9_-]{32}|NRJS-[a-f0-9]{19})\b/, ["nrak-", "nrii-", "nrjs-"]],
|
|
186
|
+
["Pulumi token", /\b(pul-[a-f0-9]{40})\b/, ["pul-"]],
|
|
187
|
+
["Square token", /\b(sq0(?:atp|csp)-[A-Za-z0-9_-]{22,43}|EAAA[A-Za-z0-9_-]{60})(?![A-Za-z0-9_-])/, ["sq0", "eaaa"]],
|
|
188
|
+
// An API key SID, like an AWS key ID: an identifier, but hidden alongside its secret for the same reason.
|
|
189
|
+
["Twilio API key SID", /\b(SK[0-9a-f]{32})\b/, ["sk"]],
|
|
190
|
+
["Facebook access token", /\b(EAA[A-Za-z0-9]{6,8}ZD[A-Za-z0-9]{60,300}|EAAG[A-Za-z0-9]{100,300})\b/, ["eaa"]],
|
|
191
|
+
[
|
|
192
|
+
"Cloudflare token",
|
|
193
|
+
/\b(cf[ua]t_[A-Za-z0-9]{40}[a-f0-9]{8}|cfk_[A-Za-z0-9]{40}[a-f0-9]{8})\b/,
|
|
194
|
+
["cfut_", "cfat_", "cfk_"],
|
|
195
|
+
],
|
|
196
|
+
["Vercel token", /\b(vc[pkiar]_[A-Za-z0-9]{24,64})\b/, ["vcp_", "vck_", "vci_", "vca_", "vcr_"]],
|
|
197
|
+
[
|
|
198
|
+
"Docker swarm token",
|
|
199
|
+
/\b(SWMTKN-1-[a-z0-9]{50}-[a-z0-9]{25}|SWMKEY-1-[A-Za-z0-9+/]{43})(?![A-Za-z0-9+/=])/,
|
|
200
|
+
["swmtkn-1-", "swmkey-1-"],
|
|
201
|
+
],
|
|
202
|
+
// The imported Resend rule wants the word "resend" nearby, and only base58; the shape (an underscore after
|
|
203
|
+
// eight characters, then 24) is distinctive on its own. Stripe's refund IDs are `re_` and 24, unbroken.
|
|
204
|
+
["Resend API key", /\b(re_[A-Za-z0-9]{8}_[A-Za-z0-9]{24})\b/, ["re_"]],
|
|
205
|
+
// Vendor tokens whose prefix or shape is distinctive on its own (each seen bare in output, with no name around it).
|
|
206
|
+
// (Not a word and hex alone, `key_<hex>` or `live_<hex>`: cache keys, file and room names have that shape. Cursor's and
|
|
207
|
+
// Retell's keys are found with their vendor's name nearby, as the imported rules do.)
|
|
208
|
+
["Twitch stream key", /\b(live_\d{6,12}_[A-Za-z0-9]{30})\b/, ["live_"]],
|
|
209
|
+
// (The second part is mixed-case; an Application Insights `Request-Id: |<32 hex>.<16 hex>` is hex.)
|
|
210
|
+
["Zhipu API key", /(?<![|\w])([0-9a-f]{32}\.(?=[a-z0-9]*[A-Z])[A-Za-z0-9]{16})\b/, ["."]],
|
|
211
|
+
["Zoho token", /\b(1000\.[0-9a-f]{32}\.[0-9a-f]{32})\b/, ["1000."]],
|
|
212
|
+
["Salesforce session ID", /\b(00D[A-Za-z0-9]{12,15}![A-Za-z0-9._]{80,200})/, ["00D"]],
|
|
213
|
+
["CodeRabbit API key", /\b(cr-[0-9a-f]{64})\b/, ["cr-"]],
|
|
214
|
+
["Riot Games API key", /\b(RGAPI-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/, ["RGAPI-"]],
|
|
215
|
+
["Pixie API key", /\b(px-(?:api|dep)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/, ["px-"]],
|
|
216
|
+
["New Relic admin key", /\b(NRAA-[0-9a-f]{27})\b/, ["NRAA-"]],
|
|
217
|
+
["Cloudflare origin CA key", /\b(v1\.0-[0-9a-f]{24}-[0-9a-f]{146})\b/, ["v1.0-"]],
|
|
218
|
+
["Infura project secret", /infura\.io\/v3\/([0-9a-f]{32})\b/, ["infura.io"]],
|
|
219
|
+
[
|
|
220
|
+
"Amazon MWS auth token",
|
|
221
|
+
/\b(amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/,
|
|
222
|
+
["amzn.mws."],
|
|
223
|
+
],
|
|
224
|
+
[
|
|
225
|
+
"Kubernetes bootstrap token",
|
|
226
|
+
/(?:bootstrap[\w-]*token[\w"']*[ \t]*[:=][ \t]*["']?|kubeadm\b[^\n]{0,200}?--token[ \t=]+["']?)([a-z0-9]{6}\.[a-z0-9]{16})\b/i,
|
|
227
|
+
["bootstrap", "kubeadm"],
|
|
228
|
+
],
|
|
229
|
+
// Nix binary-cache signing keys: a key name (`cache.example.org-1`) and 64 bytes of base64. The public half
|
|
230
|
+
// (32 bytes) is kept by policy.ts. (The name is read from a token's start, a host's length at most: one attempt a token.)
|
|
231
|
+
["Nix signing key", /(?<![\w.-])\w[\w.-]{0,252}-\d{1,5}:([A-Za-z0-9+/]{86}==)/, ["=="]],
|
|
232
|
+
// Airtable personal access tokens: `pat`, 14 characters, a dot and 64 hex (the imported rule wants "airtable" nearby).
|
|
233
|
+
["Airtable personal access token", /\b(pat[A-Za-z0-9]{14}\.[a-f0-9]{64})\b/, ["pat"]],
|
|
234
|
+
[
|
|
235
|
+
"Claude Platform on AWS API key",
|
|
236
|
+
/\b(aws-external-anthropic-api-key-[A-Za-z0-9_-]{20,1000}={0,2})/,
|
|
237
|
+
["aws-external-anthropic-api-key-"],
|
|
238
|
+
],
|
|
239
|
+
[
|
|
240
|
+
"Claude Platform on AWS API key (base64 form)",
|
|
241
|
+
/\b(AEAAQWVhQXBpS2V5[A-Za-z0-9+/]{40,2000}={0,2})/,
|
|
242
|
+
["aeaaqwvhqxbps2v5"],
|
|
243
|
+
],
|
|
244
|
+
["Amazon Bedrock API key", /\b(bedrock-api-key-[A-Za-z0-9+/_-]{40,2000}={0,2})/, ["bedrock-api-key-"]],
|
|
245
|
+
[
|
|
246
|
+
"Heroku API key",
|
|
247
|
+
/\b(HRKU-(?:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}|AA[0-9A-Za-z_-]{58}))\b/,
|
|
248
|
+
["hrku-"],
|
|
249
|
+
],
|
|
250
|
+
["Atlassian Connect token", /\b(ATCTT3xFfGN0[0-9A-Za-z_-]{150,250}=[0-9A-F]{8})\b/, ["atctt3"]],
|
|
251
|
+
["Asana personal access token", /(?<![\w/])((?:1\/\d{14,16}|2\/\d{16}\/\d{16}):[0-9a-f]{32})\b/, ["1/", "2/"]],
|
|
252
|
+
["CircleCI token", /\b(CCI(?:PAT|PRJ)_[A-Za-z0-9]{22}_[0-9a-f]{40})\b/, ["ccipat_", "cciprj_"]],
|
|
253
|
+
["Flutterwave secret key", /\b(FLWSECK(?:_TEST)?(?:-[0-9A-Ha-h]{32}-X|[0-9a-h]{12}))\b/, ["flwseck"]],
|
|
254
|
+
["GitHub App installation token", /\b(v1\.[0-9A-Fa-f]{40})\b/, ["v1."]],
|
|
255
|
+
["Mailchimp API key", /\b([0-9a-f]{32}-us\d{1,2})\b/, ["-us"]],
|
|
256
|
+
["Shopify Partner API token", /\b(prtapi_[0-9a-f]{32})\b/, ["prtapi_"]],
|
|
257
|
+
["Aiven service password", /\b(AVNS_[0-9A-Za-z_-]{15,123})\b/, ["avns_"]],
|
|
258
|
+
["Artifactory API key", /\b(AKCp[0-9A-Za-z/+]{69})(?![0-9A-Za-z/+])/, ["akcp"]],
|
|
259
|
+
["Lob API key", /(?<![\w/.-])((?:live|test)_[0-9a-f]{35})(?![\w.])/, ["live_", "test_"]],
|
|
260
|
+
["Azure API Management key", /\b(GatewayKey [A-Za-z0-9_-]{3,64}&\d{12}&[A-Za-z0-9+/]{60,90}==)/, ["gatewaykey"]],
|
|
261
|
+
["Dropbox access token", /\b(sl\.[A-Za-z0-9_-]{130,200})(?![A-Za-z0-9_-])/, ["sl."]],
|
|
262
|
+
["Oculus access token", /\b(OCA[A-Za-z0-9]{90,400})\b/, ["oca"]],
|
|
263
|
+
["New Relic REST API key", /\b(NRRA-[0-9a-f]{42})\b/, ["nrra-"]],
|
|
264
|
+
[
|
|
265
|
+
"PlanetScale token",
|
|
266
|
+
/\b(pscale_(?:app_secret|tkn|oauth|pw)_[0-9A-Za-z_=.-]{32,64})(?![0-9A-Za-z_=.-])/,
|
|
267
|
+
["pscale_"],
|
|
268
|
+
],
|
|
269
|
+
["SonarQube token", /\b(sq[upa]_[0-9a-f]{40})\b/, ["squ_", "sqp_", "sqa_"]],
|
|
270
|
+
["Dynatrace token", /\b(dt0[a-z]\d{2}\.[A-Z0-9]{8,24}\.[A-Z0-9]{64})\b/, ["dt0"]],
|
|
271
|
+
["Render API key", /\b(rnd_[A-Za-z0-9]{32})\b/, ["rnd_"]],
|
|
272
|
+
[
|
|
273
|
+
"Render deploy hook key",
|
|
274
|
+
/api\.render\.com\/deploy\/srv-[a-z0-9]{10,40}\?key=([A-Za-z0-9_-]{8,64})/,
|
|
275
|
+
["api.render.com/deploy"],
|
|
276
|
+
],
|
|
277
|
+
[
|
|
278
|
+
"PagerDuty routing key",
|
|
279
|
+
/\b(?:routing|integration|service)_key["']?[ \t]*[:=][ \t]*["']?([0-9a-f]{32})\b/,
|
|
280
|
+
["routing_key", "integration_key", "service_key"],
|
|
281
|
+
],
|
|
282
|
+
["Yandex Cloud IAM token", /\b(t1\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{60,200})(?![A-Za-z0-9_-])/, ["t1."]],
|
|
283
|
+
// DER prefixes: PKCS#8 RSA (`30 82 … 02 01 00 30 0D 06 09 2A 86 48 86 F7 0D 01 01 01`), PKCS#1 RSA, PKCS#8 and SEC1 EC P-256.
|
|
284
|
+
[
|
|
285
|
+
"private key (base64 DER, no PEM armour)",
|
|
286
|
+
/(?<![A-Za-z0-9+/])((?:MII[A-Za-z0-9+/]{3}IBADANBgkqhkiG9w0BAQEFAASC|MII[A-Za-z0-9+/]{3}IBAAKCA[QgI]EA|MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0w|MHcCAQEEI)[A-Za-z0-9+/]{60,8000}={0,2})/,
|
|
287
|
+
["mii", "mighageambmgbyqgsm49", "mhccaqeei"],
|
|
288
|
+
],
|
|
289
|
+
["Akamai EdgeGrid token", /\b(akab-[a-z0-9-]{30,40})(?![a-z0-9-])/, ["akab-"]],
|
|
290
|
+
["Elasticsearch enrollment token", /\b(eyJ2ZXIiOiI[4-9][A-Za-z0-9+/]{60,600}={0,2})/, ["eyj2zxiioii"]],
|
|
291
|
+
["New Relic license key", /\b([0-9a-f]{36}NRAL)\b/, ["nral"]],
|
|
292
|
+
["Honeycomb API key", /\b(hc(?:ai|xi|am|xm)k_[a-z0-9]{58})\b/, ["hcaik_", "hcxik_", "hcamk_", "hcxmk_"]],
|
|
293
|
+
["Mailgun signing-style API key", /\b([0-9a-f]{32}-[0-9a-f]{8}-[0-9a-f]{8})\b/, []],
|
|
294
|
+
["LaunchDarkly SDK key", /\b(sdk-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/, ["sdk-"]],
|
|
295
|
+
["Rails session cookie", /(?<![A-Za-z0-9+/=-])([A-Za-z0-9+/]{40,4000}={0,2}--[0-9a-f]{40})\b/, ["--"]],
|
|
296
|
+
["Mercado Pago access token", /\b((?:APP_USR|TEST)-\d{16}-\d{6}-[0-9a-f]{32}-\d{6,10})\b/, ["app_usr-", "test-"]],
|
|
297
|
+
["LangSmith API key", /\b(lsv2_(?:pt|sk)_[0-9a-f]{32}(?:_[0-9a-f]{10})?)\b/, ["lsv2_"]],
|
|
298
|
+
[
|
|
299
|
+
"HubSpot private app token",
|
|
300
|
+
/\b(pat-(?:na|eu|ap)\d-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/,
|
|
301
|
+
["pat-na", "pat-eu", "pat-ap"],
|
|
302
|
+
],
|
|
303
|
+
["Midtrans server key", /\b((?:SB-)?Mid-server-[A-Za-z0-9_-]{20,40})/, ["mid-server-"]],
|
|
304
|
+
[
|
|
305
|
+
"Yandex Cloud key",
|
|
306
|
+
/\b(AQVN[A-Za-z0-9_-]{35,38}|YCAJ[A-Za-z0-9_-]{21}|YC[A-Za-z0-9_-]{38})(?![A-Za-z0-9_-])/,
|
|
307
|
+
["aqvn", "ycaj", "yc"],
|
|
308
|
+
],
|
|
309
|
+
["DigitalOcean Spaces access key", /\b(DO00[A-Z0-9]{16})\b/, ["do00"]],
|
|
310
|
+
["Neon password", /\b(npg_[A-Za-z0-9]{12,40})\b/, ["npg_"]],
|
|
311
|
+
["Wakatime key", /\b(waka_[a-f0-9-]{36})\b/, ["waka_"]],
|
|
312
|
+
["Bitwarden token", /\b(0\.[a-f0-9-]{36}\.[A-Za-z0-9]{30}:[A-Za-z0-9+/=]{24})(?![A-Za-z0-9+/=])/, ["0."]],
|
|
313
|
+
[
|
|
314
|
+
"Bearer token in JSON Web Token form",
|
|
315
|
+
/\b(eyJ[A-Za-z0-9_-]{8,4096}\.eyJ[A-Za-z0-9_-]{8,8192}\.[A-Za-z0-9_-]{8,2048})/,
|
|
316
|
+
["eyj"],
|
|
317
|
+
],
|
|
318
|
+
[
|
|
319
|
+
"Encrypted JSON Web Token",
|
|
320
|
+
/\b(eyJ[A-Za-z0-9_-]{8,2048}\.[A-Za-z0-9_-]{0,2048}\.[A-Za-z0-9_-]{16,2048}\.[A-Za-z0-9_-]{16,65536}\.[A-Za-z0-9_-]{16,256})/,
|
|
321
|
+
["eyj"],
|
|
322
|
+
],
|
|
323
|
+
];
|
|
324
|
+
let formats;
|
|
325
|
+
function load() {
|
|
326
|
+
if (formats)
|
|
327
|
+
return formats;
|
|
328
|
+
formats = CORE_FORMATS.map(([name, re, keywords]) => ({
|
|
329
|
+
name,
|
|
330
|
+
re: new RegExp(re.source, `${re.flags.replace(/[gd]/g, "")}gd`),
|
|
331
|
+
keywords,
|
|
332
|
+
shortest: 1,
|
|
333
|
+
minEntropy: 0,
|
|
334
|
+
byVendorWord: false,
|
|
335
|
+
rejects: REJECTS[name],
|
|
336
|
+
gate: GATES[name],
|
|
337
|
+
}));
|
|
338
|
+
for (const [name, source, flags, keywords, minEntropy, byVendorWord] of IMPORTED_FORMATS) {
|
|
339
|
+
try {
|
|
340
|
+
formats.push({
|
|
341
|
+
name,
|
|
342
|
+
re: new RegExp(source, `${flags}gd`),
|
|
343
|
+
keywords,
|
|
344
|
+
shortest: 8,
|
|
345
|
+
minEntropy,
|
|
346
|
+
byVendorWord: byVendorWord === 1,
|
|
347
|
+
rejects: REJECTS[name],
|
|
348
|
+
gate: GATES[name],
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
// A rule this JS engine can't compile is skipped, never fatal (test/formats.test.ts checks that none is).
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const index = new Map();
|
|
356
|
+
byKeyword = [];
|
|
357
|
+
always = [];
|
|
358
|
+
formats.forEach((f, i) => {
|
|
359
|
+
if (!f.keywords.length)
|
|
360
|
+
always.push(i);
|
|
361
|
+
for (const k of f.keywords) {
|
|
362
|
+
let at = index.get(k);
|
|
363
|
+
if (at === undefined) {
|
|
364
|
+
at = index.size;
|
|
365
|
+
index.set(k, at);
|
|
366
|
+
byKeyword.push([]);
|
|
367
|
+
}
|
|
368
|
+
byKeyword[at].push(i);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
keywordSet = new KeywordSet([...index.keys()]);
|
|
372
|
+
return formats;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* The text with the letters of its escapes blank (`\n`, `\t`, `\r`, `\u0027`, `\x27` after an odd run of backslashes;
|
|
376
|
+
* not a path's `C:\\new`), the same length: where an escaped string's line breaks and quotes are, words start.
|
|
377
|
+
*/
|
|
378
|
+
function withoutEscapeLetters(text) {
|
|
379
|
+
if (!text.includes("\\"))
|
|
380
|
+
return text;
|
|
381
|
+
return text.replace(/\\(?:[ntr]|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{2})/g, (escape, at) => backslashesBefore(text, at) % 2 === 0 ? `\\${" ".repeat(escape.length - 1)}` : escape);
|
|
382
|
+
}
|
|
383
|
+
/** The front of a link that names no one's secret: its scheme, host and the all-lower-case words of its path (`https://hooks.slack.com/services/`). */
|
|
384
|
+
const URL_READABLE_PREFIX = /^(?:https?:\/\/)?[a-z0-9-]+(?:\.[a-z0-9-]+)+\/(?:[a-z]+\/)*/;
|
|
385
|
+
/**
|
|
386
|
+
* Where a rule's secret is: its first group that has no name of its own, since a rule names the parts that
|
|
387
|
+
* describe the secret (`Endpoint=(?<endpoint>https://…);Id=(?<id>…);Secret=(…)`); else its first group, else the match.
|
|
388
|
+
*/
|
|
389
|
+
function secretSpan(indices) {
|
|
390
|
+
const named = Object.values(indices.groups ?? {});
|
|
391
|
+
for (let i = 1; i < indices.length; i++)
|
|
392
|
+
if (indices[i] && !named.includes(indices[i]))
|
|
393
|
+
return indices[i];
|
|
394
|
+
return (indices[1] ?? indices[0]);
|
|
395
|
+
}
|
|
396
|
+
const CONNECTION_STRING_NAME = /connection string$/;
|
|
397
|
+
/**
|
|
398
|
+
* A connection string's key, not the whole of it: its endpoint, key name and entity path say where it connects and stay
|
|
399
|
+
* (`Endpoint=sb://shop-bus.servicebus.windows.net/;SharedAccessKeyName=Root;SharedAccessKey=<key>;EntityPath=orders`).
|
|
400
|
+
* The span is kept whole if no part of it is named like a secret.
|
|
401
|
+
*/
|
|
402
|
+
function connectionStringSecret(text, start, end) {
|
|
403
|
+
const m = /(?:^|;)[^;=]*(?:key|secret|password|pwd|signature)=([^;]*)/i.exec(text.slice(start, end));
|
|
404
|
+
if (!m)
|
|
405
|
+
return [start, end];
|
|
406
|
+
const at = start + m.index + m[0].length - m[1].length;
|
|
407
|
+
return [at, at + m[1].length];
|
|
408
|
+
}
|
|
409
|
+
/** Every credential in `text` recognizable by format. */
|
|
410
|
+
export function findFormats(text) {
|
|
411
|
+
const out = [];
|
|
412
|
+
const all = load();
|
|
413
|
+
const run = new Set(always);
|
|
414
|
+
for (const k of keywordSet.find(text))
|
|
415
|
+
for (const i of byKeyword[k])
|
|
416
|
+
run.add(i);
|
|
417
|
+
// (Matched on the text with each escape's letters blank, positions kept: after `\t` or `\n` in a repr or a log, a
|
|
418
|
+
// token starts there, not at the `t` or `n`.)
|
|
419
|
+
const scan = withoutEscapeLetters(text);
|
|
420
|
+
for (const i of [...run].sort((a, b) => a - b)) {
|
|
421
|
+
const f = all[i];
|
|
422
|
+
if (f.gate && !f.gate.test(text))
|
|
423
|
+
continue;
|
|
424
|
+
for (const m of scan.matchAll(f.re)) {
|
|
425
|
+
let [start, end] = secretSpan(m.indices);
|
|
426
|
+
// A URL-shaped rule that matches the whole link has its secret in the path: keep the readable part (`https://hooks.slack.com/services/`).
|
|
427
|
+
if (!m.indices[1])
|
|
428
|
+
start += URL_READABLE_PREFIX.exec(m[0])?.[0].length ?? 0;
|
|
429
|
+
if (CONNECTION_STRING_NAME.test(f.name))
|
|
430
|
+
[start, end] = connectionStringSecret(scan, start, end);
|
|
431
|
+
if (end - start < f.shortest)
|
|
432
|
+
continue;
|
|
433
|
+
const value = text.slice(start, end);
|
|
434
|
+
// (A token that goes on past the match isn't this vendor's: a rule of fixed length matched its prefix.)
|
|
435
|
+
if (/[A-Za-z0-9]/.test(scan[end] ?? "") && /[A-Za-z0-9]$/.test(value))
|
|
436
|
+
continue;
|
|
437
|
+
// (Publishable keys, public JWTs and client-side Google keys: decided in policy.ts.)
|
|
438
|
+
if (isPlaceholder(value))
|
|
439
|
+
continue;
|
|
440
|
+
if (f.rejects?.(value, text.slice(Math.max(0, start - 200), start)))
|
|
441
|
+
continue;
|
|
442
|
+
// A whole connection string: the URL rule hides just its password and keeps the host.
|
|
443
|
+
if (leadingUrl(value)?.user)
|
|
444
|
+
continue;
|
|
445
|
+
// Real tokens don't read as words: `sk-toggleable__label-arrow`, `sk-NotificationsClient.Models.Response`.
|
|
446
|
+
if (naturalness(value) > 1.0)
|
|
447
|
+
continue;
|
|
448
|
+
// A vendor-word rule: the name the value is assigned to must not say it's an ID, a hash or public.
|
|
449
|
+
if (f.byVendorWord) {
|
|
450
|
+
const gap = text.slice(m.index, start);
|
|
451
|
+
// `virustotal,<sha256>` in an IOC list: a comma isn't an assignment, and a bare hash isn't a key.
|
|
452
|
+
if (!/key|token|secret|pass|auth|cred|api|pat\b|sid/i.test(gap) &&
|
|
453
|
+
(!/[=:]/.test(gap) || /^[0-9a-f]+$/i.test(value)))
|
|
454
|
+
continue;
|
|
455
|
+
const name = text
|
|
456
|
+
.slice(m.index, start)
|
|
457
|
+
.match(/([A-Za-z_][\w.-]{0,80})[ \t]{0,8}["'\]]?[ \t]{0,8}(?:=|:|=>)[^=:]{0,10}$/)?.[1] ?? "";
|
|
458
|
+
const cls = name ? nameClass(name) : "neutral";
|
|
459
|
+
if (cls === "identifier" || cls === "hash" || cls === "public")
|
|
460
|
+
continue;
|
|
461
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value) && !isCredentialName(name))
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (f.minEntropy && entropy(value) < f.minEntropy)
|
|
465
|
+
continue;
|
|
466
|
+
out.push({ start, end, kind: f.name, rule: `format:${f.name}`, confidence: 1 });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return out;
|
|
470
|
+
}
|
package/dist/hmac.d.ts
ADDED
package/dist/hmac.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HMAC-SHA256 in plain JS, so fingerprints work in every runtime (browsers, workers, Deno, Bun)
|
|
3
|
+
* without node:crypto or async WebCrypto: placeholders' fingerprints and public.ts's sample JWTs, and SHA-256 for
|
|
4
|
+
* checksums.ts's address checks and structure.ts's BIP-39 checksum.
|
|
5
|
+
*/
|
|
6
|
+
const K = new Uint32Array([
|
|
7
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98,
|
|
8
|
+
0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
|
|
9
|
+
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8,
|
|
10
|
+
0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
|
11
|
+
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
|
|
12
|
+
0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
|
|
13
|
+
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
|
|
14
|
+
0xc67178f2,
|
|
15
|
+
]);
|
|
16
|
+
export function sha256(data) {
|
|
17
|
+
const h = new Uint32Array([
|
|
18
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
19
|
+
]);
|
|
20
|
+
const len = data.length;
|
|
21
|
+
const blocks = Math.ceil((len + 9) / 64);
|
|
22
|
+
const buf = new Uint8Array(blocks * 64);
|
|
23
|
+
buf.set(data);
|
|
24
|
+
buf[len] = 0x80;
|
|
25
|
+
const bits = len * 8;
|
|
26
|
+
const view = new DataView(buf.buffer);
|
|
27
|
+
view.setUint32(buf.length - 8, Math.floor(bits / 2 ** 32));
|
|
28
|
+
view.setUint32(buf.length - 4, bits >>> 0);
|
|
29
|
+
const w = new Uint32Array(64);
|
|
30
|
+
for (let b = 0; b < blocks; b++) {
|
|
31
|
+
for (let i = 0; i < 16; i++)
|
|
32
|
+
w[i] = view.getUint32(b * 64 + i * 4);
|
|
33
|
+
for (let i = 16; i < 64; i++) {
|
|
34
|
+
const s0 = ((w[i - 15] >>> 7) | (w[i - 15] << 25)) ^ ((w[i - 15] >>> 18) | (w[i - 15] << 14)) ^ (w[i - 15] >>> 3);
|
|
35
|
+
const s1 = ((w[i - 2] >>> 17) | (w[i - 2] << 15)) ^ ((w[i - 2] >>> 19) | (w[i - 2] << 13)) ^ (w[i - 2] >>> 10);
|
|
36
|
+
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
|
|
37
|
+
}
|
|
38
|
+
let [a, bb, c, d, e, f, g, hh] = h;
|
|
39
|
+
for (let i = 0; i < 64; i++) {
|
|
40
|
+
const S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
|
|
41
|
+
const t1 = (hh + S1 + ((e & f) ^ (~e & g)) + K[i] + w[i]) >>> 0;
|
|
42
|
+
const S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
|
|
43
|
+
const t2 = (S0 + ((a & bb) ^ (a & c) ^ (bb & c))) >>> 0;
|
|
44
|
+
hh = g;
|
|
45
|
+
g = f;
|
|
46
|
+
f = e;
|
|
47
|
+
e = (d + t1) >>> 0;
|
|
48
|
+
d = c;
|
|
49
|
+
c = bb;
|
|
50
|
+
bb = a;
|
|
51
|
+
a = (t1 + t2) >>> 0;
|
|
52
|
+
}
|
|
53
|
+
h[0] += a;
|
|
54
|
+
h[1] += bb;
|
|
55
|
+
h[2] += c;
|
|
56
|
+
h[3] += d;
|
|
57
|
+
h[4] += e;
|
|
58
|
+
h[5] += f;
|
|
59
|
+
h[6] += g;
|
|
60
|
+
h[7] += hh;
|
|
61
|
+
}
|
|
62
|
+
const out = new Uint8Array(32);
|
|
63
|
+
const ov = new DataView(out.buffer);
|
|
64
|
+
for (let i = 0; i < 8; i++)
|
|
65
|
+
ov.setUint32(i * 4, h[i]);
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
export function hmacSha256(key, message) {
|
|
69
|
+
if (key.length > 64)
|
|
70
|
+
key = sha256(key);
|
|
71
|
+
const block = new Uint8Array(64);
|
|
72
|
+
block.set(key);
|
|
73
|
+
const inner = new Uint8Array(64 + message.length);
|
|
74
|
+
const outer = new Uint8Array(64 + 32);
|
|
75
|
+
for (let i = 0; i < 64; i++) {
|
|
76
|
+
inner[i] = block[i] ^ 0x36;
|
|
77
|
+
outer[i] = block[i] ^ 0x5c;
|
|
78
|
+
}
|
|
79
|
+
inner.set(message, 64);
|
|
80
|
+
outer.set(sha256(inner), 64);
|
|
81
|
+
return sha256(outer);
|
|
82
|
+
}
|
|
83
|
+
export function randomKey() {
|
|
84
|
+
const key = new Uint8Array(32);
|
|
85
|
+
globalThis.crypto.getRandomValues(key);
|
|
86
|
+
return key;
|
|
87
|
+
}
|