@ai-ecoverse/slicc-kernel 1.149.1 → 1.150.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/README.md CHANGED
@@ -511,7 +511,8 @@ The module's default export (or its `main` export) is called with a context and
511
511
  - `requestCert` needs `ca` (the roots a client certificate must reach; else `EINVAL`). With it the handshake fails without a client certificate that verifies, unless `rejectUnauthorized: false`, which only asks for one and reports the verdict.
512
512
  - A connection's `tls` is `{ path: 'kernel', protocol, alpnProtocol, servername (the SNI the client sent, or ''), authorized (whether a client certificate verified), authorizationError?, peerCertificates (the client's chain) }`.
513
513
  - On a `listen({ tls })` listener, the kernel handshakes accepted connections concurrently, from the first `accept` on, and `accept` (and `for await`) yields each as its handshake finishes, so a client that connects and stays silent doesn't hold up the next. It holds at most `backlog` (up to 64) connections in a handshake or handshaken but not yet taken; beyond that it stops accepting, leaving the rest in the kernel's backlog, until one finishes or is taken. A connection whose handshake fails is closed and never yielded. `secure()` rejects instead.
514
- - `handshakeTimeout` (milliseconds, 120000 by default, as Node's) ends a handshake that hasn't finished: the connection is closed with `ERR_TLS_HANDSHAKE_TIMEOUT`. A handshake, server or loopback client, waits without holding the program's other calls, and an aborted `accept({ signal })` or `connect({ signal })` ends it. A signal delivered to the program doesn't interrupt a handshake, as with `SA_RESTART`.
514
+ - `handshakeTimeout` (milliseconds, 120000 by default, as Node's) ends a handshake that hasn't finished with `ERR_TLS_HANDSHAKE_TIMEOUT`. A listener closes that connection. After `secure()` rejects, the connection stays open and is the program's to answer or close, as with Node's `tlsClientError` ([#1504](https://github.com/ai-ecoverse/slicc-kernel/issues/1504)).
515
+ - A client that sends a plain HTTP request instead of a ClientHello fails the handshake with `ERR_SSL_HTTP_REQUEST`, as in Node. A handshake, server or loopback client, waits without holding the program's other calls, and an aborted `accept({ signal })` or `connect({ signal })` ends it. A signal delivered to the program doesn't interrupt a handshake, as with `SA_RESTART`.
515
516
  - **The key:** the kernel hands it to the engine once per listener (once per `secure()` call) and wipes its own copies; the engine keeps its copy until the listener is closed, and not in errors, logs, `ps`, `/proc` or the page. A forked child that inherits the listener shares that one copy, and it's freed when the last process closes it. **The program's own `key` string or `Buffer` stays in its realm**: JavaScript strings can't be wiped, so the kernel can't erase what the program holds.
516
517
  - **Sealed values in an A2 session** ([#1090](https://github.com/ai-ecoverse/slicc-kernel/issues/1090)) are substituted only at a credential position, only in a session the kernel verified itself (by its bundled roots, or by a vault anchor), and only as the vault allows. `substitutes` means the session **may** substitute: a placeholder at a recognized position will be offered to the vault, and whether a value comes back is the vault's and the anchor check's decision. It's true when all of these hold:
517
518
  - `verified` is true, or the engine can verify a chain on its own (wasm-tls-engine 4.2.0-5's `tls_chain_verify`) and the server name isn't an IP literal, so a vault anchor may verify the session (below);
@@ -5100,12 +5100,12 @@ var KernelPipe = class {
5100
5100
  await this.changed(signal);
5101
5101
  }
5102
5102
  const out = new Uint8Array(Math.min(max, this.size));
5103
- let filled = 0;
5103
+ let filled2 = 0;
5104
5104
  for (const chunk2 of this.chunks) {
5105
- if (filled === out.length) break;
5106
- const count2 = Math.min(chunk2.length, out.length - filled);
5107
- out.set(chunk2.subarray(0, count2), filled);
5108
- filled += count2;
5105
+ if (filled2 === out.length) break;
5106
+ const count2 = Math.min(chunk2.length, out.length - filled2);
5107
+ out.set(chunk2.subarray(0, count2), filled2);
5108
+ filled2 += count2;
5109
5109
  }
5110
5110
  return out;
5111
5111
  }
@@ -5133,12 +5133,12 @@ var KernelPipe = class {
5133
5133
  }
5134
5134
  take(n) {
5135
5135
  const out = new Uint8Array(n);
5136
- let filled = 0;
5137
- while (filled < n) {
5136
+ let filled2 = 0;
5137
+ while (filled2 < n) {
5138
5138
  const head = this.chunks[0];
5139
- const count2 = Math.min(head.length, n - filled);
5140
- out.set(head.subarray(0, count2), filled);
5141
- filled += count2;
5139
+ const count2 = Math.min(head.length, n - filled2);
5140
+ out.set(head.subarray(0, count2), filled2);
5141
+ filled2 += count2;
5142
5142
  if (count2 === head.length) this.chunks.shift();
5143
5143
  else this.chunks[0] = head.subarray(count2);
5144
5144
  }
@@ -5903,8 +5903,8 @@ var DIAL_ERRORS = [
5903
5903
  "ETIMEDOUT",
5904
5904
  "ECONNRESET"
5905
5905
  ];
5906
- function dialError(e, timedOut, closed) {
5907
- if (timedOut) return new KernelError("ETIMEDOUT");
5906
+ function dialError(e, timedOut2, closed) {
5907
+ if (timedOut2) return new KernelError("ETIMEDOUT");
5908
5908
  if (closed) return new KernelError("ECONNABORTED");
5909
5909
  const code = e?.code;
5910
5910
  const known = DIAL_ERRORS.find((errno3) => errno3 === code);
@@ -6159,7 +6159,7 @@ var KernelSocket = class _KernelSocket {
6159
6159
  get loopbackLinked() {
6160
6160
  return this.state === "connected" && !this.switch;
6161
6161
  }
6162
- async secureLink(make, signal = NEVER_ABORTED) {
6162
+ async secureLink(make, signal = NEVER_ABORTED, keeps = () => false) {
6163
6163
  const raw2 = this.connection();
6164
6164
  if (this.switch || this.served || this.writeShut || this.readShut)
6165
6165
  throw new KernelError("EINVAL");
@@ -6181,14 +6181,18 @@ var KernelSocket = class _KernelSocket {
6181
6181
  }
6182
6182
  };
6183
6183
  let made2;
6184
- const abandon = () => view2.close();
6184
+ const abandon = () => {
6185
+ if (!keeps(signal.reason)) view2.close();
6186
+ };
6185
6187
  signal.addEventListener("abort", abandon, { once: true });
6186
6188
  try {
6187
6189
  made2 = await make(view2);
6188
6190
  } catch (err) {
6189
- view2.close();
6190
- this.readShut = true;
6191
- this.writeShut = true;
6191
+ if (!(signal.aborted && keeps(signal.reason))) {
6192
+ view2.close();
6193
+ this.readShut = true;
6194
+ this.writeShut = true;
6195
+ }
6192
6196
  this.changes.wake();
6193
6197
  throw signal.aborted ? signal.reason : err;
6194
6198
  } finally {
@@ -6212,9 +6216,9 @@ var KernelSocket = class _KernelSocket {
6212
6216
  this.state = "connecting";
6213
6217
  const abort = new AbortController();
6214
6218
  this.dialing = abort;
6215
- let timedOut = false;
6219
+ let timedOut2 = false;
6216
6220
  const timer = setTimeout(() => {
6217
- timedOut = true;
6221
+ timedOut2 = true;
6218
6222
  abort.abort(new KernelError("ETIMEDOUT"));
6219
6223
  }, route2.timeoutMs ?? DIAL_TIMEOUT_MS);
6220
6224
  const abandoned = new Promise((_, reject) => {
@@ -6230,7 +6234,7 @@ var KernelSocket = class _KernelSocket {
6230
6234
  );
6231
6235
  return Promise.race([dialled, abandoned]).then(
6232
6236
  async (raw2) => {
6233
- if (abort.signal.aborted) throw dialError(abort.signal.reason, timedOut, true);
6237
+ if (abort.signal.aborted) throw dialError(abort.signal.reason, timedOut2, true);
6234
6238
  const conn = new SwitchConn(raw2);
6235
6239
  if (secure2) {
6236
6240
  clearTimeout(timer);
@@ -6256,7 +6260,7 @@ var KernelSocket = class _KernelSocket {
6256
6260
  );
6257
6261
  },
6258
6262
  (e) => {
6259
- throw this.dialFailed(abort, dialError(e, timedOut, abort.signal.aborted));
6263
+ throw this.dialFailed(abort, dialError(e, timedOut2, abort.signal.aborted));
6260
6264
  }
6261
6265
  ).finally(() => clearTimeout(timer));
6262
6266
  }
@@ -6841,7 +6845,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS2 } = {})
6841
6845
  deadline: () => AbortSignal.timeout(closeWaitMs).addEventListener("abort", late, { once: true })
6842
6846
  };
6843
6847
  credit.set(nid, flow);
6844
- const timedOut = { code: 1006, reason: "the far end did not answer the close" };
6848
+ const timedOut2 = { code: 1006, reason: "the far end did not answer the close" };
6845
6849
  let oversize = false;
6846
6850
  try {
6847
6851
  const messages = socket.messages[Symbol.asyncIterator]();
@@ -6867,7 +6871,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS2 } = {})
6867
6871
  }
6868
6872
  }
6869
6873
  if (oversize) socket.close(1e3, "a message is too big");
6870
- ending = oversize ? { code: 1009, reason: `a message is over ${MESSAGE_LIMIT} bytes` } : await Promise.race([socket.closed, unanswered.then(() => timedOut)]);
6874
+ ending = oversize ? { code: 1009, reason: `a message is over ${MESSAGE_LIMIT} bytes` } : await Promise.race([socket.closed, unanswered.then(() => timedOut2)]);
6871
6875
  } catch (e) {
6872
6876
  socket.close(1e3, "the relay failed");
6873
6877
  ending = { code: 1011, reason: e instanceof Error ? e.message : String(e) };
@@ -7911,7 +7915,7 @@ function satisfies(version, range2) {
7911
7915
  }
7912
7916
 
7913
7917
  // src/version.ts
7914
- var KERNEL_VERSION = true ? "1.149.1" : "0.0.0-dev";
7918
+ var KERNEL_VERSION = true ? "1.150.0" : "0.0.0-dev";
7915
7919
  var OS_RELEASE = "6.0.0-slicc";
7916
7920
  function procVersion() {
7917
7921
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -17711,8 +17715,8 @@ var HttpHandles = class {
17711
17715
  else entry.left = next3.value;
17712
17716
  } catch (e) {
17713
17717
  entry.done = true;
17714
- const timedOut = e?.code === "ETIMEDOUT";
17715
- return { ok: false, errno: timedOut ? "ETIMEDOUT" : "EIO", message: message2(e) };
17718
+ const timedOut2 = e?.code === "ETIMEDOUT";
17719
+ return { ok: false, errno: timedOut2 ? "ETIMEDOUT" : "EIO", message: message2(e) };
17716
17720
  }
17717
17721
  }
17718
17722
  const bytes = entry.left.subarray(0, Math.max(0, max));
@@ -18878,15 +18882,22 @@ async function tlsClient(conn, o, taken = new Uint8Array(0)) {
18878
18882
  throw err;
18879
18883
  }
18880
18884
  }
18881
- async function serverHandshake(conn, session, inbox) {
18885
+ var HTTP_REQUEST = /^[A-Z]{3,10} [!-~]/;
18886
+ function serverRefusal(err, session, first) {
18887
+ const opening2 = first ? new TextDecoder("latin1").decode(first.subarray(0, 16)) : "";
18888
+ if (HTTP_REQUEST.test(opening2))
18889
+ return new KernelError("EPROTO", "ERR_SSL_HTTP_REQUEST: http request");
18890
+ const text = err instanceof TlsError ? err.message : String(err);
18891
+ return new KernelError("EPROTO", `ERR_SSL_HANDSHAKE_FAILURE: ${text}${alertOf(session)}`);
18892
+ }
18893
+ async function serverHandshake(conn, session, inbox, first) {
18882
18894
  for (; ; ) {
18883
18895
  let done5;
18884
18896
  try {
18885
18897
  done5 = session.handshake();
18886
18898
  } catch (err) {
18887
18899
  await send(conn, session).catch(() => void 0);
18888
- const text = err instanceof TlsError ? err.message : String(err);
18889
- throw new KernelError("EPROTO", `ERR_SSL_HANDSHAKE_FAILURE: ${text}${alertOf(session)}`);
18900
+ throw serverRefusal(err, session, first());
18890
18901
  }
18891
18902
  await send(conn, session);
18892
18903
  if (done5) return;
@@ -18899,9 +18910,18 @@ async function serverHandshake(conn, session, inbox) {
18899
18910
  }
18900
18911
  }
18901
18912
  async function tlsServer(conn, session, clientCerts) {
18902
- const inbox = new Ciphertext(conn, session);
18913
+ let first;
18914
+ const watched = {
18915
+ ...conn,
18916
+ read: async () => {
18917
+ const bytes = await conn.read();
18918
+ first ??= bytes ?? void 0;
18919
+ return bytes;
18920
+ }
18921
+ };
18922
+ const inbox = new Ciphertext(watched, session);
18903
18923
  try {
18904
- await serverHandshake(conn, session, inbox);
18924
+ await serverHandshake(conn, session, inbox, () => first);
18905
18925
  const chain2 = session.peerChain();
18906
18926
  const code = clientCerts ? verifyCode(session.verifyResult) : void 0;
18907
18927
  const info = {
@@ -19371,6 +19391,7 @@ var RANK = {
19371
19391
  var CR2 = 13;
19372
19392
  var LF2 = 10;
19373
19393
  var EMPTY4 = new Uint8Array(0);
19394
+ var FILLER = 42;
19374
19395
  function register(automaton, sources, ids, bytes, source) {
19375
19396
  const key = Array.from(bytes).join(",");
19376
19397
  let id2 = ids.get(key);
@@ -19381,7 +19402,7 @@ function register(automaton, sources, ids, bytes, source) {
19381
19402
  }
19382
19403
  sources[id2].push(source);
19383
19404
  }
19384
- function prepare(secrets) {
19405
+ function prepare(secrets, sameLength) {
19385
19406
  const main = new Automaton();
19386
19407
  const wrap = new Automaton();
19387
19408
  const sources = [];
@@ -19405,7 +19426,19 @@ function prepare(secrets) {
19405
19426
  utf82(secret.placeholder),
19406
19427
  utf82(`[redacted:${secret.name}]`)
19407
19428
  ]);
19408
- return { secrets, values, main, wrap, sources, wrapSources, replacements };
19429
+ return { secrets, values, main, wrap, sources, wrapSources, replacements, sameLength };
19430
+ }
19431
+ function filled(input, start2, end) {
19432
+ const out = new Uint8Array(end - start2);
19433
+ for (let i = start2; i < end; i++) {
19434
+ const byte = input[i];
19435
+ out[i - start2] = byte === CR2 || byte === LF2 ? byte : FILLER;
19436
+ }
19437
+ return out;
19438
+ }
19439
+ function replacement(c, input, candidate) {
19440
+ if (c.sameLength) return filled(input, candidate.start, candidate.end);
19441
+ return c.replacements[candidate.secret][candidate.form === "raw" ? 0 : 1];
19409
19442
  }
19410
19443
  function better(a, b) {
19411
19444
  if (!b) return true;
@@ -19514,10 +19547,7 @@ function pass(c, input, final) {
19514
19547
  break;
19515
19548
  }
19516
19549
  const candidate = best.get(next3);
19517
- parts2.push(
19518
- input.subarray(pos, next3),
19519
- c.replacements[candidate.secret][candidate.form === "raw" ? 0 : 1]
19520
- );
19550
+ parts2.push(input.subarray(pos, next3), replacement(c, input, candidate));
19521
19551
  count(c, hits, candidate);
19522
19552
  pos = candidate.end;
19523
19553
  }
@@ -19532,7 +19562,10 @@ function merge(into, from) {
19532
19562
  }
19533
19563
  }
19534
19564
  function compile(secrets, options = {}) {
19535
- const c = prepare(secrets.map((secret) => ({ ...secret })));
19565
+ const c = prepare(
19566
+ secrets.map((secret) => ({ ...secret })),
19567
+ options.sameLength === true
19568
+ );
19536
19569
  const decoder4 = new TextDecoder();
19537
19570
  const idleMs = options.idleMs ?? 250;
19538
19571
  const scrubBytes = (input) => {
@@ -22105,12 +22138,10 @@ function timeoutOf(req) {
22105
22138
  );
22106
22139
  return ms;
22107
22140
  }
22141
+ var timedOut = new KernelError("EPROTO", "ERR_TLS_HANDSHAKE_TIMEOUT: TLS handshake timeout");
22108
22142
  function handshakeSignal(proc, ms) {
22109
22143
  const timer = new AbortController();
22110
- const id2 = setTimeout(
22111
- () => timer.abort(new KernelError("EPROTO", "ERR_TLS_HANDSHAKE_TIMEOUT: TLS handshake timeout")),
22112
- ms
22113
- );
22144
+ const id2 = setTimeout(() => timer.abort(timedOut), ms);
22114
22145
  return {
22115
22146
  signal: AbortSignal.any([handshakeAbort(proc), timer.signal]),
22116
22147
  done: () => clearTimeout(id2)
@@ -22168,7 +22199,8 @@ async function serveSecure(proc, socket, req) {
22168
22199
  const limit = handshakeSignal(proc, server.timeoutMs);
22169
22200
  const info = await socket.secureLink(
22170
22201
  (raw2) => tlsServer(raw2, engine.serverSession(leaf, alpn, store, flags2), flags2 !== 0),
22171
- limit.signal
22202
+ limit.signal,
22203
+ (reason) => !shared && reason === timedOut
22172
22204
  ).finally(limit.done);
22173
22205
  return {
22174
22206
  path: "kernel",