@ai-ecoverse/slicc-kernel 1.149.1 → 1.150.1
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 +4 -3
- package/dist/kernel-worker.js +78 -46
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +1 -1
- package/dist/node.js +78 -46
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +1 -1
- package/dist/types/kernel/scrub-file.d.ts +1 -1
- package/dist/types/kernel/socket.d.ts +1 -1
- package/package.json +3 -3
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
|
|
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);
|
|
@@ -555,7 +556,7 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
555
556
|
- a head with placeholders in two header lines;
|
|
556
557
|
- later requests on the same connection;
|
|
557
558
|
- anything once the head passes 16 KiB, or once the program pauses for 2 s in the middle of it;
|
|
558
|
-
- **What's read back:** once a process has had a value substituted for a host, everything its whole process tree reads from any A2 session to that host is scrubbed: the process itself, its ancestors and every descendant (forked, spawned or exec'd, before or after the substitution; a forked child's own new session included) with [slicc-scrub](https://github.com/ai-ecoverse/slicc-scrub),
|
|
559
|
+
- **What's read back:** once a process has had a value substituted for a host, everything its whole process tree reads from any A2 session to that host is scrubbed: the process itself, its ancestors and every descendant (forked, spawned or exec'd, before or after the substitution; a forked child's own new session included) with [slicc-scrub](https://github.com/ai-ecoverse/slicc-scrub), in every form S2 matches (the value, a fragment, JSON, percent, HTML, base64, hex, wrapped). Unlike S2, each match comes back as filler of exactly its own length: every byte `*`, CR and LF kept ([#1509](https://github.com/ai-ecoverse/slicc-kernel/issues/1509), slicc-scrub's `sameLength`). So the session's framing still holds where a server reflects a value (an HTTP `Content-Length` or chunk size, a pg message length, a WebSocket frame), and the filler shows only the match's length, which the framing shows anyway, and where a value with line breaks has them. The scrub stays until the whole tree has ended. A process outside the tree isn't covered. A value shorter than 8 bytes is never substituted, as it couldn't be scrubbed.
|
|
559
560
|
- **The bound:** the server the value goes to is the one the vault rule names, verified against the kernel's own roots. Substitution trusts that server not to reflect the value back in a form the scrub doesn't know (encrypted under a key the program chose, say). That's the same residual as A1's.
|
|
560
561
|
- **Elsewhere:** a placeholder written into any other A2 session reaches the server as written. It isn't the secret, so nothing leaks.
|
|
561
562
|
- **Limits:** no revocation checking (neither OCSP nor CRLs, as with Node's default), no session resumption, and the clock is the page's `Date.now()`.
|
|
@@ -1098,7 +1099,7 @@ The kernel then asks the hook `{ v: 1, op: 'materializeConsume', ticket, stamp,
|
|
|
1098
1099
|
- `interpreters` is the chain of `#!` and `binfmt` interpreters the kernel will really run, outermost first, each absolute with its symlinks resolved, and empty for a program of the catalog (Emscripten, WASI or JS) and the `/usr/bin/<name>` entries for one. A plain `#!/usr/bin/env X` (or `env -S X …`) is reported as `X`: a command of the catalog as `/usr/bin/X`, a script command by its file. `env` with any other option or a `NAME=value` assignment is run and reported as `/usr/bin/env` itself;
|
|
1099
1100
|
- `trusted` is true when the program and every interpreter are regular files owned by root and not writable by group or others, under directories that are all the same, up to `/` (for a command of the catalog, its package files). It's false unless `users` is `'enforce'`: under `'off'` files have no owners, and under `'report'` owners don't stop a write. The reply must be `{ values, placeholders }` (string maps) within 10 s; anything else is `EACCES`. Each variable of the child's environment whose value is exactly `placeholders[NAME]` gets `values[NAME]`; nothing else changes. The kernel keeps no copy and logs nothing of either. Like any environment, the values are inherited by whatever the child itself spawns or execs, as on Linux. A ticket isn't carried across `sudo`, which builds its own environment: a `sudo` run with `SLICC_SEALED_TICKET` set fails with `EACCES`, and burns the ticket.
|
|
1100
1101
|
|
|
1101
|
-
**The child's output is scrubbed (S2).** The kernel compiles a [slicc-scrub](https://github.com/ai-ecoverse/slicc-scrub) scrubber (1.
|
|
1102
|
+
**The child's output is scrubbed (S2).** The kernel compiles a [slicc-scrub](https://github.com/ai-ecoverse/slicc-scrub) scrubber (1.1.0, bundled; the published package has no runtime dependency) over exactly the values it swapped in, and gives the child new open file descriptions for fd 1 and fd 2, for every other descriptor it holds on the same descriptions (a `dup` of them), and for every descriptor on a terminal (fd 0 of a terminal session is one), that pass every write through it into the originals (one wrapper per original description, so `2>&1` keeps its order). A value appears as its placeholder, and a fragment or encoded form of it (JSON, percent, HTML, base64, hex, wrapped) as `[redacted:NAME]`. Bytes go through at once unless they may still be the start of a match; such a tail is let out after 50 ms without a write when it's shorter than 16 bytes, and otherwise at the next write or at close. The child's descendants inherit the scrubbed descriptions through fork, spawn, exec and `dup`, and a descendant that opens its controlling terminal (`/dev/tty`, or a terminal by name) gets one scrubbed too, as does a pseudo-terminal slave (`/dev/pts/N`) or [`/dev/clipboard`](#devclipboard) it opens, and a `/dev/clipboard` descriptor it inherited is wrapped at the redeem like fd 1 and 2, so the page's clipboard gets placeholders, never the values. Files, sockets and pipes the child or a descendant opens itself are not scrubbed, so `printf %s "$T" > f` writes the value. A scrubbed fd 1 or 2 is a stream: it doesn't seek. Under `O_NONBLOCK` it gives `EAGAIN` when the pipe or terminal under it would. A write that finds room returns at once, taking at most 64 KiB, and the scrubbed bytes the file couldn't take yet go out before anything written later. Until they have, `poll` reports it unwritable and a nonblocking write gets `EAGAIN`, taking nothing. A blocking write that waits for them, or for room, ends with `EINTR` at a signal, taking nothing; one interrupted after it began writing returns its whole count, and the rest follows. A descendant that redeems a ticket of its own adds its values: its output is scrubbed for both sets. A value under 8 bytes is swapped but can't be scrubbed. The scrubber goes when the last process and description holding it do.
|
|
1102
1103
|
|
|
1103
1104
|
**A2 sessions (`sessionMaterialize`, [#1090](https://github.com/ai-ecoverse/slicc-kernel/issues/1090)).** For a kernel-terminated TLS session (see **Sealed values in an A2 session** under JS programs' `ctx.net`), the kernel asks the hook `{ v: 1, op: 'sessionMaterialize', caller, stamp, session, host, port, protocol, position, verified, handshakeAt, exit, placeholders, head? }` (slicc-vault 1.10.0, Sessions), once per session:
|
|
1104
1105
|
- `stamp` (also `caller`) is the stamp of the process that owns the socket; `session` the kernel's id for it (16 to 128 of `A-Za-z0-9_-`); `host` the name the leaf was verified against, lower-cased without a trailing dot; `handshakeAt` when the handshake finished (epoch ms); `exit` the exit the session leaves by, as the uplink reported it (`tailscale` or `device:<id>`, the id of `[A-Za-z0-9._:-]`);
|
package/dist/kernel-worker.js
CHANGED
|
@@ -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
|
|
5103
|
+
let filled2 = 0;
|
|
5104
5104
|
for (const chunk2 of this.chunks) {
|
|
5105
|
-
if (
|
|
5106
|
-
const count2 = Math.min(chunk2.length, out.length -
|
|
5107
|
-
out.set(chunk2.subarray(0, count2),
|
|
5108
|
-
|
|
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
|
|
5137
|
-
while (
|
|
5136
|
+
let filled2 = 0;
|
|
5137
|
+
while (filled2 < n) {
|
|
5138
5138
|
const head = this.chunks[0];
|
|
5139
|
-
const count2 = Math.min(head.length, n -
|
|
5140
|
-
out.set(head.subarray(0, count2),
|
|
5141
|
-
|
|
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,
|
|
5907
|
-
if (
|
|
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 = () =>
|
|
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
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
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
|
|
6219
|
+
let timedOut2 = false;
|
|
6216
6220
|
const timer = setTimeout(() => {
|
|
6217
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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(() =>
|
|
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.
|
|
7918
|
+
var KERNEL_VERSION = true ? "1.150.1" : "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
|
|
17715
|
-
return { ok: false, errno:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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) => {
|
|
@@ -19586,9 +19619,9 @@ function compile(secrets, options = {}) {
|
|
|
19586
19619
|
var SCRUB_IDLE_MS = 50;
|
|
19587
19620
|
var SCRUB_NONBLOCK_MAX = 64 * 1024;
|
|
19588
19621
|
var encoder6 = new TextEncoder();
|
|
19589
|
-
function scrubberOf(secrets) {
|
|
19622
|
+
function scrubberOf(secrets, sameLength = false) {
|
|
19590
19623
|
const usable2 = secrets.filter((s) => encoder6.encode(s.value).length >= MIN_VALUE);
|
|
19591
|
-
return usable2.length > 0 ? compile(usable2, { idleMs: SCRUB_IDLE_MS }) : void 0;
|
|
19624
|
+
return usable2.length > 0 ? compile(usable2, { idleMs: SCRUB_IDLE_MS, sameLength }) : void 0;
|
|
19592
19625
|
}
|
|
19593
19626
|
async function writeAll3(file, bytes) {
|
|
19594
19627
|
const write = file.write;
|
|
@@ -19776,7 +19809,7 @@ function addTo(tree2, host, secrets) {
|
|
|
19776
19809
|
function scrubOf(tree2, host) {
|
|
19777
19810
|
const entry = tree2?.get(host);
|
|
19778
19811
|
if (!entry) return void 0;
|
|
19779
|
-
entry.scrubber ??= scrubberOf(entry.secrets);
|
|
19812
|
+
entry.scrubber ??= scrubberOf(entry.secrets, true);
|
|
19780
19813
|
return entry.scrubber ? { version: entry.version, scrubber: entry.scrubber } : void 0;
|
|
19781
19814
|
}
|
|
19782
19815
|
var SessionScrubs = class {
|
|
@@ -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",
|