@ai-ecoverse/slicc-kernel 1.81.0 → 1.82.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.
package/dist/node.js CHANGED
@@ -3033,6 +3033,97 @@ var MemoryMeta = class {
3033
3033
  }
3034
3034
  };
3035
3035
 
3036
+ // src/fs/integrity.ts
3037
+ var KERNEL_CALLER = { pid: 0, uid: 0 };
3038
+ function integrityOptions(options) {
3039
+ if (options && typeof options.lookup !== "function") {
3040
+ throw new TypeError("protect.integrity.lookup must be a function");
3041
+ }
3042
+ integrityUnknown(options);
3043
+ }
3044
+ function integrityUnknown(options) {
3045
+ const unknown = options?.unknown ?? "allow";
3046
+ if (unknown !== "allow" && unknown !== "deny") {
3047
+ throw new TypeError("protect.integrity.unknown must be 'allow' or 'deny'");
3048
+ }
3049
+ return unknown;
3050
+ }
3051
+ function hex(digest) {
3052
+ return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
3053
+ }
3054
+ async function sha256(bytes) {
3055
+ return hex(await crypto.subtle.digest("SHA-256", bytes));
3056
+ }
3057
+ var Integrity = class {
3058
+ prefixes;
3059
+ root;
3060
+ lookup;
3061
+ unknown;
3062
+ report;
3063
+ unverified = /* @__PURE__ */ new Set();
3064
+ constructor(prefixes2, root, options, report) {
3065
+ this.prefixes = prefixes2;
3066
+ this.root = root;
3067
+ this.lookup = options.lookup;
3068
+ this.unknown = integrityUnknown(options);
3069
+ this.report = report;
3070
+ }
3071
+ async real(path) {
3072
+ const at = normalizePath(path);
3073
+ const real = await this.root.realpath?.(at, true).catch(() => void 0) ?? at;
3074
+ return isProtected(this.prefixes, real) ? real : void 0;
3075
+ }
3076
+ async key(path) {
3077
+ const real = await this.real(path);
3078
+ if (real === void 0 || !this.root.version) return void 0;
3079
+ return `${real}\0${await this.root.version(real)}`;
3080
+ }
3081
+ async read(path, caller, plain) {
3082
+ return (await this.readKeyed(path, caller, plain)).bytes;
3083
+ }
3084
+ async readKeyed(path, caller, plain) {
3085
+ const real = await this.real(path);
3086
+ if (real === void 0) return { bytes: await plain() };
3087
+ const read = this.root.readVersioned ? await this.root.readVersioned(real) : { bytes: await this.root.readFileBuffer(real), version: void 0 };
3088
+ const { bytes } = read;
3089
+ const keyed = read.version === void 0 ? { bytes } : { bytes, key: `${real}\0${read.version}` };
3090
+ const actual = await sha256(bytes);
3091
+ const denial = { pid: caller.pid, uid: caller.uid, path: real, actual };
3092
+ const tagged = caller.tag !== void 0 ? { tag: caller.tag } : {};
3093
+ let expected;
3094
+ try {
3095
+ expected = await this.expected(real);
3096
+ } catch {
3097
+ this.say({ ...denial, ...tagged, op: "exec-integrity", errno: "EACCES" });
3098
+ throw fsError("EACCES", `integrity lookup failed: ${real}`);
3099
+ }
3100
+ if (expected === void 0) {
3101
+ if (this.unknown === "allow") {
3102
+ if (!this.unverified.has(real)) {
3103
+ this.unverified.add(real);
3104
+ this.say({ ...denial, ...tagged, op: "exec-unverified", errno: "ENODATA" });
3105
+ }
3106
+ return keyed;
3107
+ }
3108
+ } else if (expected === actual) {
3109
+ return keyed;
3110
+ }
3111
+ const known = expected !== void 0 ? { expected } : {};
3112
+ this.say({ ...denial, ...tagged, ...known, op: "exec-integrity", errno: "EACCES" });
3113
+ throw fsError("EACCES", `integrity check failed: ${real}`);
3114
+ }
3115
+ async expected(path) {
3116
+ const answer = await this.lookup(path);
3117
+ return typeof answer === "string" ? answer.toLowerCase() : void 0;
3118
+ }
3119
+ say(event) {
3120
+ try {
3121
+ this.report(event);
3122
+ } catch {
3123
+ }
3124
+ }
3125
+ };
3126
+
3036
3127
  // src/fs/protect.ts
3037
3128
  function writerTags(protection) {
3038
3129
  const writers = protection?.writers ?? [];
@@ -3885,6 +3976,29 @@ var OpfsFs = class {
3885
3976
  false
3886
3977
  );
3887
3978
  }
3979
+ async readVersioned(path) {
3980
+ const handle = await this.existing(path);
3981
+ return this.retrying(
3982
+ path,
3983
+ async () => {
3984
+ const file = await handle.getFile();
3985
+ const bytes = new Uint8Array(await file.arrayBuffer());
3986
+ return { bytes, version: `${file.lastModified}:${file.size}` };
3987
+ },
3988
+ false
3989
+ );
3990
+ }
3991
+ async version(path) {
3992
+ const handle = await this.existing(path);
3993
+ return this.retrying(
3994
+ path,
3995
+ async () => {
3996
+ const file = await handle.getFile();
3997
+ return `${file.lastModified}:${file.size}`;
3998
+ },
3999
+ false
4000
+ );
4001
+ }
3888
4002
  async pread(path, offset, length2) {
3889
4003
  const handle = await this.existing(path);
3890
4004
  return this.retrying(
@@ -5539,7 +5653,7 @@ function owned(path, st) {
5539
5653
  function unchanged(a, b, bytes) {
5540
5654
  return a.ino === b.ino && a.mtime.getTime() === b.mtime.getTime() && a.size === b.size && bytes.length === b.size;
5541
5655
  }
5542
- async function sha256(bytes) {
5656
+ async function sha2562(bytes) {
5543
5657
  const digest = new Uint8Array(
5544
5658
  await crypto.subtle.digest("SHA-256", bytes)
5545
5659
  );
@@ -5595,7 +5709,7 @@ async function attest(fs, asked, o) {
5595
5709
  const bytes = (await fs.readFileBuffer(path)).slice();
5596
5710
  const after = await fs.lstat(path);
5597
5711
  if (unchanged(before, after, bytes)) {
5598
- return attested(path, after, bytes, await sha256(bytes), o, parents);
5712
+ return attested(path, after, bytes, await sha2562(bytes), o, parents);
5599
5713
  }
5600
5714
  if (tries >= 1) refuse("EAGAIN", path, "changed while it was read");
5601
5715
  }
@@ -16295,6 +16409,13 @@ var Launcher = class {
16295
16409
  unwatchScopes = () => void 0;
16296
16410
  warned = /* @__PURE__ */ new Set();
16297
16411
  compiled = /* @__PURE__ */ new Map();
16412
+ texts = /* @__PURE__ */ new Map();
16413
+ integrity;
16414
+ scannedFs;
16415
+ get scanned() {
16416
+ this.scannedFs ??= this.verifiedFs(this.base);
16417
+ return this.scannedFs;
16418
+ }
16298
16419
  processes = /* @__PURE__ */ new Map();
16299
16420
  zombies = /* @__PURE__ */ new Set();
16300
16421
  orphans = /* @__PURE__ */ new Set();
@@ -16398,7 +16519,7 @@ var Launcher = class {
16398
16519
  if (options.uplink) this.net.useUplink({ uplink: options.uplink, routes: this.routes });
16399
16520
  enableNetwork(this.net, {
16400
16521
  transport: this.transport,
16401
- engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
16522
+ engine: kernelTlsEngine(packageTlsEngine(this.verifiedFs(options.fs), this.modulesDir)),
16402
16523
  ca: this.ca
16403
16524
  });
16404
16525
  enableCdp(this.net, this.cdp);
@@ -16433,6 +16554,16 @@ var Launcher = class {
16433
16554
  this.prefixes = protectedPrefixes(options.protect?.paths ?? []);
16434
16555
  this.processFs = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes).view(this.fs) : this.fs;
16435
16556
  options.fs.protect?.(this.prefixes);
16557
+ const integrity = options.protect?.integrity;
16558
+ integrityOptions(integrity);
16559
+ if (integrity && this.prefixes.length > 0) {
16560
+ this.integrity = new Integrity(
16561
+ this.prefixes,
16562
+ options.fs,
16563
+ integrity,
16564
+ (event) => options.onDenial?.(event)
16565
+ );
16566
+ }
16436
16567
  this.users = new UserDb(this.fs, this.owners);
16437
16568
  this.sudoer = new Sudo({
16438
16569
  fs: this.fs,
@@ -16478,7 +16609,7 @@ var Launcher = class {
16478
16609
  commands(scope = "") {
16479
16610
  let found = this.catalogs.get(scope);
16480
16611
  if (!found) {
16481
- found = this.roots(scope).then((roots) => scanCommands(this.base, roots)).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
16612
+ found = this.roots(scope).then((roots) => scanCommands(this.scanned, roots)).then((commands) => commands.set(SUDO_COMMAND.name, SUDO_COMMAND));
16482
16613
  this.catalogs.set(scope, found);
16483
16614
  }
16484
16615
  return found;
@@ -16486,7 +16617,7 @@ var Launcher = class {
16486
16617
  ownCommands(scope) {
16487
16618
  let found = this.ownCatalogs.get(scope);
16488
16619
  if (!found) {
16489
- found = pnpmGlobalRoots(this.base, scope).then((roots) => scanCommands(this.base, roots)).then((commands) => new Set(commands.keys()));
16620
+ found = pnpmGlobalRoots(this.base, scope).then((roots) => scanCommands(this.scanned, roots)).then((commands) => new Set(commands.keys()));
16490
16621
  this.ownCatalogs.set(scope, found);
16491
16622
  }
16492
16623
  return found;
@@ -16495,7 +16626,7 @@ var Launcher = class {
16495
16626
  let found = this.binfmts.get(scope);
16496
16627
  if (!found) {
16497
16628
  found = this.roots(scope).then(
16498
- (roots) => scanBinfmts(this.base, roots, (message3) => {
16629
+ (roots) => scanBinfmts(this.scanned, roots, (message3) => {
16499
16630
  if (this.warned.has(message3)) return;
16500
16631
  this.warned.add(message3);
16501
16632
  console.warn(message3);
@@ -16614,10 +16745,16 @@ var Launcher = class {
16614
16745
  }
16615
16746
  const inner = await this.interpreted(interp, [interp, ...passed], cwd, scope, depth + 1);
16616
16747
  const via = [interp, ...inner?.via ?? []];
16617
- return inner && { ...inner, target: withScriptEnv(inner.target, command), via };
16748
+ const sources = [script, ...inner?.sources ?? []];
16749
+ return inner && { ...inner, target: withScriptEnv(inner.target, command), via, sources };
16618
16750
  }
16619
16751
  const target = withScriptEnv(found, command);
16620
- return { target, args: [...target.prefix ?? [], ...passed], via: [interp] };
16752
+ return {
16753
+ target,
16754
+ args: [...target.prefix ?? [], ...passed],
16755
+ via: [interp],
16756
+ sources: [script]
16757
+ };
16621
16758
  }
16622
16759
  async plan(file, argv, cwd, scope, argv0) {
16623
16760
  const found = await this.resolve(file, argv[0] ?? file, cwd, scope);
@@ -16649,7 +16786,7 @@ var Launcher = class {
16649
16786
  }
16650
16787
  async binfmtManifests(scripts) {
16651
16788
  const sources = /* @__PURE__ */ new Map();
16652
- const binfmts = await scanBinfmts(this.base, await this.roots(), void 0, sources);
16789
+ const binfmts = await scanBinfmts(this.scanned, await this.roots(), void 0, sources);
16653
16790
  const manifests = [];
16654
16791
  for (const script of scripts) {
16655
16792
  const head = await this.head(script);
@@ -16684,25 +16821,72 @@ var Launcher = class {
16684
16821
  if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
16685
16822
  return binfmtOf(await this.interpreters(scope), path) ? "ENOENT" : "ENOEXEC";
16686
16823
  }
16687
- module(path) {
16688
- return this.base.stat(path).then((st) => {
16689
- const key = `${path}:${st.size}:${st.mtime.getTime()}`;
16690
- let module = this.compiled.get(key);
16691
- if (!module) {
16692
- module = this.base.readFileBuffer(path).then(async (bytes) => {
16693
- const memory = importedMemory(bytes);
16694
- const foreign = foreignImports(bytes);
16695
- const compiled = await WebAssembly.compile(bytes);
16696
- return {
16697
- module: compiled,
16698
- ...memory ? { memory } : {},
16699
- foreign
16700
- };
16701
- });
16702
- this.compiled.set(key, module);
16703
- module.catch(() => this.compiled.delete(key));
16704
- }
16705
- return module;
16824
+ verifiedFs(fs) {
16825
+ const read = (path) => this.verified(path, KERNEL_CALLER, fs);
16826
+ return Object.assign(Object.create(fs), {
16827
+ readFileBuffer: read,
16828
+ readFile: (path) => this.integrity ? this.text(path, KERNEL_CALLER) : fs.readFile(path)
16829
+ });
16830
+ }
16831
+ async cacheKey(path) {
16832
+ const verified = await this.integrity?.key(path);
16833
+ if (verified !== void 0) return verified;
16834
+ const st = await this.base.stat(path);
16835
+ return `${path}:${st.size}:${st.mtime.getTime()}`;
16836
+ }
16837
+ verified(path, caller, fs = this.base) {
16838
+ const plain = () => fs.readFileBuffer(path);
16839
+ return this.integrity ? this.integrity.read(path, caller, plain) : plain();
16840
+ }
16841
+ keyed(path, caller) {
16842
+ const plain = async () => this.base.readFileBuffer(path);
16843
+ return this.integrity ? this.integrity.readKeyed(path, caller, plain) : plain().then((bytes) => ({ bytes }));
16844
+ }
16845
+ cached(cache, key, load) {
16846
+ const hit = cache.get(key);
16847
+ if (hit !== void 0) return hit;
16848
+ const loading2 = load();
16849
+ const found = loading2.then(({ value }) => value);
16850
+ cache.set(key, found);
16851
+ loading2.then(
16852
+ (done4) => {
16853
+ if (done4.key === key || done4.key === void 0 && !key.includes("\0")) return;
16854
+ if (cache.get(key) === found) cache.delete(key);
16855
+ if (done4.key !== void 0) cache.set(done4.key, found);
16856
+ },
16857
+ () => cache.delete(key)
16858
+ );
16859
+ return found;
16860
+ }
16861
+ async module(path, caller) {
16862
+ const key = await this.cacheKey(path);
16863
+ return this.cached(this.compiled, key, async () => {
16864
+ const { bytes, key: read } = await this.keyed(path, caller);
16865
+ const memory = importedMemory(bytes);
16866
+ const foreign = foreignImports(bytes);
16867
+ const compiled = await WebAssembly.compile(bytes);
16868
+ return { value: { module: compiled, ...memory ? { memory } : {}, foreign }, key: read };
16869
+ });
16870
+ }
16871
+ async checkedSources(planned, caller) {
16872
+ const { target } = planned;
16873
+ const files = [target.wasm, target.glue, target.imports, ...planned.sources ?? []];
16874
+ const manifests = /* @__PURE__ */ new Set();
16875
+ for (const file of files) {
16876
+ const root = file === void 0 ? void 0 : PACKAGE_ROOT.exec(file)?.[1];
16877
+ if (root !== void 0) manifests.add(`${root}/package.json`);
16878
+ }
16879
+ for (const source of planned.sources ?? []) await this.text(source, caller);
16880
+ for (const manifest of manifests) {
16881
+ if (await this.base.exists(manifest)) await this.text(manifest, caller);
16882
+ }
16883
+ }
16884
+ async text(path, caller) {
16885
+ if (!this.integrity) return this.base.readFile(path);
16886
+ const key = await this.cacheKey(path);
16887
+ return this.cached(this.texts, key, async () => {
16888
+ const { bytes, key: read } = await this.keyed(path, caller);
16889
+ return { value: new TextDecoder().decode(bytes), key: read };
16706
16890
  });
16707
16891
  }
16708
16892
  async names(path, module) {
@@ -16714,23 +16898,23 @@ var Launcher = class {
16714
16898
  const optional = `${root}-names${beside.slice(root.length)}`;
16715
16899
  return await this.base.exists(optional) ? optional : void 0;
16716
16900
  }
16717
- async program(target, env) {
16901
+ async program(target, env, caller) {
16718
16902
  if (target.abi === "js" && target.wasm === SUDO_PATH) {
16719
16903
  return { abi: "js", glue: SUDO_SOURCE, path: SUDO_PATH };
16720
16904
  }
16721
16905
  if (target.abi === "js") {
16722
- return { abi: "js", glue: await this.base.readFile(target.wasm), path: target.wasm };
16906
+ return { abi: "js", glue: await this.text(target.wasm, caller), path: target.wasm };
16723
16907
  }
16724
16908
  if (target.abi !== "wasi") {
16725
16909
  const [glue, { module: module2 }] = await Promise.all([
16726
- this.base.readFile(target.glue),
16727
- this.module(target.wasm)
16910
+ this.text(target.glue, caller),
16911
+ this.module(target.wasm, caller)
16728
16912
  ]);
16729
16913
  return { glue, module: module2 };
16730
16914
  }
16731
- const { module, memory, foreign } = await this.module(target.wasm);
16915
+ const { module, memory, foreign } = await this.module(target.wasm, caller);
16732
16916
  const names = env.SLICC_WASM_BACKTRACE === "1" ? await this.names(target.wasm, module) : void 0;
16733
- const imports = target.imports ? await this.base.readFile(target.imports) : void 0;
16917
+ const imports = target.imports ? await this.text(target.imports, caller) : void 0;
16734
16918
  return {
16735
16919
  abi: "wasi",
16736
16920
  glue: "",
@@ -16752,7 +16936,9 @@ var Launcher = class {
16752
16936
  env.HOSTNAME = this.hostname;
16753
16937
  let program;
16754
16938
  try {
16755
- program = await this.program(target, env);
16939
+ const caller = { pid: req.ppid ?? 0, uid: req.cred.euid, tag: req.tag };
16940
+ if (this.integrity) await this.checkedSources(planned, caller);
16941
+ program = await this.program(target, env, caller);
16756
16942
  } catch (err) {
16757
16943
  await req.fds.closeAll();
16758
16944
  throw err;
@@ -17244,11 +17430,14 @@ var Launcher = class {
17244
17430
  }
17245
17431
  };
17246
17432
  }
17247
- const module = (await scanFilesystems(this.base, await this.roots())).get(type);
17433
+ const module = (await scanFilesystems(this.scanned, await this.roots())).get(type);
17248
17434
  if (!module) throw fsError("ENODEV", `unknown file system type ${type}`);
17249
17435
  if (!this.createDriverWorker)
17250
17436
  throw fsError("ENODEV", `this kernel cannot start ${type} drivers`);
17251
- const code = await this.base.readFile(module);
17437
+ const root = PACKAGE_ROOT.exec(module)?.[1];
17438
+ if (this.integrity && root !== void 0)
17439
+ await this.text(`${root}/package.json`, KERNEL_CALLER);
17440
+ const code = new TextDecoder().decode(await this.verified(module, KERNEL_CALLER));
17252
17441
  const worker = this.createDriverWorker();
17253
17442
  const driver = new MessageChannel();
17254
17443
  const client = new MessageChannel();