@blamejs/core 0.5.16 → 0.5.18
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 +2 -0
- package/lib/backup/index.js +7 -2
- package/lib/cache.js +5 -9
- package/lib/cli.js +4 -3
- package/lib/cluster.js +21 -24
- package/lib/csv.js +208 -162
- package/lib/db.js +13 -8
- package/lib/deprecate.js +3 -2
- package/lib/error-page.js +2 -1
- package/lib/external-db.js +4 -3
- package/lib/forms.js +3 -6
- package/lib/http-client-cookie-jar.js +2 -1
- package/lib/http-client.js +2 -2
- package/lib/log.js +2 -1
- package/lib/mail.js +2 -2
- package/lib/middleware/api-encrypt.js +3 -2
- package/lib/middleware/body-parser.js +2 -1
- package/lib/middleware/compression.js +3 -2
- package/lib/middleware/cors.js +1 -1
- package/lib/middleware/rate-limit.js +4 -4
- package/lib/mtls-engine-default.js +3 -2
- package/lib/nonce-store.js +4 -4
- package/lib/notify.js +1 -1
- package/lib/otel-export.js +5 -10
- package/lib/pagination.js +2 -1
- package/lib/parsers/index.js +3 -7
- package/lib/parsers/safe-env.js +1 -2
- package/lib/queue-local.js +2 -1
- package/lib/queue.js +8 -9
- package/lib/request-helpers.js +35 -3
- package/lib/restore.js +3 -3
- package/lib/router.js +5 -3
- package/lib/safe-async.js +122 -0
- package/lib/testing.js +1 -1
- package/lib/totp.js +2 -2
- package/lib/tracing.js +3 -3
- package/lib/webhook.js +5 -5
- package/lib/websocket.js +6 -4
- package/package.json +1 -1
- package/lib/parsers/safe-csv.js +0 -224
package/lib/csv.js
CHANGED
|
@@ -1,229 +1,275 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* csv — RFC 4180 parser + serializer.
|
|
3
|
+
* csv — RFC 4180 parser + serializer with operator-friendly defaults.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* var rows = b.csv.parse(text);
|
|
6
|
+
* // header row by default — returns array of objects keyed by header
|
|
7
|
+
* // [ { name: "alice", age: "30" }, ... ]
|
|
6
8
|
*
|
|
7
|
-
* b.csv.parse(text,
|
|
8
|
-
*
|
|
9
|
+
* var arrays = b.csv.parse(text, { header: false });
|
|
10
|
+
* // returns array of arrays
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* header: true default — first row becomes column names; rows
|
|
13
|
-
* are objects keyed by header
|
|
14
|
-
* maxBytes: 8 MiB default — refuses parse-bombs the same way
|
|
15
|
-
* safeJson does
|
|
16
|
-
* onBadRow: "throw" default | "skip" — what to do when a row has
|
|
17
|
-
* a different column count than the header
|
|
12
|
+
* var out = b.csv.stringify(rows);
|
|
13
|
+
* // RFC 4180 + Excel-formula-injection prevention by default
|
|
18
14
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
15
|
+
* Defaults:
|
|
16
|
+
* parse:
|
|
17
|
+
* header: true first row is column names
|
|
18
|
+
* delimiter: "," single byte; "\t" / ";" supported
|
|
19
|
+
* quote: '"'
|
|
20
|
+
* trim: false cell whitespace preserved
|
|
21
|
+
* maxBytes: 16 MiB
|
|
22
|
+
* maxRows: 1,000,000
|
|
23
|
+
* maxFieldBytes: 1 MiB
|
|
24
|
+
* onBadRow: "throw" "skip" tolerates short/long rows
|
|
25
25
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* stringify:
|
|
27
|
+
* header: true (or array of explicit columns)
|
|
28
|
+
* delimiter: ","
|
|
29
|
+
* quote: '"'
|
|
30
|
+
* eol: "\r\n" RFC 4180; "\n" via opt
|
|
31
|
+
* alwaysQuote: false only quote when needed
|
|
32
|
+
* preventFormulaInjection: true prefix '=' / '+' / '-' / '@' / TAB / CR
|
|
33
|
+
* cells with "'" so Excel doesn't
|
|
34
|
+
* execute them when the CSV is opened
|
|
35
|
+
* formulaPrefixChars: ["=","+","-","@","\t","\r"]
|
|
30
36
|
*
|
|
31
|
-
*
|
|
37
|
+
* SECURITY: stringify defends against CSV-injection (Excel/Sheets execute
|
|
38
|
+
* cells starting with "=" / "+" / "-" / "@" / TAB / CR as formulas).
|
|
39
|
+
* Operator-controlled cells stay safe; user-supplied cells get the leading
|
|
40
|
+
* single-quote so the formula engine sees a literal string.
|
|
41
|
+
*
|
|
42
|
+
* Throws CsvError (FrameworkError, permanent) on shape violations.
|
|
32
43
|
*/
|
|
33
44
|
var C = require("./constants");
|
|
34
45
|
var { defineClass } = require("./framework-error");
|
|
35
46
|
|
|
36
47
|
var CsvError = defineClass("CsvError", { alwaysPermanent: true });
|
|
37
48
|
|
|
38
|
-
var
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
var DEFAULTS_PARSE = {
|
|
50
|
+
header: true,
|
|
51
|
+
delimiter: ",",
|
|
52
|
+
quote: "\"",
|
|
53
|
+
trim: false,
|
|
54
|
+
maxBytes: C.BYTES.mib(16),
|
|
55
|
+
maxRows: 1000000,
|
|
56
|
+
maxFieldBytes: C.BYTES.mib(1),
|
|
57
|
+
onBadRow: "throw",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
var DEFAULTS_STRINGIFY = {
|
|
61
|
+
header: true,
|
|
62
|
+
delimiter: ",",
|
|
63
|
+
quote: "\"",
|
|
64
|
+
eol: "\r\n",
|
|
65
|
+
alwaysQuote: false,
|
|
66
|
+
preventFormulaInjection: true,
|
|
67
|
+
formulaPrefixChars: ["=", "+", "-", "@", "\t", "\r"],
|
|
68
|
+
columns: null,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function _validateDelim(name, value) {
|
|
72
|
+
if (typeof value !== "string" || value.length !== 1) {
|
|
73
|
+
throw new CsvError("csv/bad-delimiter",
|
|
74
|
+
name + " must be a single character, got " + JSON.stringify(value));
|
|
75
|
+
}
|
|
76
|
+
if (value === "\r" || value === "\n") {
|
|
77
|
+
throw new CsvError("csv/bad-delimiter",
|
|
78
|
+
name + " cannot be CR or LF");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
41
81
|
|
|
42
82
|
// ---- parse ----
|
|
43
83
|
|
|
44
|
-
function parse(
|
|
45
|
-
opts = opts || {};
|
|
46
|
-
|
|
84
|
+
function parse(input, opts) {
|
|
85
|
+
opts = Object.assign({}, DEFAULTS_PARSE, opts || {});
|
|
86
|
+
|
|
87
|
+
var s;
|
|
88
|
+
if (typeof input === "string") s = input;
|
|
89
|
+
else if (Buffer.isBuffer(input)) s = input.toString("utf8");
|
|
90
|
+
else if (input instanceof Uint8Array) s = Buffer.from(input).toString("utf8");
|
|
91
|
+
else {
|
|
47
92
|
throw new CsvError("csv/bad-input",
|
|
48
|
-
"parse: input must be
|
|
93
|
+
"parse: input must be string, Buffer, or Uint8Array, got " + typeof input);
|
|
49
94
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (Buffer.byteLength(s, "utf8") > maxBytes) {
|
|
95
|
+
if (s.charCodeAt(0) === 0xFEFF) s = s.slice(1);
|
|
96
|
+
if (Buffer.byteLength(s, "utf8") > opts.maxBytes) {
|
|
53
97
|
throw new CsvError("csv/too-large",
|
|
54
|
-
"parse: input exceeds maxBytes (" + maxBytes + ")");
|
|
55
|
-
}
|
|
56
|
-
var delimiter = opts.delimiter || DEFAULT_DELIMITER;
|
|
57
|
-
if (typeof delimiter !== "string" || delimiter.length !== 1) {
|
|
58
|
-
throw new CsvError("csv/bad-delimiter",
|
|
59
|
-
"parse: delimiter must be a single character, got " + JSON.stringify(delimiter));
|
|
98
|
+
"parse: input exceeds maxBytes (" + opts.maxBytes + ")");
|
|
60
99
|
}
|
|
61
|
-
|
|
100
|
+
|
|
101
|
+
_validateDelim("delimiter", opts.delimiter);
|
|
102
|
+
_validateDelim("quote", opts.quote);
|
|
103
|
+
if (opts.delimiter === opts.quote) {
|
|
62
104
|
throw new CsvError("csv/bad-delimiter",
|
|
63
|
-
"
|
|
105
|
+
"delimiter and quote must differ");
|
|
64
106
|
}
|
|
65
|
-
|
|
66
|
-
var onBadRow = opts.onBadRow || "throw";
|
|
67
|
-
if (onBadRow !== "throw" && onBadRow !== "skip") {
|
|
107
|
+
if (opts.onBadRow !== "throw" && opts.onBadRow !== "skip") {
|
|
68
108
|
throw new CsvError("csv/bad-opt",
|
|
69
|
-
"
|
|
109
|
+
"onBadRow must be 'throw' or 'skip'");
|
|
70
110
|
}
|
|
71
111
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
112
|
+
var len = s.length;
|
|
113
|
+
var pos = 0;
|
|
75
114
|
var rows = [];
|
|
76
|
-
var field = "";
|
|
77
115
|
var row = [];
|
|
78
|
-
var
|
|
79
|
-
var
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
116
|
+
var field = "";
|
|
117
|
+
var inQuote = false;
|
|
118
|
+
|
|
119
|
+
function pushField() {
|
|
120
|
+
if (Buffer.byteLength(field, "utf8") > opts.maxFieldBytes) {
|
|
121
|
+
throw new CsvError("csv/field-too-large",
|
|
122
|
+
"field exceeds maxFieldBytes at row " + (rows.length + 1));
|
|
123
|
+
}
|
|
124
|
+
row.push(opts.trim ? field.trim() : field);
|
|
125
|
+
field = "";
|
|
126
|
+
}
|
|
127
|
+
function pushRow() {
|
|
128
|
+
pushField();
|
|
129
|
+
rows.push(row);
|
|
130
|
+
if (rows.length > opts.maxRows) {
|
|
131
|
+
throw new CsvError("csv/too-many-rows",
|
|
132
|
+
"row count exceeds maxRows (" + opts.maxRows + ")");
|
|
133
|
+
}
|
|
134
|
+
row = [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
while (pos < len) {
|
|
138
|
+
var ch = s.charAt(pos);
|
|
139
|
+
if (inQuote) {
|
|
140
|
+
if (ch === opts.quote) {
|
|
141
|
+
if (pos + 1 < len && s.charAt(pos + 1) === opts.quote) {
|
|
142
|
+
field += opts.quote;
|
|
143
|
+
pos += 2;
|
|
88
144
|
continue;
|
|
89
145
|
}
|
|
90
|
-
|
|
91
|
-
|
|
146
|
+
inQuote = false;
|
|
147
|
+
pos += 1;
|
|
92
148
|
continue;
|
|
93
149
|
}
|
|
94
|
-
field +=
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
150
|
+
field += ch;
|
|
151
|
+
pos += 1;
|
|
152
|
+
} else {
|
|
153
|
+
if (ch === opts.delimiter) {
|
|
154
|
+
pushField();
|
|
155
|
+
pos += 1;
|
|
156
|
+
} else if (ch === "\r") {
|
|
157
|
+
pushRow();
|
|
158
|
+
pos += 1;
|
|
159
|
+
if (pos < len && s.charAt(pos) === "\n") pos += 1;
|
|
160
|
+
} else if (ch === "\n") {
|
|
161
|
+
pushRow();
|
|
162
|
+
pos += 1;
|
|
163
|
+
} else if (ch === opts.quote && field === "") {
|
|
164
|
+
inQuote = true;
|
|
165
|
+
pos += 1;
|
|
166
|
+
} else {
|
|
167
|
+
field += ch;
|
|
168
|
+
pos += 1;
|
|
107
169
|
}
|
|
108
|
-
field += "\"";
|
|
109
|
-
i++;
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
if (c === delimiter) {
|
|
113
|
-
row.push(field);
|
|
114
|
-
field = "";
|
|
115
|
-
i++;
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
if (c === "\r") {
|
|
119
|
-
// CRLF or bare CR — treat both as row terminator.
|
|
120
|
-
row.push(field);
|
|
121
|
-
rows.push(row);
|
|
122
|
-
field = "";
|
|
123
|
-
row = [];
|
|
124
|
-
if (i + 1 < len && s.charAt(i + 1) === "\n") i += 2;
|
|
125
|
-
else i++;
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
if (c === "\n") {
|
|
129
|
-
row.push(field);
|
|
130
|
-
rows.push(row);
|
|
131
|
-
field = "";
|
|
132
|
-
row = [];
|
|
133
|
-
i++;
|
|
134
|
-
continue;
|
|
135
170
|
}
|
|
136
|
-
field += c;
|
|
137
|
-
i++;
|
|
138
171
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
rows.push(row);
|
|
172
|
+
if (inQuote) {
|
|
173
|
+
throw new CsvError("csv/unterminated-quote",
|
|
174
|
+
"unterminated quoted field");
|
|
143
175
|
}
|
|
176
|
+
if (field.length > 0 || row.length > 0) pushRow();
|
|
144
177
|
|
|
145
|
-
if (!
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return { headers: [], rows: [] };
|
|
150
|
-
}
|
|
151
|
-
var headers = rows[0];
|
|
178
|
+
if (!opts.header) return rows;
|
|
179
|
+
if (rows.length === 0) return [];
|
|
180
|
+
|
|
181
|
+
var header = rows[0];
|
|
152
182
|
var out = [];
|
|
153
183
|
for (var r = 1; r < rows.length; r++) {
|
|
154
|
-
var
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
if (onBadRow === "skip") continue;
|
|
184
|
+
var rec = rows[r];
|
|
185
|
+
if (rec.length !== header.length) {
|
|
186
|
+
if (opts.onBadRow === "skip") continue;
|
|
158
187
|
throw new CsvError("csv/row-length-mismatch",
|
|
159
|
-
"
|
|
188
|
+
"row " + r + " has " + rec.length + " fields, header has " + header.length);
|
|
160
189
|
}
|
|
161
190
|
var obj = {};
|
|
162
|
-
for (var
|
|
191
|
+
for (var i = 0; i < header.length; i++) obj[header[i]] = rec[i];
|
|
163
192
|
out.push(obj);
|
|
164
193
|
}
|
|
165
|
-
return
|
|
194
|
+
return out;
|
|
166
195
|
}
|
|
167
196
|
|
|
168
197
|
// ---- stringify ----
|
|
169
198
|
|
|
170
199
|
function stringify(rows, opts) {
|
|
171
|
-
opts = opts || {};
|
|
200
|
+
opts = Object.assign({}, DEFAULTS_STRINGIFY, opts || {});
|
|
172
201
|
if (!Array.isArray(rows)) {
|
|
173
202
|
throw new CsvError("csv/bad-input",
|
|
174
|
-
"stringify: rows must be an array");
|
|
203
|
+
"stringify: rows must be an array, got " + typeof rows);
|
|
204
|
+
}
|
|
205
|
+
_validateDelim("delimiter", opts.delimiter);
|
|
206
|
+
_validateDelim("quote", opts.quote);
|
|
207
|
+
if (opts.delimiter === opts.quote) {
|
|
208
|
+
throw new CsvError("csv/bad-delimiter",
|
|
209
|
+
"delimiter and quote must differ");
|
|
210
|
+
}
|
|
211
|
+
if (opts.eol !== "\r\n" && opts.eol !== "\n") {
|
|
212
|
+
throw new CsvError("csv/bad-opt",
|
|
213
|
+
"eol must be '\\r\\n' or '\\n'");
|
|
175
214
|
}
|
|
176
|
-
var delimiter = opts.delimiter || DEFAULT_DELIMITER;
|
|
177
|
-
var eol = opts.eol || DEFAULT_EOL;
|
|
178
|
-
var hasHeader = opts.header !== false;
|
|
179
215
|
if (rows.length === 0) return "";
|
|
180
216
|
|
|
181
|
-
var
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
} else if (
|
|
188
|
-
|
|
217
|
+
var first = rows[0];
|
|
218
|
+
var asObjects;
|
|
219
|
+
var header;
|
|
220
|
+
if (Array.isArray(first)) {
|
|
221
|
+
asObjects = false;
|
|
222
|
+
header = Array.isArray(opts.columns) ? opts.columns : null;
|
|
223
|
+
} else if (first !== null && typeof first === "object") {
|
|
224
|
+
asObjects = true;
|
|
225
|
+
header = Array.isArray(opts.columns) ? opts.columns : Object.keys(first);
|
|
189
226
|
} else {
|
|
190
227
|
throw new CsvError("csv/bad-input",
|
|
191
|
-
"stringify: rows
|
|
228
|
+
"stringify: rows must be arrays or plain objects");
|
|
192
229
|
}
|
|
193
230
|
|
|
194
|
-
var
|
|
195
|
-
|
|
231
|
+
var prefixSet = Object.create(null);
|
|
232
|
+
for (var pi = 0; pi < opts.formulaPrefixChars.length; pi++) {
|
|
233
|
+
prefixSet[opts.formulaPrefixChars[pi]] = true;
|
|
234
|
+
}
|
|
196
235
|
|
|
197
|
-
|
|
198
|
-
var
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
236
|
+
function escapeCell(value) {
|
|
237
|
+
var str = value == null ? "" : String(value);
|
|
238
|
+
if (opts.preventFormulaInjection && str.length > 0 && prefixSet[str.charAt(0)]) {
|
|
239
|
+
str = "'" + str;
|
|
240
|
+
}
|
|
241
|
+
var needsQuote = opts.alwaysQuote ||
|
|
242
|
+
str.indexOf(opts.delimiter) !== -1 ||
|
|
243
|
+
str.indexOf(opts.quote) !== -1 ||
|
|
244
|
+
str.indexOf("\n") !== -1 ||
|
|
245
|
+
str.indexOf("\r") !== -1;
|
|
246
|
+
if (needsQuote) {
|
|
247
|
+
str = opts.quote + str.split(opts.quote).join(opts.quote + opts.quote) + opts.quote;
|
|
209
248
|
}
|
|
210
|
-
|
|
249
|
+
return str;
|
|
211
250
|
}
|
|
212
|
-
return lines.join(eol) + eol;
|
|
213
|
-
}
|
|
214
251
|
|
|
215
|
-
|
|
216
|
-
if (
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
252
|
+
var out = [];
|
|
253
|
+
if (opts.header && header) {
|
|
254
|
+
out.push(header.map(escapeCell).join(opts.delimiter));
|
|
255
|
+
}
|
|
256
|
+
for (var i = 0; i < rows.length; i++) {
|
|
257
|
+
var rec = rows[i];
|
|
258
|
+
var cells;
|
|
259
|
+
if (asObjects) {
|
|
260
|
+
cells = header.map(function (k) { return escapeCell(rec[k]); });
|
|
261
|
+
} else {
|
|
262
|
+
cells = rec.map(escapeCell);
|
|
263
|
+
}
|
|
264
|
+
out.push(cells.join(opts.delimiter));
|
|
221
265
|
}
|
|
222
|
-
return
|
|
266
|
+
return out.join(opts.eol);
|
|
223
267
|
}
|
|
224
268
|
|
|
225
269
|
module.exports = {
|
|
226
|
-
parse:
|
|
227
|
-
stringify:
|
|
228
|
-
CsvError:
|
|
270
|
+
parse: parse,
|
|
271
|
+
stringify: stringify,
|
|
272
|
+
CsvError: CsvError,
|
|
273
|
+
DEFAULTS_PARSE: DEFAULTS_PARSE,
|
|
274
|
+
DEFAULTS_STRINGIFY: DEFAULTS_STRINGIFY,
|
|
229
275
|
};
|
package/lib/db.js
CHANGED
|
@@ -55,10 +55,17 @@ var cryptoField = require("./crypto-field");
|
|
|
55
55
|
var { Query } = require("./db-query");
|
|
56
56
|
var dbSchema = require("./db-schema");
|
|
57
57
|
var { boot } = require("./log");
|
|
58
|
+
var lazyRequire = require("./lazy-require");
|
|
59
|
+
var safeAsync = require("./safe-async");
|
|
58
60
|
var safeEnv = require("./parsers/safe-env");
|
|
59
61
|
var safeJson = require("./safe-json");
|
|
60
62
|
var vault = require("./vault");
|
|
61
63
|
|
|
64
|
+
// Lazy: cluster-storage's _localDb pulls db back in, so eager require
|
|
65
|
+
// would deadlock the load order. cluster-storage is only used on the
|
|
66
|
+
// purge-audit-chain external-db path, which always runs after init.
|
|
67
|
+
var clusterStorage = lazyRequire(function () { return require("./cluster-storage"); });
|
|
68
|
+
|
|
62
69
|
var AUDIT_TIP_SCHEMA = {
|
|
63
70
|
type: "object",
|
|
64
71
|
required: ["atMonotonicCounter"],
|
|
@@ -797,12 +804,11 @@ async function init(opts) {
|
|
|
797
804
|
|
|
798
805
|
// Start periodic encrypt timer (encrypted mode only)
|
|
799
806
|
if (atRest === "encrypted") {
|
|
800
|
-
encTimer =
|
|
807
|
+
encTimer = safeAsync.repeating(function () {
|
|
801
808
|
try { encryptToDisk(); } catch (e) {
|
|
802
809
|
log.error("periodic encrypt failed: " + e.message);
|
|
803
810
|
}
|
|
804
|
-
}, C.TIME.minutes(5));
|
|
805
|
-
encTimer.unref();
|
|
811
|
+
}, C.TIME.minutes(5), { name: "db-periodic-encrypt" });
|
|
806
812
|
|
|
807
813
|
// Final encrypt on process exit. We don't try to unlink the plaintext
|
|
808
814
|
// here — the SQLite handle may still be open, and the OS reclaims tmpfs
|
|
@@ -914,7 +920,7 @@ function hashFor(table, field, value) {
|
|
|
914
920
|
function close() {
|
|
915
921
|
if (!initialized) return;
|
|
916
922
|
if (encTimer) {
|
|
917
|
-
|
|
923
|
+
encTimer.stop();
|
|
918
924
|
encTimer = null;
|
|
919
925
|
}
|
|
920
926
|
// Best-effort final checkpoint before shutdown so the audit.tip sidecar
|
|
@@ -1070,7 +1076,7 @@ async function _runNtpBootCheck(opts) {
|
|
|
1070
1076
|
|
|
1071
1077
|
// Test helpers — not part of public contract
|
|
1072
1078
|
function _resetForTest() {
|
|
1073
|
-
if (encTimer) {
|
|
1079
|
+
if (encTimer) { encTimer.stop(); encTimer = null; }
|
|
1074
1080
|
try { if (database) database.close(); } catch (_e) {}
|
|
1075
1081
|
database = null;
|
|
1076
1082
|
dbPath = null;
|
|
@@ -1119,10 +1125,9 @@ module.exports = {
|
|
|
1119
1125
|
if (!Number.isFinite(lastPurgedCounter) || lastPurgedCounter < 0) {
|
|
1120
1126
|
throw new Error("purgeAuditChain: lastPurgedCounter must be a non-negative number");
|
|
1121
1127
|
}
|
|
1122
|
-
|
|
1123
|
-
if (c.isClusterMode()) {
|
|
1128
|
+
if (cluster.isClusterMode()) {
|
|
1124
1129
|
// External-db has no append-only triggers; ordinary DELETE works.
|
|
1125
|
-
var cs =
|
|
1130
|
+
var cs = clusterStorage();
|
|
1126
1131
|
var d = await cs.execute(
|
|
1127
1132
|
"DELETE FROM audit_log WHERE monotonicCounter <= ?", [lastPurgedCounter]
|
|
1128
1133
|
);
|
package/lib/deprecate.js
CHANGED
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
* call counter is still incremented so dep.list() shows usage volume.
|
|
51
51
|
*/
|
|
52
52
|
|
|
53
|
+
var safeEnv = require("./parsers/safe-env");
|
|
53
54
|
var { FrameworkError } = require("./framework-error");
|
|
54
55
|
|
|
55
56
|
class DeprecateError extends FrameworkError {
|
|
@@ -65,12 +66,12 @@ class DeprecateError extends FrameworkError {
|
|
|
65
66
|
var _seen = new Map();
|
|
66
67
|
|
|
67
68
|
function _modeFromEnv() {
|
|
68
|
-
var env =
|
|
69
|
+
var env = safeEnv.readVar("BLAMEJS_DEPRECATIONS");
|
|
69
70
|
if (typeof env === "string" && env.length > 0) {
|
|
70
71
|
var v = env.toLowerCase();
|
|
71
72
|
if (v === "warn" || v === "silent" || v === "error") return v;
|
|
72
73
|
}
|
|
73
|
-
if (
|
|
74
|
+
if (safeEnv.readVar("NODE_ENV") === "production") return "silent";
|
|
74
75
|
return "warn";
|
|
75
76
|
}
|
|
76
77
|
|
package/lib/error-page.js
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
var lazyRequire = require("./lazy-require");
|
|
42
42
|
var requestHelpers = require("./request-helpers");
|
|
43
|
+
var safeEnv = require("./parsers/safe-env");
|
|
43
44
|
var template = require("./template");
|
|
44
45
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
45
46
|
|
|
@@ -273,7 +274,7 @@ function create(opts) {
|
|
|
273
274
|
if (modeOpt === "dev" || modeOpt === "prod") {
|
|
274
275
|
mode = modeOpt;
|
|
275
276
|
} else {
|
|
276
|
-
var nodeEnv =
|
|
277
|
+
var nodeEnv = safeEnv.readVar("NODE_ENV");
|
|
277
278
|
mode = (nodeEnv === "production") ? "prod" : "dev";
|
|
278
279
|
}
|
|
279
280
|
|
package/lib/external-db.js
CHANGED
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
var retryHelper = require("./retry");
|
|
54
54
|
var C = require("./constants");
|
|
55
55
|
var lazyRequire = require("./lazy-require");
|
|
56
|
+
var safeAsync = require("./safe-async");
|
|
56
57
|
var { ExternalDbError } = require("./framework-error");
|
|
57
58
|
|
|
58
59
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
@@ -78,8 +79,8 @@ class Pool {
|
|
|
78
79
|
this.idle = []; // [{ client, lastUsedAt }]
|
|
79
80
|
this.active = 0; // count of in-use clients
|
|
80
81
|
this.waiters = []; // queued acquisitions when at max
|
|
81
|
-
this._reaper =
|
|
82
|
-
|
|
82
|
+
this._reaper = safeAsync.repeating(this._reapIdle.bind(this),
|
|
83
|
+
C.TIME.seconds(10), { name: "external-db-reaper" });
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
async acquire() {
|
|
@@ -141,7 +142,7 @@ class Pool {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
async drain() {
|
|
144
|
-
if (this._reaper) {
|
|
145
|
+
if (this._reaper) { this._reaper.stop(); this._reaper = null; }
|
|
145
146
|
var idleClients = this.idle.map(function (e) { return e.client; });
|
|
146
147
|
this.idle = [];
|
|
147
148
|
var self = this;
|
package/lib/forms.js
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
* forms.escapeAttribute(value) → string (double-quoted-attr context)
|
|
36
36
|
* forms.escapeHtml = template.escapeHtml (re-export for convenience)
|
|
37
37
|
*/
|
|
38
|
-
var
|
|
38
|
+
var crypto = require("./crypto");
|
|
39
39
|
var safeSchema = require("./safe-schema");
|
|
40
40
|
var safeUrl = require("./safe-url");
|
|
41
41
|
var template = require("./template");
|
|
@@ -47,16 +47,13 @@ var template = require("./template");
|
|
|
47
47
|
var CSRF_TOKEN_BYTES = 32;
|
|
48
48
|
|
|
49
49
|
function generateCsrfToken() {
|
|
50
|
-
return
|
|
50
|
+
return crypto.generateToken(CSRF_TOKEN_BYTES);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function verifyCsrfToken(submitted, expected) {
|
|
54
54
|
if (typeof submitted !== "string" || typeof expected !== "string") return false;
|
|
55
55
|
if (submitted.length === 0 || submitted.length !== expected.length) return false;
|
|
56
|
-
|
|
57
|
-
var b = Buffer.from(expected, "utf8");
|
|
58
|
-
if (a.length !== b.length) return false;
|
|
59
|
-
return nodeCrypto.timingSafeEqual(a, b);
|
|
56
|
+
return crypto.timingSafeEqual(submitted, expected);
|
|
60
57
|
}
|
|
61
58
|
|
|
62
59
|
// ============================================================
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
* }
|
|
44
44
|
*/
|
|
45
45
|
|
|
46
|
+
var C = require("./constants");
|
|
46
47
|
var safeUrl = require("./safe-url");
|
|
47
48
|
var { defineClass } = require("./framework-error");
|
|
48
49
|
|
|
@@ -186,7 +187,7 @@ function create(opts) {
|
|
|
186
187
|
if (attrs["max-age"] !== undefined) {
|
|
187
188
|
var maxAge = parseInt(attrs["max-age"], 10);
|
|
188
189
|
if (!isNaN(maxAge)) {
|
|
189
|
-
expiresAt = maxAge <= 0 ? 0 : (now + maxAge
|
|
190
|
+
expiresAt = maxAge <= 0 ? 0 : (now + C.TIME.seconds(maxAge));
|
|
190
191
|
}
|
|
191
192
|
} else if (attrs.expires) {
|
|
192
193
|
expiresAt = _parseHttpDate(attrs.expires);
|
package/lib/http-client.js
CHANGED
|
@@ -64,10 +64,10 @@
|
|
|
64
64
|
var http = require("http");
|
|
65
65
|
var https = require("https");
|
|
66
66
|
var http2 = require("http2");
|
|
67
|
-
var nodeCrypto = require("node:crypto");
|
|
68
67
|
var nodeStream = require("node:stream");
|
|
69
68
|
var { URL } = require("url");
|
|
70
69
|
var C = require("./constants");
|
|
70
|
+
var crypto = require("./crypto");
|
|
71
71
|
var pqcAgent = require("./pqc-agent");
|
|
72
72
|
var safeAsync = require("./safe-async");
|
|
73
73
|
var safeBuffer = require("./safe-buffer");
|
|
@@ -356,7 +356,7 @@ function _attachJarCookie(headers, jar, url) {
|
|
|
356
356
|
// parser accepts so round-trip from one blamejs app's outbound to
|
|
357
357
|
// another's inbound is exact.
|
|
358
358
|
function _buildMultipartBody(spec) {
|
|
359
|
-
var boundary = "----blamejs-mp-" +
|
|
359
|
+
var boundary = "----blamejs-mp-" + crypto.generateToken(16);
|
|
360
360
|
var CRLF = "\r\n";
|
|
361
361
|
var parts = [];
|
|
362
362
|
|
package/lib/log.js
CHANGED
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
*/
|
|
61
61
|
|
|
62
62
|
var { AsyncLocalStorage } = require("node:async_hooks");
|
|
63
|
+
var nodeCrypto = require("node:crypto");
|
|
63
64
|
var redact = require("./redact");
|
|
64
65
|
var validateOpts = require("./validate-opts");
|
|
65
66
|
var { FrameworkError } = require("./framework-error");
|
|
@@ -315,7 +316,7 @@ function create(opts) {
|
|
|
315
316
|
? mwOpts.generate
|
|
316
317
|
: function () {
|
|
317
318
|
// 16 random hex chars — short, sufficient correlation entropy
|
|
318
|
-
return
|
|
319
|
+
return nodeCrypto.randomBytes(8).toString("hex");
|
|
319
320
|
};
|
|
320
321
|
return function logRequestIdMiddleware(req, res, next) {
|
|
321
322
|
var inbound = req.headers && req.headers[headerName];
|