@aikdna/kdna-core 0.10.0 → 0.11.0

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/v1/index.js +60 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikdna/kdna-core",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "KDNA core library — load, validate, lint, and render KDNA domain judgment assets. Supports KDNA Container format (payload.kdnab via CBOR).",
5
5
  "type": "commonjs",
6
6
  "main": "src/index.js",
package/src/v1/index.js CHANGED
@@ -37,6 +37,7 @@
37
37
  const fs = require('node:fs');
38
38
  const path = require('node:path');
39
39
  const zlib = require('node:zlib');
40
+ const crypto = require('node:crypto');
40
41
 
41
42
  const MIMETYPE_V1 = 'application/vnd.kdna.asset';
42
43
  const MIMETYPE_V2 = 'application/vnd.aikdna.kdna+zip';
@@ -431,7 +432,7 @@ function buildInspectOutput(v1) {
431
432
  load_contract_default_profile: m.load_contract ? m.load_contract.default_profile : null,
432
433
  };
433
434
  if (m.signatures !== undefined) out.signature_count = Array.isArray(m.signatures) ? m.signatures.length : 0;
434
- if (v1.map.checksums) out.checksums_present = true;
435
+ if (v1.map['checksums.json']) out.checksums_present = true;
435
436
  return out;
436
437
  }
437
438
 
@@ -498,10 +499,10 @@ function runValidate(v1) {
498
499
  }
499
500
 
500
501
  // checksums gate — checksums.json against checksums.schema.json
501
- if (v1.map.checksums) {
502
+ if (v1.map['checksums.json']) {
502
503
  let checks;
503
504
  try {
504
- checks = JSON.parse(v1.map.checksums.toString('utf8'));
505
+ checks = JSON.parse(v1.map['checksums.json'].toString('utf8'));
505
506
  } catch (e) {
506
507
  result.checksums_valid = false;
507
508
  problems.push(`checksums: not valid JSON (${e.message})`);
@@ -512,6 +513,10 @@ function runValidate(v1) {
512
513
  problems.push(`checksums: ${err.instancePath || '<root>'} ${err.message}`);
513
514
  }
514
515
  }
516
+ // Digest matching verification — compute actual hashes and compare.
517
+ if (checks) {
518
+ result.checksums_valid = verifyDigests(checks, v1.map, problems, result);
519
+ }
515
520
  }
516
521
 
517
522
  // load_contract gate — only if manifest references a load_contract block
@@ -532,6 +537,38 @@ function runValidate(v1) {
532
537
  return finalizeValidate(result, problems);
533
538
  }
534
539
 
540
+ function verifyDigests(checksums, map, problems, result) {
541
+ const algo = checksums.algorithm || 'sha256';
542
+ if (algo !== 'sha256') {
543
+ problems.push(`checksums: unsupported digest algorithm ${algo} (supported: sha256)`);
544
+ result.checksums_valid = false;
545
+ return;
546
+ }
547
+ const entryMap = {
548
+ manifest_digest: 'kdna.json',
549
+ payload_digest: 'payload.kdnab',
550
+ };
551
+ let stillValid = true;
552
+ for (const [digestKey, entryName] of Object.entries(entryMap)) {
553
+ const declared = checksums[digestKey];
554
+ if (!declared) continue;
555
+ if (!map[entryName]) {
556
+ problems.push(`checksums: ${digestKey} references missing entry ${entryName}`);
557
+ stillValid = false;
558
+ continue;
559
+ }
560
+ const entryBytes = map[entryName];
561
+ const actual = crypto.createHash('sha256').update(entryBytes).digest('hex');
562
+ const expected = declared.replace(/^sha256:/, '');
563
+ if (actual !== expected) {
564
+ problems.push(`checksums: ${digestKey} mismatch (declared ${expected.slice(0, 8)}..., actual ${actual.slice(0, 8)}...)`);
565
+ stillValid = false;
566
+ }
567
+ }
568
+ if (!stillValid) result.checksums_valid = false;
569
+ return stillValid;
570
+ }
571
+
535
572
  function finalizeValidate(result, problems) {
536
573
  result.overall_valid =
537
574
  result.format_valid &&
@@ -735,6 +772,26 @@ function loadV1(inputPath, opts = {}) {
735
772
  throw err;
736
773
  }
737
774
 
775
+ // Digest verification — refuse to load if checksums.json is present and digests mismatch.
776
+ if (v1.map['checksums.json']) {
777
+ try {
778
+ const checks = JSON.parse(v1.map['checksums.json'].toString('utf8'));
779
+ const problems = [];
780
+ const ok = verifyDigests(checks, v1.map, problems, {});
781
+ if (!ok) {
782
+ const err = new Error(`checksum verification failed: ${problems.join('; ')}`);
783
+ err.code = 'checksum_mismatch';
784
+ throw err;
785
+ }
786
+ } catch (e) {
787
+ if (e.code === 'checksum_mismatch') throw e;
788
+ // Invalid JSON — already caught by validate, but still refuse to load.
789
+ const err = new Error(`checksums.json is not valid: ${e.message}`);
790
+ err.code = 'checksum_parse_error';
791
+ throw err;
792
+ }
793
+ }
794
+
738
795
  const result = { status: 'loaded', profile, asset_id: m.asset_id, title: m.title };
739
796
 
740
797
  if (profile === 'index') {