@blamejs/core 0.8.36 → 0.8.37
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/db.js +56 -1
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.8.x
|
|
10
10
|
|
|
11
|
+
- **0.8.37** (2026-05-08) — D-L family SQL/schema hygiene. **`_blamejs_audit_purge_anchor.scope` CHECK constraint** — `scope IN ('audit', 'consent')`. Pre-v0.8.37 a typo silently created a parallel anchor; the chain verifier walked the wrong anchor and missed tampering. **`personalDataCategories` vocabulary validation** — operator-supplied categories validated against the GDPR Article 9 special-category vocabulary + the framework's general categories at `db.init`. Unknown categories don't refuse (operators have legitimate custom labels) but emit a `db.personal_data_category_unknown` audit row so typos surface in regulator reviews. Allowed: `name`, `email`, `phone`, `address`, `ip`, `id-document`, `biometric`, `health`, `genetic`, `sexual-orientation`, `racial-or-ethnic-origin`, `political-opinion`, `religious-belief`, `trade-union-membership`, `criminal-record`, `financial`, `location`, `behavioral`, `device-id`, `child-data`, `education`, `employment`, `operator-defined`.
|
|
12
|
+
|
|
11
13
|
- **0.8.36** (2026-05-08) — HTTP/web G-class LOW cleanup + scope-aware bearer challenge. **WS handshake (RFC 6455 §4.1)** — `Sec-WebSocket-Key` validated as base64 of 16 random bytes (`/^[A-Za-z0-9+/]{22}==$/`). Pre-v0.8.36 only the presence was checked; truncated / arbitrary-token values flowed through. **Permissions-Policy default** — `fullscreen` flipped to `()` (deny) instead of `(self)`; operators wanting fullscreen pass an explicit override. **`b.middleware.bearerAuth` insufficient_scope (RFC 6750 §3)** — new `requiredScopes: ["scope1", "scope2"]` opt enforces operator-declared scopes. Token's `user.scope` (string, space-separated) or `user.scopes` (array) is checked; missing scopes refuse with HTTP 403 + `WWW-Authenticate: Bearer error="insufficient_scope", scope="..."`. **`b.requestHelpers.parseListHeader({strictToken: true})`** — RFC 9110 §5.6.2 token-grammar enforcement. Refuses non-token entries (anything outside `!#$%&'*+-.^_\\`|~` + alnum). **Multipart boundary validation (RFC 2046 §5.1.1)** — `_parseMultipart` refuses boundaries longer than 70 chars OR violating the `bcharsnospace` grammar. Closes the quadratic-match risk on pathological boundaries.
|
|
12
14
|
|
|
13
15
|
- **0.8.35** (2026-05-08) — `b.tcpa10dlc` + `b.iabMspa` primitives. **`b.tcpa10dlc`** — TCPA 10DLC consent-record audit. 47 USC §227 + 47 CFR §64.1200 + FCC 1:1 disclosure rule (effective 2025-01-27, vacated 11th Circuit IMC v. FCC 2025 but TCPA standard still applies). $500-$1,500/violation exposure. `recordConsent({phoneE164, brand, disclosureText, disclosurePartyKind, formUrl, ip, userAgent})` writes a tamper-evident audit row with the carrier-required fields; `lookup(phoneE164)` for the carrier "produce-on-demand" workflow; `revoke(phoneE164, reason)` records consumer-initiated opt-out with audit trail. **`b.iabMspa`** — IAB Multi-State Privacy Agreement / Global Privacy Platform (GPP) universal opt-out signal codec. `parseGpp(gppString)` decodes the framing (header + per-section payloads, section-id mapping for usnat / usca / usva / usco / usct / usut / usnv / usia / usde / usnj / ustx / usor / usmt / usnh). `checkOptOut(parsed, {dataUse, state})` returns `{ mustHonor, signals }` against operator-decoded section opt-outs (sale / sharing / targeted-ads / sensitive / child-data). `refuseProcessing(parsed, opts)` throws `IabMspaError` to halt the operator's data-flow at the same point a CCPA "do-not-sell" header would. `gpcFromHeaders(req)` reads the W3C `Sec-GPC: 1` browser signal — universal opt-out per CCPA / CPRA §1798.135(b)(1) and similar state laws.
|
package/lib/db.js
CHANGED
|
@@ -245,7 +245,12 @@ var FRAMEWORK_SCHEMA = [
|
|
|
245
245
|
{
|
|
246
246
|
name: "_blamejs_audit_purge_anchor",
|
|
247
247
|
columns: {
|
|
248
|
-
|
|
248
|
+
// CHECK constraint: scope is one of the framework's audit-
|
|
249
|
+
// chain anchor scopes (`audit` / `consent`). Pre-v0.8.37 a
|
|
250
|
+
// typo silently created a parallel anchor; the chain verifier
|
|
251
|
+
// walked the wrong anchor and missed tampering. The CHECK
|
|
252
|
+
// refuses unknown scope strings at INSERT time.
|
|
253
|
+
scope: "TEXT PRIMARY KEY CHECK (scope IN ('audit', 'consent'))",
|
|
249
254
|
lastPurgedCounter: "INTEGER NOT NULL",
|
|
250
255
|
lastPurgedRowHash: "TEXT NOT NULL",
|
|
251
256
|
archiveBundleId: "TEXT NOT NULL",
|
|
@@ -780,6 +785,56 @@ async function init(opts) {
|
|
|
780
785
|
for (var si = 0; si < opts.schema.length; si++) {
|
|
781
786
|
var st = opts.schema[si];
|
|
782
787
|
if (st.subjectField) {
|
|
788
|
+
// Validate personalDataCategories shape + audit-emit on
|
|
789
|
+
// unknown vocabulary. Pre-v0.8.37 this was a free-form JSON
|
|
790
|
+
// blob; a typo silently dropped the column from subject-export
|
|
791
|
+
// / erase walks. The framework checks the value is a string
|
|
792
|
+
// (catches null / number / object typos) and emits a warning
|
|
793
|
+
// audit when the category is outside the GDPR Art 9 + general
|
|
794
|
+
// vocabulary so operators can audit-trail their custom labels.
|
|
795
|
+
if (st.personalDataCategories) {
|
|
796
|
+
if (typeof st.personalDataCategories !== "object" || Array.isArray(st.personalDataCategories)) {
|
|
797
|
+
throw new DbError("db/bad-personal-data-categories",
|
|
798
|
+
"table '" + st.name + "': personalDataCategories must be an object mapping field name → category");
|
|
799
|
+
}
|
|
800
|
+
var FRAMEWORK_CATEGORY_VOCAB = [
|
|
801
|
+
"name", "email", "phone", "address", "ip", "id-document",
|
|
802
|
+
"biometric", "health", "genetic", "sexual-orientation",
|
|
803
|
+
"racial-or-ethnic-origin", "political-opinion", "religious-belief",
|
|
804
|
+
"trade-union-membership", "criminal-record",
|
|
805
|
+
"financial", "location", "behavioral", "device-id",
|
|
806
|
+
"child-data", "education", "employment", "operator-defined",
|
|
807
|
+
];
|
|
808
|
+
Object.keys(st.personalDataCategories).forEach(function (field) {
|
|
809
|
+
var cat = st.personalDataCategories[field];
|
|
810
|
+
if (typeof cat !== "string" || cat.length === 0) {
|
|
811
|
+
throw new DbError("db/bad-personal-data-category",
|
|
812
|
+
"table '" + st.name + "' field '" + field +
|
|
813
|
+
"': category must be a non-empty string");
|
|
814
|
+
}
|
|
815
|
+
if (FRAMEWORK_CATEGORY_VOCAB.indexOf(cat) === -1) {
|
|
816
|
+
// Unknown — emit a one-time audit per (table,field,category)
|
|
817
|
+
// tuple so operators see typos in their categorical
|
|
818
|
+
// taxonomy. Lazy require to avoid circular load (audit
|
|
819
|
+
// imports db for chain hashing).
|
|
820
|
+
try {
|
|
821
|
+
var auditMod = require("./audit"); // allow:inline-require — circular-load defense (audit imports db)
|
|
822
|
+
auditMod.safeEmit({
|
|
823
|
+
action: "db.personal_data_category_unknown",
|
|
824
|
+
outcome: "success",
|
|
825
|
+
metadata: {
|
|
826
|
+
severity: "warning",
|
|
827
|
+
table: st.name,
|
|
828
|
+
field: field,
|
|
829
|
+
category: cat,
|
|
830
|
+
vocabHint: "use one of: " + FRAMEWORK_CATEGORY_VOCAB.join(", ") +
|
|
831
|
+
" (or operator-defined for genuinely-custom)",
|
|
832
|
+
},
|
|
833
|
+
});
|
|
834
|
+
} catch (_e) { /* drop-silent */ }
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
}
|
|
783
838
|
subjectTables.push({
|
|
784
839
|
name: st.name,
|
|
785
840
|
subjectField: st.subjectField,
|
package/package.json
CHANGED
package/sbom.cyclonedx.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:2a183a44-815b-479c-8f1f-f705a55d7749",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-07T15:
|
|
8
|
+
"timestamp": "2026-05-07T15:51:51.404Z",
|
|
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.8.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.8.37",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.8.
|
|
25
|
+
"version": "0.8.37",
|
|
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.8.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.8.37",
|
|
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.8.
|
|
57
|
+
"ref": "@blamejs/core@0.8.37",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|