@smthrs/journal 0.0.0-stage → 1.0.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +149 -0
- package/LICENSE +21 -0
- package/README.md +206 -2
- package/dist/cjs/CallFact.d.ts +129 -0
- package/dist/cjs/CallFact.d.ts.map +1 -0
- package/dist/cjs/CallFact.js +106 -0
- package/dist/cjs/CallFact.js.map +7 -0
- package/dist/cjs/Consensus.d.ts +440 -0
- package/dist/cjs/Consensus.d.ts.map +1 -0
- package/dist/cjs/Consensus.js +251 -0
- package/dist/cjs/Consensus.js.map +7 -0
- package/dist/cjs/EngineEvent.d.ts +890 -0
- package/dist/cjs/EngineEvent.d.ts.map +1 -0
- package/dist/cjs/EngineEvent.js +383 -0
- package/dist/cjs/EngineEvent.js.map +7 -0
- package/dist/cjs/ExecutionFact.d.ts +216 -0
- package/dist/cjs/ExecutionFact.d.ts.map +1 -0
- package/dist/cjs/ExecutionFact.js +303 -0
- package/dist/cjs/ExecutionFact.js.map +7 -0
- package/dist/cjs/Journal.d.ts +617 -0
- package/dist/cjs/Journal.d.ts.map +1 -0
- package/dist/cjs/Journal.js +190 -0
- package/dist/cjs/Journal.js.map +7 -0
- package/dist/cjs/JournalEvent.d.ts +421 -0
- package/dist/cjs/JournalEvent.d.ts.map +1 -0
- package/dist/cjs/JournalEvent.js +281 -0
- package/dist/cjs/JournalEvent.js.map +7 -0
- package/dist/cjs/JournalGeneration.d.ts +34 -0
- package/dist/cjs/JournalGeneration.d.ts.map +1 -0
- package/dist/cjs/JournalGeneration.js +72 -0
- package/dist/cjs/JournalGeneration.js.map +7 -0
- package/dist/cjs/JournalMetrics.d.ts +72 -0
- package/dist/cjs/JournalMetrics.d.ts.map +1 -0
- package/dist/cjs/JournalMetrics.js +55 -0
- package/dist/cjs/JournalMetrics.js.map +7 -0
- package/dist/cjs/Migrations.d.ts +42 -0
- package/dist/cjs/Migrations.d.ts.map +1 -0
- package/dist/cjs/Migrations.js +58 -0
- package/dist/cjs/Migrations.js.map +7 -0
- package/dist/cjs/OwnerId.d.ts +40 -0
- package/dist/cjs/OwnerId.d.ts.map +1 -0
- package/dist/cjs/OwnerId.js +40 -0
- package/dist/cjs/OwnerId.js.map +7 -0
- package/dist/cjs/Projection.d.ts +29 -0
- package/dist/cjs/Projection.d.ts.map +1 -0
- package/dist/cjs/Projection.js +25 -0
- package/dist/cjs/Projection.js.map +7 -0
- package/dist/cjs/RedactedLogger.d.ts +121 -0
- package/dist/cjs/RedactedLogger.d.ts.map +1 -0
- package/dist/cjs/RedactedLogger.js +235 -0
- package/dist/cjs/RedactedLogger.js.map +7 -0
- package/dist/cjs/Redaction.d.ts +350 -0
- package/dist/cjs/Redaction.d.ts.map +1 -0
- package/dist/cjs/Redaction.js +928 -0
- package/dist/cjs/Redaction.js.map +7 -0
- package/dist/cjs/SqlConsensus.d.ts +44 -0
- package/dist/cjs/SqlConsensus.d.ts.map +1 -0
- package/dist/cjs/SqlConsensus.js +243 -0
- package/dist/cjs/SqlConsensus.js.map +7 -0
- package/dist/cjs/SqlJournal.d.ts +173 -0
- package/dist/cjs/SqlJournal.d.ts.map +1 -0
- package/dist/cjs/SqlJournal.js +1568 -0
- package/dist/cjs/SqlJournal.js.map +7 -0
- package/dist/cjs/StepFact.d.ts +66 -0
- package/dist/cjs/StepFact.d.ts.map +1 -0
- package/dist/cjs/StepFact.js +68 -0
- package/dist/cjs/StepFact.js.map +7 -0
- package/dist/cjs/index.d.ts +95 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +63 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/JournalInternalFault.d.ts +37 -0
- package/dist/cjs/internal/JournalInternalFault.d.ts.map +1 -0
- package/dist/cjs/internal/JournalInternalFault.js +43 -0
- package/dist/cjs/internal/JournalInternalFault.js.map +7 -0
- package/dist/cjs/migrations/0001_initial.d.ts +15 -0
- package/dist/cjs/migrations/0001_initial.d.ts.map +1 -0
- package/dist/cjs/migrations/0001_initial.js +54 -0
- package/dist/cjs/migrations/0001_initial.js.map +7 -0
- package/dist/cjs/migrations/0002_checkpoints.d.ts +27 -0
- package/dist/cjs/migrations/0002_checkpoints.d.ts.map +1 -0
- package/dist/cjs/migrations/0002_checkpoints.js +48 -0
- package/dist/cjs/migrations/0002_checkpoints.js.map +7 -0
- package/dist/cjs/migrations/0003_startup_index.d.ts +15 -0
- package/dist/cjs/migrations/0003_startup_index.d.ts.map +1 -0
- package/dist/cjs/migrations/0003_startup_index.js +40 -0
- package/dist/cjs/migrations/0003_startup_index.js.map +7 -0
- package/dist/cjs/migrations/0004_dedup.d.ts +15 -0
- package/dist/cjs/migrations/0004_dedup.d.ts.map +1 -0
- package/dist/cjs/migrations/0004_dedup.js +57 -0
- package/dist/cjs/migrations/0004_dedup.js.map +7 -0
- package/dist/cjs/migrations/0005_run_event_type.d.ts +15 -0
- package/dist/cjs/migrations/0005_run_event_type.d.ts.map +1 -0
- package/dist/cjs/migrations/0005_run_event_type.js +40 -0
- package/dist/cjs/migrations/0005_run_event_type.js.map +7 -0
- package/dist/cjs/migrations/0006_consensus.d.ts +22 -0
- package/dist/cjs/migrations/0006_consensus.d.ts.map +1 -0
- package/dist/cjs/migrations/0006_consensus.js +84 -0
- package/dist/cjs/migrations/0006_consensus.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/test/Notifying.d.ts +65 -0
- package/dist/cjs/test/Notifying.d.ts.map +1 -0
- package/dist/cjs/test/Notifying.js +62 -0
- package/dist/cjs/test/Notifying.js.map +7 -0
- package/dist/cjs/test/TestJournal.d.ts +43 -0
- package/dist/cjs/test/TestJournal.d.ts.map +1 -0
- package/dist/cjs/test/TestJournal.js +43 -0
- package/dist/cjs/test/TestJournal.js.map +7 -0
- package/dist/esm/CallFact.d.ts +129 -0
- package/dist/esm/CallFact.d.ts.map +1 -0
- package/dist/esm/CallFact.js +96 -0
- package/dist/esm/CallFact.js.map +1 -0
- package/dist/esm/Consensus.d.ts +440 -0
- package/dist/esm/Consensus.d.ts.map +1 -0
- package/dist/esm/Consensus.js +454 -0
- package/dist/esm/Consensus.js.map +1 -0
- package/dist/esm/EngineEvent.d.ts +890 -0
- package/dist/esm/EngineEvent.d.ts.map +1 -0
- package/dist/esm/EngineEvent.js +614 -0
- package/dist/esm/EngineEvent.js.map +1 -0
- package/dist/esm/ExecutionFact.d.ts +216 -0
- package/dist/esm/ExecutionFact.d.ts.map +1 -0
- package/dist/esm/ExecutionFact.js +374 -0
- package/dist/esm/ExecutionFact.js.map +1 -0
- package/dist/esm/Journal.d.ts +617 -0
- package/dist/esm/Journal.d.ts.map +1 -0
- package/dist/esm/Journal.js +323 -0
- package/dist/esm/Journal.js.map +1 -0
- package/dist/esm/JournalEvent.d.ts +421 -0
- package/dist/esm/JournalEvent.d.ts.map +1 -0
- package/dist/esm/JournalEvent.js +517 -0
- package/dist/esm/JournalEvent.js.map +1 -0
- package/dist/esm/JournalGeneration.d.ts +34 -0
- package/dist/esm/JournalGeneration.d.ts.map +1 -0
- package/dist/esm/JournalGeneration.js +65 -0
- package/dist/esm/JournalGeneration.js.map +1 -0
- package/dist/esm/JournalMetrics.d.ts +72 -0
- package/dist/esm/JournalMetrics.d.ts.map +1 -0
- package/dist/esm/JournalMetrics.js +79 -0
- package/dist/esm/JournalMetrics.js.map +1 -0
- package/dist/esm/Migrations.d.ts +42 -0
- package/dist/esm/Migrations.d.ts.map +1 -0
- package/dist/esm/Migrations.js +59 -0
- package/dist/esm/Migrations.js.map +1 -0
- package/dist/esm/OwnerId.d.ts +40 -0
- package/dist/esm/OwnerId.d.ts.map +1 -0
- package/dist/esm/OwnerId.js +33 -0
- package/dist/esm/OwnerId.js.map +1 -0
- package/dist/esm/Projection.d.ts +29 -0
- package/dist/esm/Projection.d.ts.map +1 -0
- package/dist/esm/Projection.js +13 -0
- package/dist/esm/Projection.js.map +1 -0
- package/dist/esm/RedactedLogger.d.ts +121 -0
- package/dist/esm/RedactedLogger.d.ts.map +1 -0
- package/dist/esm/RedactedLogger.js +445 -0
- package/dist/esm/RedactedLogger.js.map +1 -0
- package/dist/esm/Redaction.d.ts +350 -0
- package/dist/esm/Redaction.d.ts.map +1 -0
- package/dist/esm/Redaction.js +1584 -0
- package/dist/esm/Redaction.js.map +1 -0
- package/dist/esm/SqlConsensus.d.ts +44 -0
- package/dist/esm/SqlConsensus.d.ts.map +1 -0
- package/dist/esm/SqlConsensus.js +252 -0
- package/dist/esm/SqlConsensus.js.map +1 -0
- package/dist/esm/SqlJournal.d.ts +173 -0
- package/dist/esm/SqlJournal.d.ts.map +1 -0
- package/dist/esm/SqlJournal.js +1858 -0
- package/dist/esm/SqlJournal.js.map +1 -0
- package/dist/esm/StepFact.d.ts +66 -0
- package/dist/esm/StepFact.d.ts.map +1 -0
- package/dist/esm/StepFact.js +50 -0
- package/dist/esm/StepFact.js.map +1 -0
- package/dist/esm/index.d.ts +95 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +95 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/JournalInternalFault.d.ts +37 -0
- package/dist/esm/internal/JournalInternalFault.d.ts.map +1 -0
- package/dist/esm/internal/JournalInternalFault.js +28 -0
- package/dist/esm/internal/JournalInternalFault.js.map +1 -0
- package/dist/esm/migrations/0001_initial.d.ts +15 -0
- package/dist/esm/migrations/0001_initial.d.ts.map +1 -0
- package/dist/esm/migrations/0001_initial.js +32 -0
- package/dist/esm/migrations/0001_initial.js.map +1 -0
- package/dist/esm/migrations/0002_checkpoints.d.ts +27 -0
- package/dist/esm/migrations/0002_checkpoints.d.ts.map +1 -0
- package/dist/esm/migrations/0002_checkpoints.js +38 -0
- package/dist/esm/migrations/0002_checkpoints.js.map +1 -0
- package/dist/esm/migrations/0003_startup_index.d.ts +15 -0
- package/dist/esm/migrations/0003_startup_index.d.ts.map +1 -0
- package/dist/esm/migrations/0003_startup_index.js +18 -0
- package/dist/esm/migrations/0003_startup_index.js.map +1 -0
- package/dist/esm/migrations/0004_dedup.d.ts +15 -0
- package/dist/esm/migrations/0004_dedup.d.ts.map +1 -0
- package/dist/esm/migrations/0004_dedup.js +38 -0
- package/dist/esm/migrations/0004_dedup.js.map +1 -0
- package/dist/esm/migrations/0005_run_event_type.d.ts +15 -0
- package/dist/esm/migrations/0005_run_event_type.d.ts.map +1 -0
- package/dist/esm/migrations/0005_run_event_type.js +18 -0
- package/dist/esm/migrations/0005_run_event_type.js.map +1 -0
- package/dist/esm/migrations/0006_consensus.d.ts +22 -0
- package/dist/esm/migrations/0006_consensus.d.ts.map +1 -0
- package/dist/esm/migrations/0006_consensus.js +69 -0
- package/dist/esm/migrations/0006_consensus.js.map +1 -0
- package/dist/esm/test/Notifying.d.ts +65 -0
- package/dist/esm/test/Notifying.d.ts.map +1 -0
- package/dist/esm/test/Notifying.js +62 -0
- package/dist/esm/test/Notifying.js.map +1 -0
- package/dist/esm/test/TestJournal.d.ts +43 -0
- package/dist/esm/test/TestJournal.d.ts.map +1 -0
- package/dist/esm/test/TestJournal.js +27 -0
- package/dist/esm/test/TestJournal.js.map +1 -0
- package/docs/README.md +164 -0
- package/docs/api.md +639 -0
- package/docs/concepts/compaction.md +157 -0
- package/docs/concepts/idempotency.md +127 -0
- package/docs/concepts/owner-fence.md +124 -0
- package/docs/concepts/redaction.md +218 -0
- package/docs/concepts/state-event-authority.md +159 -0
- package/docs/concepts/two-channels.md +167 -0
- package/docs/guides/commit-state-and-entry.md +114 -0
- package/docs/guides/compact-a-run.md +134 -0
- package/docs/guides/fold-a-projection.md +106 -0
- package/docs/guides/read-a-run.md +139 -0
- package/docs/guides/redact-log-output.md +94 -0
- package/docs/guides/testing.md +124 -0
- package/docs/guides/write-lifecycle-events.md +110 -0
- package/docs/installation.md +133 -0
- package/docs/quickstart.md +143 -0
- package/docs/troubleshooting.md +214 -0
- package/package.json +263 -3
- package/src/CallFact.ts +117 -0
- package/src/Consensus.ts +683 -0
- package/src/EngineEvent.ts +823 -0
- package/src/ExecutionFact.ts +433 -0
- package/src/Journal.ts +688 -0
- package/src/JournalEvent.ts +654 -0
- package/src/JournalGeneration.ts +77 -0
- package/src/JournalMetrics.ts +89 -0
- package/src/Migrations.ts +62 -0
- package/src/OwnerId.ts +42 -0
- package/src/Projection.ts +31 -0
- package/src/RedactedLogger.ts +483 -0
- package/src/Redaction.ts +1784 -0
- package/src/SqlConsensus.ts +344 -0
- package/src/SqlJournal.ts +2732 -0
- package/src/StepFact.ts +69 -0
- package/src/index.ts +109 -0
- package/src/internal/JournalInternalFault.ts +40 -0
- package/src/migrations/0001_initial.ts +41 -0
- package/src/migrations/0002_checkpoints.ts +46 -0
- package/src/migrations/0003_startup_index.ts +19 -0
- package/src/migrations/0004_dedup.ts +43 -0
- package/src/migrations/0005_run_event_type.ts +19 -0
- package/src/migrations/0006_consensus.ts +83 -0
- package/src/test/Notifying.ts +101 -0
- package/src/test/TestJournal.ts +52 -0
|
@@ -0,0 +1,928 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var Redaction_exports = {};
|
|
30
|
+
__export(Redaction_exports, {
|
|
31
|
+
RedactionDepthError: () => RedactionDepthError,
|
|
32
|
+
binaryMarker: () => binaryMarker,
|
|
33
|
+
binaryWalkLimit: () => binaryWalkLimit,
|
|
34
|
+
defaultRules: () => defaultRules,
|
|
35
|
+
depthMarker: () => depthMarker,
|
|
36
|
+
diagnosticRules: () => diagnosticRules,
|
|
37
|
+
functionMarker: () => functionMarker,
|
|
38
|
+
isSensitiveKey: () => isSensitiveKey,
|
|
39
|
+
lineRedactor: () => lineRedactor,
|
|
40
|
+
make: () => make,
|
|
41
|
+
makeNoop: () => makeNoop,
|
|
42
|
+
maxDepth: () => maxDepth,
|
|
43
|
+
maxHeldLines: () => maxHeldLines,
|
|
44
|
+
maxPartialLine: () => maxPartialLine,
|
|
45
|
+
omittedLine: () => omittedLine,
|
|
46
|
+
placeholder: () => placeholder,
|
|
47
|
+
redact: () => redact,
|
|
48
|
+
redactDiagnostic: () => redactDiagnostic,
|
|
49
|
+
redactJsonString: () => redactJsonString,
|
|
50
|
+
symbolMarker: () => symbolMarker
|
|
51
|
+
});
|
|
52
|
+
module.exports = __toCommonJS(Redaction_exports);
|
|
53
|
+
var Result = __toESM(require("effect/Result"), 1);
|
|
54
|
+
var Schema = __toESM(require("effect/Schema"), 1);
|
|
55
|
+
const UnknownFromJsonString = Schema.fromJsonString(Schema.Unknown);
|
|
56
|
+
const placeholder = "[REDACTED]";
|
|
57
|
+
const isQuote = (char) => char === '"' || char === "'" || char === "`";
|
|
58
|
+
const closers = { "{": "}", "[": "]", "(": ")" };
|
|
59
|
+
let lastClosed = false;
|
|
60
|
+
const skipQuoted = (text, open, depth, modulus = 2 * (depth + 1), limit = text.length) => {
|
|
61
|
+
let end = open + 1;
|
|
62
|
+
while (end < limit) {
|
|
63
|
+
if (text[end] === text[open]) {
|
|
64
|
+
let slashes = 0;
|
|
65
|
+
for (let i = end - 1; i > open && text[i] === "\\"; i--) slashes++;
|
|
66
|
+
if (slashes % modulus === depth) {
|
|
67
|
+
lastClosed = true;
|
|
68
|
+
return end + 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
end++;
|
|
72
|
+
}
|
|
73
|
+
lastClosed = false;
|
|
74
|
+
return end;
|
|
75
|
+
};
|
|
76
|
+
const skipContainer = (text, open, limit = text.length) => {
|
|
77
|
+
const pending = [];
|
|
78
|
+
let end = open;
|
|
79
|
+
while (end < limit) {
|
|
80
|
+
const char = text[end];
|
|
81
|
+
if (isQuote(char)) {
|
|
82
|
+
let depth = 0;
|
|
83
|
+
while (text[end - 1 - depth] === "\\") depth++;
|
|
84
|
+
end = skipQuoted(text, end, depth, 2 * (depth + 1), limit);
|
|
85
|
+
if (!lastClosed) return end;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const closer = closers[char];
|
|
89
|
+
if (closer !== void 0) pending.push(closer);
|
|
90
|
+
else if (char === pending.at(-1)) {
|
|
91
|
+
pending.pop();
|
|
92
|
+
if (pending.length === 0) {
|
|
93
|
+
lastClosed = true;
|
|
94
|
+
return end + 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
end++;
|
|
98
|
+
}
|
|
99
|
+
lastClosed = false;
|
|
100
|
+
return end;
|
|
101
|
+
};
|
|
102
|
+
const escapeDepth = (slashes) => (slashes ^ slashes + 1) >> 1;
|
|
103
|
+
const innermostString = (stack) => stack.at(-1)?.string ?? -1;
|
|
104
|
+
const track = (stack, text, from, to) => {
|
|
105
|
+
for (let i = from; i < to; i++) {
|
|
106
|
+
const char = text[i];
|
|
107
|
+
if (isQuote(char)) {
|
|
108
|
+
let slashes = 0;
|
|
109
|
+
while (text[i - 1 - slashes] === "\\") slashes++;
|
|
110
|
+
const inner = innermostString(stack);
|
|
111
|
+
if (inner === -1) {
|
|
112
|
+
stack.push({ close: char, depth: escapeDepth(slashes), string: stack.length });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (stack[inner].close !== char) continue;
|
|
116
|
+
let closes = inner;
|
|
117
|
+
while (closes !== -1) {
|
|
118
|
+
const depth = stack[closes].depth;
|
|
119
|
+
if (slashes % (2 * (depth + 1)) === depth) break;
|
|
120
|
+
closes = closes === 0 ? -1 : stack[closes - 1].string;
|
|
121
|
+
}
|
|
122
|
+
if (closes === -1) stack.push({ close: char, depth: escapeDepth(slashes), string: stack.length });
|
|
123
|
+
else stack.length = closes;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const closer = closers[char];
|
|
127
|
+
if (closer !== void 0) stack.push({ close: closer, string: innermostString(stack) });
|
|
128
|
+
else if (stack.at(-1)?.depth === void 0 && char === stack.at(-1)?.close) stack.pop();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const closesEnclosing = (text, end, stack) => {
|
|
132
|
+
let at = end;
|
|
133
|
+
for (let k = stack.length - 1; k >= 0 && stack[k].depth === void 0 && text[at] === stack[k].close; k--) at++;
|
|
134
|
+
return at > end && /^(?:[\s,;"'`]|\\+["'`]|$)/.test(text.slice(at, at + 2));
|
|
135
|
+
};
|
|
136
|
+
const credentialWord = "(?:KEY|TOKEN|SECRET|PASSWORD|PASSPHRASE|CREDENTIAL)S?";
|
|
137
|
+
const separator = (space) => String.raw`(?:\\*["'\x60])?${space}(?:=>|[=:])${space}`;
|
|
138
|
+
const credentialName = String.raw`(?<![A-Za-z0-9_-])([A-Za-z0-9_-]*${credentialWord})`;
|
|
139
|
+
const credentialNames = new RegExp(credentialName + separator("[ \\t]*"), "gi");
|
|
140
|
+
const diagnosticNames = new RegExp(credentialName + separator(String.raw`\s*`), "gi");
|
|
141
|
+
const authorizationName = "(?:proxy-)?authorization".replace(/[a-z]/g, (char) => `[${char}${char.toUpperCase()}]`);
|
|
142
|
+
const headerWord = String.raw`${authorizationName}|${"(?:set-)?cookies?".replace(/[a-z]/g, (char) => `[${char}${char.toUpperCase()}]`)}`;
|
|
143
|
+
const headerName = String.raw`(?<![A-Za-z0-9!#$%&*+.^_|~-])(?<![A-Za-z0-9!#$%&'*+.^_\x60|~-]['\x60])(${headerWord})`;
|
|
144
|
+
const headerNames = new RegExp(
|
|
145
|
+
String.raw`((?:proxy-)?authorization|(?:set-)?cookies?)${separator(String.raw`\s*`)}`,
|
|
146
|
+
"gi"
|
|
147
|
+
);
|
|
148
|
+
const diagnosticHeaderNames = new RegExp(
|
|
149
|
+
// A trailing token quote requires an opener; double quotes are never token characters.
|
|
150
|
+
`${headerName}(?:` + String.raw`(?:(?<=["'\x60](?:${headerWord}))|(?!\\*['\x60]))${separator(String.raw`\s*`)}|` + String.raw`(?<=${authorizationName})\s+(?=(?:Token|TOKEN|[Bb]earer|BEARER|Basic|BASIC)[ \t]+\S))`,
|
|
151
|
+
"g"
|
|
152
|
+
);
|
|
153
|
+
const anyName = new RegExp(`${credentialNames.source}|${headerNames.source}`, "gi");
|
|
154
|
+
const nameBefore = new RegExp(`(?:${credentialNames.source}|${headerNames.source})$`, "i");
|
|
155
|
+
const nextName = (text, from, quoted) => {
|
|
156
|
+
const cached = nameSearches[quoted ? 1 : 0];
|
|
157
|
+
if (cached.text === text && from >= cached.from && from <= cached.found) return cached.found;
|
|
158
|
+
let found = text.length;
|
|
159
|
+
anyName.lastIndex = from;
|
|
160
|
+
for (let match = anyName.exec(text); match !== null; match = anyName.exec(text)) {
|
|
161
|
+
if (!quoted || isQuote(text[match.index - 1])) {
|
|
162
|
+
found = match.index;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
nameSearches[quoted ? 1 : 0] = { text, from, found };
|
|
167
|
+
return found;
|
|
168
|
+
};
|
|
169
|
+
const nameSearches = [{ text: "", from: 0, found: 0 }, { text: "", from: 0, found: 0 }];
|
|
170
|
+
const lineEnd = (text, index) => {
|
|
171
|
+
if (lastLine.text === text && index >= lastLine.from && index <= lastLine.end) return lastLine.end;
|
|
172
|
+
newline.lastIndex = index;
|
|
173
|
+
const end = newline.exec(text)?.index ?? text.length;
|
|
174
|
+
lastLine = { text, from: index, end };
|
|
175
|
+
return end;
|
|
176
|
+
};
|
|
177
|
+
const newline = /[\r\n]/g;
|
|
178
|
+
let lastLine = { text: "", from: 0, end: 0 };
|
|
179
|
+
const continuesAcrossSpace = (text, start, end) => {
|
|
180
|
+
const run = text.slice(start, end);
|
|
181
|
+
let next = end;
|
|
182
|
+
while (text[next] === " " || text[next] === " ") next++;
|
|
183
|
+
if (run === "new") return next;
|
|
184
|
+
const prefix = /^[\w$.]+$/.test(run) || /[)\]>]$/.test(run);
|
|
185
|
+
return prefix && closers[text[next] ?? ""] !== void 0 ? next : void 0;
|
|
186
|
+
};
|
|
187
|
+
const separatesPairs = (text, end) => text[end] === "&" ? /^&[A-Za-z_][\w.-]*=/.test(text.slice(end, end + 64)) : /^[,;](?:\s|$|\s*\\*["'`]?[A-Za-z_][\w.-]*\\*["'`]?\s*(?:=>|[=:]))/.test(text.slice(end, end + 64));
|
|
188
|
+
const containerWindow = 4096;
|
|
189
|
+
const containerBudget = 4;
|
|
190
|
+
const bareValueEnd = (text, start, scope, stack, budget) => {
|
|
191
|
+
const inner = stack[innermostString(stack)];
|
|
192
|
+
const bound = scope.diagnostic ? text.length : lineEnd(text, start);
|
|
193
|
+
const window = scope.diagnostic ? text.length : Math.min(text.length, start + Math.max(containerWindow, budget.remaining));
|
|
194
|
+
const unclosed = (open, end2) => Math.max(open, Math.min(end2, bound));
|
|
195
|
+
const container = (open) => {
|
|
196
|
+
if (scope.diagnostic) return skipContainer(text, open, window);
|
|
197
|
+
const end2 = skipContainer(text, open, bound);
|
|
198
|
+
if (lastClosed || !/[[{(,]\s*$/.test(text.slice(open, bound))) return end2;
|
|
199
|
+
const past = skipContainer(text, open, window);
|
|
200
|
+
budget.remaining -= past - open;
|
|
201
|
+
return past;
|
|
202
|
+
};
|
|
203
|
+
let end = start;
|
|
204
|
+
let limit = nextName(text, start + 1, scope.header);
|
|
205
|
+
if (scope.header && closers[text[start]] !== void 0) {
|
|
206
|
+
end = container(start);
|
|
207
|
+
if (!/^[^\s,;"'`\\)\]}]/.test(text[end] ?? " ")) return lastClosed ? end : unclosed(start, end);
|
|
208
|
+
}
|
|
209
|
+
while (end < text.length) {
|
|
210
|
+
if (end >= limit) {
|
|
211
|
+
if (end === limit) break;
|
|
212
|
+
limit = nextName(text, end, scope.header);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const char = text[end];
|
|
216
|
+
if (closers[char] !== void 0) {
|
|
217
|
+
const open = end;
|
|
218
|
+
end = container(open);
|
|
219
|
+
if (!lastClosed) return unclosed(open, end);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (char === "<") {
|
|
223
|
+
angleClose.lastIndex = end;
|
|
224
|
+
const close = angleClose.exec(text)?.index;
|
|
225
|
+
if (close !== void 0 && text[close] === ">") {
|
|
226
|
+
end = close + 1;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
let depth = 0;
|
|
231
|
+
while (text[end + depth] === "\\") depth++;
|
|
232
|
+
const quote = text[end + depth];
|
|
233
|
+
if (isQuote(quote)) {
|
|
234
|
+
const closesString = inner !== void 0 && quote === inner.close && depth % (2 * (inner.depth + 1)) <= inner.depth && !nameBefore.test(text.slice(Math.max(start, end - 256), end));
|
|
235
|
+
if (closesString || !scope.header && inner === void 0 && stack.length > 0) break;
|
|
236
|
+
if (depth === 0 && /\w/.test(text[end - 1] ?? "") && /\w/.test(text[end + 1] ?? "")) {
|
|
237
|
+
end++;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const open = end;
|
|
241
|
+
end = skipQuoted(text, end + depth, depth, 2 * (depth + 1), bound);
|
|
242
|
+
if (!lastClosed) return unclosed(open, end);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (char === "\r" || char === "\n") break;
|
|
246
|
+
if (!scope.header && (char === "&" && separatesPairs(text, end) || (char === "," || char === ";") && (stack.length > 0 || separatesPairs(text, end)))) break;
|
|
247
|
+
if (!scope.header && !scope.diagnostic && (char === " " || char === " ")) {
|
|
248
|
+
const next = continuesAcrossSpace(text, start, end);
|
|
249
|
+
if (next === void 0) break;
|
|
250
|
+
end = next;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (closesEnclosing(text, end, stack)) break;
|
|
254
|
+
end++;
|
|
255
|
+
}
|
|
256
|
+
while (end > start && /\s/.test(text[end - 1])) end--;
|
|
257
|
+
return end;
|
|
258
|
+
};
|
|
259
|
+
const plainKeyName = (name) => /keys?$/i.test(name) && !isSensitiveKey(name);
|
|
260
|
+
const userinfoEnd = (text, start, end) => {
|
|
261
|
+
if (!/\/\/[^\s/@?#]*[:=]$/.test(text.slice(Math.max(0, start - 128), start))) return end;
|
|
262
|
+
const value = text.slice(start, end);
|
|
263
|
+
const authority = value.search(/[/?#]/);
|
|
264
|
+
const at = value.lastIndexOf("@", authority < 0 ? value.length : authority);
|
|
265
|
+
return at > 0 ? start + at : end;
|
|
266
|
+
};
|
|
267
|
+
const namesCredential = (name, value) => !plainKeyName(name) && !(/tokens$/i.test(name) && /^["'`]?\d+(?![\w.])/.test(value));
|
|
268
|
+
const angleClose = /[>\r\n]/g;
|
|
269
|
+
const continuation = /(?:\s|\\+[nrt])*\+(?:\s|\\+[nrt])*/y;
|
|
270
|
+
const alreadyRedacted = /^\[REDACTED\]$/;
|
|
271
|
+
const redactValues = (text, names, scope) => {
|
|
272
|
+
const keys = new RegExp(names.source, names.flags);
|
|
273
|
+
let output = "";
|
|
274
|
+
let position = 0;
|
|
275
|
+
const stack = [];
|
|
276
|
+
const budget = { remaining: containerBudget * text.length };
|
|
277
|
+
let scanned = 0;
|
|
278
|
+
for (let match = keys.exec(text); match !== null; match = keys.exec(text)) {
|
|
279
|
+
const name = match[1];
|
|
280
|
+
if (!scope.header && plainKeyName(name)) continue;
|
|
281
|
+
track(stack, text, scanned, keys.lastIndex);
|
|
282
|
+
scanned = keys.lastIndex;
|
|
283
|
+
let start = keys.lastIndex;
|
|
284
|
+
let depth = 0;
|
|
285
|
+
while (text[start + depth] === "\\") depth++;
|
|
286
|
+
const opener = text[start + depth];
|
|
287
|
+
const quote = isQuote(opener) ? opener : void 0;
|
|
288
|
+
let end;
|
|
289
|
+
if (quote !== void 0) {
|
|
290
|
+
start += depth;
|
|
291
|
+
const inner = stack[innermostString(stack)];
|
|
292
|
+
if (inner !== void 0) {
|
|
293
|
+
end = skipQuoted(text, start, depth, (inner.close === quote ? 1 : 2 * (inner.depth + 1)) * 2 * (depth + 1));
|
|
294
|
+
} else {
|
|
295
|
+
const undoubled = skipQuoted(text, start, depth);
|
|
296
|
+
end = skipQuoted(text, start, depth, Math.max(4, 2 * (depth + 1)));
|
|
297
|
+
const next = nextName(text, undoubled, false);
|
|
298
|
+
if (next < end) end = /^[\s,;&]*$/.test(text.slice(undoubled, next)) ? undoubled : next;
|
|
299
|
+
}
|
|
300
|
+
const closed2 = end - start > 1 && text[end - 1] === quote;
|
|
301
|
+
if (!closed2 && !scope.diagnostic) end = Math.min(end, lineEnd(text, start));
|
|
302
|
+
if (depth === 0) {
|
|
303
|
+
for (continuation.lastIndex = end; continuation.test(text); continuation.lastIndex = end) {
|
|
304
|
+
const piece = continuation.lastIndex;
|
|
305
|
+
if (!isQuote(text[piece])) break;
|
|
306
|
+
end = skipQuoted(text, piece, 0);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (alreadyRedacted.test(text.slice(start + 1, end - (text[end - 1] === quote ? 1 + depth : 0)))) continue;
|
|
310
|
+
} else {
|
|
311
|
+
end = bareValueEnd(text, start, scope, stack, budget);
|
|
312
|
+
end = userinfoEnd(text, start, end);
|
|
313
|
+
const bounded = !scope.diagnostic && text.startsWith(placeholder, start) && /\s/.test(text[start + placeholder.length] ?? "");
|
|
314
|
+
const kept = end === start || bounded || alreadyRedacted.test(text.slice(start, end)) || /tokens$/i.test(name) && /^\d+(?![\w.])/.test(text.slice(start, end)) || // A Bearer or Basic value the default rules already replaced keeps its scheme.
|
|
315
|
+
scope.header && /^(?:bearer|basic)\s+\[REDACTED/i.test(text.slice(start, start + 24));
|
|
316
|
+
if (kept) continue;
|
|
317
|
+
}
|
|
318
|
+
const escape = quote === void 0 ? "" : "\\".repeat(depth);
|
|
319
|
+
const closed = quote !== void 0 && end - start > 1 && text[end - 1] === quote;
|
|
320
|
+
output += text.slice(position, keys.lastIndex) + escape + (quote ?? "") + placeholder + (closed ? escape + quote : "");
|
|
321
|
+
position = end;
|
|
322
|
+
scanned = end;
|
|
323
|
+
keys.lastIndex = end;
|
|
324
|
+
}
|
|
325
|
+
return output + text.slice(position);
|
|
326
|
+
};
|
|
327
|
+
const argumentSeparator = String.raw`(?:\s+|["'\x60]\s*,\s*["'\x60])`;
|
|
328
|
+
const credentialFlag = new RegExp(
|
|
329
|
+
String.raw`(?<![A-Za-z0-9_-])(--?[A-Za-z0-9_-]*${credentialWord})${argumentSeparator}`,
|
|
330
|
+
"gi"
|
|
331
|
+
);
|
|
332
|
+
const shortPasswordFlag = new RegExp(
|
|
333
|
+
String.raw`(?<![^\s"'\x60,[(])-p${argumentSeparator}(?!(?:\.{0,2}|~)\/|--)`,
|
|
334
|
+
"g"
|
|
335
|
+
);
|
|
336
|
+
const passwordFlag = new RegExp(
|
|
337
|
+
String.raw`(?<![^\s"'\x60,[(])-p(?:w|ass(?:in|out|wd)?)${argumentSeparator}`,
|
|
338
|
+
"g"
|
|
339
|
+
);
|
|
340
|
+
const attachedPasswordFlag = new RegExp(
|
|
341
|
+
String.raw`(?<![^\s"'\x60,[(])-p(?!(?:rint\w*|rune|erm|ath|threads?|edantic[\w-]*|ipe|ie|g|retty|lain|rogress|rofile|arents|reserve|ass(?:in|out|wd)?|w)(?![^\s"'\x60,\]]))(?=[^\s,\]])`,
|
|
342
|
+
"g"
|
|
343
|
+
);
|
|
344
|
+
const userFlag = new RegExp(
|
|
345
|
+
String.raw`(?<![^\s"'\x60,[(])(?:(?:-u|--user)(?:=|${argumentSeparator})|-u(?=[^\s=-]))`,
|
|
346
|
+
"g"
|
|
347
|
+
);
|
|
348
|
+
const commandPasswordFlag = new RegExp(
|
|
349
|
+
String.raw`\b(?:(?:(?:un)?zip|zipcloak)\b[^\r\n|;&]{0,256}?[\s"'\x60,[(]-P|redis-cli\b[^\r\n|;&]{0,256}?[\s"'\x60,[(](?:-a|--pass))${argumentSeparator}`,
|
|
350
|
+
"g"
|
|
351
|
+
);
|
|
352
|
+
const headerPair = /(?<![A-Za-z0-9_-])(?:(?:proxy-)?authorization|(?:set-)?cookies?)\\*["'`]\s*,\s*(?=\\*["'`])/gi;
|
|
353
|
+
const redactHeaderPairs = (text) => {
|
|
354
|
+
const pattern = new RegExp(headerPair.source, headerPair.flags);
|
|
355
|
+
let output = "";
|
|
356
|
+
let position = 0;
|
|
357
|
+
for (let match = pattern.exec(text); match !== null; match = pattern.exec(text)) {
|
|
358
|
+
let depth = 0;
|
|
359
|
+
while (text[pattern.lastIndex + depth] === "\\") depth++;
|
|
360
|
+
const open = pattern.lastIndex + depth;
|
|
361
|
+
const end = skipQuoted(text, open, depth);
|
|
362
|
+
const close = lastClosed ? end - 1 - depth : end;
|
|
363
|
+
if (alreadyRedacted.test(text.slice(open + 1, close))) continue;
|
|
364
|
+
output += text.slice(position, open + 1) + placeholder;
|
|
365
|
+
position = close;
|
|
366
|
+
pattern.lastIndex = end;
|
|
367
|
+
}
|
|
368
|
+
return output + text.slice(position);
|
|
369
|
+
};
|
|
370
|
+
const redactedArgument = /^(?:\\*(["'`]))?\[REDACTED\](?:\\*\1)?$/;
|
|
371
|
+
const wordEnd = (text, start) => {
|
|
372
|
+
let end = start;
|
|
373
|
+
while (end < text.length && !/\s/.test(text[end])) {
|
|
374
|
+
const quote = text[end];
|
|
375
|
+
if (!isQuote(quote)) {
|
|
376
|
+
end++;
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
let close = end + 1;
|
|
380
|
+
while (close < text.length && !/[\s"'`,:{}[\]]/.test(text[close])) close++;
|
|
381
|
+
if (close === end + 1 || text[close] !== quote) break;
|
|
382
|
+
end = close + 1;
|
|
383
|
+
}
|
|
384
|
+
return end;
|
|
385
|
+
};
|
|
386
|
+
const redactArguments = (text, flags, accept) => {
|
|
387
|
+
const pattern = new RegExp(flags.source, flags.flags);
|
|
388
|
+
let output = "";
|
|
389
|
+
let position = 0;
|
|
390
|
+
for (let match = pattern.exec(text); match !== null; match = pattern.exec(text)) {
|
|
391
|
+
const start = pattern.lastIndex;
|
|
392
|
+
let depth = 0;
|
|
393
|
+
while (text[start + depth] === "\\") depth++;
|
|
394
|
+
let end = start;
|
|
395
|
+
if (isQuote(text[start + depth])) end = skipQuoted(text, start + depth, depth);
|
|
396
|
+
else end = wordEnd(text, start);
|
|
397
|
+
const value = text.slice(start, end);
|
|
398
|
+
if (end === start || redactedArgument.test(value) || accept?.(match, value) === false) continue;
|
|
399
|
+
output += text.slice(position, start) + placeholder;
|
|
400
|
+
position = end;
|
|
401
|
+
pattern.lastIndex = end;
|
|
402
|
+
}
|
|
403
|
+
return output + text.slice(position);
|
|
404
|
+
};
|
|
405
|
+
const durable = { header: false, diagnostic: false };
|
|
406
|
+
const defaultRules = [
|
|
407
|
+
{
|
|
408
|
+
id: "private-key-block",
|
|
409
|
+
// Stop an unterminated block at the next header so repeated headers cannot
|
|
410
|
+
// each rescan the remaining input. A valid PEM body contains no headers.
|
|
411
|
+
// A block with no footer is a key a bound or a producer truncated, and its
|
|
412
|
+
// body is as secret as a whole one's, so it runs to the next header or to
|
|
413
|
+
// the end of the text. It runs first: a block inside URL userinfo, once
|
|
414
|
+
// replaced, is userinfo the URL rule then reads whole, so a second pass
|
|
415
|
+
// finds nothing left to change.
|
|
416
|
+
pattern: /-----BEGIN[^-]*PRIVATE KEY(?: BLOCK)?-----(?:(?!-----BEGIN)[\s\S])*?(?:-----END[^-]*-----|(?=-----BEGIN)|$)/g
|
|
417
|
+
},
|
|
418
|
+
// Git HTTPS OAuth credentials place the secret in the username. Match
|
|
419
|
+
// before password rewriting removes the x-oauth-basic marker.
|
|
420
|
+
{
|
|
421
|
+
id: "git-oauth-userinfo",
|
|
422
|
+
pattern: /(https?:\/\/)[^\s/@:"'\\]+:x-oauth-basic@/gi,
|
|
423
|
+
replace: "$1[REDACTED]@"
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
id: "url-credentials",
|
|
427
|
+
// The scheme is bounded rather than open. `-` is not a word character, so
|
|
428
|
+
// `\b[a-z][a-z0-9+.-]*` starts a fresh scan after every hyphen, and on a
|
|
429
|
+
// long run of that shape the rule rescanned the tail from each one: 400 kB
|
|
430
|
+
// of `aaaaaaaa-` cost 11 seconds, on the path every journal write and every
|
|
431
|
+
// log line takes. A scheme longer than 30 characters is not one this net is
|
|
432
|
+
// for, and the cap makes each start position cost a constant.
|
|
433
|
+
pattern: /\b([a-z][a-z0-9+.-]{0,30}:\/\/[^\s:@/]+):(?!\[REDACTED\]@)[^\s:@/]+@/gi,
|
|
434
|
+
replace: "$1:[REDACTED]@"
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
id: "bearer-token",
|
|
438
|
+
pattern: /\bBearer\s+[A-Za-z0-9._~+/-]{4,}=*/gi,
|
|
439
|
+
replace: "Bearer [REDACTED_TOKEN]"
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
id: "basic-authorization",
|
|
443
|
+
pattern: /\bBasic\s+[A-Za-z0-9+/]{4,}={0,2}/gi,
|
|
444
|
+
replace: "Basic [REDACTED_TOKEN]"
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
id: "jwt",
|
|
448
|
+
// Check the three segments once per token before looking for a header.
|
|
449
|
+
// Starting at every `-eyJ` would rescan a malformed token's entire tail.
|
|
450
|
+
// Preserve any prefix so a JWT following a hyphen still gets redacted.
|
|
451
|
+
pattern: /(?<![A-Za-z0-9_-])(?=[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-])([A-Za-z0-9_-]*?)\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
|
|
452
|
+
replace: "$1[REDACTED_TOKEN]"
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
id: "api-key",
|
|
456
|
+
// Not `\b`, for the reason the assignment rule below gives: an underscore
|
|
457
|
+
// is a word character, so `\bsk` never fires after `ANTHROPIC_` or after
|
|
458
|
+
// Effect's log-span sanitizer folds `token=` into `token_`. The lookbehind
|
|
459
|
+
// excludes only letters and digits, so a key still reads as a key when an
|
|
460
|
+
// underscore or a hyphen runs into it.
|
|
461
|
+
pattern: /(?<![A-Za-z0-9])(?:sk|pk)[-_][A-Za-z0-9][A-Za-z0-9_-]{7,}/g,
|
|
462
|
+
replace: "[REDACTED_API_KEY]"
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
id: "github-token",
|
|
466
|
+
pattern: /\bgh[pousr]_[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
|
|
467
|
+
replace: "[REDACTED]"
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
id: "github-fine-grained-token",
|
|
471
|
+
pattern: /\bgithub_pat_[A-Za-z0-9_]{20,}(?![A-Za-z0-9_])/g,
|
|
472
|
+
replace: "[REDACTED]"
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
id: "aws-access-key",
|
|
476
|
+
pattern: /\bAKIA[0-9A-Z]{16}\b/g,
|
|
477
|
+
replace: "[REDACTED]"
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
id: "slack-token",
|
|
481
|
+
pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g,
|
|
482
|
+
replace: "[REDACTED]"
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
id: "google-api-key",
|
|
486
|
+
pattern: /\bAIza[0-9A-Za-z_-]{35,}(?![0-9A-Za-z_-])/g,
|
|
487
|
+
replace: "[REDACTED]"
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
id: "assignment",
|
|
491
|
+
// A credential name, `:`, `=` or `=>`, and its whole value: quoted at any
|
|
492
|
+
// escape depth, a container, or several bare words. See `redactValues`.
|
|
493
|
+
pattern: credentialNames,
|
|
494
|
+
rewrite: (text) => redactValues(text, credentialNames, durable)
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
id: "credential-flag",
|
|
498
|
+
// `--password hunter2`, `--api-key "a b"`, or the same pair as two argv
|
|
499
|
+
// elements. `--token=x` is an assignment above; this is the spelling a
|
|
500
|
+
// command line separates with a space. The next word is the value even
|
|
501
|
+
// when it starts with `-`: a credential flag always takes one.
|
|
502
|
+
pattern: credentialFlag,
|
|
503
|
+
rewrite: (text) => redactArguments(text, credentialFlag, (match, value) => namesCredential(match[1].replace(/^-+/, ""), value))
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
id: "cookie-session-assignment",
|
|
507
|
+
pattern: /(?<![A-Za-z0-9-])((?:COOKIE|SESSION)S?)(\s*=\s*)(?!\[REDACTED\])("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|(?!["'])[^\s;,]+)/gi,
|
|
508
|
+
replace: "$1$2[REDACTED]"
|
|
509
|
+
}
|
|
510
|
+
];
|
|
511
|
+
const diagnosticRules = [
|
|
512
|
+
...defaultRules.map(
|
|
513
|
+
(rule) => rule.id === "assignment" ? {
|
|
514
|
+
id: "assignment",
|
|
515
|
+
pattern: diagnosticNames,
|
|
516
|
+
rewrite: (text) => redactValues(text, diagnosticNames, { header: false, diagnostic: true })
|
|
517
|
+
} : rule
|
|
518
|
+
),
|
|
519
|
+
{
|
|
520
|
+
id: "authorization-header",
|
|
521
|
+
// The header value is a credential whatever its scheme (`Token`, a cookie
|
|
522
|
+
// list, `Digest` with quoted parameters), so the value runs to the end of
|
|
523
|
+
// its line or the close of the string it was written in.
|
|
524
|
+
// A lookbehind-free superset lets the compiled prefilter skip ordinary prose.
|
|
525
|
+
pattern: /(?:proxy-)?authorization|(?:set-)?cookies?/gi,
|
|
526
|
+
rewrite: (text) => redactValues(text, diagnosticHeaderNames, { header: true, diagnostic: true })
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
id: "bearer-or-basic",
|
|
530
|
+
// A token shorter than the default rules' minimum. A lowercase `basic` is
|
|
531
|
+
// prose ("a basic example"), so only the scheme's own spellings count.
|
|
532
|
+
pattern: /(\b(?:[Bb]earer|BEARER|Basic|BASIC)\s+)(?!\[REDACTED)[^\s,;"'\\]+/g,
|
|
533
|
+
replace: "$1[REDACTED]"
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
id: "password-flag",
|
|
537
|
+
// The whole next argument, a path or a leading `-` included.
|
|
538
|
+
pattern: passwordFlag,
|
|
539
|
+
rewrite: (text) => redactArguments(text, passwordFlag)
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
id: "sshpass-password",
|
|
543
|
+
// `sshpass -p` always takes a value, even one that starts with `--`,
|
|
544
|
+
// which `mysql -p --database x` reads as the next option.
|
|
545
|
+
pattern: /(\bsshpass(?:\s+-[^\sp]\S*){0,8}\s+-p\s+)--(?!\[REDACTED\])[^\s"'`]*/g,
|
|
546
|
+
replace: "$1[REDACTED]"
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
id: "user-flag",
|
|
550
|
+
// `curl -u user:pass`, `--user=user:pass`, quoted or not: the whole
|
|
551
|
+
// argument, commas included, when it carries a `:`.
|
|
552
|
+
pattern: userFlag,
|
|
553
|
+
rewrite: (text) => redactArguments(text, userFlag, (_match, value) => value.includes(":"))
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
id: "header-pair",
|
|
557
|
+
pattern: headerPair,
|
|
558
|
+
rewrite: redactHeaderPairs
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
id: "command-password-flag",
|
|
562
|
+
// The whole next argument, whatever it starts with.
|
|
563
|
+
pattern: commandPasswordFlag,
|
|
564
|
+
rewrite: (text) => redactArguments(text, commandPasswordFlag)
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
id: "url-userinfo",
|
|
568
|
+
pattern: /(\/\/)(?!\[REDACTED\]@)(?![^/@\s"'\\]*:\[REDACTED\]@)[^/@\s"'\\]+@/g,
|
|
569
|
+
replace: "$1[REDACTED]@"
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
id: "signed-query",
|
|
573
|
+
pattern: /([?&][\w.-]*(?:key|sig|signature|auth|credential)=)(?!\[REDACTED\])[^&#\s"'\\]+/gi,
|
|
574
|
+
replace: "$1[REDACTED]"
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
id: "short-password-flag",
|
|
578
|
+
// A path is kept, and so is a flag: `mysql -p --database x` prompts for
|
|
579
|
+
// the password rather than taking one.
|
|
580
|
+
pattern: shortPasswordFlag,
|
|
581
|
+
rewrite: (text) => redactArguments(text, shortPasswordFlag)
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
id: "attached-password-flag",
|
|
585
|
+
// `-psecret`. The single-dash words that start with `p` and are not a
|
|
586
|
+
// password, `find -print` and the compilers' `-pthread` among them, are kept.
|
|
587
|
+
pattern: attachedPasswordFlag,
|
|
588
|
+
rewrite: (text) => redactArguments(text, attachedPasswordFlag)
|
|
589
|
+
}
|
|
590
|
+
];
|
|
591
|
+
const redactDiagnostic = (value) => redact(value, { rules: diagnosticRules, onTooDeep: "name" });
|
|
592
|
+
const sensitiveKeySuffixes = [
|
|
593
|
+
"auth",
|
|
594
|
+
"authorization",
|
|
595
|
+
"cookie",
|
|
596
|
+
"apikey",
|
|
597
|
+
"token",
|
|
598
|
+
"password",
|
|
599
|
+
"passphrase",
|
|
600
|
+
"secret",
|
|
601
|
+
"secretaccesskey",
|
|
602
|
+
"credential",
|
|
603
|
+
"dsn",
|
|
604
|
+
"connection",
|
|
605
|
+
"connectionstring",
|
|
606
|
+
"connectionuri",
|
|
607
|
+
"connectionurl",
|
|
608
|
+
"secretkey",
|
|
609
|
+
"privatekey",
|
|
610
|
+
"sshkey",
|
|
611
|
+
"signingkey",
|
|
612
|
+
"signature",
|
|
613
|
+
"encryptionkey",
|
|
614
|
+
"session",
|
|
615
|
+
"sessionkey"
|
|
616
|
+
];
|
|
617
|
+
const trailingKeyWord = /(?:^|[^A-Za-z0-9])key$/i;
|
|
618
|
+
const canonicalKey = (key) => key.toLowerCase().replace(/[^a-z0-9]/g, "").replace(/s$/, "");
|
|
619
|
+
const tokenCounterKeys = /* @__PURE__ */ new Set([
|
|
620
|
+
"inputtoken",
|
|
621
|
+
"outputtoken",
|
|
622
|
+
"cachedinputtoken",
|
|
623
|
+
"reasoningtoken",
|
|
624
|
+
"totaltoken"
|
|
625
|
+
]);
|
|
626
|
+
const isTokenCount = (key, value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && (tokenCounterKeys.has(canonicalKey(key)) || key.toLowerCase() === "tokens");
|
|
627
|
+
const isSensitiveKey = (key) => {
|
|
628
|
+
if (trailingKeyWord.test(key)) return true;
|
|
629
|
+
const canonical = canonicalKey(key);
|
|
630
|
+
return sensitiveKeySuffixes.some((suffix) => canonical.endsWith(suffix));
|
|
631
|
+
};
|
|
632
|
+
const redactMember = (key, value, walk) => isSensitiveKey(key) && !isTokenCount(key, value) ? placeholder : walk(value);
|
|
633
|
+
const redactKey = (key, rules) => {
|
|
634
|
+
const redacted = redactString(key, rules);
|
|
635
|
+
return redacted === key ? key : placeholder;
|
|
636
|
+
};
|
|
637
|
+
const fixedPointPasses = 8;
|
|
638
|
+
const candidate = (pattern) => {
|
|
639
|
+
const cached = candidates.get(pattern);
|
|
640
|
+
if (cached !== void 0) return cached;
|
|
641
|
+
const leading = /^\(\?<!\[(\^?)((?:\\.|[^\]\\])*)\]\)/.exec(pattern.source);
|
|
642
|
+
const rest = leading === null ? pattern.source : pattern.source.slice(leading[0].length);
|
|
643
|
+
const compiled = /\(\?<[!=]|\\(?:[1-9]|k<)/.test(rest) ? null : new RegExp(
|
|
644
|
+
leading === null ? rest : `(?:^|[${leading[1] === "" ? "^" : ""}${leading[2]}])${rest}`,
|
|
645
|
+
pattern.flags.replace(/[gy]/g, "")
|
|
646
|
+
);
|
|
647
|
+
candidates.set(pattern, compiled);
|
|
648
|
+
return compiled;
|
|
649
|
+
};
|
|
650
|
+
const candidates = /* @__PURE__ */ new WeakMap();
|
|
651
|
+
const applyRule = (text, rule) => {
|
|
652
|
+
if (candidate(rule.pattern)?.test(text) === false) return text;
|
|
653
|
+
return rule.rewrite === void 0 ? text.replace(rule.pattern, rule.replace ?? placeholder) : rule.rewrite(text);
|
|
654
|
+
};
|
|
655
|
+
const redactString = (value, rules) => {
|
|
656
|
+
let text = value;
|
|
657
|
+
for (let pass = 0; pass < fixedPointPasses; pass++) {
|
|
658
|
+
const next = rules.reduce(applyRule, text);
|
|
659
|
+
if (next === text) return text;
|
|
660
|
+
text = next;
|
|
661
|
+
}
|
|
662
|
+
return placeholder;
|
|
663
|
+
};
|
|
664
|
+
const maxDepth = 256;
|
|
665
|
+
class RedactionDepthError extends Schema.TaggedError()(
|
|
666
|
+
"@smthrs/journal/RedactionDepthError",
|
|
667
|
+
{ code: Schema.Literal("depth_exceeded"), maxDepth: Schema.Number, message: Schema.String }
|
|
668
|
+
) {
|
|
669
|
+
name = "RedactionDepthError";
|
|
670
|
+
}
|
|
671
|
+
const binaryWalkLimit = 65536;
|
|
672
|
+
const byteLengthGetters = [
|
|
673
|
+
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Uint8Array.prototype), "byteLength").get,
|
|
674
|
+
Object.getOwnPropertyDescriptor(DataView.prototype, "byteLength").get,
|
|
675
|
+
Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "byteLength").get
|
|
676
|
+
];
|
|
677
|
+
const byteLength = (node) => {
|
|
678
|
+
for (const getter of byteLengthGetters) {
|
|
679
|
+
try {
|
|
680
|
+
return Reflect.apply(getter, node, []);
|
|
681
|
+
} catch {
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return void 0;
|
|
685
|
+
};
|
|
686
|
+
const binaryPrototypes = /* @__PURE__ */ new Set([
|
|
687
|
+
Object.getPrototypeOf(Uint8Array.prototype),
|
|
688
|
+
DataView.prototype,
|
|
689
|
+
ArrayBuffer.prototype
|
|
690
|
+
]);
|
|
691
|
+
const prototypeWalkLimit = 64;
|
|
692
|
+
const isBinary = (node) => {
|
|
693
|
+
if (ArrayBuffer.isView(node) || node instanceof ArrayBuffer) return true;
|
|
694
|
+
try {
|
|
695
|
+
let prototype = Object.getPrototypeOf(node);
|
|
696
|
+
for (let step = 0; prototype !== null && step < prototypeWalkLimit; step++) {
|
|
697
|
+
if (binaryPrototypes.has(prototype)) return true;
|
|
698
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
699
|
+
}
|
|
700
|
+
} catch {
|
|
701
|
+
}
|
|
702
|
+
return false;
|
|
703
|
+
};
|
|
704
|
+
const indexKey = /^(?:0|[1-9][0-9]*)$/;
|
|
705
|
+
const describeBinary = (node, size) => {
|
|
706
|
+
if (size === void 0) return binaryMarker;
|
|
707
|
+
try {
|
|
708
|
+
return `${node.constructor.name} ${size} bytes`;
|
|
709
|
+
} catch {
|
|
710
|
+
return binaryMarker;
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
const binaryMarker = "[Binary]";
|
|
714
|
+
const functionMarker = "[Function]";
|
|
715
|
+
const symbolMarker = "[Symbol]";
|
|
716
|
+
const depthMarker = "[Deep]";
|
|
717
|
+
const redact = (value, options) => {
|
|
718
|
+
const onTooDeep = options?.onTooDeep ?? "throw";
|
|
719
|
+
const rules = globalRules(options?.rules ?? defaultRules);
|
|
720
|
+
const binary = (node, ancestors, depth) => {
|
|
721
|
+
const size = byteLength(node);
|
|
722
|
+
const entries = [[binaryMarker, redactString(describeBinary(node, size), rules)]];
|
|
723
|
+
if (size !== void 0 && size <= binaryWalkLimit) {
|
|
724
|
+
let walked = 0;
|
|
725
|
+
for (const [key, field] of Object.entries(node)) {
|
|
726
|
+
if (indexKey.test(key)) continue;
|
|
727
|
+
if (walked >= binaryWalkLimit) break;
|
|
728
|
+
walked++;
|
|
729
|
+
entries.push([
|
|
730
|
+
redactKey(key, rules),
|
|
731
|
+
redactMember(key, field, (value2) => walk(value2, ancestors, depth + 1, key))
|
|
732
|
+
]);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return Object.fromEntries(entries);
|
|
736
|
+
};
|
|
737
|
+
const walk = (node, ancestors, depth, key) => {
|
|
738
|
+
if (depth > maxDepth) {
|
|
739
|
+
if (onTooDeep === "name") return depthMarker;
|
|
740
|
+
throw new RedactionDepthError({
|
|
741
|
+
code: "depth_exceeded",
|
|
742
|
+
maxDepth,
|
|
743
|
+
message: `redaction depth exceeds ${maxDepth}`
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
if (typeof node === "string") return redactString(node, rules);
|
|
747
|
+
if (typeof node === "function") return functionMarker;
|
|
748
|
+
if (typeof node === "symbol") return symbolMarker;
|
|
749
|
+
if (node === null || typeof node !== "object") return node;
|
|
750
|
+
if (isBinary(node)) return binary(node, ancestors, depth);
|
|
751
|
+
if (ancestors.has(node)) return "[Circular]";
|
|
752
|
+
ancestors.add(node);
|
|
753
|
+
try {
|
|
754
|
+
const toJSON = node.toJSON;
|
|
755
|
+
if (typeof toJSON === "function") return walk(toJSON.call(node, key), ancestors, depth, key);
|
|
756
|
+
if (Array.isArray(node)) {
|
|
757
|
+
const length = node.length;
|
|
758
|
+
const result = new Array(length);
|
|
759
|
+
const elements = new Array(length);
|
|
760
|
+
for (let index = 0; index < length; index++) {
|
|
761
|
+
if (!(index in node)) continue;
|
|
762
|
+
elements[index] = node[index];
|
|
763
|
+
result[index] = walk(elements[index], ancestors, depth + 1, String(index));
|
|
764
|
+
}
|
|
765
|
+
for (let index = 0; index + 1 < length; index++) {
|
|
766
|
+
const flag = elements[index];
|
|
767
|
+
const value2 = elements[index + 1];
|
|
768
|
+
if (typeof flag !== "string" || typeof value2 !== "string" || !/^-[^=\s]+$/.test(flag)) continue;
|
|
769
|
+
const name = flag.replace(/^-+/, "");
|
|
770
|
+
const before = elements.slice(Math.max(0, index - 3), index + 1).filter(
|
|
771
|
+
(element) => typeof element === "string"
|
|
772
|
+
).join(" ");
|
|
773
|
+
const consumes = isSensitiveKey(name) && namesCredential(name, value2) || redactString(`${before} ${value2}`, rules) !== `${redactString(before, rules)} ${redactString(value2, rules)}`;
|
|
774
|
+
if (consumes) result[index + 1] = placeholder;
|
|
775
|
+
}
|
|
776
|
+
return result;
|
|
777
|
+
}
|
|
778
|
+
return Object.fromEntries(
|
|
779
|
+
Object.entries(node).map(([key2, field]) => [
|
|
780
|
+
// The KEY is text too. Rewriting only values let a credential used as
|
|
781
|
+
// a log annotation key reach the operator, since Effect renders an
|
|
782
|
+
// annotation as `key=value`, and become an OTLP span attribute name.
|
|
783
|
+
redactKey(key2, rules),
|
|
784
|
+
redactMember(key2, field, (value2) => walk(value2, ancestors, depth + 1, key2))
|
|
785
|
+
])
|
|
786
|
+
);
|
|
787
|
+
} finally {
|
|
788
|
+
ancestors.delete(node);
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
return walk(value, /* @__PURE__ */ new WeakSet(), 0, "");
|
|
792
|
+
};
|
|
793
|
+
const make = (options) => (value) => redact(value, options);
|
|
794
|
+
const redactJsonString = (json, redactor) => {
|
|
795
|
+
const decoded = Schema.decodeUnknownResult(UnknownFromJsonString)(json);
|
|
796
|
+
if (Result.isFailure(decoded)) return json;
|
|
797
|
+
const attempted = Result.try(() => Schema.encodeUnknownResult(UnknownFromJsonString)(redactor(decoded.success)));
|
|
798
|
+
if (Result.isFailure(attempted)) return JSON.stringify(placeholder);
|
|
799
|
+
return Result.isSuccess(attempted.success) ? attempted.success.success : JSON.stringify(placeholder);
|
|
800
|
+
};
|
|
801
|
+
const maxHeldLines = 256;
|
|
802
|
+
const maxHeldBytes = 64 * 1024;
|
|
803
|
+
const eagerHeldLines = 16;
|
|
804
|
+
const maxDroppedDepth = 1024;
|
|
805
|
+
const maxPartialLine = 32 * 1024;
|
|
806
|
+
const omittedLine = "[overlong line omitted]";
|
|
807
|
+
const continuationProbe = "\n'probe'";
|
|
808
|
+
const globalRules = (rules) => rules.map(
|
|
809
|
+
(rule) => rule.pattern.flags.includes("g") ? rule : { ...rule, pattern: new RegExp(rule.pattern.source, `${rule.pattern.flags}g`) }
|
|
810
|
+
);
|
|
811
|
+
const lineRedactor = (rules = diagnosticRules) => {
|
|
812
|
+
const held = [];
|
|
813
|
+
let heldBytes = 0;
|
|
814
|
+
let askAt = 0;
|
|
815
|
+
let heldOverlong = false;
|
|
816
|
+
let withheld = false;
|
|
817
|
+
const edge = maxPartialLine / 2;
|
|
818
|
+
let head = "";
|
|
819
|
+
let tail = "";
|
|
820
|
+
let overlong = false;
|
|
821
|
+
let middle = [];
|
|
822
|
+
let middleDeep = false;
|
|
823
|
+
let middleKey = false;
|
|
824
|
+
let carry = "";
|
|
825
|
+
const resetPartial = () => {
|
|
826
|
+
head = "";
|
|
827
|
+
tail = "";
|
|
828
|
+
overlong = false;
|
|
829
|
+
middle = [];
|
|
830
|
+
middleDeep = false;
|
|
831
|
+
middleKey = false;
|
|
832
|
+
carry = "";
|
|
833
|
+
};
|
|
834
|
+
const scanDropped = (dropped) => {
|
|
835
|
+
for (let from = 0; from < dropped.length && !middleDeep; from += edge) {
|
|
836
|
+
track(middle, dropped, from, Math.min(dropped.length, from + edge));
|
|
837
|
+
if (middle.length > maxDroppedDepth) {
|
|
838
|
+
middleDeep = true;
|
|
839
|
+
middle = [];
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
const seen = carry + dropped;
|
|
843
|
+
const begin = seen.search(/-----BEGIN[^-]*PRIVATE KEY/);
|
|
844
|
+
const lastBegin = seen.lastIndexOf("-----BEGIN");
|
|
845
|
+
const lastEnd = seen.lastIndexOf("-----END");
|
|
846
|
+
if (begin !== -1 || lastEnd !== -1) middleKey = lastBegin > lastEnd;
|
|
847
|
+
carry = seen.slice(-64);
|
|
848
|
+
};
|
|
849
|
+
const redacted = (text) => redactString(text, globalRules(rules));
|
|
850
|
+
const release = () => {
|
|
851
|
+
const text = redacted(held.join("\n"));
|
|
852
|
+
const overlongBlock = heldOverlong;
|
|
853
|
+
held.length = 0;
|
|
854
|
+
heldBytes = 0;
|
|
855
|
+
askAt = 0;
|
|
856
|
+
heldOverlong = false;
|
|
857
|
+
return overlongBlock ? [omittedLine] : text.split("\n");
|
|
858
|
+
};
|
|
859
|
+
const part = (text) => {
|
|
860
|
+
if (withheld) return;
|
|
861
|
+
if (!overlong) {
|
|
862
|
+
head += text;
|
|
863
|
+
if (head.length <= maxPartialLine) return;
|
|
864
|
+
overlong = true;
|
|
865
|
+
tail = head.slice(edge);
|
|
866
|
+
head = head.slice(0, edge);
|
|
867
|
+
} else tail += text;
|
|
868
|
+
if (tail.length > edge) {
|
|
869
|
+
scanDropped(tail.slice(0, tail.length - edge));
|
|
870
|
+
tail = tail.slice(-edge);
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
const line = (rest) => {
|
|
874
|
+
if (withheld) {
|
|
875
|
+
resetPartial();
|
|
876
|
+
return [];
|
|
877
|
+
}
|
|
878
|
+
part(rest);
|
|
879
|
+
let text = head;
|
|
880
|
+
if (overlong) {
|
|
881
|
+
const risky = middle.length > 0 || middleDeep || middleKey;
|
|
882
|
+
text = `${head} ${tail}`;
|
|
883
|
+
resetPartial();
|
|
884
|
+
if (risky) {
|
|
885
|
+
const before = held.length === 0 ? [] : [placeholder];
|
|
886
|
+
held.length = 0;
|
|
887
|
+
heldBytes = 0;
|
|
888
|
+
askAt = 0;
|
|
889
|
+
heldOverlong = false;
|
|
890
|
+
withheld = true;
|
|
891
|
+
return [...before, omittedLine];
|
|
892
|
+
}
|
|
893
|
+
heldOverlong = true;
|
|
894
|
+
} else resetPartial();
|
|
895
|
+
held.push(text);
|
|
896
|
+
heldBytes += text.length + 1;
|
|
897
|
+
if (held.length <= eagerHeldLines || heldBytes >= askAt) {
|
|
898
|
+
if (redacted(held.join("\n") + continuationProbe).endsWith(continuationProbe)) return release();
|
|
899
|
+
askAt = heldBytes + (heldBytes >> 1);
|
|
900
|
+
}
|
|
901
|
+
if (held.length <= maxHeldLines && heldBytes <= maxHeldBytes) return [];
|
|
902
|
+
withheld = true;
|
|
903
|
+
return release();
|
|
904
|
+
};
|
|
905
|
+
const peek = () => {
|
|
906
|
+
if (withheld) return [placeholder];
|
|
907
|
+
const pending = overlong ? [omittedLine] : head === "" ? [] : [head];
|
|
908
|
+
if (heldOverlong) return [omittedLine];
|
|
909
|
+
const text = [...held, ...overlong ? [] : pending];
|
|
910
|
+
const out = text.length === 0 ? [] : redacted(text.join("\n")).split("\n");
|
|
911
|
+
return overlong ? [...out, omittedLine] : out;
|
|
912
|
+
};
|
|
913
|
+
return {
|
|
914
|
+
line,
|
|
915
|
+
part,
|
|
916
|
+
peek,
|
|
917
|
+
flush: () => {
|
|
918
|
+
const out = head !== "" || overlong ? [...line("")] : [];
|
|
919
|
+
if (withheld) {
|
|
920
|
+
withheld = false;
|
|
921
|
+
return [...out, placeholder];
|
|
922
|
+
}
|
|
923
|
+
return held.length === 0 ? out : [...out, ...release()];
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
};
|
|
927
|
+
const makeNoop = () => (value) => value;
|
|
928
|
+
//# sourceMappingURL=Redaction.js.map
|