@blamejs/core 0.4.25 → 0.4.26
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 +1 -0
- package/lib/auth/oauth.js +17 -8
- package/lib/forms.js +12 -2
- package/lib/middleware/bot-guard.js +12 -8
- package/lib/middleware/csrf-protect.js +2 -1
- package/lib/middleware/rate-limit.js +5 -1
- package/lib/middleware/require-auth.js +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,7 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.4.x
|
|
10
10
|
|
|
11
|
+
- **0.4.25** (2026-04-30) — b.objectStore.bucketOps: bucket-level operations (SigV4)
|
|
11
12
|
- **0.4.24** (2026-04-30) — b.objectStore: multipart upload + server-side encryption
|
|
12
13
|
- **0.4.23** (2026-04-30) — b.mail.dkim signing + calendar invites
|
|
13
14
|
- **0.4.22** (2026-04-30) — b.mail attachments + inline images + plain/HTML alternatives
|
package/lib/auth/oauth.js
CHANGED
|
@@ -216,15 +216,24 @@ function _validateUrl(url, allowHttp, label) {
|
|
|
216
216
|
if (typeof url !== "string" || url.length === 0) {
|
|
217
217
|
throw new OAuthError("auth-oauth/bad-url", label + ": URL is required");
|
|
218
218
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
// Operator-supplied OAuth issuer / endpoint URL — route through
|
|
220
|
+
// safeUrl so the scheme allowlist is consistent with the rest of the
|
|
221
|
+
// framework's outbound gates. Map safe-url's error codes to the
|
|
222
|
+
// domain-specific oauth codes operators already key alerts on.
|
|
223
|
+
try {
|
|
224
|
+
safeUrl.parse(url, {
|
|
225
|
+
allowedProtocols: allowHttp ? safeUrl.ALLOW_HTTP_ALL : safeUrl.ALLOW_HTTP_TLS,
|
|
226
|
+
});
|
|
227
|
+
} catch (e) {
|
|
228
|
+
if (e && e.code === "safe-url/protocol-disallowed") {
|
|
229
|
+
throw new OAuthError("auth-oauth/insecure-url",
|
|
230
|
+
label + ": must be https" + (allowHttp ? " or http" : "") +
|
|
231
|
+
" (got '" + url + "')");
|
|
232
|
+
}
|
|
233
|
+
throw new OAuthError("auth-oauth/bad-url",
|
|
234
|
+
label + ": invalid URL '" + url + "'");
|
|
223
235
|
}
|
|
224
|
-
|
|
225
|
-
if (parsed.protocol === "http:" && allowHttp) return url;
|
|
226
|
-
throw new OAuthError("auth-oauth/insecure-url",
|
|
227
|
-
label + ": must be https (got '" + parsed.protocol + "//" + parsed.host + "')");
|
|
236
|
+
return url;
|
|
228
237
|
}
|
|
229
238
|
|
|
230
239
|
// ---- JOSE alg → node:crypto verify parameters ----
|
package/lib/forms.js
CHANGED
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
* forms.escapeHtml = template.escapeHtml (re-export for convenience)
|
|
37
37
|
*/
|
|
38
38
|
var nodeCrypto = require("crypto");
|
|
39
|
+
var safeUrl = require("./safe-url");
|
|
39
40
|
var template = require("./template");
|
|
40
41
|
|
|
41
42
|
// ============================================================
|
|
@@ -327,8 +328,17 @@ function validate(spec, body) {
|
|
|
327
328
|
}
|
|
328
329
|
}
|
|
329
330
|
if (f.type === "url" && typeof coerced === "string") {
|
|
330
|
-
|
|
331
|
-
|
|
331
|
+
// Form `url` fields come from the request body — operator/external
|
|
332
|
+
// input. Route through safeUrl so the scheme allowlist is honored
|
|
333
|
+
// (https-only by default; operator opts in to http via field meta).
|
|
334
|
+
try {
|
|
335
|
+
safeUrl.parse(coerced, {
|
|
336
|
+
allowedProtocols: f.allowHttp ? safeUrl.ALLOW_HTTP_ALL : safeUrl.ALLOW_HTTP_TLS,
|
|
337
|
+
});
|
|
338
|
+
} catch (_e) {
|
|
339
|
+
errors[f.name] = (f.label || f.name) + " must be a valid URL";
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
332
342
|
}
|
|
333
343
|
if (typeof coerced === "string") {
|
|
334
344
|
if (f.minlength !== undefined && coerced.length < Number(f.minlength)) {
|
|
@@ -45,9 +45,19 @@ var DEFAULT_BLOCKED_AGENTS = [
|
|
|
45
45
|
];
|
|
46
46
|
|
|
47
47
|
var lazyRequire = require("../lazy-require");
|
|
48
|
+
var requestHelpers = require("../request-helpers");
|
|
48
49
|
var validateOpts = require("../validate-opts");
|
|
49
50
|
var audit = lazyRequire(function () { return require("../audit"); });
|
|
50
51
|
|
|
52
|
+
// Bot-guard's "trust the proxy header" semantics for actor.ip — the
|
|
53
|
+
// audit event records the apparent source even when behind a CDN.
|
|
54
|
+
// extractActorContext defaults to socket.remoteAddress; we override.
|
|
55
|
+
function _xffIp(req) {
|
|
56
|
+
var xff = req.headers && req.headers["x-forwarded-for"];
|
|
57
|
+
if (xff) return String(xff).split(",")[0].trim();
|
|
58
|
+
return (req.socket && req.socket.remoteAddress) || null;
|
|
59
|
+
}
|
|
60
|
+
|
|
51
61
|
function create(opts) {
|
|
52
62
|
opts = opts || {};
|
|
53
63
|
validateOpts(opts, [
|
|
@@ -105,10 +115,7 @@ function create(opts) {
|
|
|
105
115
|
req.suspectedBot = hit;
|
|
106
116
|
try {
|
|
107
117
|
audit().emit({
|
|
108
|
-
actor: {
|
|
109
|
-
ip: (req.headers && req.headers["x-forwarded-for"]) || (req.socket && req.socket.remoteAddress),
|
|
110
|
-
userAgent: req.headers && req.headers["user-agent"],
|
|
111
|
-
},
|
|
118
|
+
actor: requestHelpers.extractActorContext(req, { ip: _xffIp(req) }),
|
|
112
119
|
action: "system.botguard.tag",
|
|
113
120
|
outcome: "denied",
|
|
114
121
|
reason: hit,
|
|
@@ -121,10 +128,7 @@ function create(opts) {
|
|
|
121
128
|
|
|
122
129
|
try {
|
|
123
130
|
audit().emit({
|
|
124
|
-
actor: {
|
|
125
|
-
ip: (req.headers && req.headers["x-forwarded-for"]) || (req.socket && req.socket.remoteAddress),
|
|
126
|
-
userAgent: req.headers && req.headers["user-agent"],
|
|
127
|
-
},
|
|
131
|
+
actor: requestHelpers.extractActorContext(req, { ip: _xffIp(req) }),
|
|
128
132
|
action: "system.botguard.block",
|
|
129
133
|
outcome: "denied",
|
|
130
134
|
reason: hit,
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
*/
|
|
67
67
|
var lazyRequire = require("../lazy-require");
|
|
68
68
|
var forms = require("../forms");
|
|
69
|
+
var requestHelpers = require("../request-helpers");
|
|
69
70
|
var validateOpts = require("../validate-opts");
|
|
70
71
|
var audit = lazyRequire(function () { return require("../audit"); });
|
|
71
72
|
|
|
@@ -203,7 +204,7 @@ function create(opts) {
|
|
|
203
204
|
audit().safeEmit({
|
|
204
205
|
action: "auth.csrf.denied",
|
|
205
206
|
outcome: "denied",
|
|
206
|
-
actor:
|
|
207
|
+
actor: requestHelpers.extractActorContext(req),
|
|
207
208
|
reason: reason,
|
|
208
209
|
metadata: { method: req.method, path: (req.url || "").split("?")[0] },
|
|
209
210
|
});
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
*/
|
|
46
46
|
var C = require("../constants");
|
|
47
47
|
var lazyRequire = require("../lazy-require");
|
|
48
|
+
var requestHelpers = require("../request-helpers");
|
|
48
49
|
var validateOpts = require("../validate-opts");
|
|
49
50
|
var clusterStorage = require("../cluster-storage");
|
|
50
51
|
|
|
@@ -252,8 +253,11 @@ function create(opts) {
|
|
|
252
253
|
if (verdict.retryAfter > 0) res.setHeader("Retry-After", String(verdict.retryAfter));
|
|
253
254
|
}
|
|
254
255
|
try {
|
|
256
|
+
// Override `ip` with the x-forwarded-for-aware client IP so the
|
|
257
|
+
// audit event carries the proxied origin even when extractActorContext
|
|
258
|
+
// would have read the socket address.
|
|
255
259
|
audit().emit({
|
|
256
|
-
actor: { ip: _clientIp(req)
|
|
260
|
+
actor: requestHelpers.extractActorContext(req, { ip: _clientIp(req) }),
|
|
257
261
|
action: "system.ratelimit.block",
|
|
258
262
|
outcome: "denied",
|
|
259
263
|
reason: "rate limit exceeded",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* }
|
|
30
30
|
*/
|
|
31
31
|
var lazyRequire = require("../lazy-require");
|
|
32
|
+
var requestHelpers = require("../request-helpers");
|
|
32
33
|
var validateOpts = require("../validate-opts");
|
|
33
34
|
var audit = lazyRequire(function () { return require("../audit"); });
|
|
34
35
|
|
|
@@ -61,7 +62,7 @@ function create(opts) {
|
|
|
61
62
|
audit().emit({
|
|
62
63
|
action: "auth.required.denied",
|
|
63
64
|
outcome: "denied",
|
|
64
|
-
actor:
|
|
65
|
+
actor: requestHelpers.extractActorContext(req),
|
|
65
66
|
reason: "no authenticated user on request",
|
|
66
67
|
metadata: { method: req.method, path: req.pathname || (req.url || "").split("?")[0] },
|
|
67
68
|
});
|