@volter/world-runtime 3.0.11 → 3.0.12

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.
@@ -920,11 +920,15 @@ export async function main() {
920
920
  caFile = join(mkdtempSync(join(tmpdir(), 'volter-attach-')), 'ca.pem');
921
921
  writeFileSync(caFile, manifest.ca);
922
922
  }
923
+ // the World's twins, each a loopback listener here that adds its key, so a command with its own HTTP stack reaches
924
+ // them as it would a local World's (the manifest pointed at the listeners before its env is made)
925
+ const { bridgeTwins } = createRequire(import.meta.url)('@volter/world-core/http-bridge');
926
+ await bridgeTwins(manifest, manifest.env.VOLTER_TWINS_KEY ?? attachToken ?? '', manifest.ca ?? undefined);
923
927
  const injectPath = createRequire(import.meta.url).resolve('@volter/world-core/inject');
924
928
  const env = remoteAttachEnv(manifest, { injectPath, ...(caFile === undefined ? {} : { caFile }) });
925
929
  // the world's byte-stream doors, bridged to loopback listeners here, beside a command in any language
926
930
  const { bridgeStreams, drainStreams } = createRequire(import.meta.url)('@volter/world-core/stream-bridge');
927
- const bridged = manifest.streams ? await bridgeStreams(manifest.streams, attachToken ?? manifest.env.VOLTER_TWINS_KEY ?? '') : { env: {} };
931
+ const bridged = manifest.streams ? await bridgeStreams(manifest.streams, manifest.env.VOLTER_TWINS_KEY ?? attachToken ?? '') : { env: {} };
928
932
  // VOLTER_WORLD names the World to the injector: a request addressed to its paths carries its key
929
933
  const commandEnv = withoutVaultSignIn({ ...process.env, ...env, ...bridged.env, VOLTER_WORLD: resolved.ref });
930
934
  commandEnv.VOLTER_TWIN_INJECT_QUIET = head.includes('--verbose') ? '0' : commandEnv.VOLTER_TWIN_INJECT_QUIET ?? '1';
@@ -26,7 +26,7 @@ import { getActiveWorldStore, withHistoryLocks, captureHistory, historyAtInstant
26
26
  // to `.volter/token` and `.volter/token.read` (0600).
27
27
  // One writer per world by construction: the doors run in this process, serialized per twin.
28
28
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
29
- import { CALLER_HEADER, callerForHeader, newTraceparent, parseTraceparent, traceparentForDelivery, readTwinRequestJournalFrom, assertSafeChangesetName, blobDigest, destinationOf, getActiveBlobStore, journalTwinRequest, loadCheck, twinRequestJournalFailures, openSealedAt, readTwinRequestJournal, serveHttp, twinRequestCredentials, TWIN_PREFIX_HEADER, TWIN_SITES_HEADER, validateRemoteOrigin } from '@volter/world-core/runtime';
29
+ import { CALLER_HEADER, callerForHeader, newTraceparent, parseTraceparent, traceparentForDelivery, readTwinRequestJournalFrom, assertSafeChangesetName, blobDigest, destinationOf, getActiveBlobStore, journalTwinRequest, loadCheck, twinRequestJournalFailures, openSealedAt, readTwinRequestJournal, serveHttp, twinRequestCredentials, TWIN_PREFIX_HEADER, TWIN_SITES_HEADER, ORIGINAL_PATH_HEADER, validateRemoteOrigin } from '@volter/world-core/runtime';
30
30
  import { APP_READ_ENDPOINT_ENV } from "./init.js";
31
31
  import { packFacts } from "./pack-facts.js";
32
32
  import { grantOf, presentedSecrets, sameSecret, spentPasses, verifyPass } from '@volter/world-access';
@@ -982,6 +982,12 @@ export class WorldDoors {
982
982
  // the request's trace: the caller's trace-id goes on (or a new one), under a parent-id the World mints, which the
983
983
  // request's log entries carry, so each entry is joined to its caller by an id no caller chose
984
984
  headers.delete(CALLER_HEADER);
985
+ // the address a request was signed for (a CLI's loopback bridge says it, @volter/world-core/http-bridge) is a
986
+ // credential holder's word: a browser's session cannot name another, so a signature is checked on the path it reached
987
+ if (via === 'session') {
988
+ headers.delete(ORIGINAL_PATH_HEADER);
989
+ headers.delete('x-volter-twin-original-host');
990
+ }
985
991
  const who = this.callerOf(request);
986
992
  if (who)
987
993
  headers.set(CALLER_HEADER, callerForHeader(who));
@@ -1144,6 +1150,35 @@ export class WorldDoors {
1144
1150
  // ending a browser's session needs no token: it only clears the caller's own cookie, which never
1145
1151
  // reaches these /-/ paths, so a page holding just the session could not otherwise sign out
1146
1152
  const sessionPath = path.replace(/\/+$/, '');
1153
+ // THE HAND-OFF: a page of no UI that opens this World's session from the token in its fragment, then goes to the page
1154
+ // of this World it names (`land`): how a page elsewhere links to this World's own pages (the console's comparison
1155
+ // opening a vendor's page in a copy of the World, read only). The token never reaches the server in a URL; its script
1156
+ // is a file of this origin, so the page keeps a strict policy. It is never framed: a session opened in another
1157
+ // site's frame is not this World's own page's
1158
+ if (sessionPath === 'session/hand-off' && request.method === 'GET') {
1159
+ return new Response(`<!doctype html><meta charset="utf-8"><title>${this.served}</title><body data-world="${this.served}"><script src="/-/${this.served}/session/hand-off.js"></script></body>`, { headers: { 'content-type': 'text/html; charset=utf-8', 'content-security-policy': "default-src 'none'; script-src 'self'; connect-src 'self'; base-uri 'none'; frame-ancestors 'none'", 'cache-control': 'no-store', 'referrer-policy': 'no-referrer' } });
1160
+ }
1161
+ if (sessionPath === 'session/hand-off.js' && request.method === 'GET') {
1162
+ const script = `(() => {
1163
+ const world = document.body.dataset.world;
1164
+ const carried = new URLSearchParams(location.hash.slice(1));
1165
+ history.replaceState(null, '', location.pathname);
1166
+ const token = carried.get('token') || '';
1167
+ const land = carried.get('land') || '';
1168
+ // resolved as the browser will (encoded dots too), on this origin and under this World
1169
+ let to = null;
1170
+ try { const u = new URL(land, location.origin); if (land.startsWith('/') && !/[\\\\\\s]/.test(land) && u.origin === location.origin && u.pathname.startsWith('/' + world + '/')) to = u.pathname + u.search; } catch {}
1171
+ const open = () => fetch('/-/' + world + '/session', { method: 'POST', headers: { 'x-volter-token': token } });
1172
+ if (!token || !to) { document.body.textContent = 'Nothing to open.'; return; }
1173
+ // a session this browser already holds here is kept: a page elsewhere cannot replace, downgrade or fix it
1174
+ fetch('/-/' + world + '/session')
1175
+ .then((held) => (held.ok ? held : open()))
1176
+ .then((r) => { if (r.ok) location.replace(to); else document.body.textContent = 'This World did not open.'; })
1177
+ .catch(() => { document.body.textContent = 'This World did not answer.'; });
1178
+ })();
1179
+ `;
1180
+ return new Response(script, { headers: { 'content-type': 'text/javascript; charset=utf-8', 'cache-control': 'no-store' } });
1181
+ }
1147
1182
  // a person signing in from a page (the console's form, posted to this origin): the token in the
1148
1183
  // form body, the session set here, then on to a page of this World
1149
1184
  // a form or beacon from another site never opens or ends a session here (the console, on this host's
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-runtime",
3
- "version": "3.0.11",
3
+ "version": "3.0.12",
4
4
  "description": "World configs for twins: boot named local runtimes, allocate ports, generate world.env/instance.json, and run apps against fake-key twin worlds.",
5
5
  "keywords": [
6
6
  "twin",
@@ -68,14 +68,14 @@
68
68
  "node": ">=22.3"
69
69
  },
70
70
  "peerDependencies": {
71
- "@volter/world-core": "3.0.11",
72
- "@volter/world-console": "3.0.6"
71
+ "@volter/world-core": "3.0.12",
72
+ "@volter/world-console": "3.0.7"
73
73
  },
74
74
  "dependencies": {
75
75
  "@electric-sql/pglite": "0.5.8",
76
76
  "@electric-sql/pglite-pgvector": "0.0.9",
77
77
  "@volter/world-access": "3.0.1",
78
- "@volter/world-core": "3.0.11",
78
+ "@volter/world-core": "3.0.12",
79
79
  "pg-gateway": "0.3.0-beta.4",
80
80
  "smol-toml": "^1.8.0",
81
81
  "yaml": "2.9.0"
@@ -802,11 +802,15 @@ export async function main(): Promise<void> {
802
802
  caFile = join(mkdtempSync(join(tmpdir(), 'volter-attach-')), 'ca.pem');
803
803
  writeFileSync(caFile, manifest.ca);
804
804
  }
805
+ // the World's twins, each a loopback listener here that adds its key, so a command with its own HTTP stack reaches
806
+ // them as it would a local World's (the manifest pointed at the listeners before its env is made)
807
+ const { bridgeTwins } = createRequire(import.meta.url)('@volter/world-core/http-bridge') as { bridgeTwins: (manifest: WorldManifest, key: string, ca?: string) => Promise<unknown[]> };
808
+ await bridgeTwins(manifest, manifest.env.VOLTER_TWINS_KEY ?? attachToken ?? '', manifest.ca ?? undefined);
805
809
  const injectPath = createRequire(import.meta.url).resolve('@volter/world-core/inject');
806
810
  const env = remoteAttachEnv(manifest, { injectPath, ...(caFile === undefined ? {} : { caFile }) });
807
811
  // the world's byte-stream doors, bridged to loopback listeners here, beside a command in any language
808
812
  const { bridgeStreams, drainStreams } = createRequire(import.meta.url)('@volter/world-core/stream-bridge') as { bridgeStreams: (streams: WorldManifest['streams'], token: string) => Promise<{ env: Record<string, string> }>; drainStreams: (ms?: number) => Promise<void> };
809
- const bridged = manifest.streams ? await bridgeStreams(manifest.streams, attachToken ?? manifest.env.VOLTER_TWINS_KEY ?? '') : { env: {} };
813
+ const bridged = manifest.streams ? await bridgeStreams(manifest.streams, manifest.env.VOLTER_TWINS_KEY ?? attachToken ?? '') : { env: {} };
810
814
  // VOLTER_WORLD names the World to the injector: a request addressed to its paths carries its key
811
815
  const commandEnv: Record<string, string | undefined> = withoutVaultSignIn({ ...process.env, ...env, ...bridged.env, VOLTER_WORLD: resolved.ref });
812
816
  commandEnv.VOLTER_TWIN_INJECT_QUIET = head.includes('--verbose') ? '0' : commandEnv.VOLTER_TWIN_INJECT_QUIET ?? '1';
@@ -26,7 +26,7 @@ import { getActiveWorldStore, withHistoryLocks, captureHistory, historyAtInstant
26
26
  // to `.volter/token` and `.volter/token.read` (0600).
27
27
  // One writer per world by construction: the doors run in this process, serialized per twin.
28
28
  import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync , rmSync} from 'node:fs';
29
- import { CALLER_HEADER, callerForHeader, newTraceparent, parseTraceparent, traceparentForDelivery, readTwinRequestJournalFrom, type TwinRequestJournalCursor, assertSafeChangesetName, blobDigest, destinationOf, getActiveBlobStore, journalTwinRequest, loadCheck, twinRequestJournalFailures, openSealedAt, readTwinRequestJournal, serveHttp, twinRequestCredentials, TWIN_PREFIX_HEADER, TWIN_SITES_HEADER, validateRemoteOrigin, type SealedCredential, type TwinStreamConnection, type TwinStreamSink, type BoardFrame } from '@volter/world-core/runtime';
29
+ import { CALLER_HEADER, callerForHeader, newTraceparent, parseTraceparent, traceparentForDelivery, readTwinRequestJournalFrom, type TwinRequestJournalCursor, assertSafeChangesetName, blobDigest, destinationOf, getActiveBlobStore, journalTwinRequest, loadCheck, twinRequestJournalFailures, openSealedAt, readTwinRequestJournal, serveHttp, twinRequestCredentials, TWIN_PREFIX_HEADER, TWIN_SITES_HEADER, ORIGINAL_PATH_HEADER, validateRemoteOrigin, type SealedCredential, type TwinStreamConnection, type TwinStreamSink, type BoardFrame } from '@volter/world-core/runtime';
30
30
  import { APP_READ_ENDPOINT_ENV } from './init.ts';
31
31
  import { packFacts } from './pack-facts.ts';
32
32
  import { grantOf, presentedSecrets, sameSecret, spentPasses, verifyPass, type TrustedIssuer, type WorldCredentials } from '@volter/world-access';
@@ -869,6 +869,9 @@ export class WorldDoors {
869
869
  // the request's trace: the caller's trace-id goes on (or a new one), under a parent-id the World mints, which the
870
870
  // request's log entries carry, so each entry is joined to its caller by an id no caller chose
871
871
  headers.delete(CALLER_HEADER);
872
+ // the address a request was signed for (a CLI's loopback bridge says it, @volter/world-core/http-bridge) is a
873
+ // credential holder's word: a browser's session cannot name another, so a signature is checked on the path it reached
874
+ if (via === 'session') { headers.delete(ORIGINAL_PATH_HEADER); headers.delete('x-volter-twin-original-host'); }
872
875
  const who = this.callerOf(request); if (who) headers.set(CALLER_HEADER, callerForHeader(who));
873
876
  headers.set('traceparent', traceparentForDelivery(headers.get('traceparent')) ?? newTraceparent());
874
877
  // at a twin that enforces the read scope, every request of the read token is marked read-only (a GET
@@ -997,6 +1000,36 @@ export class WorldDoors {
997
1000
  // ending a browser's session needs no token: it only clears the caller's own cookie, which never
998
1001
  // reaches these /-/ paths, so a page holding just the session could not otherwise sign out
999
1002
  const sessionPath = path.replace(/\/+$/, '');
1003
+ // THE HAND-OFF: a page of no UI that opens this World's session from the token in its fragment, then goes to the page
1004
+ // of this World it names (`land`): how a page elsewhere links to this World's own pages (the console's comparison
1005
+ // opening a vendor's page in a copy of the World, read only). The token never reaches the server in a URL; its script
1006
+ // is a file of this origin, so the page keeps a strict policy. It is never framed: a session opened in another
1007
+ // site's frame is not this World's own page's
1008
+ if (sessionPath === 'session/hand-off' && request.method === 'GET') {
1009
+ return new Response(`<!doctype html><meta charset="utf-8"><title>${this.served}</title><body data-world="${this.served}"><script src="/-/${this.served}/session/hand-off.js"></script></body>`,
1010
+ { headers: { 'content-type': 'text/html; charset=utf-8', 'content-security-policy': "default-src 'none'; script-src 'self'; connect-src 'self'; base-uri 'none'; frame-ancestors 'none'", 'cache-control': 'no-store', 'referrer-policy': 'no-referrer' } });
1011
+ }
1012
+ if (sessionPath === 'session/hand-off.js' && request.method === 'GET') {
1013
+ const script = `(() => {
1014
+ const world = document.body.dataset.world;
1015
+ const carried = new URLSearchParams(location.hash.slice(1));
1016
+ history.replaceState(null, '', location.pathname);
1017
+ const token = carried.get('token') || '';
1018
+ const land = carried.get('land') || '';
1019
+ // resolved as the browser will (encoded dots too), on this origin and under this World
1020
+ let to = null;
1021
+ try { const u = new URL(land, location.origin); if (land.startsWith('/') && !/[\\\\\\s]/.test(land) && u.origin === location.origin && u.pathname.startsWith('/' + world + '/')) to = u.pathname + u.search; } catch {}
1022
+ const open = () => fetch('/-/' + world + '/session', { method: 'POST', headers: { 'x-volter-token': token } });
1023
+ if (!token || !to) { document.body.textContent = 'Nothing to open.'; return; }
1024
+ // a session this browser already holds here is kept: a page elsewhere cannot replace, downgrade or fix it
1025
+ fetch('/-/' + world + '/session')
1026
+ .then((held) => (held.ok ? held : open()))
1027
+ .then((r) => { if (r.ok) location.replace(to); else document.body.textContent = 'This World did not open.'; })
1028
+ .catch(() => { document.body.textContent = 'This World did not answer.'; });
1029
+ })();
1030
+ `;
1031
+ return new Response(script, { headers: { 'content-type': 'text/javascript; charset=utf-8', 'cache-control': 'no-store' } });
1032
+ }
1000
1033
  // a person signing in from a page (the console's form, posted to this origin): the token in the
1001
1034
  // form body, the session set here, then on to a page of this World
1002
1035
  // a form or beacon from another site never opens or ends a session here (the console, on this host's