@blamejs/core 0.16.10 → 0.16.11

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.16.x
10
10
 
11
+ - v0.16.11 (2026-07-11) — **Fix a TOML parser bug where every double-quoted key decoded to the empty string — which also let a quoted "__proto__" key slip past the prototype-pollution guard — and return a real 201 from SCIM resource creation, both surfaced by covering previously-untested error and adversarial branches.** Writing behavioral tests for the uncovered error, adversarial, and defensive branches of the framework's most under-tested files surfaced two genuine defects, now fixed at the root. b.parsers.toml decoded every double-quoted (basic) key to the empty string: a document like "host" = "db1" parsed to { "": "db1" }, multiple distinct quoted keys collided onto one empty key, and — the security consequence — a quoted "__proto__" or "constructor" key decoded to the empty string and so slipped past the poisoned-key guard that rejects those names. TOML keys are usually written bare, so the bug was latent since the parser shipped. It is fixed to decode quoted keys correctly, and a quoted "__proto__" / "constructor" is now rejected with toml/poisoned-key. Separately, the SCIM server returned an undefined HTTP status instead of 201 Created when creating a User or Group singleton, because it referenced a status constant that did not exist; it now returns 201 with the created representation. **Fixed:** *b.parsers.toml decodes double-quoted keys correctly* — A basic (double-quoted) key with no escape sequence decoded to the empty string — "host" = "db1" became { "": "db1" }, and multiple distinct quoted keys in one table collided onto a single empty key. The decoder now appends the verbatim key run for both quoted-key kinds, so quoted, literal, dotted, and escaped keys all decode to their intended names. · *SCIM resource creation returns 201 Created* — Creating a User or Group via POST returned an undefined HTTP status because the handler referenced a CREATED status constant that was not defined, so the created representation was written with no valid status code. It now returns 201 with the created resource. **Security:** *b.parsers.toml rejects a quoted __proto__ / constructor key* — The double-quoted-key decoder never appended the key's characters for a basic key, so every double-quoted key resolved to the empty string. A quoted "__proto__" = ... or "constructor" = ... therefore decoded to "", which the poisoned-key guard does not flag, letting those names through. Quoted keys now decode to their real value, and a quoted __proto__ / constructor is rejected with toml/poisoned-key. Bare-key documents were unaffected; only double-quoted keys were mis-decoded.
12
+
11
13
  - v0.16.10 (2026-07-11) — **Restore five APIs that silently didn't work — certificate-fingerprint hashing from PEM, the tus-upload close handler, the mail-delivery factory, the log-stream local-sink env boot, and the CIBA internal-IdP opt — and make single-base guard profile composition work, all surfaced by writing a test for every documented primitive.** Writing a behavioral test for every documented primitive that previously had none surfaced a batch of APIs that were advertised but silently non-working, now fixed at the root. b.crypto.hashCertFingerprint and b.crypto.isCertRevoked threw a TypeError on the PEM-string input their own examples pass (a helper was called on its lazy-require getter instead of the module), so only the raw-DER path worked; b.middleware.tusUpload.close and b.mail.send.deliver.create were documented but never attached to their export, so calling the documented form threw 'not a function'; b.logStream.bootFromEnv advertised a 'local' protocol that threw at init because it wired the sink with the wrong option; and the CIBA client dropped the allowInternal opt on its own backchannel and token POSTs, so an operator who explicitly opted into an internal or loopback identity provider was still SSRF-blocked. Separately, gate-contract profile composition now honors its documented extends: string | string[] contract — a single base profile name passed as a bare string was silently ignored, which made the buildProfile example every guard shares a no-op. The EU AI Act fundamental-rights-impact-assessment and GPAI training-data-summary builders now raise typed ComplianceError refusals with descriptive messages instead of a bare Error whose message was only the error code. Five documentation examples are corrected to their real emitted values (the SRI integrity attribute, two guard rule-ids, a guard issue kind, and the guard-archive profile-composition call). The static gate that requires every documented primitive to be referenced by a test now has an empty backlog register: every documented primitive is exercised. **Added:** *A test for every documented primitive* — The primitive-without-test static gate's backlog register, which listed documented primitives that no test referenced, is now empty: every documented primitive is exercised by a behavioral test through its real consumer path. A new primitive must land with its own test reference; the register accepts an entry only with a specific documented reason coverage is indirect. **Changed:** *Guard profile composition accepts a single base profile as a bare string* — gate-contract profile composition documents extends: string | string[], but a bare-string extends was silently ignored — only the array form composed — which made the buildProfile example every guard shares (buildProfile({ extends: "strict" })) a no-op that returned an empty caps object. A string extends is now normalized to a one-element list, so single-base composition works across every b.guard*.buildProfile. The array form is unchanged. **Fixed:** *b.crypto.hashCertFingerprint / isCertRevoked accept PEM input again* — Both threw 'TypeError: safeBuffer.byteLengthOf is not a function' on a PEM-string certificate — the internal PEM-to-DER step called the byte-length helper on its lazy-require getter rather than the resolved module, so only the pre-parsed DER/Buffer path (which returns before that line) worked. Their documented examples pass a PEM string, so the advertised behavior was silently broken. Fixed at the call site. · *b.middleware.tusUpload.close is reachable* — The middleware export wired tusUpload.create and .memoryStore but never attached the documented .close handler, so b.middleware.tusUpload.close(middleware) threw 'not a function'. It is now attached to the export. · *b.mail.send.deliver.create is reachable* — b.mail.send.deliver was bound directly to the factory function but never carried the documented .create property, so the documented b.mail.send.deliver.create(opts) form resolved to undefined. The .create factory is now attached; the existing collapsed b.mail.send.deliver(opts) callable is unchanged. · *b.logStream.bootFromEnv local protocol works* — BLAMEJS_LOG_STREAM_PROTOCOL=local threw 'log-stream local requires { dir }' at init: bootFromEnv wired the local sink with a path option, but the directory-based local sink requires dir. BLAMEJS_LOG_STREAM_PATH is now mapped to the sink directory, so the advertised local protocol boots and writes end-to-end. · *CIBA client honors allowInternal on its own requests* — The backchannel-authentication and token POSTs of the CIBA client dropped the allowInternal option that create() accepts and threads to the OAuth client for discovery/JWKS, so an operator who explicitly set allowInternal:true for an internal or loopback identity provider was still refused with ssrf-guard/blocked-loopback on exactly those two endpoints. The option is now threaded through per request. The SSRF guard stays on by default; nothing changes unless the operator opts in. · *EU AI Act FRIA / training-data-summary raise typed refusals with real messages* — b.compliance.aiAct.fundamentalRightsImpactAssessment and b.compliance.aiAct.gpai.trainingDataSummary validated required fields by passing the built-in Error as the error class, so the descriptive message was dropped (its slot became the Error options bag) and the thrown message was only the error code. Both now raise a typed ComplianceError with the descriptive message and a stable code, matching the sibling adherenceForm builder. · *Five documentation examples corrected to real values* — The b.crypto.sri example carried a fabricated SHA-384 value (an operator copy-pasting the integrity attribute would get a mismatch); it now shows the correct digest. The b.guardTime.validate example named rule-id time.year-out-of-range (actual time.year-window); b.guardUuid named uuid.nil / uuid.max (actual uuid.nil-uuid / uuid.max-uuid); b.htmlBalance.checkSafe named issue kind event-handler-attribute (actual event-handler). The b.guardArchive.buildProfile example passed { profile: "strict" }, which resolves no caps; it now uses the correct { extends: "strict" } form.
12
14
 
13
15
  - v0.16.9 (2026-07-10) — **`b.safeBuffer.quoteString` — one RFC quoted-string serializer behind eight header and protocol emitters — plus a consolidated per-primitive test suite and a static gate that refuses a documented primitive no test references.** Eight places in the framework serialized an RFC quoted-string by hand — escape backslash and DQUOTE, wrap in DQUOTEs — for Cache-Status and Signature-Input sf-strings, Link header parameters, Authentication-Results reason, IMAP quoted strings and METADATA values, ManageSieve responses, and Server-Timing descriptions. Those copies are consolidated into one primitive, b.safeBuffer.quoteString, so an unescaped quote can never terminate a string early and smuggle extra parameters into a protocol line, and the escaping cannot drift between emitters. Output is byte-identical to before. The test suite is reorganized so every primitive's tests live in a canonical per-primitive file (thirty-eight auxiliary files folded or renamed; every assertion preserved — the full suite passes with the exact same check count). On top of that reorganization, the comment-block validator gains a primitive-without-test check: a documented @primitive that no test references — neither the full dotted form nor its namespace plus a method invocation — now fails the static gate. One hundred six existing primitives currently lack any test reference; they are recorded in an explicit shrink-only register in the validator, each entry an open test-backfill item, and the gate refuses any new primitive shipping untested. **Added:** *b.safeBuffer.quoteString — RFC quoted-string serialization* — Coerces to string, escapes every backslash and DQUOTE with a leading backslash, and wraps the result in DQUOTEs — the quoted-string grammar shared by RFC 8941 §3.3.3 Structured Fields sf-string, RFC 8288 Link header parameters, RFC 8601 §2.2 Authentication-Results reason, RFC 3501 §4.3 IMAP quoted strings, and RFC 5804 §1.2 ManageSieve strings. Escaping only: a grammar with a restricted character range (sf-string is printable-ASCII; IMAP quoted strings cannot carry CR/LF) enforces its range check before calling it. · *Static gate: a documented primitive must be referenced by a test* — The comment-block validator now scans the test corpus for every @primitive: the full dotted form (b.namespace.method) anywhere under test/, or the namespace / owning module referenced together with the method invoked in the same test file — the same-file rule stops a ubiquitous method name like .create( in an unrelated test from counting as coverage. A primitive with no reference fails the pre-push static gate. One hundred six existing primitives currently have no reference; they are listed in a shrink-only register inside the validator — each entry an open test-backfill item that is deleted when its test lands — and a new primitive cannot ship untested. **Changed:** *Eight quoted-string emitters route through the one serializer* — Cache-Status key/detail, Signature-Input sf-strings, Link header parameter values, Authentication-Results reason, IMAP METADATA values and quoted strings, ManageSieve OK/NO/BYE/LISTSCRIPTS responses, and Server-Timing desc now compose b.safeBuffer.quoteString instead of hand-rolling the escape. Emitted bytes are unchanged. · *Per-primitive canonical test files* — Thirty-eight auxiliary test files are folded into (or renamed to) their primitive's canonical test file — one file per primitive domain, concern-named splits kept (e.g. http-client-cache / -stream / -throttle). No assertion was changed, added, or removed by the reorganization; the full suite passes with the exact same check count before and after. The b.*-surface reference gate that previously ran inside the smoke suite is superseded by the stronger validator check above. **Detectors:** *rfc-quoted-string-escape-owned-by-safeBuffer* — Flags an inline backslash-then-DQUOTE escape chain in lib/ outside the primitive's home — the quoted-string serializer re-implemented, whose escaping can drift from the canonical one. While being proven against the pre-consolidation tree it caught an eighth emitter the manual sweep had missed (an IMAP response-literal helper), which is consolidated in this release too.
@@ -262,7 +262,7 @@ async function _dispatch(req, res, basePath, bearer, opts, maxPageSize, bulkCfg)
262
262
  if (req.method === "POST" && !resourceId) {
263
263
  _assertSchema(body, resourceType === "Users" ? SCIM_CORE_SCHEMA_USER : SCIM_CORE_SCHEMA_GROUP);
264
264
  var created = await impl.create(body, ctx);
265
- _writeJson(res, H.CREATED, created);
265
+ _writeJson(res, 201, created); // 201 Created (HTTP_STATUS has no CREATED key)
266
266
  return;
267
267
  }
268
268
 
@@ -201,7 +201,16 @@ function parse(input, opts) {
201
201
  while (!_eof()) {
202
202
  var c = _peek();
203
203
  if (c === quote) {
204
- if (!basic) out = input.substring(start, pos);
204
+ // Append the pending verbatim run for BOTH kinds. A basic key
205
+ // accumulates decoded escapes into `out` and resets `start` past
206
+ // each; the trailing literal run (or the whole key when it has no
207
+ // escapes) must still be appended here. A literal key never
208
+ // touches `out` before this point, so the append yields its full
209
+ // verbatim content. Assigning only for the literal branch dropped
210
+ // every escape-free basic key to "" — collapsing distinct quoted
211
+ // keys together and letting a quoted `"__proto__"` slip past the
212
+ // poisoned-key guard by decoding to the empty string.
213
+ out += input.substring(start, pos);
205
214
  _advance();
206
215
  return out;
207
216
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.16.10",
3
+ "version": "0.16.11",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
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:1c234c55-1297-48c3-bbd2-7b7b1f134fa5",
5
+ "serialNumber": "urn:uuid:97a20bb6-c995-41f3-9276-1b4e0fb8e9d2",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-07-11T03:08:14.929Z",
8
+ "timestamp": "2026-07-11T06:28:18.910Z",
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.16.10",
22
+ "bom-ref": "@blamejs/core@0.16.11",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.16.10",
25
+ "version": "0.16.11",
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.16.10",
29
+ "purl": "pkg:npm/%40blamejs/core@0.16.11",
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.16.10",
57
+ "ref": "@blamejs/core@0.16.11",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]