@blamejs/core 0.7.77 → 0.7.78

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 CHANGED
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.7.x
10
10
 
11
+ - **0.7.78** (2026-05-06) — `b.cloudEvents.wrap` / `.parse` — CloudEvents 1.0 envelope (cloudevents.io/spec/v1.0). Vendor-neutral event-format spec adopted by AWS EventBridge, Knative, Azure Event Grid, Google Eventarc, Datadog, and the broader CNCF event ecosystem; operators wrap outbound events from webhook / pubsub / queue boundaries to interop with these consumers without each consumer learning a bespoke shape. **`b.cloudEvents.wrap({ source, type, data?, subject?, time?, id?, datacontenttype?, dataschema?, extensions? })`** produces a CloudEvents 1.0 envelope: required attributes (`id` auto-minted as RFC 4122 v4 UUID when omitted, `source`, `specversion="1.0"`, `type`, `time` auto-set to `new Date().toISOString()`), optional attributes (`subject`, `datacontenttype` auto-set to `"application/json"` when `data` is JSON-serializable or `"application/octet-stream"` when `data` is a `Buffer` — base64-encoded into `data_base64`, `dataschema`), plus operator-defined extension attributes that conform to the §3.1 naming rules (lowercase ASCII alnum, 1-20 chars). **`b.cloudEvents.parse(envelope)`** validates the envelope shape and returns a structured form with `extensions` surfaced as a separate object so consumers can route on operator-defined fields without grepping the envelope. Refuses `data` + `data_base64` together (CloudEvents §3.1.1), unsupported specversion, missing required attributes, malformed extension names. **Test cleanup**: the `testAuditSafeEmitRedacts` smoke fixture from v0.7.75 now registers the `test` audit namespace before emitting so the audit handler's noise log line ("namespace 'test' is not registered") doesn't appear in CI smoke output.
12
+
11
13
  - **0.7.77** (2026-05-06) — Argon2 switched from vendored prebuilds to Node's built-in `crypto.argon2*` (Node 24+). The framework's `lib/vendor/argon2/` directory (with the `argon2.cjs` bundle and the `prebuilds/` tree of platform-specific `.glibc.node` / `.musl.node` artifacts for darwin-arm64 / darwin-x64 / freebsd-arm64 / freebsd-x64 / linux-arm / linux-arm64 / linux-x64 / win32-x64) is **deleted**. New `lib/argon2-builtin.js` is a thin wrapper over `crypto.argon2Sync` that produces and parses the PHC string format (`$argon2id$v=19$m=...,t=...,p=...$<salt>$<hash>`). Wire-format compatibility preserved: existing rows in operator databases continue to verify. Behavior preserved: `b.auth.password.hash` / `.verify` / `.needsRehash` retain their async signatures and return shapes; `b.vault.wrap` and `b.backupCrypto.deriveKey` use the same `raw: true` path for raw-bytes output. Operators wanting to supply their own argon2 implementation (pinned upstream, hardware-accelerated, etc.) override at the call site via `opts.argon2` — the supplied object MUST expose the same `hash` / `verify` / `needsRehash` shape. Drops ~440 KB of platform-specific native prebuilds from the repository and shipped npm tarball; eliminates a supply-chain hop. The vendor manifest's `argon2` entry is removed; `scripts/vendor-update.sh argon2` now refuses with a pointer to `lib/argon2-builtin.js`.
12
14
 
13
15
  - **0.7.76** (2026-05-06) — CVE-class web hardening sweep — Trojan Source (CVE-2021-42574) log defense + drive-by-MIME attachment opt for `staticServe`. **Trojan Source defense** in `b.log` output: every log line now post-processes Unicode bidi / format-control characters (U+061C / U+200E-200F / U+202A-202E / U+2066-2069) into their `\uXXXX` literal escape on the wire so a hostile log message can't re-order the visible line in a TTY / syslog / file reader. JSON.stringify alone does NOT escape these codepoints, so a captured-error message containing `U+202E` (RIGHT-TO-LEFT OVERRIDE) survives into the log surface and silently flips visible field/key associations. The escape applies to the entire serialized JSON line including any extras / bound-context fields. **`staticServe.create({ safeAttachmentForRiskyMimes: true })`** — opt-in flag that adds `Content-Disposition: attachment` to responses whose Content-Type is in the risky-inline-MIME set (`text/html`, `text/xml`, `application/xml`, `application/xhtml+xml`, `image/svg+xml`, `application/javascript`, `text/javascript`, `application/x-javascript`). Defends against drive-by execution of user-uploaded HTML / JS / SVG (CVE-2017-15012 SVG XSS / CVE-2009-1312 HTML drive-by class). Default `false` — operators serving framework asset bundles continue to render inline; operators serving user-content directories opt in. Filename is RFC 5987-encoded (ASCII filename + `filename*=UTF-8''...`) so non-ASCII filenames survive without allowing CR/LF header injection.
package/index.js CHANGED
@@ -211,6 +211,7 @@ var fileUpload = require("./lib/file-upload");
211
211
  var dualControl = require("./lib/dual-control");
212
212
  var retention = require("./lib/retention");
213
213
  var network = require("./lib/network");
214
+ var cloudEvents = require("./lib/cloud-events");
214
215
 
215
216
  module.exports = {
216
217
  crypto: crypto,
@@ -357,6 +358,7 @@ module.exports = {
357
358
  dualControl: dualControl,
358
359
  retention: retention,
359
360
  network: network,
361
+ cloudEvents: cloudEvents,
360
362
  ntpCheck: ntpCheck,
361
363
  version: constants.version,
362
364
  };
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ /**
3
+ * CloudEvents 1.0 envelope (cloudevents.io/spec/v1.0).
4
+ *
5
+ * A vendor-neutral event-format spec adopted by AWS EventBridge,
6
+ * Knative, Azure Event Grid, Google Eventarc, Datadog, and the
7
+ * CNCF event ecosystem. Operators wrap outbound events from
8
+ * webhook / pubsub / queue boundaries to interop with these
9
+ * consumers without each consumer having to learn a bespoke shape.
10
+ *
11
+ * var ce = b.cloudEvents.wrap({
12
+ * source: "/services/orders",
13
+ * type: "com.example.order.created",
14
+ * subject: "order/o-1234",
15
+ * data: { id: "o-1234", total: 4250 },
16
+ * });
17
+ * // → {
18
+ * // specversion: "1.0",
19
+ * // id: "<auto-uuid-v4>",
20
+ * // source: "/services/orders",
21
+ * // type: "com.example.order.created",
22
+ * // time: "2026-05-06T...",
23
+ * // subject: "order/o-1234",
24
+ * // datacontenttype: "application/json",
25
+ * // data: { id: "o-1234", total: 4250 },
26
+ * // }
27
+ *
28
+ * var ce = b.cloudEvents.parse(envelope); // throws on shape violation
29
+ *
30
+ * Spec compliance — REQUIRED attributes (CloudEvents §3.1):
31
+ * id, source, specversion, type
32
+ *
33
+ * OPTIONAL attributes:
34
+ * datacontenttype, dataschema, subject, time, data, data_base64
35
+ *
36
+ * Operator-defined extension attributes are passed through unchanged
37
+ * if they conform to the spec's naming rules (lowercase ASCII letters
38
+ * + digits, length 1–20).
39
+ */
40
+
41
+ var nodeCrypto = require("crypto");
42
+ var validateOpts = require("./validate-opts");
43
+ var { defineClass } = require("./framework-error");
44
+
45
+ var CloudEventsError = defineClass("CloudEventsError", { alwaysPermanent: true });
46
+
47
+ var SPECVERSION = "1.0";
48
+
49
+ // CloudEvents §3.1 — required string attributes.
50
+ var REQUIRED_ATTRS = ["id", "source", "specversion", "type"];
51
+
52
+ // CloudEvents §3.1 — known optional attributes (other strings get
53
+ // passed through as extension attributes if they conform to the
54
+ // naming rules).
55
+ var KNOWN_OPTIONAL_ATTRS = {
56
+ datacontenttype: 1,
57
+ dataschema: 1,
58
+ subject: 1,
59
+ time: 1,
60
+ data: 1,
61
+ data_base64: 1,
62
+ };
63
+
64
+ // CloudEvents §3.1 attribute naming — lowercase letters + digits,
65
+ // length 1-20.
66
+ var EXT_ATTR_NAME_RE = /^[a-z0-9]{1,20}$/;
67
+
68
+ function _isoNow() { return new Date().toISOString(); }
69
+
70
+ function _genId() {
71
+ // RFC 4122 v4 UUID — 16 random bytes with version + variant bits.
72
+ return nodeCrypto.randomUUID();
73
+ }
74
+
75
+ // ---- wrap ----
76
+
77
+ function wrap(opts) {
78
+ validateOpts.requireObject(opts, "cloudEvents.wrap", CloudEventsError);
79
+ validateOpts.requireNonEmptyString(opts.source,
80
+ "cloudEvents.wrap: source", CloudEventsError, "cloud-events/bad-source");
81
+ validateOpts.requireNonEmptyString(opts.type,
82
+ "cloudEvents.wrap: type", CloudEventsError, "cloud-events/bad-type");
83
+ validateOpts.optionalNonEmptyString(opts.id,
84
+ "cloudEvents.wrap: id", CloudEventsError, "cloud-events/bad-id");
85
+ validateOpts.optionalNonEmptyString(opts.subject,
86
+ "cloudEvents.wrap: subject", CloudEventsError, "cloud-events/bad-subject");
87
+ validateOpts.optionalNonEmptyString(opts.time,
88
+ "cloudEvents.wrap: time", CloudEventsError, "cloud-events/bad-time");
89
+ validateOpts.optionalNonEmptyString(opts.datacontenttype,
90
+ "cloudEvents.wrap: datacontenttype", CloudEventsError, "cloud-events/bad-datacontenttype");
91
+ validateOpts.optionalNonEmptyString(opts.dataschema,
92
+ "cloudEvents.wrap: dataschema", CloudEventsError, "cloud-events/bad-dataschema");
93
+
94
+ var out = {
95
+ specversion: SPECVERSION,
96
+ id: opts.id || _genId(),
97
+ source: opts.source,
98
+ type: opts.type,
99
+ time: opts.time || _isoNow(),
100
+ };
101
+ if (opts.subject !== undefined && opts.subject !== null) out.subject = opts.subject;
102
+ if (opts.dataschema !== undefined && opts.dataschema !== null) out.dataschema = opts.dataschema;
103
+
104
+ // data — choose JSON vs binary based on Buffer-ness; auto-set
105
+ // datacontenttype when caller doesn't supply one.
106
+ if (opts.data !== undefined) {
107
+ if (Buffer.isBuffer(opts.data)) {
108
+ out.data_base64 = opts.data.toString("base64");
109
+ out.datacontenttype = opts.datacontenttype || "application/octet-stream";
110
+ } else {
111
+ out.data = opts.data;
112
+ out.datacontenttype = opts.datacontenttype || "application/json";
113
+ }
114
+ } else if (opts.datacontenttype) {
115
+ out.datacontenttype = opts.datacontenttype;
116
+ }
117
+
118
+ // Extension attributes — operator-defined, must conform to the
119
+ // §3.1 naming rules (lowercase ASCII alnum, 1-20 chars).
120
+ if (opts.extensions !== undefined && opts.extensions !== null) {
121
+ validateOpts.optionalPlainObject(opts.extensions,
122
+ "cloudEvents.wrap: extensions", CloudEventsError, "cloud-events/bad-extensions");
123
+ var extKeys = Object.keys(opts.extensions);
124
+ for (var i = 0; i < extKeys.length; i += 1) {
125
+ var k = extKeys[i];
126
+ // bound BEFORE regex test — k.length > 0 && k.length <= 20
127
+ if (typeof k !== "string" || k.length === 0 || k.length > 20 || !EXT_ATTR_NAME_RE.test(k)) {
128
+ throw new CloudEventsError("cloud-events/bad-extension-name",
129
+ "cloudEvents.wrap: extension '" + k + "' must match [a-z0-9]{1,20}");
130
+ }
131
+ if (REQUIRED_ATTRS.indexOf(k) !== -1 || KNOWN_OPTIONAL_ATTRS[k]) {
132
+ throw new CloudEventsError("cloud-events/extension-conflicts-with-spec",
133
+ "cloudEvents.wrap: extension '" + k + "' conflicts with a spec attribute");
134
+ }
135
+ out[k] = opts.extensions[k];
136
+ }
137
+ }
138
+ return out;
139
+ }
140
+
141
+ // ---- parse ----
142
+
143
+ function parse(envelope) {
144
+ if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
145
+ throw new CloudEventsError("cloud-events/bad-envelope",
146
+ "cloudEvents.parse: envelope must be a plain object");
147
+ }
148
+ for (var i = 0; i < REQUIRED_ATTRS.length; i += 1) {
149
+ var k = REQUIRED_ATTRS[i];
150
+ if (typeof envelope[k] !== "string" || envelope[k].length === 0) {
151
+ throw new CloudEventsError("cloud-events/missing-required",
152
+ "cloudEvents.parse: required attribute '" + k + "' missing or empty (CloudEvents §3.1)");
153
+ }
154
+ }
155
+ if (envelope.specversion !== SPECVERSION) {
156
+ throw new CloudEventsError("cloud-events/unsupported-specversion",
157
+ "cloudEvents.parse: specversion='" + envelope.specversion +
158
+ "' is not supported (this primitive implements CloudEvents 1.0)");
159
+ }
160
+ if (envelope.data !== undefined && envelope.data_base64 !== undefined) {
161
+ throw new CloudEventsError("cloud-events/data-conflict",
162
+ "cloudEvents.parse: envelope has both 'data' and 'data_base64' (CloudEvents §3.1.1)");
163
+ }
164
+
165
+ // Decode binary data if the envelope used base64 mode.
166
+ var decodedData = envelope.data;
167
+ if (envelope.data_base64 !== undefined) {
168
+ if (typeof envelope.data_base64 !== "string") {
169
+ throw new CloudEventsError("cloud-events/bad-data-base64",
170
+ "cloudEvents.parse: data_base64 must be a string");
171
+ }
172
+ try { decodedData = Buffer.from(envelope.data_base64, "base64"); }
173
+ catch (e) {
174
+ throw new CloudEventsError("cloud-events/bad-data-base64",
175
+ "cloudEvents.parse: data_base64 decode failed: " + ((e && e.message) || String(e)));
176
+ }
177
+ }
178
+
179
+ // Surface extension attributes separately so consumers can route on
180
+ // operator-defined fields without grepping the envelope.
181
+ var extensions = {};
182
+ var keys = Object.keys(envelope);
183
+ for (var j = 0; j < keys.length; j += 1) {
184
+ var key = keys[j];
185
+ if (REQUIRED_ATTRS.indexOf(key) !== -1) continue;
186
+ if (KNOWN_OPTIONAL_ATTRS[key]) continue;
187
+ extensions[key] = envelope[key];
188
+ }
189
+
190
+ return {
191
+ specversion: envelope.specversion,
192
+ id: envelope.id,
193
+ source: envelope.source,
194
+ type: envelope.type,
195
+ time: envelope.time || null,
196
+ subject: envelope.subject || null,
197
+ datacontenttype: envelope.datacontenttype || null,
198
+ dataschema: envelope.dataschema || null,
199
+ data: decodedData,
200
+ extensions: extensions,
201
+ };
202
+ }
203
+
204
+ module.exports = {
205
+ wrap: wrap,
206
+ parse: parse,
207
+ SPECVERSION: SPECVERSION,
208
+ REQUIRED_ATTRS: REQUIRED_ATTRS,
209
+ CloudEventsError: CloudEventsError,
210
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.7.77",
3
+ "version": "0.7.78",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:3d6aa43b-6da6-44c8-a687-434e93ef3b07",
5
+ "serialNumber": "urn:uuid:357e4142-2f9f-4e03-8132-644e87b5224e",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-06T04:48:07.561Z",
8
+ "timestamp": "2026-05-06T05:12:59.485Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/core@0.7.77",
22
+ "bom-ref": "@blamejs/core@0.7.78",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.7.77",
25
+ "version": "0.7.78",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "The Node framework that owns its stack.",
29
- "purl": "pkg:npm/%40blamejs/core@0.7.77",
29
+ "purl": "pkg:npm/%40blamejs/core@0.7.78",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/core@0.7.77",
57
+ "ref": "@blamejs/core@0.7.78",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]