@blamejs/core 0.8.27 → 0.8.29

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
Binary file
package/index.js CHANGED
@@ -104,6 +104,7 @@ var budr = require("./lib/budr");
104
104
  var secCyber = require("./lib/sec-cyber");
105
105
  var iabTcf = require("./lib/iab-tcf");
106
106
  var fapi2 = require("./lib/fapi2");
107
+ var contentCredentials = require("./lib/content-credentials");
107
108
  var safeUrl = require("./lib/safe-url");
108
109
  var safeRedirect = require("./lib/safe-redirect");
109
110
  var pick = require("./lib/pick");
@@ -295,6 +296,7 @@ module.exports = {
295
296
  secCyber: secCyber,
296
297
  iabTcf: iabTcf,
297
298
  fapi2: fapi2,
299
+ contentCredentials: contentCredentials,
298
300
  safeUrl: safeUrl,
299
301
  safeRedirect: safeRedirect,
300
302
  pick: pick,
package/lib/audit.js CHANGED
@@ -242,6 +242,7 @@ var FRAMEWORK_NAMESPACES = [
242
242
  "seccyber", // b.secCyber (seccyber.eight_k_artifact)
243
243
  "iabtcf", // b.iabTcf (iabtcf.refused / iabtcf.accepted)
244
244
  "fapi2", // b.fapi2 (fapi2.posture_asserted)
245
+ "contentcredentials", // b.contentCredentials (contentcredentials.signed / verified)
245
246
  ];
246
247
  var registeredNamespaces = new Set(FRAMEWORK_NAMESPACES);
247
248
 
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+ /**
3
+ * b.contentCredentials — California SB-942 / AB-853 + C2PA 2.1
4
+ * content-provenance manifest builder for AI-generated assets.
5
+ *
6
+ * California SB-942 (Cal. Bus. & Prof. Code §22757) + AB-853, both
7
+ * effective 2026-08-02, require providers of generative AI systems
8
+ * to embed a latent (machine-readable) provenance disclosure in
9
+ * every AI-generated image / video / audio asset distributed in
10
+ * California. The disclosure MUST carry:
11
+ *
12
+ * - Provider name
13
+ * - System (model) identifier + version
14
+ * - Content timestamp (when generated)
15
+ * - Unique content ID
16
+ *
17
+ * SB-942 specifically cites C2PA (Coalition for Content Provenance
18
+ * and Authenticity) as an acceptable disclosure format. C2PA 2.1+
19
+ * manifests carry signed assertions with the same fields.
20
+ *
21
+ * The framework can't embed the manifest into image/video/audio
22
+ * bytes directly (that requires format-specific muxers — JPEG XMP /
23
+ * PNG iTXt / MP4 ContentBoxes / etc. that vary per codec). What it
24
+ * CAN do:
25
+ *
26
+ * - Build a C2PA-shaped manifest carrying the required fields.
27
+ * - Sign the manifest with the framework's audit-sign keypair
28
+ * (ML-DSA-87 — or operator-supplied SigStore key).
29
+ * - Emit a tamper-evident audit row recording the disclosure.
30
+ * - Validate inbound manifests presented by upstream content
31
+ * pipelines (the receiver side of the same chain).
32
+ *
33
+ * Operator workflow:
34
+ *
35
+ * var manifest = b.contentCredentials.build({
36
+ * provider: "Acme AI Inc.",
37
+ * system: "acme-image-v3",
38
+ * systemVersion: "3.2.1",
39
+ * contentId: "img-2026-05-08-abc123",
40
+ * contentType: "image/png",
41
+ * contentSha3: hashHex,
42
+ * // operator's display attribution + machine-readable fields
43
+ * });
44
+ * var signed = b.contentCredentials.sign(manifest, { signWith: ... });
45
+ * // operator hands `signed.manifest` to their muxer for embedding
46
+ *
47
+ * Public API:
48
+ *
49
+ * contentCredentials.build(opts) -> manifest (unsigned)
50
+ * contentCredentials.sign(manifest, opts) -> { manifest, signature }
51
+ * contentCredentials.verify(envelope, publicKeyPem) -> { valid, claims }
52
+ * contentCredentials.required(opts) -> array of missing-field errors
53
+ * (returns [] when the operator's input satisfies SB-942 minimums)
54
+ */
55
+
56
+ var crypto = require("./crypto");
57
+ var canonicalJson = require("./canonical-json");
58
+ var validateOpts = require("./validate-opts");
59
+ var audit = require("./audit");
60
+ var { defineClass } = require("./framework-error");
61
+ var ContentCredentialsError = defineClass("ContentCredentialsError", { alwaysPermanent: true });
62
+
63
+ var STR_LEN_MAX = 256; // allow:raw-byte-literal — string-length cap, not bytes
64
+ var ID_LEN_MAX = 128; // allow:raw-byte-literal — string-length cap, not bytes
65
+ var SEMVER_RE = /^[0-9]+\.[0-9]+(?:\.[0-9]+)?(?:[-+][A-Za-z0-9.-]+)?$/;
66
+ var ID_RE = /^[a-zA-Z0-9._:/-]{1,128}$/;
67
+ var SHA3_HEX_LEN = 128; // allow:raw-byte-literal — SHA3-512 hex length, not bytes
68
+
69
+ // Required fields per SB-942 §22757(a) — every AI-generated asset
70
+ // must disclose provider + system + timestamp + contentId.
71
+ var REQUIRED_FIELDS = ["provider", "system", "systemVersion", "contentId"];
72
+
73
+ function _validateBuildOpts(opts) {
74
+ if (!opts || typeof opts !== "object") {
75
+ throw ContentCredentialsError.factory("BAD_OPTS",
76
+ "contentCredentials.build: opts required");
77
+ }
78
+ for (var i = 0; i < REQUIRED_FIELDS.length; i += 1) {
79
+ var f = REQUIRED_FIELDS[i];
80
+ validateOpts.requireNonEmptyString(opts[f],
81
+ "contentCredentials.build: " + f, ContentCredentialsError, "MISSING_" + f.toUpperCase());
82
+ }
83
+ if (opts.provider.length > STR_LEN_MAX) {
84
+ throw ContentCredentialsError.factory("BAD_PROVIDER",
85
+ "provider exceeds " + STR_LEN_MAX + " chars");
86
+ }
87
+ if (opts.system.length > ID_LEN_MAX || !ID_RE.test(opts.system)) {
88
+ throw ContentCredentialsError.factory("BAD_SYSTEM",
89
+ "system must match " + ID_RE);
90
+ }
91
+ if (opts.systemVersion.length > 64 || !SEMVER_RE.test(opts.systemVersion)) { // allow:raw-byte-literal — semver length cap, not bytes
92
+ throw ContentCredentialsError.factory("BAD_VERSION",
93
+ "systemVersion must be semver");
94
+ }
95
+ if (opts.contentId.length > ID_LEN_MAX || !ID_RE.test(opts.contentId)) {
96
+ throw ContentCredentialsError.factory("BAD_CONTENT_ID",
97
+ "contentId must match " + ID_RE);
98
+ }
99
+ if (opts.contentType !== undefined) {
100
+ if (typeof opts.contentType !== "string" || opts.contentType.length === 0 ||
101
+ opts.contentType.length > ID_LEN_MAX || !/^[a-zA-Z]+\/[A-Za-z0-9._+-]+$/.test(opts.contentType)) {
102
+ throw ContentCredentialsError.factory("BAD_CONTENT_TYPE",
103
+ "contentType must be a valid IANA media type");
104
+ }
105
+ }
106
+ if (opts.contentSha3 !== undefined) {
107
+ if (typeof opts.contentSha3 !== "string" || opts.contentSha3.length !== SHA3_HEX_LEN ||
108
+ !/^[a-f0-9]+$/i.test(opts.contentSha3)) {
109
+ throw ContentCredentialsError.factory("BAD_CONTENT_HASH",
110
+ "contentSha3 must be lowercase hex SHA3-512 (" + SHA3_HEX_LEN + " chars)");
111
+ }
112
+ }
113
+ }
114
+
115
+ function build(opts) {
116
+ _validateBuildOpts(opts);
117
+ var generatedAt = typeof opts.generatedAt === "number" ? opts.generatedAt : Date.now();
118
+ var manifest = {
119
+ "@context": "https://c2pa.org/specifications/specifications/2.1/",
120
+ type: "c2pa.manifest",
121
+ aiGenerated: true,
122
+ provider: {
123
+ name: opts.provider,
124
+ contact: opts.providerContact || null,
125
+ },
126
+ system: {
127
+ id: opts.system,
128
+ version: opts.systemVersion,
129
+ },
130
+ content: {
131
+ id: opts.contentId,
132
+ type: opts.contentType || null,
133
+ sha3_512: opts.contentSha3 || null,
134
+ },
135
+ generatedAt: generatedAt,
136
+ generatedAtIso: new Date(generatedAt).toISOString(),
137
+ citations: ["california-sb-942", "california-ab-853", "c2pa-2.1"],
138
+ // Optional operator-supplied display assertion (SB-942 §22757(b))
139
+ visibleDisclosure: opts.visibleDisclosure || null,
140
+ };
141
+ return Object.freeze(manifest);
142
+ }
143
+
144
+ function required(opts) {
145
+ var errors = [];
146
+ if (!opts || typeof opts !== "object") return ["opts-required"];
147
+ for (var i = 0; i < REQUIRED_FIELDS.length; i += 1) {
148
+ if (typeof opts[REQUIRED_FIELDS[i]] !== "string" || opts[REQUIRED_FIELDS[i]].length === 0) {
149
+ errors.push("missing-" + REQUIRED_FIELDS[i]);
150
+ }
151
+ }
152
+ return errors;
153
+ }
154
+
155
+ function sign(manifest, opts) {
156
+ opts = opts || {};
157
+ if (!manifest || typeof manifest !== "object") {
158
+ throw ContentCredentialsError.factory("BAD_MANIFEST",
159
+ "contentCredentials.sign: manifest required");
160
+ }
161
+ validateOpts.requireNonEmptyString(opts.privateKeyPem,
162
+ "contentCredentials.sign: privateKeyPem", ContentCredentialsError, "BAD_KEY");
163
+ var canonical = canonicalJson.stringify(manifest);
164
+ var signature = crypto.sign(Buffer.from(canonical, "utf8"), opts.privateKeyPem);
165
+ var auditOn = opts.audit !== false;
166
+ if (auditOn) {
167
+ audit.safeEmit({
168
+ action: "contentcredentials.signed",
169
+ outcome: "success",
170
+ metadata: {
171
+ provider: manifest.provider && manifest.provider.name,
172
+ system: manifest.system && manifest.system.id,
173
+ contentId: manifest.content && manifest.content.id,
174
+ },
175
+ });
176
+ }
177
+ return {
178
+ manifest: manifest,
179
+ signature: signature.toString("base64"),
180
+ };
181
+ }
182
+
183
+ function verify(envelope, publicKeyPem, opts) {
184
+ opts = opts || {};
185
+ if (!envelope || typeof envelope !== "object" || !envelope.manifest || !envelope.signature) {
186
+ return { valid: false, claims: null, reason: "envelope-shape" };
187
+ }
188
+ if (typeof publicKeyPem !== "string" || publicKeyPem.length === 0) {
189
+ return { valid: false, claims: null, reason: "public-key-required" };
190
+ }
191
+ var canonical = canonicalJson.stringify(envelope.manifest);
192
+ var sigBuf;
193
+ try { sigBuf = Buffer.from(envelope.signature, "base64"); }
194
+ catch (_e) {
195
+ return { valid: false, claims: null, reason: "signature-base64-bad" };
196
+ }
197
+ var ok = crypto.verify(Buffer.from(canonical, "utf8"), sigBuf, publicKeyPem);
198
+ if (!ok) {
199
+ return { valid: false, claims: null, reason: "signature-mismatch" };
200
+ }
201
+ // SB-942 §22757(a) field-presence check on the verified manifest.
202
+ var missing = required({
203
+ provider: envelope.manifest.provider && envelope.manifest.provider.name,
204
+ system: envelope.manifest.system && envelope.manifest.system.id,
205
+ systemVersion: envelope.manifest.system && envelope.manifest.system.version,
206
+ contentId: envelope.manifest.content && envelope.manifest.content.id,
207
+ });
208
+ if (missing.length > 0) {
209
+ return { valid: false, claims: null, reason: "missing-required:" + missing.join(",") };
210
+ }
211
+ if (opts.audit !== false) {
212
+ audit.safeEmit({
213
+ action: "contentcredentials.verified",
214
+ outcome: "success",
215
+ metadata: {
216
+ provider: envelope.manifest.provider.name,
217
+ system: envelope.manifest.system.id,
218
+ contentId: envelope.manifest.content.id,
219
+ },
220
+ });
221
+ }
222
+ return { valid: true, claims: envelope.manifest, reason: null };
223
+ }
224
+
225
+ module.exports = {
226
+ build: build,
227
+ sign: sign,
228
+ verify: verify,
229
+ required: required,
230
+ REQUIRED_FIELDS: REQUIRED_FIELDS.slice(),
231
+ ContentCredentialsError: ContentCredentialsError,
232
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.8.27",
3
+ "version": "0.8.29",
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:7acc2751-65c7-408f-8206-0688a7f5439e",
5
+ "serialNumber": "urn:uuid:34fc0ebb-9c11-4245-a15a-7919aa7897e2",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-07T13:52:40.926Z",
8
+ "timestamp": "2026-05-07T14:08:07.987Z",
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.27",
22
+ "bom-ref": "@blamejs/core@0.8.29",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.8.27",
25
+ "version": "0.8.29",
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.27",
29
+ "purl": "pkg:npm/%40blamejs/core@0.8.29",
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.27",
57
+ "ref": "@blamejs/core@0.8.29",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]