@blamejs/core 0.18.5 → 0.18.7
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 +4 -0
- package/lib/mail-server-managesieve.js +7 -0
- package/lib/mail-server-tls.js +23 -7
- package/lib/mail.js +22 -5
- package/lib/request-helpers.js +7 -2
- package/lib/restore-rollback.js +5 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.18.x
|
|
10
10
|
|
|
11
|
+
- v0.18.7 (2026-08-02) — **STARTTLS idle-timeout no longer injects plaintext into the encrypted channel; broader mail-server test coverage.** A STARTTLS upgrade on the POP3 / IMAP / ManageSieve / SMTP-submission / MX servers left the pre-upgrade plain-socket idle-timeout handler armed, so a post-handshake idle wrote a plaintext timeout reply into the now-encrypted stream (the peer saw a TLS decode error / reset). The shared socket-upgrade path now strips that handler so the idle timeout is delivered encrypted. Alongside the fix, error and edge branches of b.mail.server.pop3, b.mail.server.imap, and b.mail.auth are now under test. Except for the fix, behaviour is unchanged. **Changed:** *Error- and edge-path tests for the POP3, IMAP, and mail-authentication primitives* — b.mail.server.pop3 (the STLS upgrade and post-handshake idle timeout, pipelined-command draining, AUTH / APOP / USER-PASS branches, and store-fallback and tenant-refusal shapes), b.mail.server.imap (APPEND / CATENATE literal handling, command-dispatch and guard-error branches, and the LOGIN / NOTIFY paths), and b.mail.auth (SPF macro expansion and record parsing, iprev forward-confirm, and dual-CIDR a/mx parsing) now have explicit tests for their boundary paths. **Security:** *STARTTLS idle timeout is delivered over the encrypted channel, not injected as plaintext* — The POP3, IMAP, ManageSieve, SMTP-submission, and MX servers arm a plain-socket idle timer whose handler writes a plaintext idle-timeout reply and closes the connection. After a STARTTLS upgrade the shared socket-upgrade path stripped only the plain socket's data listeners, so that plaintext handler survived the wrap and, on a post-handshake idle, injected cleartext into the now-encrypted stream — the peer observed a TLS decode error or connection reset instead of a clean encrypted timeout reply. The upgrade path now strips the plain socket's timeout listeners and disarms its idle timer, so the upgraded TLS socket owns the idle timeout and replies over the encrypted channel. **Detectors:** *STARTTLS upgrade strips the plaintext idle-timeout handler* — A source guard keeps the shared socket-upgrade path stripping the plain socket's timeout listeners alongside its data listeners, so a plaintext idle-timeout reply cannot survive into the encrypted channel.
|
|
12
|
+
|
|
13
|
+
- v0.18.6 (2026-08-01) — **Mail SMTPUTF8 + credential-validation fixes and a static-quota fail-open fix, with broad error- and edge-path test coverage.** b.mail no longer forces SMTPUTF8 on a long-but-pure-ASCII subject or address (so such a message delivers to a peer that does not advertise SMTPUTF8), and now rejects a non-string credential/identity option at config time instead of crashing mid-send. b.static enforces its per-actor concurrency and bandwidth quotas even when a proxy blanks the client IP, closing a fail-open. Alongside the fixes, error, edge, and adversarial branches of the blamejs CLI, b.mail, b.mail.bimi, and b.static are now under test. Except for the noted fixes, behaviour is unchanged. **Changed:** *restore rollback names the resolved target in its missing-path error* — b.restoreRollback.rollback's missing-target error now names the resolved rollback path, so an operator — and the CLI's relative `--rollback` resolution — can see exactly which path was checked and found missing. · *Error- and edge-path tests for the CLI, mail sender, BIMI verifier, and static-file server* — The blamejs CLI (migrate/seed runner coded-error paths, the dev runner's --watch / --ignore / --kill-signal handling and start-rejection exit, restore inspect/apply/rollback and list-rollbacks, backup inspect/verify, mtls show-cert / status, vault status, and config-drift inspect on a tampered sidecar), b.mail (SMTPUTF8 handling, reverse-DNS forward-confirm, address and EAI-domain validation edges, the outbound SMTP-smuggling refusal, DATA dot-stuffing transparency, the send-audit recipient counts, DKIM-sign and interpret error fallbacks, the console / http / resend transports and their option defaults, guardDomain profile defaults and cc/bcc validation, and the CAN-SPAM refusal audit), b.mail.bimi's verification branches (including the empty-canonical-domain SAN-authorization backstop), and b.static's request, path-resolution, and quota-enforcement branches (including concurrency-slot release on a mid-stream client abort) now have explicit tests for their boundary paths. **Fixed:** *A long pure-ASCII subject or address no longer forces SMTPUTF8* — b.mail treated any subject or address longer than 254 characters as non-ASCII content, so an otherwise-deliverable pure-ASCII message required SMTPUTF8 (RFC 6531) and was refused with `eai-required-not-supported` whenever the receiving server did not advertise SMTPUTF8. The requirement is now decided solely by whether a field carries a non-ASCII code point, independent of length: long ASCII subjects and addresses deliver to any peer, and genuinely non-ASCII content still opts into SMTPUTF8 where the peer advertises it. · *Non-string SMTP transport options are rejected at config time* — b.mail.transports.smtp accepted a present-but-non-string user, pass, host, servername, or ehloName at build, then failed later on the wire — a numeric user, for example, reached `Buffer.from` at AUTH and threw, failing the send instead of authenticating. Such a value is now rejected at config time with `mail/smtp-misconfigured`, so a misconfiguration fails fast at boot rather than at first send. **Security:** *A blank client IP no longer disables b.static per-actor quotas* — The shared request-context reader let an empty req.ip shadow the socket-address fallback, yielding a null actor key that silently skipped b.static's per-actor concurrency and bandwidth caps (fail-open) — for example behind a proxy that blanks req.ip when it can't resolve a forwarded address. A blank req.ip now falls through to the real peer address, so the quotas are enforced. **Detectors:** *SMTPUTF8 requirement stays content-based, not length-bounded* — A source guard keeps b.mail's SMTPUTF8 decision keyed on the presence of a non-ASCII code point rather than the length-bounded ASCII validity check, so the long-ASCII-subject deliverability regression cannot return. · *SMTP field validation fails closed on a non-string value* — A source guard keeps b.mail's SMTP identity/credential field check rejecting a present non-string value at config time rather than passing it through to crash on the wire. · *A blank req.ip must not shadow the socket-address fallback* — A source guard keeps the request-context reader falling through to the socket address on an empty req.ip, so per-actor quotas that key off the resolved IP cannot be silently disabled.
|
|
14
|
+
|
|
11
15
|
- v0.18.5 (2026-08-01) — **Error- and edge-path tests for the agent orchestrator, mTLS request gate, and i18n primitives.** Error, edge, and adversarial branches of b.agent.orchestrator (registry hydrate/register validation, consumer spawn and start lifecycle, cluster election cache and leader probes, drain and quiesce budgets, tenant-scope authorization, per-row reseal), the b.middleware.requireMtls request gate (no-peer-cert and fingerprint-failure refusals, allow/deny-list gates, revocation-source and onAuthenticated error paths, audit outcomes), and b.i18n's translation-tree and file-load edges are now under test. Behaviour is unchanged -- these pin guarantees the primitives already met. **Changed:** *Error- and edge-path tests for the agent orchestrator, mTLS request gate, and i18n primitives* — b.agent.orchestrator (registry hydrate/register non-object refusals, consumer spawn missing-agent/missing-queue refusals and the shard-topic naming, consumer start double-start refusal and the direct / dotted-nested / unknown consume-handler method resolution, cluster onTransition election-cache clear, elect argument validation and the election cache-hit path, currentLeader/isLeader/fencingToken error handling, drain timeout and the quiesce poll budget, tenant-scope actor-missing / no-tenant authorization, and reseal store-validation with per-cell AAD-sealed filtering), the b.middleware.requireMtls gate (no-opts default, allow/deny-list normalization and gate outcomes, the connection fallback and null-peer-cert / no-raw / fingerprint-failed refusals, deny-match refusal, the revocationSource and onAuthenticated error paths including a non-Error rejection, and audit allowed/refused outcomes), and b.i18n's translation-tree validation and file-load edge branches now have explicit tests for their error and boundary paths. Behaviour is unchanged -- the additions pin guarantees the primitives already met.
|
|
12
16
|
|
|
13
17
|
- v0.18.4 (2026-08-01) — **`b.session.updateData({ merge: true })` now merges an inner object one level deep instead of discarding the existing nested keys, alongside error- and edge-path tests for the queue, encrypted-field, session, and CalDAV/CardDAV primitives.** b.session.updateData(token, data, { merge: true }) documents a one-level-deep merge, but an inner object value shallow-replaced the whole nested object -- so updating one nested key silently dropped the operator's other nested keys. It now merges an inner plain object into the existing one (its keys survive); an array or non-object value still replaces. Separately, error, edge, and adversarial branches of b.queue (init and backend resolution, consume success/retry/dead-letter, enqueueFlow validation and dependency ordering, dead-letter list/retry/size, shutdown drain, bootFromEnv), b.cryptoField (seal/unseal envelope-floor enforcement, derived-hash modes, per-row-key unseal rate cooldown, data residency, read-time hash upgrade, erase-row vacuum cascade, hash lookup), b.session (fingerprint-drift scoring and fail-closed binding, anonymous mint, rotate carry-forward and re-key, updateData merge guards, validFrom scheduling), and b.mail.dav (CalDAV/CardDAV request and response branches) are now under test. **Changed:** *Error- and edge-path tests for the queue, encrypted-field, session, and CalDAV/CardDAV primitives* — b.queue (init idempotency and the missing-backends / unknown-backend refusals, consume success/retry/dead-letter-write/rate-limit-wait/lease-extend/progress paths, enqueueFlow validation and cycle/dependency ordering, dead-letter list/retry/size with the SQS-unsupported refusals, shutdown pre-init no-op and in-flight drain, bootFromEnv config resolution), b.cryptoField (seal-envelope-floor enforcement, derived-hash keyed/legacy modes and truncation, per-row-key unseal rate cooldown and its audit, data residency, read-time derived-hash upgrade, the erase-row vacuum cascade, and hash lookup), b.session (fingerprint-drift scoring with clamp and fail-closed admission, strict-binding missing/unreadable refusal, anonymous mint and the anonymous-plus-userId refusal, rotate data-replace / carry-forward / bound re-key, updateData token and merge guards, and validFrom read-back with check admit/revoke), and b.mail.dav's request and response branches now have explicit tests for their error and boundary paths. Behaviour is unchanged for these -- the additions pin guarantees the primitives already met. · *Vendored @blamejs/pki refreshed to 0.3.28* — The bundled PKI toolkit (lib/vendor/blamejs-pki.cjs, the mTLS CA engine's certificate/CMS backend) is refreshed from 0.3.27 to 0.3.28 so the vendored copy matches the latest published release. It adds no runtime dependency and no public framework API; the upstream release adds HTTP Digest authentication (RFC 7616) to the EST client transport. **Fixed:** *b.session.updateData({ merge: true }) merges an inner object one level deep instead of discarding existing nested keys* — updateData(token, data, { merge: true }) is documented to merge one level deep -- inner objects merge, arrays replace. It merged only the top-level keys: an inner object value was assigned wholesale (next[k] = data[k]), so updateData(token, { prefs: { theme: "dark" } }, { merge: true }) against a stored { prefs: { locale: "en" } } dropped prefs.locale, leaving only { prefs: { theme: "dark" } }. An inner PLAIN-object value now merges into the existing plain object (its keys survive, then the incoming keys apply); an array, a non-plain object (a Date, Buffer, or class instance -- stored as its own JSON form rather than mangled into the retained old object or byte-index keys), or a non-object value replaces, and merging beyond one level replaces. Operators who relied on the previous top-level-only behaviour, or who intend to replace a whole nested object, can pass the full nested value they want stored. **Detectors:** *session.updateData({ merge: true }) must merge an inner object one level deep* — A source check asserts the merge path merges an inner value only when BOTH are PLAIN objects (a prototype-based _isPlainObject) as Object.assign({}, existing[k], data[k]), never a bare next[k] = data[k] shallow replace, so the nested-key data-loss regression -- and a Date/Buffer being merged instead of replaced -- cannot return unnoticed.
|
|
@@ -517,6 +517,13 @@ function create(opts) {
|
|
|
517
517
|
idleTimeoutMs: idleTimeoutMs,
|
|
518
518
|
clearFields: ["pendingLiteral", "pendingAuth"],
|
|
519
519
|
drain: _drainBuffer,
|
|
520
|
+
// Post-STARTTLS idle teardown MUST be TLS-aware — upgradeSocket strips the
|
|
521
|
+
// plain-socket timeout handler, so without this the upgraded session would
|
|
522
|
+
// never idle out (RFC 5804 BYE) and could stay open indefinitely.
|
|
523
|
+
onTimeout: function (tlsSocket) {
|
|
524
|
+
_writeBye(tlsSocket, "Idle timeout");
|
|
525
|
+
_close(tlsSocket);
|
|
526
|
+
},
|
|
520
527
|
onSecure: function (tlsSocket) {
|
|
521
528
|
_emit("mail.server.managesieve.starttls_upgraded",
|
|
522
529
|
{ connectionId: state.id });
|
package/lib/mail-server-tls.js
CHANGED
|
@@ -414,6 +414,15 @@ function upgradeSocket(opts) {
|
|
|
414
414
|
// post-TLS dispatcher and execute as if they had been sent over
|
|
415
415
|
// the authenticated channel.
|
|
416
416
|
plainSocket.removeAllListeners("data");
|
|
417
|
+
// The pre-upgrade plain-socket idle timer's "timeout" listener wrote a PLAINTEXT
|
|
418
|
+
// idle reply and closed the connection (every line-protocol server arms
|
|
419
|
+
// socket.setTimeout + a "timeout" handler at connect). Left attached, it fires on
|
|
420
|
+
// the wrapped socket after the handshake and injects cleartext into the now-
|
|
421
|
+
// encrypted stream — the peer sees a TLS decode error / reset instead of the TLS-
|
|
422
|
+
// aware onTimeout replying encrypted. Strip the plaintext listener; the TLSSocket
|
|
423
|
+
// arms its own idle timer + a TLS-aware onTimeout IMMEDIATELY below (before the
|
|
424
|
+
// handshake), so the reply is encrypted AND the handshake window stays bounded.
|
|
425
|
+
plainSocket.removeAllListeners("timeout");
|
|
417
426
|
// Pause so the kernel TCP buffer doesn't drain into the old
|
|
418
427
|
// handler in the window before TLSSocket attaches its own.
|
|
419
428
|
if (typeof plainSocket.pause === "function") {
|
|
@@ -425,14 +434,21 @@ function upgradeSocket(opts) {
|
|
|
425
434
|
secureContext: opts.secureContext,
|
|
426
435
|
});
|
|
427
436
|
|
|
437
|
+
// Arm the TLS-aware idle timeout NOW, before the handshake completes — a peer
|
|
438
|
+
// that sends STARTTLS then withholds / trickles the ClientHello must not hold the
|
|
439
|
+
// connection (and its tracked rate-limit slot) open indefinitely. The TLSSocket
|
|
440
|
+
// owns the timer for the whole post-upgrade lifetime; onTimeout closes it (its
|
|
441
|
+
// reply serializes encrypted once secure). Arming only on "secure" left the
|
|
442
|
+
// handshake interval unbounded.
|
|
443
|
+
if (idleTimeoutMs !== undefined && typeof tlsSocket.setTimeout === "function") {
|
|
444
|
+
try { tlsSocket.setTimeout(idleTimeoutMs); }
|
|
445
|
+
catch (_e) { /* tolerate */ }
|
|
446
|
+
}
|
|
447
|
+
if (typeof opts.onTimeout === "function") {
|
|
448
|
+
tlsSocket.on("timeout", function () { opts.onTimeout(tlsSocket); });
|
|
449
|
+
}
|
|
450
|
+
|
|
428
451
|
tlsSocket.on("secure", function () {
|
|
429
|
-
if (idleTimeoutMs !== undefined && typeof tlsSocket.setTimeout === "function") {
|
|
430
|
-
try { tlsSocket.setTimeout(idleTimeoutMs); }
|
|
431
|
-
catch (_e) { /* tolerate */ }
|
|
432
|
-
}
|
|
433
|
-
if (typeof opts.onTimeout === "function") {
|
|
434
|
-
tlsSocket.on("timeout", function () { opts.onTimeout(tlsSocket); });
|
|
435
|
-
}
|
|
436
452
|
try { opts.onSecure(tlsSocket); }
|
|
437
453
|
catch (e) { try { opts.onError(e); } catch (_e) { /* drop-silent */ } }
|
|
438
454
|
});
|
package/lib/mail.js
CHANGED
|
@@ -123,6 +123,15 @@ function _isAscii(s) {
|
|
|
123
123
|
return !NON_ASCII_RE.test(s);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// Length-agnostic non-ASCII content probe. Unlike _isAscii (a length-bounded
|
|
127
|
+
// address validity/ReDoS guard), this answers only "does the string carry a
|
|
128
|
+
// code point >= U+0080" — the sole question the SMTPUTF8 (RFC 6531 §3.2) wire
|
|
129
|
+
// requirement turns on. NON_ASCII_RE is a single character class, so the scan
|
|
130
|
+
// is linear even on unbounded subjects.
|
|
131
|
+
function _hasNonAscii(s) {
|
|
132
|
+
return NON_ASCII_RE.test(String(s)); // allow:regex-no-length-cap — single negated char-class /[^\x00-\x7f]/ is linear (no backtracking); the SMTPUTF8 content decision MUST stay length-agnostic
|
|
133
|
+
}
|
|
134
|
+
|
|
126
135
|
/**
|
|
127
136
|
* @primitive b.mail.toAscii
|
|
128
137
|
* @signature b.mail.toAscii(domain)
|
|
@@ -301,13 +310,13 @@ function _isValidEmail(addr) {
|
|
|
301
310
|
// contains non-ASCII octets.
|
|
302
311
|
function _messageRequiresSmtpUtf8(message) {
|
|
303
312
|
if (!message) return false;
|
|
304
|
-
if (
|
|
305
|
-
if (
|
|
313
|
+
if (_hasNonAscii(message.from || "")) return true;
|
|
314
|
+
if (_hasNonAscii(message.subject || "")) return true;
|
|
306
315
|
var lists = [message.to, message.cc, message.bcc];
|
|
307
316
|
for (var li = 0; li < lists.length; li += 1) {
|
|
308
317
|
var arr = Array.isArray(lists[li]) ? lists[li] : (lists[li] ? [lists[li]] : []);
|
|
309
318
|
for (var i = 0; i < arr.length; i += 1) {
|
|
310
|
-
if (
|
|
319
|
+
if (_hasNonAscii(arr[i])) return true;
|
|
311
320
|
}
|
|
312
321
|
}
|
|
313
322
|
return false;
|
|
@@ -832,7 +841,15 @@ function smtpTransport(opts) {
|
|
|
832
841
|
// rather than silently emitting a smuggled command at first send.
|
|
833
842
|
function _refuseCtlBytes(label, val) {
|
|
834
843
|
if (val === undefined || val === null) return;
|
|
835
|
-
|
|
844
|
+
// A present-but-non-string value can't be CR/LF-checked and is itself a
|
|
845
|
+
// misconfiguration — it slips through the builder and crashes on the wire
|
|
846
|
+
// (e.g. Buffer.from(numericUser) throws ERR_INVALID_ARG_TYPE at AUTH). Fail
|
|
847
|
+
// closed at config-time so the operator's boot dies at the bad option line.
|
|
848
|
+
if (typeof val !== "string") {
|
|
849
|
+
throw new MailError("mail/smtp-misconfigured",
|
|
850
|
+
"smtp transport: opts." + label + " must be a string when set (got " + typeof val + ")",
|
|
851
|
+
true);
|
|
852
|
+
}
|
|
836
853
|
if (/[\r\n\0]/.test(val)) { // allow:regex-no-length-cap — CRLF/NUL is a 3-codepoint class
|
|
837
854
|
throw new MailError("mail/smtp-misconfigured",
|
|
838
855
|
"smtp transport: opts." + label + " contains CR/LF/NUL bytes " +
|
|
@@ -840,7 +857,7 @@ function smtpTransport(opts) {
|
|
|
840
857
|
true);
|
|
841
858
|
}
|
|
842
859
|
}
|
|
843
|
-
_refuseCtlBytes("ehloName", ehloName);
|
|
860
|
+
_refuseCtlBytes("ehloName", opts.ehloName); // validate the SUPPLIED value, not the post-`|| "blamejs"` default, so a falsy non-string (false / 0 / NaN) is rejected like the others rather than silently defaulted
|
|
844
861
|
_refuseCtlBytes("user", opts.user);
|
|
845
862
|
_refuseCtlBytes("pass", opts.pass);
|
|
846
863
|
_refuseCtlBytes("host", opts.host);
|
package/lib/request-helpers.js
CHANGED
|
@@ -127,8 +127,13 @@ function extractActorContext(req, override) {
|
|
|
127
127
|
userId: null,
|
|
128
128
|
};
|
|
129
129
|
if (req && typeof req === "object") {
|
|
130
|
-
// Direct properties first (Express-shaped frameworks set req.ip).
|
|
131
|
-
|
|
130
|
+
// Direct properties first (Express-shaped frameworks set req.ip). An EMPTY
|
|
131
|
+
// req.ip must NOT shadow the socket fallback — a proxy/middleware that sets
|
|
132
|
+
// req.ip = "" when it can't resolve a forwarded address would otherwise yield
|
|
133
|
+
// a null actor key, silently disabling per-actor rate/concurrency/bandwidth
|
|
134
|
+
// quotas that key off ctx.ip (fail-open). Fall through to the real peer
|
|
135
|
+
// address instead (matches the length-guarded reader below).
|
|
136
|
+
if (typeof req.ip === "string" && req.ip.length > 0) ctx.ip = req.ip;
|
|
132
137
|
else if (req.connection && typeof req.connection.remoteAddress === "string") {
|
|
133
138
|
ctx.ip = req.connection.remoteAddress;
|
|
134
139
|
} else if (req.socket && typeof req.socket.remoteAddress === "string") {
|
package/lib/restore-rollback.js
CHANGED
|
@@ -211,8 +211,12 @@ async function rollback(opts) {
|
|
|
211
211
|
"rollback: opts.dataDir is required");
|
|
212
212
|
}
|
|
213
213
|
if (typeof opts.rollbackPath !== "string" || !nodeFs.existsSync(opts.rollbackPath)) {
|
|
214
|
+
// Name the resolved path (when one was given) so an operator — or the CLI's
|
|
215
|
+
// relative --rollback resolution — can see exactly which target was missing.
|
|
216
|
+
var _named = (typeof opts.rollbackPath === "string" && opts.rollbackPath.length > 0)
|
|
217
|
+
? " '" + opts.rollbackPath + "'" : "";
|
|
214
218
|
throw new RestoreRollbackError("restore-rollback/no-rollback",
|
|
215
|
-
"rollback: opts.rollbackPath is required and must exist");
|
|
219
|
+
"rollback: opts.rollbackPath" + _named + " is required and must exist");
|
|
216
220
|
}
|
|
217
221
|
|
|
218
222
|
// Move the current dataDir aside (so the rollback's rename target is empty)
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -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:
|
|
5
|
+
"serialNumber": "urn:uuid:212a25f7-e069-40b5-9c4b-b7f4658f9a8a",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-08-
|
|
8
|
+
"timestamp": "2026-08-02T04:21:19.772Z",
|
|
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.18.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.18.7",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.18.
|
|
25
|
+
"version": "0.18.7",
|
|
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.18.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.18.7",
|
|
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.18.
|
|
57
|
+
"ref": "@blamejs/core@0.18.7",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|