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/urls.d.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { type Span } from "./syntax.ts";
2
+ export interface Url {
3
+ start: number;
4
+ end: number;
5
+ /** `https`, `postgres`, …; "" for a path (`/reset/<token>`). */
6
+ scheme: string;
7
+ user?: Span;
8
+ password?: Span;
9
+ host?: Span;
10
+ /** The path's segments, without the slashes. */
11
+ segments: Span[];
12
+ }
13
+ export declare const URL_SCHEME: string;
14
+ /**
15
+ * Where a URL that starts at `at` ends: at a blank, a quote, an angle bracket, a backtick, a backslash (a JSON
16
+ * string's escaped quote), or a closing bracket it didn't open (a Markdown link's `)`); sentence punctuation at its
17
+ * end isn't part of it (`…/reset/<token>.`). A password's brackets are (`app:p)dgbs@db`, `admin:<Harbor2291x>@host`).
18
+ * However long the password: each look-ahead for its `@` is remembered to where it stopped (`reachesAt`).
19
+ */
20
+ export declare function urlEnd(text: string, at: number): number;
21
+ /** Whether `value` starts with a URL with a scheme. */
22
+ export declare const isUrl: (value: string) => boolean;
23
+ /** The URL with a scheme that `value` starts with, read to where urlEnd ends it; undefined if it starts with none. */
24
+ export declare function leadingUrl(value: string): Url | undefined;
25
+ /** Whether `value` is, whole, one URL with a scheme and no password: an endpoint, a setting. */
26
+ export declare function isPasswordlessUrl(value: string): boolean;
27
+ /** The URLs in `text` with a scheme (`https://…`, `postgres://…`), and with `paths`, paths that start with a segment and a slash (`/reset/<token>`). */
28
+ export declare function urls(text: string, { paths }?: {
29
+ paths?: boolean | undefined;
30
+ }): Generator<Url>;
31
+ /**
32
+ * Where the URL with a scheme that holds `text[at, end)` starts, looking back no further than `from`; -1 if that
33
+ * stretch isn't inside one. (The innermost: `?next=https://…/<token>` is inside the `next` URL.)
34
+ */
35
+ export declare function urlAround(text: string, at: number, end?: number, from?: number): number;
36
+ /**
37
+ * The user information starting at `at` in `url` (after `scheme://`, or a DSN's start): where the user ends, where the
38
+ * password starts (-1 for none), and where the `@` that ends it is; undefined if there is none. The password may hold
39
+ * `/`, `@`, `?` and `#` (`Sparrow2024?`, `Vq8#Lm2!`), but not a blank, nor a `?` or `#` after a `/` (that's a path's
40
+ * query: `host:443/docs?contact=ops@x`), nor a port and a path (`localhost:4873/@babel/core`); it ends at the first `@` whose host reaches `hostEnd` with no other `@`. An
41
+ * IPv6 host (`[::1]`) has none.
42
+ */
43
+ export declare function readUserinfo(url: string, at: number, hostEnd?: RegExp): {
44
+ userEnd: number;
45
+ passwordStart: number;
46
+ end: number;
47
+ } | undefined;
48
+ export interface Param {
49
+ name: string;
50
+ value: Span;
51
+ }
52
+ /** A parameter's start and name: after `?`, `&` (or HTML's `&amp;`) or `#`, a name with PHP's `[]` allowed. */
53
+ export declare const PARAM_START: string;
54
+ export declare const PARAM_NAME: string;
55
+ /**
56
+ * The `name=value` pairs in `text`: a URL's query and fragment (`#access_token=…`), a form body. A value ends at `&`,
57
+ * `#`, a blank, a quote, an angle bracket, a backtick or a backslash (JSON's `&` escape); closing parentheses it didn't
58
+ * open end it only at its end (`(see https://x.io/?k=abc)` is `abc`; a Markdown link's): `?key=Xk9(mP2)vQ7` is one value.
59
+ */
60
+ export declare function queryParams(text: string, names?: string): Generator<Param>;
61
+ /**
62
+ * The percent-encoded text in `text` that encodes text of its own (a URL in `redirect=`, JSON in a query, a sentence,
63
+ * in a log): each run decoded, `+` as a blank as forms write one, with the position in `text` of each decoded
64
+ * character (and one past the last). A run that encodes a single token is the random layer's (unnamed.ts
65
+ * `candidates`); one that isn't UTF-8 is left alone.
66
+ */
67
+ export declare function percentEncoded(text: string): Generator<{
68
+ decoded: string;
69
+ map: number[];
70
+ }>;
package/dist/urls.js ADDED
@@ -0,0 +1,303 @@
1
+ /**
2
+ * URLs read into their parts, each with where it is: user and password, host, path segments; and the query
3
+ * parameters (`?name=value&…`) anywhere in a text, in a URL or a form body. One definition of where a URL and a
4
+ * parameter's value end, for every rule that reads them.
5
+ */
6
+ import { lastText, lowerBound } from "./lines.js";
7
+ import { MAX_VALUE, withoutUnopenedClosers } from "./syntax.js";
8
+ /**
9
+ * A URL's scheme, as a regex source for use inside other patterns: `https`, `postgres`, `mongodb+srv`, SQLAlchemy's
10
+ * dialect and driver (`redshift+redshift_connector`, `oracle+cx_oracle`, whose drivers' names hold `_`). Two characters
11
+ * at least: a drive letter (`C://Users`) is none.
12
+ */
13
+ const SCHEME_LENGTH = 41;
14
+ export const URL_SCHEME = String.raw `[A-Za-z][\w+.-]{1,${SCHEME_LENGTH - 1}}`;
15
+ /** A scheme and its `://` that starts a text. */
16
+ const SCHEME = new RegExp(String.raw `^${URL_SCHEME}:\/\/`);
17
+ /** A scheme, its `://` and the user information after it, to its first `/` or `@`. */
18
+ const USERINFO = new RegExp(String.raw `${URL_SCHEME}:\/\/[^\s/@]*`, "y");
19
+ /**
20
+ * Where a URL that starts at `at` ends: at a blank, a quote, an angle bracket, a backtick, a backslash (a JSON
21
+ * string's escaped quote), or a closing bracket it didn't open (a Markdown link's `)`); sentence punctuation at its
22
+ * end isn't part of it (`…/reset/<token>.`). A password's brackets are (`app:p)dgbs@db`, `admin:<Harbor2291x>@host`).
23
+ * However long the password: each look-ahead for its `@` is remembered to where it stopped (`reachesAt`).
24
+ */
25
+ export function urlEnd(text, at) {
26
+ const open = { ")": 0, "]": 0 };
27
+ const closerIsPassword = reachesAt(text, /[^\s"'<>`\\/@]*(@)?/y);
28
+ const quoteIsPassword = reachesAt(text, /[^\s"<>`\\/@,;]*(@)?/y);
29
+ // A quote is a password's only in `scheme://user:password` (no `/` or `@` yet), not after a port (`host:5432'`).
30
+ USERINFO.lastIndex = at;
31
+ const userinfoEnd = USERINFO.exec(text) ? USERINFO.lastIndex : -1;
32
+ const colon = userinfoEnd < 0 ? -1 : text.indexOf(":", text.indexOf("://", at) + 3);
33
+ const bracketed = /<[^\s<>@"'`]+>@/y;
34
+ let end = at;
35
+ for (; end < text.length && end - at < MAX_VALUE; end++) {
36
+ const c = text[end];
37
+ if (c === "(")
38
+ open[")"]++;
39
+ else if (c === "[")
40
+ open["]"]++;
41
+ else if (c === ")" || c === "]") {
42
+ // (Before the `@` that ends a user and password, it's the password's: `postgres://app:p)dgbs@db`.)
43
+ if (open[c] === 0 && !closerIsPassword(end + 1))
44
+ break;
45
+ open[c] = Math.max(0, open[c] - 1);
46
+ }
47
+ else if (c === "<" && text[end - 1] === ":") {
48
+ bracketed.lastIndex = end;
49
+ if (!bracketed.test(text))
50
+ break;
51
+ end = bracketed.lastIndex - 1;
52
+ }
53
+ else if ((c === "'" || c === '"') &&
54
+ quoteIsPassword(end + 1) &&
55
+ colon >= 0 &&
56
+ colon < end &&
57
+ end <= userinfoEnd &&
58
+ !endsInPort(text, end)) {
59
+ // (In a password, before the `@` that ends it: `postgres://app:Kx9m'Qv2@db`, `postgres://app:Kx9m"Qv2@db`.)
60
+ }
61
+ else if (/[\s"'<>`\\]/.test(c))
62
+ break;
63
+ }
64
+ while (end > at && /[.,;:!?]/.test(text[end - 1]))
65
+ end--;
66
+ return end;
67
+ }
68
+ /**
69
+ * Whether the characters from `from` on, up to the first one `stop` doesn't take, are followed by an `@` (`stop` is a
70
+ * sticky pattern with the `@` as its one group). The answer holds for every `from` up to where that run stopped, so a
71
+ * URL of many brackets looks ahead once per run, not once per bracket.
72
+ */
73
+ function reachesAt(text, stop) {
74
+ let runEnd = -1;
75
+ let found = false;
76
+ return (from) => {
77
+ if (from > runEnd) {
78
+ stop.lastIndex = from;
79
+ found = stop.exec(text)[1] !== undefined;
80
+ runEnd = stop.lastIndex - (found ? 1 : 0);
81
+ }
82
+ return found;
83
+ };
84
+ }
85
+ /** Whether the text before `end` ends in a port (`:5432`, digits after a colon). */
86
+ function endsInPort(text, end) {
87
+ let digits = end;
88
+ while (digits > 0 && text[digits - 1] >= "0" && text[digits - 1] <= "9")
89
+ digits--;
90
+ return digits < end && text[digits - 1] === ":";
91
+ }
92
+ /** Whether `value` starts with a URL with a scheme. */
93
+ export const isUrl = (value) => SCHEME.test(value);
94
+ /** The URL with a scheme that `value` starts with, read to where urlEnd ends it; undefined if it starts with none. */
95
+ export function leadingUrl(value) {
96
+ return isUrl(value) ? parseUrl(value, 0, urlEnd(value, 0)) : undefined;
97
+ }
98
+ /** Whether `value` is, whole, one URL with a scheme and no password: an endpoint, a setting. */
99
+ export function isPasswordlessUrl(value) {
100
+ const url = leadingUrl(value);
101
+ return !!url && url.end === value.length && !url.password;
102
+ }
103
+ /** The URLs in `text` with a scheme (`https://…`, `postgres://…`), and with `paths`, paths that start with a segment and a slash (`/reset/<token>`). */
104
+ export function* urls(text, { paths = false } = {}) {
105
+ const start = paths
106
+ ? new RegExp(String.raw `\b${URL_SCHEME}:\/\/|(?<![\w.:/])\/[\w.-]{1,80}\/`, "g")
107
+ : new RegExp(String.raw `\b${URL_SCHEME}:\/\/`, "g");
108
+ // (exec, not matchAll, which iterates a copy: the next URL is looked for after this one ends.)
109
+ for (let m = start.exec(text); m; m = start.exec(text)) {
110
+ const end = urlEnd(text, m.index);
111
+ yield parseUrl(text, m.index, end);
112
+ start.lastIndex = Math.max(start.lastIndex, end);
113
+ }
114
+ }
115
+ /** Where each `://` of a text is, in order: found once a text, so the URL around a position is a binary search away. */
116
+ const schemeSeparators = lastText((text) => {
117
+ const out = [];
118
+ for (let i = text.indexOf("://"); i >= 0; i = text.indexOf("://", i + 1))
119
+ out.push(i);
120
+ return out;
121
+ });
122
+ /** A scheme that ends a text, as a whole word. */
123
+ const SCHEME_BEFORE = new RegExp(String.raw `(?<![\w+.-])${URL_SCHEME}$`);
124
+ /**
125
+ * Where the URL with a scheme that holds `text[at, end)` starts, looking back no further than `from`; -1 if that
126
+ * stretch isn't inside one. (The innermost: `?next=https://…/<token>` is inside the `next` URL.)
127
+ */
128
+ export function urlAround(text, at, end = at, from = Math.max(0, at - 4096)) {
129
+ // (A stretch may start at the `//` itself: `https:` then `//<host>`.)
130
+ const seps = schemeSeparators(text);
131
+ const sep = seps[lowerBound(seps, at) - 1] ?? -1;
132
+ if (sep < from)
133
+ return -1;
134
+ // (A scheme's characters and the one before it.)
135
+ const scheme = SCHEME_BEFORE.exec(text.slice(Math.max(from, sep - SCHEME_LENGTH - 1), sep));
136
+ if (!scheme)
137
+ return -1;
138
+ const start = sep - scheme[0].length;
139
+ return urlEnd(text, start) >= end ? start : -1;
140
+ }
141
+ /**
142
+ * A URL's parts. The authority's user information, as clients read it: a user with no `/?#@:` (or an email address
143
+ * before a `:`, `writer@proj.iam.gserviceaccount.com:pw@host`), then `@`, or `:` and a password that runs to the first
144
+ * `@` whose host runs to a `/`, `?`, `#` or the end with no other `@`, so a generated password may hold `@` and `/`
145
+ * (`app:Xk9/mQ@2pL@host`). An `@` in the path or the query (`?email=bob@example.com`, `react@18.3.1`) is not a user's.
146
+ */
147
+ function parseUrl(text, start, end) {
148
+ const url = text.slice(start, end);
149
+ // (A scheme starts the URL: a path's `?next=https://…` is its query's.)
150
+ const sep = isUrl(url) ? url.indexOf("://") : -1;
151
+ const scheme = sep > 0 ? url.slice(0, sep) : "";
152
+ let at = sep > 0 ? sep + 3 : 0;
153
+ const span = (from, to) => ({
154
+ value: url.slice(from, to),
155
+ start: start + from,
156
+ end: start + to,
157
+ });
158
+ const out = { start, end, scheme, segments: [] };
159
+ if (scheme) {
160
+ const userinfo = readUserinfo(url, at);
161
+ if (userinfo) {
162
+ out.user = span(at, userinfo.userEnd);
163
+ if (userinfo.passwordStart >= 0)
164
+ out.password = span(userinfo.passwordStart, userinfo.end);
165
+ at = userinfo.end + 1;
166
+ }
167
+ const hostEnd = url.slice(at).search(/[/?#]|$/) + at;
168
+ out.host = span(at, hostEnd);
169
+ at = hostEnd;
170
+ }
171
+ const pathEnd = url.slice(at).search(/[?#]|$/) + at;
172
+ for (let s = at; s < pathEnd;) {
173
+ const slash = url.indexOf("/", s + 1);
174
+ const segEnd = slash < 0 || slash > pathEnd ? pathEnd : slash;
175
+ if (url[s] === "/")
176
+ s++;
177
+ if (segEnd > s)
178
+ out.segments.push(span(s, segEnd));
179
+ s = segEnd;
180
+ }
181
+ return out;
182
+ }
183
+ /**
184
+ * The user information starting at `at` in `url` (after `scheme://`, or a DSN's start): where the user ends, where the
185
+ * password starts (-1 for none), and where the `@` that ends it is; undefined if there is none. The password may hold
186
+ * `/`, `@`, `?` and `#` (`Sparrow2024?`, `Vq8#Lm2!`), but not a blank, nor a `?` or `#` after a `/` (that's a path's
187
+ * query: `host:443/docs?contact=ops@x`), nor a port and a path (`localhost:4873/@babel/core`); it ends at the first `@` whose host reaches `hostEnd` with no other `@`. An
188
+ * IPv6 host (`[::1]`) has none.
189
+ */
190
+ export function readUserinfo(url, at, hostEnd = /[/?#]/) {
191
+ if (url[at] === "[")
192
+ return undefined;
193
+ // A service account's email as the user, if a password follows it.
194
+ const email = /^[\w.+-]{1,64}@[\w-]{1,63}(?:\.[\w-]{1,63}){1,8}(?=:)/.exec(url.slice(at, at + 200));
195
+ const withEmail = email && password(url, at, at + email[0].length, hostEnd);
196
+ if (withEmail)
197
+ return withEmail;
198
+ const userEnd = at + url.slice(at).search(/[/?#@:\s]|$/);
199
+ return url[userEnd] === "@" ? { userEnd, passwordStart: -1, end: userEnd } : password(url, at, userEnd, hostEnd);
200
+ }
201
+ /** A `:` at `userEnd` and the password after it, to its `@`. */
202
+ function password(url, userStart, userEnd, hostEnd) {
203
+ // (`host:5432/path@ref`: a port, not a password.)
204
+ if (url[userEnd] !== ":" || /^\d{1,5}(?:[/?#]|$)/.test(url.slice(userEnd + 1, userEnd + 8)))
205
+ return undefined;
206
+ let a = -1;
207
+ let slash = false;
208
+ for (let i = userEnd + 1; i < url.length && !/\s/.test(url[i]) && !(slash && a < 0 && /[?#]/.test(url[i])); i++) {
209
+ if (url[i] === "/" && a < 0)
210
+ slash = true;
211
+ if (url[i] === "@")
212
+ a = i;
213
+ // The host after the last `@` ended with no `@` in it.
214
+ else if (a >= 0 && hostEnd.test(url[i]))
215
+ break;
216
+ }
217
+ if (a < 0)
218
+ return undefined;
219
+ // `git.example.com:org/repo@v1.2`: a host, a path and a ref (no host after the `@`), not a user and a password.
220
+ const after = url.slice(a + 1).split(/[/?#]/)[0];
221
+ if (slash && /^[\w-]+(?:\.[\w-]+)+$/.test(url.slice(userStart, userEnd)) && !isHost(after))
222
+ return undefined;
223
+ return { userEnd, passwordStart: userEnd + 1, end: a };
224
+ }
225
+ /** A host as a URL names one: a domain with a TLD (`db.internal`, `example.com`), an IPv4 address, a name and a port. */
226
+ const isHost = (h) => /^(?:[\w-]+\.)+[A-Za-z][\w-]*(?::\d{1,5})?$|^\d{1,3}(?:\.\d{1,3}){3}(?::\d{1,5})?$|^[\w-]+:\d{1,5}$/.test(h);
227
+ /** A parameter's start and name: after `?`, `&` (or HTML's `&amp;`) or `#`, a name with PHP's `[]` allowed. */
228
+ export const PARAM_START = String.raw `(?:&amp;|[?&#])`;
229
+ export const PARAM_NAME = String.raw `[\w.\[\]-]{1,60}`;
230
+ /**
231
+ * The `name=value` pairs in `text`: a URL's query and fragment (`#access_token=…`), a form body. A value ends at `&`,
232
+ * `#`, a blank, a quote, an angle bracket, a backtick or a backslash (JSON's `&` escape); closing parentheses it didn't
233
+ * open end it only at its end (`(see https://x.io/?k=abc)` is `abc`; a Markdown link's): `?key=Xk9(mP2)vQ7` is one value.
234
+ */
235
+ export function* queryParams(text, names = PARAM_NAME) {
236
+ for (const m of text.matchAll(new RegExp(String.raw `${PARAM_START}(${names})=([^&#\s"'<>\`\\]+)`, "dgi"))) {
237
+ const value = withoutUnopenedClosers(m[2], ")");
238
+ if (!value)
239
+ continue;
240
+ const start = m.indices[2][0];
241
+ yield { name: m[1], value: { value, start, end: start + value.length } };
242
+ }
243
+ }
244
+ const UTF8 = new TextEncoder();
245
+ /**
246
+ * A run of what percent-encoding leaves as it is and `%XX` escapes, matched whole (each run once: linear), and kept
247
+ * when it holds an escape: an encoded URL, query or JSON.
248
+ */
249
+ const PERCENT_RUN = /(?:[A-Za-z0-9_.~+-]|%[0-9A-Fa-f]{2})+/g;
250
+ /**
251
+ * The percent-encoded text in `text` that encodes text of its own (a URL in `redirect=`, JSON in a query, a sentence,
252
+ * in a log): each run decoded, `+` as a blank as forms write one, with the position in `text` of each decoded
253
+ * character (and one past the last). A run that encodes a single token is the random layer's (unnamed.ts
254
+ * `candidates`); one that isn't UTF-8 is left alone.
255
+ */
256
+ export function* percentEncoded(text) {
257
+ if (!text.includes("%"))
258
+ return;
259
+ const utf8 = new TextDecoder("utf-8", { fatal: true });
260
+ for (const m of text.matchAll(PERCENT_RUN)) {
261
+ const run = m[0];
262
+ if (!run.includes("%"))
263
+ continue;
264
+ let decoded = "";
265
+ const map = [];
266
+ let ok = true;
267
+ for (let i = 0; i < run.length && ok;) {
268
+ if (run[i] !== "%") {
269
+ decoded += run[i] === "+" ? " " : run[i];
270
+ map.push(m.index + i++);
271
+ continue;
272
+ }
273
+ // (A run of escapes is decoded as UTF-8 together, each character mapped to its first byte's escape.)
274
+ const bytes = [];
275
+ const at = [];
276
+ while (run[i] === "%") {
277
+ bytes.push(parseInt(run.slice(i + 1, i + 3), 16));
278
+ at.push(m.index + i);
279
+ i += 3;
280
+ }
281
+ let chars = "";
282
+ try {
283
+ chars = utf8.decode(new Uint8Array(bytes));
284
+ }
285
+ catch {
286
+ ok = false;
287
+ }
288
+ let byte = 0;
289
+ for (const c of chars) {
290
+ decoded += c;
291
+ for (let k = 0; k < c.length; k++)
292
+ map.push(at[byte]);
293
+ byte += UTF8.encode(c).length;
294
+ }
295
+ }
296
+ // (Text of its own: a URL, a path, pairs, JSON, words. A token with a `:`, a `/` or base64's padding in it is a
297
+ // token: `<id>:<time>`, `+TGFu…3M=`.)
298
+ if (!ok || decoded.length < 12 || !/[?&{"@ ]|:\/\/|^\/\w|=(?!=*$)/.test(decoded))
299
+ continue;
300
+ map.push(m.index + run.length);
301
+ yield { decoded, map };
302
+ }
303
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Tests on a value that sits under a credential name: is it a literal secret, or something that only
3
+ * stands in for one (a reference, a template, a placeholder, a mask, code)? Agents read config and
4
+ * code full of `password: ${DB_PASSWORD}` and `api_key = os.environ["KEY"]`; hiding those costs the
5
+ * agent the structure it needs and protects nothing.
6
+ */
7
+ /** An identifier path: `config.db.password`, `cfg?.pw`, `$this->password`, `Settings::SECRET`. */
8
+ export declare const IDENT_PATH: string;
9
+ /** Strips quotes, a trailing comma or semicolon, and an inline comment from a raw value. */
10
+ export declare function cleanValue(raw: string): string;
11
+ /**
12
+ * Words, short numbers and short IDs joined by separators: names like `acme-prod-uploads-2024`,
13
+ * `refs/pull/482/merge` or `--max-old-space-size=4096`, not keys.
14
+ */
15
+ export declare function isWordy(value: string): boolean;
16
+ /**
17
+ * Identifier segments that read as names (a word's case, or short): `md5`, `htmlspecialchars`, `jmat.gId`, not
18
+ * `IWNwpPK.QC.BXh`, nor humps a generator's case flips made (`hqArruGhkoRtvpQplEac`).
19
+ */
20
+ export declare function isNameLike(path: string): boolean;
21
+ /** Which inert shape a value has (INERT_SHAPES), if any. (Written in markup's escapes, a shape is that shape: `&lt;redacted&gt;`.) */
22
+ export declare function inertShape(value: string): string | undefined;
23
+ /** A value that can't be a credential: empty, a boolean, a path, a placeholder or a reference to one held elsewhere. */
24
+ export declare function isInert(value: string): boolean;
25
+ /** A file path: path-shaped, with no segment that a generator made (`/NKR7uXt0UfYN9/`, `/eB7/ZDoDhDrnTAZWfbkgNMM`). */
26
+ export declare function isPath(path: string): boolean;
27
+ /**
28
+ * A path segment a generator made, not a person: one that mixes cases and digits and is long, isn't a name, or has a
29
+ * symbol no name has (`Tv$-ycX.K9+`); mixed-case letters that read as no word, lower-cased (`ZDoDhDrnTAZWfbkgNMM`:
30
+ * the character model reads each camel-case hump on its own, and random humps often score as words); or a `+`
31
+ * inside mixed-case letters, base64's (`GbeaKB+qGmA+X`; mkcert's `localhost+2-key.pem` is one case, `c++` ends with it).
32
+ */
33
+ export declare function isGeneratedSegment(seg: string): boolean;
34
+ /**
35
+ * Most of a value's letters are in words: its runs of letters (camel-case humps apart) of three or more read as
36
+ * words or as code's abbreviations, and hold two thirds of its letters. `env(DATABASE_URL)`, `CORP\svc_deploy`,
37
+ * `logger.warning(` do; `JujvAIngWfOqw`, `)AM{?NfoSuFiMi.s` don't.
38
+ */
39
+ export declare function readsAsWords(value: string): boolean;
40
+ /**
41
+ * A value made of words, run together or joined by `_` or `-` (`required`, `not-set`, `sameorigin`, `Bearer`), as
42
+ * opposed to random letters, which have no digit by chance (`XGxAdcVIefCUMAVsDhkYhErsQwPz`,
43
+ * `kXbQzMwRpLtY-vNcDhJsGfWqZr_mKdTy`): every part of three letters or more reads as a word.
44
+ */
45
+ export declare function isWordsValue(value: string): boolean;
46
+ /**
47
+ * A token a generator made, by its characters alone: 16 or more of a token's alphabet, with letters, random as the
48
+ * character model reads it lower-cased, and not half words. Hex with digits is one whatever the model reads in its
49
+ * letters (`cafe`, `dead`). Fewer than one random token in two hundred fails this at 16 characters (hex with one
50
+ * letter reads as a number), one in ten thousand at 32; words run together (`reset-password-confirm`, `ProjectSettingsPage`), with numbers in them
51
+ * (`VerifyEmailPage2024.tsx`, `sso-saml-setup-v2`), and a date with a letter in it (`2024-01-15T10-30-00`) pass.
52
+ */
53
+ export declare function isRandomToken(v: string, min?: number): boolean;
54
+ /**
55
+ * A credential's name written as an identifier and ending in its credential word (`set_guest_wifi_password`,
56
+ * `DB_PASSWORD`, `githubToken`): a value that names a credential, not one. (Not kebab-case, which passwords are written
57
+ * in too, `test-pass-123`; nor a passphrase in title case, `FluffyBunnyPassword`; nor one that only holds the word,
58
+ * `verde_dev_pw`, `admin_pass1`.)
59
+ */
60
+ export declare function namesCredential(value: string): boolean;
61
+ /** Brackets that close in order: `f(a[0], {b})`, not `Ssaw(53n9DYc!(l7)`. (Quotes aren't tracked.) */
62
+ export declare function isBalanced(s: string): boolean;
63
+ /**
64
+ * A type as a declaration writes one, in Python, TypeScript, Go, Rust, Kotlin, Java or C#: a name, maybe qualified, with
65
+ * its arguments (`Optional[str]`, `Dict[str, Any]`, `Map<String, String>`, `Vec<u8>`) and marks (`string[]`, `String?`,
66
+ * `[]byte`, `*string`, `char*`, `&'a str`, `String!`), in a union (`str | None`, `string | null`; not an intersection,
67
+ * `Path&Ember&winter` is a passphrase), a tuple, a function's type (`() => string`) or a type query (`typeof DEFAULT`).
68
+ * One of its names at least is a type the language builds in, and every other reads as a name:
69
+ * `Optional[PasswordData]` is a type, `Summer|Winter`, `Hunter2!` and `Optional[Xk9mQ2vL]` aren't. With `classes`, a
70
+ * class named as types are (TYPE_CLASS) counts as built in: `"any"` where code is written; `"marked"` in a literal
71
+ * context, where a name alone is a type only by a type's own ending, `SecretStr`, `PasswordField` (`LittleSecret` is a
72
+ * passphrase there, and `Uint8Array` alone may be a password), but `Optional[ClientSecret]` is a type.
73
+ */
74
+ export declare function isTypeExpression(value: string, classes?: "none" | "marked" | "any"): boolean;
75
+ /**
76
+ * Code rather than a literal value: an environment lookup, a call, member access, a variable.
77
+ * Quoted strings are literals, unless built from code. Each shape below is code in any context; in a literal context
78
+ * (`NAME=value` in env, `key: value` in YAML, a CLI flag) only REFERENCES are code besides; elsewhere CODE_ONLY too,
79
+ * where an unquoted lower-case word without digits is taken as a variable (`password = userPassword`; with digits,
80
+ * `PASSWORD=hunter2` in a .env, it's a literal).
81
+ */
82
+ export declare function isCode(raw: string, literal?: boolean): boolean;
83
+ /**
84
+ * Digits in groups, six or more of them, as a PIN or a phone number is written: `4821-7730`, `0171 555 2368`. Under a
85
+ * password's or a PIN's name, that is the password; elsewhere an ID. (`12/12`, `8-64`, `1.2.3` are counts, lengths and
86
+ * versions under any name, and an ISO date is the date a listing shows: `password: 2024-05-01`, when it was set.)
87
+ */
88
+ export declare function isGroupedDigits(value: string): boolean;
89
+ /**
90
+ * Password hashes in text, as a global search: each format's whole shape, every `$` possibly doubled (Compose escapes
91
+ * it: `$$2y$$10$$…`), with no hash character on either side (a full stop after it ends a sentence, unless more hash follows).
92
+ */
93
+ export declare const PASSWORD_HASH_IN_TEXT: RegExp;
94
+ /** Password hashes: bcrypt, argon2, scrypt, crypt(3), Django, LDAP. Sensitive, but not a credential anyone can use. */
95
+ export declare function isPasswordHash(value: string): boolean;
96
+ /**
97
+ * Digits as a stand-in writes them: one or two, a count (`123`, `1234`, `6789`) or one digit repeated (`000`). A real
98
+ * password's digits are arbitrary (`Example7392`).
99
+ */
100
+ export declare function isCountingDigits(digits: string): boolean;
101
+ /** A stand-in credential from a test or a doc: only test words and a few digits as a stand-in writes them. */
102
+ export declare function isTestValue(value: string): boolean;
103
+ /**
104
+ * Names built from short parts: `BOB_sect163r2`, `KAS-ECC-CDH_P-256_C14`, `ED448-1-PUBLIC-Raw`, `ALICE_cf_c2pnb368w1`,
105
+ * `us-east-1a`, `login_and_password_removed`. The first part starts with a letter, and every part reads as a name
106
+ * part: short, a number, lower-case hex, a word, an acronym, letters then digits (`amd64`, `SECP521R1`), or a word then
107
+ * a code (`brainpoolP224r1`). Beside a word (`ALICE`, `PUBLIC`), any part of one case with digits is a code
108
+ * (`c2pnb368w1`). A random part mixes cases with digits (`JWPa`, `Xk9mP2`), and a product key's groups are random
109
+ * letters and digits of one length (`VKXQR-8B2MP-7WJ4T`): not names.
110
+ */
111
+ export declare function isIdentifierShaped(value: string): boolean;
112
+ /**
113
+ * A Kubernetes pod's name: its workload's name, then the generated suffixes of a ReplicaSet (6–10 characters)
114
+ * and of the pod (5): `ledger-api-58d4f7c9b-rbjl3`, `search-i12seefem-wq3g4`. (A word in the workload's name: its first
115
+ * part may be none, `k8s-agent-…`.)
116
+ */
117
+ export declare function isPodName(value: string): boolean;
118
+ /**
119
+ * A lower-case letter, a capital and a digit: the classes a password rule asks for, in a password made of words
120
+ * (`Maple-Orbit-42`, `Harvest2025`) or by a generator (`Xk9mP2vQ7`).
121
+ */
122
+ export declare function hasThreeClasses(value: string): boolean;
123
+ /**
124
+ * A password made to a password rule's demands: eight characters or more of three classes, a symbol among them (not
125
+ * only a name's `.` `_` `-`), and neither code nor words: `CANYpfso8s%`, `Harbor2291!`.
126
+ */
127
+ export declare function isMadePassword(value: string): boolean;
128
+ /**
129
+ * Names of algorithms, encodings and formats that sentences about credentials mention: `password (UTF-8)`, `SHA-256`;
130
+ * and password hashers as libraries name them: Django's `pbkdf2_sha256`, `bcrypt_sha256`, passlib's `sha512_crypt`.
131
+ */
132
+ export declare const ALGORITHM: RegExp;
133
+ /** A number with the word that counts in CJK: `6位`, `8文字`, `3回`, `8자`, `2번`. */
134
+ export declare const CJK_COUNT: RegExp;
135
+ /**
136
+ * A quantity written with its unit: a ratio (`3.52x`), a price (`~$280`), a test count and its share (`40/42 —
137
+ * 95.2%`). Benign, except under a password's name, where `1337x` and `$4821` are what was chosen. (A PIN's value is
138
+ * digits: named.ts proposes no other.)
139
+ */
140
+ export declare const QUANTITY: RegExp;
141
+ /**
142
+ * Values that are never a secret, whatever names them and whichever rule read them: counts, ratios and
143
+ * percentages (`12/12`, `98.5%`), ranges (`8-64`), versions (`v2.4.1`), dates and times, algorithm and
144
+ * encoding names, key chords (`ctrl+shift+F12`, `ctrl-p,ctrl-q`), type annotations (`str|None`), cloud regions, the
145
+ * words of a password rule (`min.`, `at least`), and code or markup that a reader can take for a value: regular
146
+ * expressions, package and class paths, IAM actions (`iam:ChangePassword`), XAML bindings, HTML elements, tickets
147
+ * (`[GH-784]`), hash schemes (`md5$`), short escaped or percent-encoded bytes, hex numbers, constants and the brackets
148
+ * after them (`None):`). Every name- and structure-based finding passes this
149
+ * gate (policy.ts keepReason), so a new rule can't bring back an over-redaction another rule already ruled out.
150
+ * Values from secret files and formats don't: there the file or the vendor format says what it is.
151
+ */
152
+ export declare function isBenignValue(value: string): boolean;
153
+ /**
154
+ * Unquoted code that refers to a credential by name, maybe ending its statement: `account.password`, `m_password;`,
155
+ * `md5($password);`, `secrets.db` (policy.ts, for unquoted values only). (Not a credential's member, `passwords.pop`,
156
+ * `txtPassword.Text`: under an env line's name that may be the password.)
157
+ */
158
+ export declare function isCredentialReference(value: string): boolean;
159
+ /**
160
+ * What an encryption tool writes before its ciphertext: SOPS `ENC[AES256_GCM,data:`, Jasypt `ENC(`, Spring Cloud
161
+ * Config `{cipher}`, Vault's `${VAULT::…::`, IIS configuration encryption `[enc:IISCngProvider:`, DigitalOcean App
162
+ * Platform's `EV[1:<nonce>:`. A value that starts with one (ENCRYPTED_VALUE), or a token right after one (the random
163
+ * layer's `encrypted` veto), is ciphertext, whose key isn't there.
164
+ */
165
+ export declare const ENCRYPTION_WRAPPER: string;
166
+ /** Default passwords and words that show up in ordinary output, too common to be anyone's secret. */
167
+ export declare const COMMON_WORDS: Set<string>;
168
+ /**
169
+ * Whether a value under a credential name is a literal secret worth hiding: not a type, a placeholder,
170
+ * a reference, code, a lone word or a number. (A password hash, and text in another script, are the decision stage's
171
+ * to keep: policy.ts keepReason, isBenignValue.)
172
+ */
173
+ export declare function looksSecret(raw: string, literal?: boolean): boolean;
174
+ /**
175
+ * Analytics, consent, load-balancer and preference cookies: `_ga=GA1.1.35…`, `_gid`, `_fbp`, `AWSALB`, `__cf_bm`,
176
+ * PostHog's `ph_…_posthog`, a locale or a theme. Not a session.
177
+ */
178
+ export declare const ANALYTICS_COOKIE: RegExp;