redac---n 0.0.0-stage → 0.0.2

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.
Files changed (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +323 -2
  3. package/THIRD_PARTY_NOTICES.md +59 -0
  4. package/dist/aho.d.ts +25 -0
  5. package/dist/aho.js +116 -0
  6. package/dist/base64.d.ts +10 -0
  7. package/dist/base64.js +29 -0
  8. package/dist/bip39-data.d.ts +2 -0
  9. package/dist/bip39-data.js +2 -0
  10. package/dist/cache.d.ts +14 -0
  11. package/dist/cache.js +40 -0
  12. package/dist/calls.d.ts +64 -0
  13. package/dist/calls.js +269 -0
  14. package/dist/charlm-data.d.ts +2 -0
  15. package/dist/charlm-data.js +3 -0
  16. package/dist/charlm.d.ts +7 -0
  17. package/dist/charlm.js +65 -0
  18. package/dist/checksums.d.ts +10 -0
  19. package/dist/checksums.js +98 -0
  20. package/dist/commands.d.ts +72 -0
  21. package/dist/commands.js +787 -0
  22. package/dist/context.d.ts +98 -0
  23. package/dist/context.js +469 -0
  24. package/dist/encoded.d.ts +11 -0
  25. package/dist/encoded.js +336 -0
  26. package/dist/environment.d.ts +10 -0
  27. package/dist/environment.js +62 -0
  28. package/dist/formats-data.d.ts +2 -0
  29. package/dist/formats-data.js +405 -0
  30. package/dist/formats.d.ts +5 -0
  31. package/dist/formats.js +470 -0
  32. package/dist/hmac.d.ts +3 -0
  33. package/dist/hmac.js +87 -0
  34. package/dist/index.d.ts +101 -0
  35. package/dist/index.js +488 -0
  36. package/dist/json.d.ts +82 -0
  37. package/dist/json.js +392 -0
  38. package/dist/known.d.ts +34 -0
  39. package/dist/known.js +340 -0
  40. package/dist/lines.d.ts +28 -0
  41. package/dist/lines.js +69 -0
  42. package/dist/lookup.d.ts +5 -0
  43. package/dist/lookup.js +7 -0
  44. package/dist/model-data.d.ts +3 -0
  45. package/dist/model-data.js +4 -0
  46. package/dist/model.d.ts +2 -0
  47. package/dist/model.js +19 -0
  48. package/dist/named.d.ts +20 -0
  49. package/dist/named.js +3257 -0
  50. package/dist/names.d.ts +106 -0
  51. package/dist/names.js +1517 -0
  52. package/dist/normalize.d.ts +25 -0
  53. package/dist/normalize.js +529 -0
  54. package/dist/pem.d.ts +32 -0
  55. package/dist/pem.js +269 -0
  56. package/dist/policy.d.ts +3 -0
  57. package/dist/policy.js +470 -0
  58. package/dist/public.d.ts +44 -0
  59. package/dist/public.js +362 -0
  60. package/dist/publickeys.d.ts +41 -0
  61. package/dist/publickeys.js +232 -0
  62. package/dist/random.d.ts +29 -0
  63. package/dist/random.js +165 -0
  64. package/dist/repeats.d.ts +21 -0
  65. package/dist/repeats.js +80 -0
  66. package/dist/rules.d.ts +42 -0
  67. package/dist/rules.js +58 -0
  68. package/dist/runs.d.ts +10 -0
  69. package/dist/runs.js +36 -0
  70. package/dist/secretfile.d.ts +52 -0
  71. package/dist/secretfile.js +722 -0
  72. package/dist/shell.d.ts +127 -0
  73. package/dist/shell.js +703 -0
  74. package/dist/spans.d.ts +61 -0
  75. package/dist/spans.js +287 -0
  76. package/dist/sql.d.ts +20 -0
  77. package/dist/sql.js +66 -0
  78. package/dist/stream.d.ts +46 -0
  79. package/dist/stream.js +150 -0
  80. package/dist/structure.d.ts +24 -0
  81. package/dist/structure.js +1625 -0
  82. package/dist/syntax.d.ts +156 -0
  83. package/dist/syntax.js +446 -0
  84. package/dist/transcript.d.ts +30 -0
  85. package/dist/transcript.js +713 -0
  86. package/dist/types.d.ts +81 -0
  87. package/dist/types.js +1 -0
  88. package/dist/unnamed.d.ts +59 -0
  89. package/dist/unnamed.js +939 -0
  90. package/dist/urls.d.ts +70 -0
  91. package/dist/urls.js +303 -0
  92. package/dist/values.d.ts +178 -0
  93. package/dist/values.js +1621 -0
  94. package/dist/yaml.d.ts +40 -0
  95. package/dist/yaml.js +174 -0
  96. package/docs/creddata.svg +318 -0
  97. package/docs/guessing.md +139 -0
  98. package/docs/holdout.svg +85 -0
  99. package/docs/layers.md +80 -0
  100. package/docs/models.svg +81 -0
  101. package/docs/real-text.svg +112 -0
  102. package/package.json +57 -5
package/dist/policy.js ADDED
@@ -0,0 +1,470 @@
1
+ /**
2
+ * The decision stage. The layers (known values, formats, structure, names, random tokens) propose spans;
3
+ * this decides, once and the same way for every layer, which proposals are values that are public by
4
+ * design or never secrets at all, and so stay. A policy written here can't drift between layers: a
5
+ * format match and a named match on the same value get the same answer.
6
+ *
7
+ * Not judged here: known values (the caller vouched for them), secret files and a secret command's output
8
+ * (`file:`, `command:`: there the file or command says what the value is), and repeats of a value already
9
+ * judged. Each rule still decides what counts as a value at all (placeholders, references, code).
10
+ */
11
+ import { clientSdkAt, isClientGoogleKey, isPublicJwt, PUBLIC_FORMAT, PUBLIC_SDK_PARAM, PUBLISHED_EXAMPLE, } from "./public.js";
12
+ import { isBase58Address, isBech32Address, isNatsPublicKey, isStellarPublicKey, isXrpAddress } from "./checksums.js";
13
+ import { isPairLabel } from "./named.js";
14
+ import { nameClass, namesPasswordOrPin } from "./names.js";
15
+ import { base64Binary, base64Prefix } from "./base64.js";
16
+ import { isDerPublicKey, isPgpPublicKey, isPublicPem } from "./publickeys.js";
17
+ import { inJwk } from "./json.js";
18
+ import { lineEnd, trailingRun } from "./lines.js";
19
+ import { isPlaceholderBasic, isPublishedMnemonic } from "./structure.js";
20
+ import { IDENT_PATH, isBenignValue, isCountingDigits, QUANTITY, isCredentialReference, namesCredential, isGroupedDigits, isNameLike, isPasswordHash, isTypeExpression, looksSecret, readsAsWords, } from "./values.js";
21
+ import { CONTRACTION, escapedLineStart } from "./syntax.js";
22
+ import { isGuess, isKeyMaterial, isVouched, layerOf, readsByPlace } from "./rules.js";
23
+ import { Doc } from "./context.js";
24
+ import { publicReason } from "./unnamed.js";
25
+ /**
26
+ * Passwords products ship with and document, each with the line it is that product's on: the JDK's truststore
27
+ * (`-storepass changeit`), Android's debug keystore (`-keystore ~/.android/debug.keystore -storepass android`),
28
+ * Oracle's sample schema (`scott/tiger`), Sauce Labs' demo shop (`login("standard_user", "secret_sauce")`).
29
+ */
30
+ const PUBLISHED_DEFAULTS = [
31
+ ["changeit", /cacerts|trust[-_]?store|keytool|javax\.net\.ssl|keystore/i],
32
+ ["android", /debug\.keystore|androiddebugkey/],
33
+ ["tiger", /\bscott\//i],
34
+ ["secret_sauce", /standard_user|locked_out_user|problem_user|performance_glitch_user|saucedemo/],
35
+ ];
36
+ /**
37
+ * A proposal that says what it is (a vendor format whose prefix says secret, private-key armour or DER) is
38
+ * never kept by the context around it: a public-sounding name (`VITE_OPENAI_API_KEY=sk-proj-…`), a public
39
+ * key earlier on the line, a nearby `kty` or `sitekey`. Context decides only for values whose shape is
40
+ * shared by public and secret ones: Google `AIza` keys, JWTs, a format found only by a keyword nearby
41
+ * (an Amplitude key is 32 hex), a format for a public key (Langfuse `pk-lf-`), and whatever names and
42
+ * places propose.
43
+ */
44
+ function saysWhatItIs(f, value, secretJwt) {
45
+ if (secretJwt || isKeyMaterial(f.rule))
46
+ return true;
47
+ // A vendor format with a prefix (`sk-proj-`, `ghp_`, `xoxb-`) or an AWS key ID, but not one whose shape public
48
+ // values share (`AIza`, `eyJ`) or a format for a public value.
49
+ return (layerOf(f.rule) === "format" &&
50
+ /^(?:[A-Za-z][A-Za-z0-9]{0,11}[-_.]|A[KS]IA[A-Z0-9]{16}$)/.test(value) &&
51
+ !/^(?:AIza|eyJ)/.test(value) &&
52
+ !/Google API key|GCP API key|JSON Web Token|JWT|algolia|public|publishable|client[ _-]?(?:id|key|sdk)|sdk key|site key/i.test(f.rule));
53
+ }
54
+ /** Whether a value that SDK initialiser `sdk` takes under `name` is its public key, `before` being the text before it: its line up to it and the line above (not a trailing comment, `algoliasearch(id, key) // public docs`). */
55
+ function isClientSdkKey(sdk, name, before) {
56
+ if (/secret|passw|private|admin|master|server|write/i.test(name))
57
+ return false;
58
+ // (The unnamed layer names a positional argument by its call: `algoliasearch`.)
59
+ const callee = sdk.replace(/^new\s+/, "").replace(/\s*[({[:=].*$/s, "");
60
+ const positional = !name || callee === name;
61
+ if (!positional)
62
+ return PUBLIC_SDK_PARAM.test(name.replace(/^.*[.\s]/, ""));
63
+ return (!/^(?:algoliasearch|instantsearch)/.test(sdk) || /search[-_ ]?(?:only|key)|public|read[-_ ]?only/i.test(before));
64
+ }
65
+ /**
66
+ * A `WWW-Authenticate` or `Proxy-Authenticate` header's scheme and parameters, up to inside one of the parameters the
67
+ * challenge schemes define (RFC 7235, 7616, 6750, 9728, Azure's), which say where and how to authenticate.
68
+ */
69
+ const AUTH_CHALLENGE = /\b(?:WWW|Proxy)-Authenticate["']?[ \t]*[:=][ \t]*["']?[A-Za-z][\w-]*(?:[ \t]+[\w-]+=(?:"[^"\n]*"|[^\s,"]*)[ \t]*,)*[ \t]+(?:realm|authorization(?:_uri)?|resource(?:_metadata)?|scope|error|nonce|opaque|algorithm|qop|domain|stale|charset|userhash|trusted_issuers|client_id)=(?:"[^"\n]*|[^\s,"]*)$/i;
70
+ /** A line's text with JSON's escaped quotes read as quotes, and without a CRLF's `\r`. */
71
+ const unescapeQuotes = (line) => line.replace(/\\+(["'])/g, "$1").replace(/\r$/, "");
72
+ /** The line before the one starting at `start` (after a line break, real or escaped), looking back no further than `floor`. */
73
+ function lineBefore(text, start, floor) {
74
+ const end = text[start - 1] === "\n" ? start - 1 : text.startsWith("\\n", start - 2) ? start - 2 : -1;
75
+ return end < floor ? "" : text.slice(escapedLineStart(text, end, floor), end);
76
+ }
77
+ /** Why a proposed span stays in the text, or `undefined` to hide it. `hashes: "hide"` hides password hashes too. */
78
+ export function keepReason(text, f, hashes = "keep") {
79
+ // In a secret file or a secret command's output everything is hidden, whatever it looks like (README), except
80
+ // what is there under no credential's name (`file:opaque`).
81
+ // (`known` and `known:partial`: the caller vouched for the value, or it was learned from where it was a secret.)
82
+ if (isVouched(f.rule))
83
+ return undefined;
84
+ const value = text.slice(f.start, f.end);
85
+ const left = text.slice(Math.max(0, f.start - 200), f.start);
86
+ // A device's published default PIN, counting or one digit repeated, that its line calls the default or an example
87
+ // (`The default Bluetooth PIN is 0000`, `factory PIN: 1234`). Anywhere else a PIN of `0000` is someone's PIN
88
+ // (`pin_value=0000`, `--password 123456`).
89
+ if (/^\d{4,8}$/.test(value) &&
90
+ isCountingDigits(value) &&
91
+ f.name !== undefined &&
92
+ namesPasswordOrPin(f.name) &&
93
+ /\b(?:default|factory|example|such as|for instance)\b|\be\.g\./i.test(left.slice(left.lastIndexOf("\n") + 1)))
94
+ return "a published default PIN";
95
+ // The token the span sits in, to blanks, quotes and separators, and its part after a label (`key_id:rzp_…`,
96
+ // `SHA256:…`; a Mapbox `pk.` token's segment, a JWT behind a diff's `+`): a layer may have cut into either.
97
+ const leftRun = trailingRun(left, /[\s"'`=,;()]/);
98
+ const token = leftRun + value + (text.slice(f.end, f.end + 200).match(/^[^\s"'`,;()]*/)?.[0] ?? "");
99
+ const word = token.slice(leftRun.lastIndexOf(":") + 1).replace(/^[+-](?=eyJ)/, "");
100
+ // Password hashes (bcrypt, argon2, crypt and its `$$`-escaped Compose form, LDAP, MySQL, Pi-hole's BALLOON):
101
+ // kept unless asked otherwise. A weak password's hash can be cracked offline; most labelled data keeps them.
102
+ // A hash by its format, read whole (the token too: a layer that cut into a hash, `$9$salt$X.<tail>`, leaves it
103
+ // whole), or by its place: a hash-named field (structure.ts).
104
+ if (hashes === "keep" &&
105
+ (f.rule === "structure:hash-field" ||
106
+ [value, token].some((v) => isPasswordHash(v.replace(/\$\$/g, "$").replace(/^[\w.@-]{1,64}:(?=\$)/, "")))))
107
+ return "password hash";
108
+ // Encrypted values: Windows DPAPI blobs (NuGet), and a JWE (five parts, an `enc` header) named as encrypted
109
+ // data. Not a JWE elsewhere: Auth.js session cookies and encrypted ID tokens are bearer credentials, accepted
110
+ // as they are. Not Jenkins' or Maven's `{…}`: their key sits on the same machine (`secrets/master.key`,
111
+ // `settings-security.xml`), where an agent that read one can usually read the other.
112
+ // (And AWS KMS ciphertext, `AQICAH…`: a data key a SOPS file's `kms:` entry holds, decrypted only by KMS.)
113
+ if (/^AQAAANCMnd8BFdERjHoAwE\/Cl\+s|^AQICAH[A-Za-z0-9+/]{40}/.test(value))
114
+ return "encrypted value";
115
+ // A Sealed Secret's ciphertext (`Ag…`), in a SealedSecret's `encryptedData`: by its context, as a random base64
116
+ // secret starts with `Ag` too, 1 time in 4,096.
117
+ if (/^Ag[A-Za-z0-9+/]{100,}={0,2}$/.test(value) &&
118
+ /kind:[ \t]*SealedSecret|"kind":[ \t]*"SealedSecret"|encryptedData["']?:/.test(text.slice(Math.max(0, f.start - 20_000), f.start)))
119
+ return "encrypted value";
120
+ // The line the span is on, up to it and after it, and the line above, read as text: in JSON-escaped text a `\n`
121
+ // ends a line too and `\"` is a quote.
122
+ const lineAt = escapedLineStart(text, f.start, f.start - left.length);
123
+ const line = unescapeQuotes(text.slice(lineAt, f.start));
124
+ const lineAfter = text.slice(f.end, lineEnd(text, f.end, 200));
125
+ const above = unescapeQuotes(lineBefore(text, lineAt, f.start - left.length));
126
+ // A server's authentication challenge holds no client's secret: its parameters say where and how to get one
127
+ // (`WWW-Authenticate: Bearer authorization="https://login…/<tenant>", resource="…"`, a Digest `nonce`).
128
+ // (A vendor's format echoed in one, `error_description="… ghp_…"`, is that token still.)
129
+ // (Recorded as YAML too: `www-authenticate:` above, ` - Bearer authorization="…"` on the line.)
130
+ const challenge = AUTH_CHALLENGE.test(line) ||
131
+ (/(?:www|proxy)-authenticate["']?[ \t]*:[ \t]*$/i.test(above) &&
132
+ AUTH_CHALLENGE.test(`WWW-Authenticate: ${line.replace(/^[ \t]*-[ \t]+/, "")}`));
133
+ if (layerOf(f.rule) !== "format" && challenge)
134
+ return "authentication challenge";
135
+ // (The whole token, whichever part of it a layer found, named on the line before it.)
136
+ const wordLead = leftRun.slice(leftRun.lastIndexOf(":") + 1);
137
+ if (/^eyJ[\w-]+\.[\w-]*\.[\w-]+\.[\w-]+\.[\w-]+$/.test(word) &&
138
+ // (Read as far as it decodes: a header with a character past its last whole group still names its `enc`.)
139
+ /"enc"\s*:/.test(new TextDecoder().decode(base64Prefix(word.split(".")[0]) ?? new Uint8Array())) &&
140
+ /(?:encrypt|cipher|jwe)\w*["'`]?[ \t]*[:=][ \t]*["'`]?$/i.test(line.endsWith(wordLead) ? line.slice(0, line.length - wordLead.length) : line))
141
+ return "encrypted value";
142
+ // The name the value sits under (`NAME=`, `"name":`, `name:`, `| name |`), if any.
143
+ // The name the layer read, or the one read here (without a shell's `export`/`set` before it), whichever says
144
+ // more: a layer that read `key` in `anon key:` or `client key:` gets the qualifier back.
145
+ // (Seen through a type annotation, `NAME: string = "`, `api_key: str = "`; a subscript, `cfg["NAME"] = "`; `setx NAME "`;
146
+ // and without a search tool's prefix, `src/a.ts:7:` or `rg`'s `7:`.)
147
+ const plain = line
148
+ .replace(/^(?:[\w./@+-]{1,200}\.\w{1,10}[:-])?\d{1,7}[:-](?=[A-Za-z_])/, "")
149
+ .replace(/(?<!\w)([A-Za-z_]\w*)\??:[ \t]*[A-Za-z_][\w.<>[\]|, ]{0,40}?(?=[ \t]*=(?!=))/, "$1")
150
+ .replace(/\[["']([\w.-]{1,60})["']\][ \t]*(?==)/g, ".$1")
151
+ .replace(/^([ \t]*)setx[ \t]+([\w.-]{1,60})[ \t]+(?=["'])/i, "$1$2=");
152
+ const read = /(?<![\w.-])([A-Za-z_][\w.-]{0,60}(?: [A-Za-z][\w-]{0,30})?)["'`]?[ \t]*(?:[:=]|=>|\|)[ \t]*["'`]?$/
153
+ .exec(plain)?.[1]
154
+ ?.replace(/^(?:export|set|setx|declare|local|readonly|const|let|var) /, "")
155
+ .replace(/^.*\./, (m) => (/^(?:cfg|config|settings|env|os\.environ|process\.env)\.$/.test(m) ? "" : m)) ?? "";
156
+ const squash = (s) => s.toLowerCase().replace(/[\s_.-]/g, "");
157
+ // (A type a layer took for the name, `NAME: string = "…"`, isn't one.)
158
+ const layerName = f.name && !isTypeExpression(f.name) ? f.name : undefined;
159
+ const name = layerName && !(read.length > layerName.length && squash(read).endsWith(squash(layerName))) ? layerName : read;
160
+ // Never secrets, whatever names them (counts, versions, algorithm names, key chords…), for the rules that
161
+ // read a value by its name or its place; formats and private-key armour say what a value is themselves.
162
+ if (readsByPlace(f.rule)) {
163
+ // (A number under a password's or a PIN's name is one, and so is one a rule read as a password by its place:
164
+ // `PIN=4821-7730`, `DB_PASSWORD=21.05.1987`, `mysql -p21.05.1987`, `postgres://admin:4821-7730@db`; an ISO date,
165
+ // `2024-05-01`, is a date.)
166
+ // (A bare `pass` is as often the word: `single-pass: 3.52x`.)
167
+ if (isBenignValue(value) &&
168
+ !((isGroupedDigits(value) || (QUANTITY.test(value.trim()) && !/(?:^|[^a-z])pass$/i.test(name))) &&
169
+ (namesPasswordOrPin(name) || f.kind === "password")))
170
+ return "never a secret";
171
+ if (namesCredential(value))
172
+ return "names a credential";
173
+ if (isVariableSetIn(text, value))
174
+ return "a variable the text sets";
175
+ // A Twilio resource's SID (`AccountSid`, `streamSid`: two letters and 32 hex), an ID; its auth token is the secret.
176
+ if (/sid$/i.test(name) && /^[A-Z]{2}[0-9a-f]{32}$/.test(value))
177
+ return "a Twilio SID";
178
+ // A service account's Secrets by their generated names: `kubectl describe sa`'s `Image pull secrets: <sa>-dockercfg-4qz9g`.
179
+ if (/(?:^|[\s_-])(?:secrets|tokens)$/i.test(name) &&
180
+ /^[a-z0-9]+(?:-[a-z0-9]+)*-(?:token|dockercfg)-[a-z0-9]{5}$/.test(value))
181
+ return "a Secret's name";
182
+ const code = codeShape(text, f, value, left);
183
+ if (code)
184
+ return code;
185
+ }
186
+ // What the random layer's vetoes know is public by design (a key's fingerprint, an APK checksum, an AWS unique ID…),
187
+ // for the layers that read a value by its name or its place.
188
+ if (["named", "structure"].includes(layerOf(f.rule))) {
189
+ const reason = publicReason(Doc.of(text), f.start, f.end);
190
+ if (reason)
191
+ return `public by its shape (${reason})`;
192
+ }
193
+ // Example values published in RFCs and docs, whichever layer read them.
194
+ if (PUBLISHED_EXAMPLE.test(value) || isPublishedMnemonic(value))
195
+ return "published example";
196
+ // (A JWT whose claims give it a secret role says what it is too, whatever it's named: Supabase's `service_role`.)
197
+ const secretJwt = /^eyJ[\w-]+\.eyJ[\w-]+\./.test(word) &&
198
+ /"role"\s*:\s*"(?:service_role|admin|supabase_admin)"/.test(base64Binary(word.split(".")[1]) ?? "");
199
+ // A vendor format matched in the middle of a longer base64 run (a pasted image, a certificate): not that token.
200
+ if (layerOf(f.rule) === "format" &&
201
+ /[A-Za-z0-9+]$/.test(left) &&
202
+ /^[A-Za-z0-9+][A-Za-z0-9+/]{15}/.test(text.slice(f.end, f.end + 16)))
203
+ return "inside a longer token";
204
+ const typed = saysWhatItIs(f, value, secretJwt);
205
+ // Publishable keys: `pk_live_`, `pk.eyJ`, Datadog `pub…`, PostHog `phc_`, Razorpay key IDs, Supabase `sb_publishable_`,
206
+ // Square app IDs, SonarQube badge tokens.
207
+ // (Also after a label the span took in: `auth=key_id:rzp_live_…`.)
208
+ if (PUBLIC_FORMAT.test(value) ||
209
+ PUBLIC_FORMAT.test(word) ||
210
+ PUBLIC_FORMAT.test(value.replace(/^[A-Za-z][\w.-]{0,40}:/, "")))
211
+ return "publishable key";
212
+ // A Basic credential that decodes to a docs placeholder (`user:pass`), whichever layer read it.
213
+ if (/\bBasic[ \t]+$/i.test(left) && isPlaceholderBasic(value))
214
+ return "placeholder";
215
+ if (/(?:^|[^\w])pk[._]$/.test(left))
216
+ return "publishable key";
217
+ if (typed)
218
+ return undefined;
219
+ // A status-badge URL's token, made to sit in a README: `dl.circleci.com/status-badge/…?circle-token=…`.
220
+ if (/(?:status-badge|project_badges|\/badges?)\/[^\s"'<>()]*[?&](?:circle-)?token=$/.test(left))
221
+ return "badge token";
222
+ if (PUBLISHED_DEFAULTS.some(([v, where]) => v === value && where.test(line + lineAfter)))
223
+ return "published default";
224
+ // A reCAPTCHA or Turnstile site key where a page or an SDK takes one (its secret key has the same shape).
225
+ if (/(?:ReCaptcha\w*Provider|grecaptcha\.\w+|data-sitekey|sitekey)[ \t"'`=:(,]{0,4}$/i.test(left))
226
+ return "publishable key";
227
+ const serverLine = /server|backend|admin|gemini|genai|generative|vertex/i.test(line);
228
+ // A value under a name that says it's public (`MAPS_BROWSER_KEY`, `STRIPE_PUBLISHABLE_KEY`, `NEXT_PUBLIC_…`),
229
+ // from whichever layer, unless the line says server or Gemini.
230
+ // (A name of key-like words: `Token for the public API:` is a sentence, not a name.)
231
+ if (name && nameClass(name.replace(/\s+/g, "_")) === "public" && /key|token|id\b|dsn|sid/i.test(name) && !serverLine)
232
+ return "public by its name";
233
+ // A random token the unnamed layer guessed at, under a name that says ID or hash (`bridgeSessionId`, `sha`): what the
234
+ // unnamed layer's own name vetoes say, for a finding whose name only this stage sees (a transcript leaf's key).
235
+ if (isGuess(f.rule) && ["identifier", "hash"].includes(nameClass(name.replace(/\s+/g, "_"))))
236
+ return "an ID by its name";
237
+ // JWTs meant to be public (anon and publishable roles, published samples). Under a credential's name, only
238
+ // a published sample is, unless the name itself says anon or public: an anon key assigned to `SUPABASE_KEY`
239
+ // is treated as the key it's named; `anon key:` in `supabase status` is what it says.
240
+ if (/^eyJ/.test(word) && isPublicJwt(word, layerOf(f.rule) === "named" && !/anon|public|publishable/i.test(name)))
241
+ return "public JWT";
242
+ if (isJwtHeader(word))
243
+ return "a JWT's header";
244
+ // The ID half of an `id:secret` value under a name (`credential: <client id>:<secret>`): the secret half is
245
+ // hidden on its own (named.ts credentialPair).
246
+ // (Not Basic auth's user info, where both halves are the credential: `basic.auth.user.info=<key>:<secret>`.)
247
+ // (Or its secret half already a placeholder: redacting redacted text changes nothing.)
248
+ if (isGuess(f.rule) &&
249
+ /^:(?:[A-Za-z0-9+/_~-]{16,}|\[REDACTED )/.test(text.slice(f.end, f.end + 20)) &&
250
+ /[:=][ \t]*["'`]?$/.test(left) &&
251
+ !/user[._-]?info|basic/i.test(name) &&
252
+ isPairLabel(name, value))
253
+ return "ID of an ID and secret pair";
254
+ // Client-side SDK configuration takes public keys: `Bugsnag.start({ apiKey: … })`, `new Pusher({ key: … })`,
255
+ // `braintree.client.create({ authorization: … })`. Not a name that says it's server-side: `new Pusher({ secret })`,
256
+ // `algoliasearch(id, adminKey)`. A positional argument is public unless the SDK takes its admin key in the same
257
+ // place: Algolia's search key and admin key are both `algoliasearch(appId, key)`, so only a search key said so.
258
+ const sdk = clientSdkAt(text, f.start);
259
+ if (sdk && isClientSdkKey(sdk, name, `${above}\n${line}`))
260
+ return "client SDK key";
261
+ // A Google API key in a client app's configuration (see isClientGoogleKey).
262
+ if (/^AIza/.test(value) && isClientGoogleKey(text, f.start))
263
+ return "client-side Google key";
264
+ // The search-only key of an Algolia DocSearch config (`appId`, `apiKey`, `indexName`), in every docs site;
265
+ // never a key named admin, write or secret.
266
+ if (/^[0-9a-f]{32}$/.test(value)) {
267
+ const near = text.slice(Math.max(0, f.start - 300), f.end + 300);
268
+ const key = /([\w-]+)["']?[ \t]*[:=][ \t]*["'`]?$/.exec(left.slice(-80))?.[1] ?? "";
269
+ if (/\bindexName\b/.test(near) && /\bappId\b/.test(near) && !/admin|write|secret|private|password/i.test(key))
270
+ return "DocSearch key";
271
+ }
272
+ // Public identifiers and keys whose shape or checksum says so.
273
+ if (/^wx[0-9a-f]{16}$/.test(value))
274
+ return "WeChat app ID";
275
+ if (isNatsPublicKey(value) ||
276
+ isStellarPublicKey(value) ||
277
+ isBase58Address(value) ||
278
+ isXrpAddress(value) ||
279
+ isBech32Address(value))
280
+ return "public key (checksum verified)";
281
+ if (isDerPublicKey(value))
282
+ return "DER public key";
283
+ if (isPgpPublicKey(value))
284
+ return "OpenPGP public key";
285
+ // Public keys and fingerprints: Nix cache keys (`name:<32 bytes base64>=`), a JWK's public members next to
286
+ // `kty`, SSH fingerprints, base64 of a PEM certificate or public key, anything labelled a public key.
287
+ // (A Nix key's name is a host and a number, `cache.nixos.org-1:`; `ci-bot:<base64>=` is a user and a password.)
288
+ const nixName = /^[\w-]+(?:\.[\w-]+)*-\d+:/;
289
+ const nix = nixName.test(value) ? value : (/(?:^|[^\w.-])([\w-]+(?:\.[\w-]+)*-\d+:)$/.exec(left)?.[1] ?? "") + value;
290
+ if (/^[\w-]+(?:\.[\w-]+)*-\d+:[A-Za-z0-9+/]{43}=$/.test(nix))
291
+ return "public key";
292
+ // A Nix signature has the shape of a Nix secret key (a key name and 64 bytes): kept where signatures are listed.
293
+ // (Not one under a credential's name there, `"signatures": [{"secretKey": …`; a narinfo's `Sig:` label names a
294
+ // signature, though `sig` alone reads as a credential's word.)
295
+ if (/^[\w-]+(?:\.[\w-]+)*-\d+:[A-Za-z0-9+/]{86}==$/.test(nix) &&
296
+ /"signatures"\s*:\s*\[[^\]]*$|(?:^|\n)[ \t]*Sig:[ \t]*[\w.-]*:?$/.test(text.slice(Math.max(0, f.start - 2000), f.start)) &&
297
+ (name === "Sig" || nameClass(name) !== "credential"))
298
+ return "signature";
299
+ if (/^(?:x|y|n|e|crv|kid|x5t)$/.test(name) && inJwk(text, f.start, f.end))
300
+ return "public key";
301
+ if ((/(?:SHA256|SHA1|MD5):$/.test(left) || /^(?:SHA256|MD5):/.test(word)) && nameClass(name) !== "credential")
302
+ return "fingerprint";
303
+ // (Base64 of PEM, read whole: a certificate with its key after it isn't public.)
304
+ if (/^LS0tLS1CRUdJTi/.test(value) && isPublicPem(base64Binary(value) ?? ""))
305
+ return "public certificate";
306
+ // (The label applies to the value right after it, not to everything later on the line.)
307
+ // (`Public Key: main:<base64>`: a key name may sit between; `key id (public half):`, `site key (frontend):`.)
308
+ // (A label alone on the line above counts too: `Public Key:` then the key, as `web-push generate-vapid-keys` prints.)
309
+ const labelLine = line.trim() === "" ? above : line.replace(/[\w.-]{1,60}:$/, "");
310
+ if (/\bpublic[\s_-]?key\b|\bpubkey\b|public half/i.test(name) ||
311
+ /(?:\bpublic[\s_-]?key|\bpubkey|public half|\bsite[\s_-]?key)s?\)?(?:\s*\([^)\n]{0,30}\))?(?:\s+is)?[ \t"'`]*[:=]?[ \t"'`]*$/i.test(labelLine))
312
+ return "public key";
313
+ if (/(?:google-site-verification|msvalidate\.01|facebook-domain-verification|yandex-verification|p:domain_verify)["']?\s+content=["']$/i.test(left))
314
+ return "site verification token";
315
+ return undefined;
316
+ }
317
+ /**
318
+ * Whether `value` is a variable (`$P12Pass`, `${P12Pass}`) that the text sets: a shell's `P12Pass=…` (`local P12Pass=…`,
319
+ * `export …`), PHP's, Perl's or PowerShell's `$P12Pass = …`, a shell's `read -rsp "Password: " P12Pass`. Then it is a
320
+ * reference, however its name is spelled: `$Secr3t` alone reads as a password.
321
+ */
322
+ function isVariableSetIn(text, value) {
323
+ const m = /^\$(\{?)([A-Za-z_]\w{0,80})(\}?)$/.exec(value);
324
+ if (!m || m[1].length !== m[3].length)
325
+ return false;
326
+ return new RegExp(String.raw `(?:^|[\s;&|(])${m[2]}=|\$${m[2]}[ \t]*=(?!=)|\bread(?:[ \t]+-\w+(?:[ \t]+(?:"[^"\n]*"|'[^'\n]*'))?)*[ \t]+${m[2]}[ \t]*$`, "m").test(text);
327
+ }
328
+ /** A JOSE header's parameters (RFC 7515, 7516): what a JWT's first part may say. */
329
+ const JOSE_HEADER = /^(?:alg|typ|cty|kid|jku|jwk|x5u|x5c|x5t|x5t#S256|crit|enc|zip|b64)$/;
330
+ /**
331
+ * A JWT shown cut short before its claims, its header alone and an ellipsis (`Bearer eyJhbGciOiJIUzI1NiI...` in docs): the
332
+ * header names an algorithm and a type, the same in every token signed so; the claims and the signature are the
333
+ * credential, and they aren't there. (Base64 of any other JSON, `{"password":…}`, isn't a header; and a header without
334
+ * the ellipsis may be a whole token mangled, or one a test makes up, and is judged as one.)
335
+ */
336
+ function isJwtHeader(word) {
337
+ const m = /^(eyJ[A-Za-z0-9_-]{4,200})\.?(?:\.\.\.|…)$/.exec(word);
338
+ if (!m)
339
+ return false;
340
+ const json = new TextDecoder().decode(base64Prefix(m[1]) ?? new Uint8Array());
341
+ const keys = [...json.matchAll(/"([^"]*)"\s*:/g)].map((k) => k[1]);
342
+ return /^\{\s*"/.test(json) && keys.length > 0 && keys.every((k) => JOSE_HEADER.test(k)) && !/\}./s.test(json);
343
+ }
344
+ /** An identifier path: `pw`, `this.old_pwd`, `StrTools::unMagic`, `FS->Pass`, `$sql_passwd`, `{$secrets`. */
345
+ const PATH = String.raw `\{?${IDENT_PATH}`;
346
+ /** A format string's hole, with its index, conversion and format spec: `{pw}`, `{cfg.db}`, `{args[0]!r}`, `{n:>8}`. */
347
+ const FORMAT_HOLE = /\{[A-Za-z_][\w.]*(?:\[[^\]{}]{0,40}\])?(?:![rsa])?(?::[^{}]{0,20})?\}/g;
348
+ /** A GraphQL type with its non-null marks and directives: `[String!]!`, `String! @constraint(minLength: 12)`. */
349
+ const GRAPHQL_TYPE = /^\[*[A-Z]\w*!?\]*!?(?:[ \t]+@\w+(?:\([^()\n]{0,80}\))?)*$/;
350
+ /**
351
+ * Proposals that are code, read from the characters around the span: whatever a rule took for the value
352
+ * after `password=` isn't one when the quote before it closes a string literal and the span is the code
353
+ * between two literals (`"&pwd=" + passwd + "&v="`, `'pass=' . md5($pw) . '&'`, `"<Password>",FS->Pass,"`),
354
+ * a call or subscript cut at its quote (`md5('$password')`, `datastore['PASS']`), a Go struct field's type
355
+ * and tag, a hole in an interpolated string (`$"Pwd={DB_Password};"`), or a SQL bind parameter.
356
+ */
357
+ function codeShape(text, f, value, left) {
358
+ const v = value.replace(/^["'`]|["'`]$/g, "");
359
+ const before = left.slice(-1);
360
+ const after = text[f.end] ?? "";
361
+ // (Without contractions: an apostrophe in prose before it, `Here's the fix: export TOKEN='…'`, is no quote.)
362
+ const line = left.slice(left.lastIndexOf("\n") + 1).replace(CONTRACTION, "");
363
+ // A declaration's type, a class the code names, with no value given: `val passwordData: PasswordData?,`. (A type the
364
+ // language builds in, `privateKey: Uint8Array;`, `str | None`, is never a secret: isBenignValue.)
365
+ // (A type's mark says so, nullable, generic or an array: `SunnyDay`, `BlueKey`, `HarborConfig` alone are passwords,
366
+ // as after "The password is:"; a declaration's unmarked type is code the readers pass over. A generic's arguments
367
+ // may follow the name read: `Branded<string, "Password">;`; Rust's `let password: Secret<String> = …`.)
368
+ if (/:[ \t]*$/.test(line) &&
369
+ !f.literal &&
370
+ v === value &&
371
+ (/^[A-Z][a-z0-9]*(?:[A-Z][a-z0-9]*)+(?:<[\w, ]*>)?(?:\[\])?\??$/.test(v) ||
372
+ (/^[A-Z][a-z0-9]+$/.test(v) && text[f.end] === "<") ||
373
+ /^[A-Z][a-z0-9]+<[\w, :<>]*>$/.test(v)) &&
374
+ /[?<[]/.test(v + text[f.end]) &&
375
+ readsAsWords(v.replace(/[<?[].*$/, "")) &&
376
+ /^[?;,)]?[ \t]*(?:[;,)=]|\r?$|\n)/.test(text
377
+ .slice(f.end, f.end + 204)
378
+ .replace(/^<[^<>\n]{0,200}>/, "")
379
+ .slice(0, 4)))
380
+ return "a type";
381
+ // A GraphQL field's type: a scalar (in a list or not) or a type with a directive, non-null marks and directives after it:
382
+ // `password: String!`, `tokens: [String!]!`, `password: String! @constraint(minLength: 12)`.
383
+ // (A lone `Hunter2!` is a password: the type needs a scalar's name or a directive.)
384
+ if (/:[ \t]*$/.test(line) &&
385
+ v === value &&
386
+ GRAPHQL_TYPE.test(v) &&
387
+ (/^\[*(?:String|Int|Float|Boolean|ID)\b/.test(v) || v.includes("@")))
388
+ return "a type";
389
+ // An operator at the edge: `+ passwd +`, `, 40, true, 20, `, `) { |a| …`, `+= decipher.final(…)`, `!== form.password`.
390
+ if (/^\s*[)\]]*\s*(?:[+.,)]|\+=|-=|\.=|!==?|===?|\|\||&&|<<|\.\.)\s/.test(v) ||
391
+ /\s(?:[+.,(]|\|\||&&|<<|\.\.)\s*$|\S[,(]\s+$/.test(v))
392
+ return "code between string literals";
393
+ // Without spaces, the operator must follow the quote that closed a literal and lead into a name: `'+pass+'`, `".htmlspecialchars($x)."`.
394
+ // (The quote closes a literal when the line has an even number of them up to it: `"x=" + y` vs `pw = "+Xk9…+"`.)
395
+ const upTo = line + (v !== value ? value[0] : "");
396
+ const q = upTo.slice(-1);
397
+ const closes = /["'`]/.test(q) && upTo.replace(/\\./g, "").split(q).length % 2 === 1;
398
+ const splice = new RegExp(String.raw `^(?:\+|\.|,|\.\.|\|\|)(${PATH})(?:\(|\[|(?:\+|\.|,|\.\.|\|\|)$)`).exec(v.trimEnd());
399
+ // (Parity breaks across lines and HTML attributes; a name with a sigil, a member or a call is code anyway.)
400
+ // (Only after a quote, and the name a name: `.GJP9Evi(` alone is a password. Without a closed literal before
401
+ // it, only a sigil, a member path or a call says code: `$sql_passwd`, `this.old_pwd`, `str(pw)`.)
402
+ // (A call's name reads as words: `,X9MY$(^JM…` is a password.)
403
+ const codey = splice &&
404
+ (/^\$[A-Za-z_]/.test(splice[1]) ||
405
+ /\.|->|::/.test(splice[1]) ||
406
+ (v.trimEnd()[1 + splice[1].length] === "(" && readsAsWords(splice[1]))) &&
407
+ splice[1].length >= 3 &&
408
+ (splice[1].length > 4 || /^\$?[a-z_][a-z0-9_]*$/.test(splice[1]));
409
+ if (splice && (/["'`]/.test(before) || v !== value) && isNameLike(splice[1]) && (closes || codey))
410
+ return "code between string literals";
411
+ // Whatever starts with punctuation after the quote that closed the literal holding the name is code:
412
+ // `"Password:"),h?m.default.createElement(`, `"pass=");`, `'token:']`.
413
+ // (Blanks first too: VB's `" & txtPassword.Text & "`, Elixir's `<>`, Haskell's `++`, MATLAB's juxtaposition.)
414
+ if (closes && v === value && (/^[)\],;?:|&+.]/.test(v) || /^[ \t]/.test(v)))
415
+ return "code after a string literal";
416
+ // A literal that starts a string built with code: `"password=" + cnti.password`, `"root@" + host`, `" --password-file=" + p`.
417
+ // (PHP's and Perl's `.` joins only before a variable, a call or a blank: `'…' . $x`; `' .env` is a file's name.)
418
+ if (/[=:@/&?;,\s-]$|\\[nrt]$/.test(v) &&
419
+ /^["'`][ \t]*(?:\+[ \t]*[$@A-Za-z_(]|\.\.[ \t]*[$@A-Za-z_(]|<<[ \t]*[$@A-Za-z_(]|\.[ \t]*[$@]|\.[ \t]+[A-Za-z_(]|\.(?=\w*\())/.test(text.slice(f.end, f.end + 40)))
420
+ return "start of a built string";
421
+ // The next attribute's name: `placeholder="Enter Password:" name="password"`.
422
+ if (/^\s+[A-Za-z_][\w-]*=$/.test(v) && /^["']/.test(after))
423
+ return "attribute name";
424
+ // A call or subscript cut at its argument's quote: `md5(` and `credentials[` before the quote that opens the argument,
425
+ // `URLEncoder.encode(password, ` before the next one's, `result[0` before its `]`. (A quote that closes a literal,
426
+ // by the line's count of it, ends a password: `"Winter(24"`, `-p'Harbor(99'`, `-u 'admin:a22F(UG,'`.)
427
+ const opens = /["'`]/.test(after) && (line + value).replace(/\\./g, "").split(after).length % 2 === 1;
428
+ const cut = new RegExp(String.raw `^(?:${PATH}[([])+(?:(?<index>\$?\d{1,3})|${PATH}\s*,\s*)?$`).exec(v);
429
+ if (cut && isNameLike(v) && (cut.groups.index !== undefined ? after === "]" : opens))
430
+ return "call or subscript";
431
+ // Unquoted code that refers to a credential by name: `passwd: account.password`, `pwd=m_password;`.
432
+ // (Its credential part as names are written, so `Company.Password1` in an env line is a password.)
433
+ if (v === value && !/["'`]/.test(before) && isCredentialReference(v.trim()))
434
+ return "reference";
435
+ // An identifier ending a struct or object literal's line: `IssuerPrivateKey: keyRSA2048,` (a literal would be quoted).
436
+ if (!f.literal &&
437
+ v === value &&
438
+ !/["'`]/.test(before) &&
439
+ /^[A-Za-z_$][\w$]*$/.test(v) &&
440
+ /[a-z]/.test(v) &&
441
+ /[A-Z_]/.test(v) &&
442
+ readsAsWords(v) &&
443
+ /^,[ \t]*(?:\/\/[^\n]*)?(?:\r?\n|$)/.test(text.slice(f.end, f.end + 200)))
444
+ return "identifier";
445
+ // A Go struct field: `Password string \`json:"password"\``.
446
+ if (/^(?:\*|\[\])*[\w.]+\s+`[a-z]+:"/.test(v))
447
+ return "struct field";
448
+ // A hole in an interpolated or format string: C# `$"…{DB_Password}"`, Python f-strings and `.format`, Rust `format!`;
449
+ // or a value with holes whose literal text holds no secret, `f"--password={pw}xyz"`, `"password={pw}xyz".format(…)`.
450
+ const formatted = /\$@?"|@\$"|\bf["']|format!?\(|String\.Format\(/.test(line) ||
451
+ /^[^\n]*?["'`]\)?\.format\(/.test(text.slice(f.end, f.end + 200));
452
+ const literalText = v.replace(FORMAT_HOLE, "");
453
+ if (formatted &&
454
+ ((before === "{" && after === "}" && new RegExp(`^${PATH}$`).test(v)) ||
455
+ (literalText !== v && !looksSecret(`"${literalText}"`, true))))
456
+ return "template hole";
457
+ // A SQL bind parameter: `WHERE password=@CONTRASEÑA`, `:password`. (A bare `?` is no value to begin with.)
458
+ // (A SQL token in a SQL statement: after a comparison or in `VALUES (…)`, in a statement its keywords say is one, in
459
+ // capitals or starting the line; not cmd's `set NAME=@…` nor a sentence's "update the password to @…".)
460
+ // (Right after the operator, so not a quoted literal's text, `= ':Hunter2x'`.)
461
+ if (!/^structure:cli/.test(f.rule) &&
462
+ /^[@:?][\p{L}_][\p{L}\d_]*$/u.test(v) &&
463
+ v === value &&
464
+ /(?:[=<>]|\(|,)[ \t]*$/.test(line) &&
465
+ !/^[ \t]*set[ \t]+[\w.-]+=$/i.test(line) &&
466
+ (/\b(?:SELECT|INSERT|UPDATE|DELETE|WHERE|VALUES|SET)\b/.test(line) ||
467
+ /^[ \t]*(?:select|insert|update|delete)\b/i.test(line)))
468
+ return "bind parameter";
469
+ return undefined;
470
+ }
@@ -0,0 +1,44 @@
1
+ /** Keys that are public by design: publishable, site and project keys meant for browsers. */
2
+ export declare const PUBLIC_FORMAT: RegExp;
3
+ /**
4
+ * A Google API key in a client app's configuration, where it ships to every user (Google: "API keys for
5
+ * Firebase are not secret"): the object a Firebase web or Flutter config builds, `google-services.json`,
6
+ * the Maps JS loader URL, an Android manifest's or the iOS SDK's Maps key. Elsewhere an `AIza` key is
7
+ * hidden, whatever it's named (`GOOGLE_MAPS_API_KEY=` in a server's environment, a Gemini key): an
8
+ * unrestricted server key costs money. Used by the decision stage, policy.ts.
9
+ */
10
+ export declare function isClientGoogleKey(text: string, start: number): boolean;
11
+ /**
12
+ * Example keys from vendor docs and RFCs, which are published on purpose: RFC 6749's authorization code,
13
+ * tokens and client credentials, RFC 6750's bearer token, RFC 7636's PKCE verifier and challenge, RFC 5849's
14
+ * OAuth 1 examples, RFC 6265's and 9110's, and the TOTP secret every authenticator library's docs use. Kept in
15
+ * every syntax (policy.ts).
16
+ */
17
+ export declare const PUBLISHED_EXAMPLE: RegExp;
18
+ /**
19
+ * A token shown cut short, as tools print keys: a vendor's prefix and a few of the key before an ellipsis and a few
20
+ * after (`sk-proj-abc...xyz`, `AKIA…WXYZ`, `...4f2a`), or words after it (`sk-...set-in-1password`). Not a password
21
+ * with an ellipsis in it, a word before it (`Winter...2024`, `Maple...River3012`), or a key with only its front cut
22
+ * off (`...` then 300 characters).
23
+ */
24
+ export declare function isTruncated(token: string): boolean;
25
+ /**
26
+ * A stand-in for a key, not one: mostly one repeated character (`sk_test_xxxxxxxx`, `ghp_0000…`, `AKIAXXXX…`; not
27
+ * base64 of bytes with many zeros, `TlRMTVNTUAADAAAAGAAY…`, where the other characters vary), a published
28
+ * example or a placeholder word, a counting sequence, or a template whose literal text is too short or too
29
+ * plain to be a secret on its own (`Bearer ${TOKEN}`, `postgres://${USER}:${PASS}@db:5432/app`; not
30
+ * `{;${EC}4S*A)HbUw`).
31
+ */
32
+ export declare function isPlaceholder(token: string): boolean;
33
+ /**
34
+ * A JWT meant to be public or published: Supabase anon keys, the jwt.io and RFC samples. With
35
+ * `samplesOnly`, only the published samples (an anon key assigned to a name is still hidden).
36
+ */
37
+ export declare function isPublicJwt(token: string, samplesOnly?: boolean): boolean;
38
+ /**
39
+ * The client SDK initialiser whose arguments `at` is directly inside, or inside an object or list given as one of them,
40
+ * if the call starts within 300 characters before it: its call as written (`algoliasearch(`).
41
+ */
42
+ export declare function clientSdkAt(text: string, at: number): string | undefined;
43
+ /** The parameters client SDKs take public keys under (positional arguments count too). */
44
+ export declare const PUBLIC_SDK_PARAM: RegExp;