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.
- 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 +787 -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 +488 -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 +106 -0
- package/dist/names.js +1517 -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 +80 -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 +722 -0
- package/dist/shell.d.ts +127 -0
- package/dist/shell.js +703 -0
- package/dist/spans.d.ts +61 -0
- package/dist/spans.js +287 -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 +1625 -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 +713 -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
package/dist/pem.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PEM blocks (RFC 7468, with RFC 1421's headers) as text carries them: a BEGIN line; headers, then a blank line,
|
|
3
|
+
* before the material; lines of base64; an END line. The lines may be escaped in a string (`\n`, `\\n`, `\r\n`), and
|
|
4
|
+
* the string written a line at a time in source (`"…\n" +`, Python's `"…\n"` juxtaposed); carry a line number or a
|
|
5
|
+
* grep path read off the BEGIN line; or have become blanks (`echo $KEY`). A block cut short ends with its material
|
|
6
|
+
* (`head -3 cert.pem`); one whose BEGIN was cut off (`tail -5 server.key`) is read up from its END. Both readers take
|
|
7
|
+
* each line once, so a scan of a text's blocks is linear.
|
|
8
|
+
*/
|
|
9
|
+
import { lastText, lineEnd, lineStart } from "./lines.js";
|
|
10
|
+
/** Characters of base64 material. */
|
|
11
|
+
function isArmorChar(c) {
|
|
12
|
+
return (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || (c >= 48 && c <= 57) || c === 43 || c === 47 || c === 61;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A BEGIN or END line, in PEM's armor (`-----BEGIN RSA PRIVATE KEY-----`) or RFC 4716's (`---- BEGIN SSH2 ENCRYPTED
|
|
16
|
+
* PRIVATE KEY ----`, ssh.com's and PuTTYgen's export). Its label: capitals, or OpenVPN's `OpenVPN Static key V1`.
|
|
17
|
+
*/
|
|
18
|
+
const ARMOR = (word) => String.raw `-{4,5} ?${word} ([A-Za-z0-9][A-Za-z0-9 ]{0,59}?) ?-{4,5}`;
|
|
19
|
+
const END_LINE = new RegExp(ARMOR("END"), "y");
|
|
20
|
+
/** Where an END line may start. */
|
|
21
|
+
const END_START = /-{4,5} ?END /y;
|
|
22
|
+
/** The BEGIN lines of a text, each with its label, in order. */
|
|
23
|
+
export function* beginLines(text) {
|
|
24
|
+
for (const m of text.matchAll(new RegExp(ARMOR("BEGIN"), "g")))
|
|
25
|
+
yield { start: m.index, end: m.index + m[0].length, label: m[1] };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Every armored block of a text, read once a text, in order. (A BEGIN line inside an earlier block's body is that
|
|
29
|
+
* body's text, not a block of its own.)
|
|
30
|
+
*/
|
|
31
|
+
export const armoredBlocks = lastText((text) => {
|
|
32
|
+
const out = [];
|
|
33
|
+
for (const begin of beginLines(text)) {
|
|
34
|
+
if (out.length && begin.start < out[out.length - 1].end)
|
|
35
|
+
continue;
|
|
36
|
+
out.push({ start: begin.start, labelEnd: begin.end, label: begin.label, ...pemBody(text, begin.start, begin.end) });
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
});
|
|
40
|
+
/** Whether an END line starts at `at`. */
|
|
41
|
+
function endsAt(text, at) {
|
|
42
|
+
END_START.lastIndex = at;
|
|
43
|
+
return END_START.test(text);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The prefix every line of an output carries, read off one line's text before its armor: a line number (`cat -n`,
|
|
47
|
+
* `nl`, an agent's Read tool ` 1→`), a `grep -r` path (`key.pem:12:` on a match, `key.pem-13-` on context), a log's
|
|
48
|
+
* timestamp, syslog header, level tag or compose service bar. Digit runs and blank runs vary from line to line; the
|
|
49
|
+
* rest is the same on each. None when the armor starts its line.
|
|
50
|
+
*/
|
|
51
|
+
function linePrefix(before) {
|
|
52
|
+
if (!before.trim())
|
|
53
|
+
return undefined;
|
|
54
|
+
const grep = /^([\w./~-]{1,200}?)[:-](?:\d{1,7}[:-])?$/.exec(before);
|
|
55
|
+
if (grep)
|
|
56
|
+
return new RegExp(`${escape(grep[1])}[:-](?:\\d{1,7}[:-])?`, "y");
|
|
57
|
+
return new RegExp(escape(before)
|
|
58
|
+
.replace(/\d+/g, "\\d+")
|
|
59
|
+
.replace(/[ \t]+/g, "[ \\t]+"), "y");
|
|
60
|
+
}
|
|
61
|
+
const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
62
|
+
/**
|
|
63
|
+
* The body after a BEGIN line (`begin` its start, `from` its end): headers and a blank line before the material, then
|
|
64
|
+
* material lines up to END. Anything else ends a block cut short, a blank line or a `Name: value` line after material
|
|
65
|
+
* has begun among them (the HTTP headers after `head -3 cert.pem`).
|
|
66
|
+
*/
|
|
67
|
+
function pemBody(text, begin, from) {
|
|
68
|
+
const prefix = linePrefix(text.slice(lineStart(text, begin, 300), begin));
|
|
69
|
+
let i = from;
|
|
70
|
+
let material = 0;
|
|
71
|
+
let end = i;
|
|
72
|
+
let lines = 0;
|
|
73
|
+
let lastRun = 0;
|
|
74
|
+
while (i < text.length && lines < 2000) {
|
|
75
|
+
// A line break, real or escaped inside a string, after any trailing blanks.
|
|
76
|
+
for (let n = 0; n < 40 && (text[i] === " " || text[i] === "\t"); n++)
|
|
77
|
+
i++;
|
|
78
|
+
const brk = breakAt(text, i);
|
|
79
|
+
// A key whose line breaks became spaces (`echo $KEY`): material after a blank, right after BEGIN or after a
|
|
80
|
+
// full line of material. One whose breaks were stripped (`-----BEGIN OPENSSH PRIVATE KEY-----b3Bl…`): its
|
|
81
|
+
// material right after BEGIN, its END right after a full line of it.
|
|
82
|
+
const blank = text[i - 1] === " " || text[i - 1] === "\t";
|
|
83
|
+
const glued = (lines === 0 && isArmorChar(text.charCodeAt(i))) || (lastRun >= 40 && endsAt(text, i));
|
|
84
|
+
if (!brk &&
|
|
85
|
+
!glued &&
|
|
86
|
+
!(blank && (endsAt(text, i) || ((lines === 0 || lastRun >= 40) && isArmorChar(text.charCodeAt(i))))))
|
|
87
|
+
break;
|
|
88
|
+
let j = i + brk;
|
|
89
|
+
lines++;
|
|
90
|
+
if (prefix) {
|
|
91
|
+
prefix.lastIndex = j;
|
|
92
|
+
if (prefix.exec(text))
|
|
93
|
+
j = prefix.lastIndex;
|
|
94
|
+
}
|
|
95
|
+
// Leading indentation, quotes, comment markers, diff markers, `+"` concatenations.
|
|
96
|
+
while (j < text.length && " \t\"'+#>|-".includes(text[j]) && j - (i + brk) < 40) {
|
|
97
|
+
if (text[j] === "-" && text.startsWith("----", j))
|
|
98
|
+
break;
|
|
99
|
+
if (text[j] === "+" && isArmorChar(text.charCodeAt(j + 1)) && !brk)
|
|
100
|
+
break; // (material's own `+`, after a blank)
|
|
101
|
+
j++;
|
|
102
|
+
}
|
|
103
|
+
if (endsAt(text, j)) {
|
|
104
|
+
END_LINE.lastIndex = j;
|
|
105
|
+
const e = END_LINE.exec(text);
|
|
106
|
+
return { end: e ? j + e[0].length : end, material };
|
|
107
|
+
}
|
|
108
|
+
const lineFrom = j;
|
|
109
|
+
let run = 0;
|
|
110
|
+
if (material === 0 && /^[A-Za-z][A-Za-z0-9-]{0,40}: /.test(text.slice(lineFrom, lineFrom + 44))) {
|
|
111
|
+
// Headers, before the material only: `Proc-Type: 4,ENCRYPTED`, `Version: GnuPG v2`, `Comment: …`.
|
|
112
|
+
j = lineEnd(text, lineFrom, 300);
|
|
113
|
+
// (A header ends at an escaped break too, in whichever form: `Comment: x\n`, `\\n`.)
|
|
114
|
+
for (let k = lineFrom + 1; k < j; k++) {
|
|
115
|
+
if (escapedBreakAt(text, k)) {
|
|
116
|
+
j = k;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
while (j < text.length && isArmorChar(text.charCodeAt(j)))
|
|
123
|
+
j++;
|
|
124
|
+
run = j - lineFrom;
|
|
125
|
+
// (A blank line separates the headers from the material; once material has begun, it ends the block.)
|
|
126
|
+
if (run === 0 && (material > 0 || !breakAt(text, j)))
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
// The line's ending: blanks, a closing quote, a comma. (Not a `+`: before the line's break, breakAt reads it as an
|
|
130
|
+
// operator, `"…" +⏎`; after a blank, it starts the next line's material in a key joined by blanks.)
|
|
131
|
+
const runEnd = j;
|
|
132
|
+
while (j < text.length && " \t\"',;".includes(text[j]))
|
|
133
|
+
j++;
|
|
134
|
+
// A line of material is all armor: what follows is the line's end, the END line, or (in a key whose line breaks
|
|
135
|
+
// became blanks) more material after a full run. A word that only starts like it (`hello world`) ends the block;
|
|
136
|
+
// a full run that something else follows is the last material, cut where output was shortened (`…AB…`,
|
|
137
|
+
// `…AB... [truncated]`).
|
|
138
|
+
if (run > 0) {
|
|
139
|
+
const lineEnds = j >= text.length || breakAt(text, j) > 0 || endsAt(text, j);
|
|
140
|
+
// (Under four characters only as the material's last line, the END line next however the line ends, past the
|
|
141
|
+
// prefix every line carries: `Q==`, `"Q==\n" +⏎ "-----END`, `Q== -----END`, `<timestamp> -----END`.)
|
|
142
|
+
const lastLine = lineEnds && endLineNext(text, j, prefix);
|
|
143
|
+
if (run < 4 && !lastLine)
|
|
144
|
+
break;
|
|
145
|
+
if (!(lineEnds || (run >= 40 && j > runEnd && isArmorChar(text.charCodeAt(j))))) {
|
|
146
|
+
if (run >= 40)
|
|
147
|
+
return { end: Math.max(end, lineFrom + run), material: material + run };
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
material += run;
|
|
151
|
+
}
|
|
152
|
+
lastRun = run;
|
|
153
|
+
end = Math.max(end, lineFrom + run);
|
|
154
|
+
i = j;
|
|
155
|
+
}
|
|
156
|
+
return { end, material };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Whether an END line follows `at`, where a line's material ends: on the next line, past `prefix` and the quotes and
|
|
160
|
+
* blanks pemBody skips, or on the same line.
|
|
161
|
+
*/
|
|
162
|
+
function endLineNext(text, at, prefix) {
|
|
163
|
+
const brk = breakAt(text, at);
|
|
164
|
+
let j = at + brk;
|
|
165
|
+
if (brk && prefix) {
|
|
166
|
+
prefix.lastIndex = j;
|
|
167
|
+
if (prefix.exec(text))
|
|
168
|
+
j = prefix.lastIndex;
|
|
169
|
+
}
|
|
170
|
+
while (j < text.length && j - at < 40 && " \t\"'".includes(text[j]))
|
|
171
|
+
j++;
|
|
172
|
+
return text.startsWith("-----END ", j);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The material above an END line at `end` whose BEGIN was cut off: the lines of base64 up from it, each with the
|
|
176
|
+
* prefix and the endings pemBody reads (a line number, quotes, a `\n` escape), of any length (a key stored unwrapped),
|
|
177
|
+
* up to the first line that isn't one; that line's own trailing material too, where the cut fell inside it (`out=…`,
|
|
178
|
+
* `"private_key": "…`).
|
|
179
|
+
*/
|
|
180
|
+
export function pemTail(text, end) {
|
|
181
|
+
let start = end;
|
|
182
|
+
let material = 0;
|
|
183
|
+
let above = lineAbove(text, end);
|
|
184
|
+
// (The END line's prefix is every line's: a log's timestamp, a line number.)
|
|
185
|
+
const prefix = linePrefix(text.slice(above.start, end));
|
|
186
|
+
for (let n = 0; n < 2000 && above.breakAt >= 0; n++) {
|
|
187
|
+
const line = lineAbove(text, above.breakAt);
|
|
188
|
+
let afterPrefix = line.start;
|
|
189
|
+
if (prefix) {
|
|
190
|
+
prefix.lastIndex = afterPrefix;
|
|
191
|
+
if (prefix.exec(text) && prefix.lastIndex <= above.breakAt)
|
|
192
|
+
afterPrefix = prefix.lastIndex;
|
|
193
|
+
}
|
|
194
|
+
let rawEnd = above.breakAt;
|
|
195
|
+
while (rawEnd > afterPrefix && CONTINUATION.includes(text[rawEnd - 1]))
|
|
196
|
+
rawEnd--;
|
|
197
|
+
const raw = text.slice(afterPrefix, rawEnd);
|
|
198
|
+
let from = raw.length;
|
|
199
|
+
while (from > 0 && isArmorChar(raw.charCodeAt(from - 1)))
|
|
200
|
+
from--;
|
|
201
|
+
const head = raw.slice(0, from);
|
|
202
|
+
const whole = /^[ \t]*(?:\d{1,7}(?:[\t→:|]|[ \t]{1,4}))?[ \t"'+#>|]*$/.test(head);
|
|
203
|
+
if (raw.length - from < (whole ? 4 : 16))
|
|
204
|
+
break;
|
|
205
|
+
material += raw.length - from;
|
|
206
|
+
start = afterPrefix + from;
|
|
207
|
+
if (!whole)
|
|
208
|
+
break;
|
|
209
|
+
above = line;
|
|
210
|
+
}
|
|
211
|
+
return { start, material };
|
|
212
|
+
}
|
|
213
|
+
/** Between a string's line and the source's own break after it: the string's closing quote, an operator, a comma. */
|
|
214
|
+
const CONTINUATION = ` \t"',;+`;
|
|
215
|
+
/**
|
|
216
|
+
* The length of the line break at `i`, or 0: a real one, after what may end a string written a line at a time in
|
|
217
|
+
* source (`"-----BEGIN PRIVATE KEY-----",⏎`, a key as an array of lines); or one escaped in a string (`\n`, `\r\n`,
|
|
218
|
+
* `\\n`, `\r\n` escaped), together with that before the source's own break (`…\n" +⏎`, `…\n"⏎`).
|
|
219
|
+
*/
|
|
220
|
+
function breakAt(text, i) {
|
|
221
|
+
const escaped = escapedBreakAt(text, i);
|
|
222
|
+
let j = i + escaped;
|
|
223
|
+
while (j < text.length && j - i < 40 && CONTINUATION.includes(text[j]))
|
|
224
|
+
j++;
|
|
225
|
+
if (text[j] === "\r" && text[j + 1] === "\n")
|
|
226
|
+
return j + 2 - i;
|
|
227
|
+
return text[j] === "\n" ? j + 1 - i : escaped;
|
|
228
|
+
}
|
|
229
|
+
/** The length of an escaped line break at `i` (`\n`, `\\n`, `\r\n` escaped), or 0. */
|
|
230
|
+
function escapedBreakAt(text, i) {
|
|
231
|
+
if (text[i] !== "\\")
|
|
232
|
+
return 0;
|
|
233
|
+
if (text[i + 1] === "n")
|
|
234
|
+
return 2;
|
|
235
|
+
if (text[i + 1] === "\\" && text[i + 2] === "n")
|
|
236
|
+
return 3;
|
|
237
|
+
if (text[i + 1] === "r" && text[i + 2] === "\\" && text[i + 3] === "n")
|
|
238
|
+
return 4;
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* The line that ends at `at`: where it starts, after the break above it, and where that break starts (-1 when the
|
|
243
|
+
* text starts the line, or the line is longer than 16,384 characters, an 8192-bit key's base64 on one line). breakAt read backwards: base64 has no
|
|
244
|
+
* backslash, so an `n` after one is an escaped break's; a real break after an escaped one and its closing quote is
|
|
245
|
+
* the same break.
|
|
246
|
+
*/
|
|
247
|
+
function lineAbove(text, at) {
|
|
248
|
+
for (let k = at - 1; k >= 0 && k >= at - 16_384; k--) {
|
|
249
|
+
if (text[k] === "\n") {
|
|
250
|
+
let e = text[k - 1] === "\r" ? k - 1 : k;
|
|
251
|
+
for (let n = 0; n < 40 && e > 0 && CONTINUATION.includes(text[e - 1]); n++)
|
|
252
|
+
e--;
|
|
253
|
+
const escaped = escapedBreakEnding(text, e);
|
|
254
|
+
return { start: k + 1, breakAt: escaped >= 0 ? escaped : text[k - 1] === "\r" ? k - 1 : k };
|
|
255
|
+
}
|
|
256
|
+
if (text[k] === "n" && text[k - 1] === "\\")
|
|
257
|
+
return { start: k + 1, breakAt: escapedBreakEnding(text, k + 1) };
|
|
258
|
+
}
|
|
259
|
+
return { start: Math.max(0, at - 16_384), breakAt: -1 };
|
|
260
|
+
}
|
|
261
|
+
/** Where an escaped line break that ends at `end` starts (`\n`, `\\n`, `\r\n` escaped), or -1. */
|
|
262
|
+
function escapedBreakEnding(text, end) {
|
|
263
|
+
if (text[end - 1] !== "n" || text[end - 2] !== "\\")
|
|
264
|
+
return -1;
|
|
265
|
+
let b = text[end - 3] === "\\" ? end - 3 : end - 2;
|
|
266
|
+
if (text[b - 1] === "r" && text[b - 2] === "\\")
|
|
267
|
+
b -= 2;
|
|
268
|
+
return b;
|
|
269
|
+
}
|
package/dist/policy.d.ts
ADDED