@tt-a1i/hive 1.7.0 → 2.0.2
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/CHANGELOG.md +60 -0
- package/README.en.md +73 -11
- package/README.md +41 -8
- package/dist/src/cli/hive-remote.d.ts +46 -0
- package/dist/src/cli/hive-remote.js +257 -0
- package/dist/src/cli/hive-update.js +7 -2
- package/dist/src/cli/hive.d.ts +6 -0
- package/dist/src/cli/hive.js +64 -0
- package/dist/src/cli/team.d.ts +22 -0
- package/dist/src/cli/team.js +255 -5
- package/dist/src/server/agent-command-resolver.js +10 -3
- package/dist/src/server/agent-exit-classification.d.ts +6 -0
- package/dist/src/server/agent-exit-classification.js +6 -0
- package/dist/src/server/agent-manager-support.d.ts +2 -1
- package/dist/src/server/agent-manager-support.js +59 -15
- package/dist/src/server/agent-manager.d.ts +3 -0
- package/dist/src/server/agent-manager.js +22 -7
- package/dist/src/server/agent-run-bootstrap.d.ts +14 -0
- package/dist/src/server/agent-run-bootstrap.js +11 -4
- package/dist/src/server/agent-run-exit-handler.js +14 -8
- package/dist/src/server/agent-run-starter.d.ts +3 -1
- package/dist/src/server/agent-run-starter.js +22 -5
- package/dist/src/server/agent-run-sync.js +13 -5
- package/dist/src/server/agent-runtime-types.d.ts +1 -0
- package/dist/src/server/agent-runtime.d.ts +2 -1
- package/dist/src/server/agent-runtime.js +9 -2
- package/dist/src/server/agent-startup-instructions.d.ts +2 -1
- package/dist/src/server/agent-startup-instructions.js +8 -4
- package/dist/src/server/agent-stdin-dispatcher.d.ts +4 -2
- package/dist/src/server/agent-stdin-dispatcher.js +35 -3
- package/dist/src/server/command-preset-defaults.d.ts +6 -1
- package/dist/src/server/command-preset-defaults.js +56 -0
- package/dist/src/server/fs-browse.d.ts +2 -0
- package/dist/src/server/fs-browse.js +165 -31
- package/dist/src/server/fs-pick-folder.js +6 -69
- package/dist/src/server/fs-sandbox.d.ts +5 -3
- package/dist/src/server/fs-sandbox.js +5 -3
- package/dist/src/server/hive-team-guidance.js +18 -6
- package/dist/src/server/machine-name.d.ts +2 -0
- package/dist/src/server/machine-name.js +13 -0
- package/dist/src/server/open-target-commands.d.ts +1 -0
- package/dist/src/server/open-target-commands.js +4 -1
- package/dist/src/server/orchestrator-autostart.js +1 -1
- package/dist/src/server/platform-path.d.ts +1 -0
- package/dist/src/server/platform-path.js +14 -1
- package/dist/src/server/post-start-input-writer.js +50 -13
- package/dist/src/server/preset-launch-support.js +1 -0
- package/dist/src/server/recovery-summary.d.ts +2 -1
- package/dist/src/server/recovery-summary.js +2 -1
- package/dist/src/server/remote-audit-store.d.ts +51 -0
- package/dist/src/server/remote-audit-store.js +108 -0
- package/dist/src/server/remote-config-keys.d.ts +17 -0
- package/dist/src/server/remote-config-keys.js +27 -0
- package/dist/src/server/remote-control-constants.d.ts +30 -0
- package/dist/src/server/remote-control-constants.js +29 -0
- package/dist/src/server/remote-device-session.d.ts +40 -0
- package/dist/src/server/remote-device-session.js +22 -0
- package/dist/src/server/remote-device-store.d.ts +36 -0
- package/dist/src/server/remote-device-store.js +67 -0
- package/dist/src/server/remote-frame-bridge.d.ts +102 -0
- package/dist/src/server/remote-frame-bridge.js +791 -0
- package/dist/src/server/remote-gateway-client.d.ts +14 -0
- package/dist/src/server/remote-gateway-client.js +36 -0
- package/dist/src/server/remote-loopback-auth.d.ts +6 -0
- package/dist/src/server/remote-loopback-auth.js +112 -0
- package/dist/src/server/remote-pairing-tunnel.d.ts +59 -0
- package/dist/src/server/remote-pairing-tunnel.js +146 -0
- package/dist/src/server/remote-pairing.d.ts +58 -0
- package/dist/src/server/remote-pairing.js +237 -0
- package/dist/src/server/remote-tunnel.d.ts +113 -0
- package/dist/src/server/remote-tunnel.js +514 -0
- package/dist/src/server/restart-policy-support.d.ts +4 -1
- package/dist/src/server/restart-policy-support.js +3 -1
- package/dist/src/server/restart-policy.d.ts +1 -1
- package/dist/src/server/restart-policy.js +19 -3
- package/dist/src/server/route-types.d.ts +1 -1
- package/dist/src/server/routes-dispatches.js +1 -1
- package/dist/src/server/routes-fs.js +3 -3
- package/dist/src/server/routes-marketplace.js +2 -2
- package/dist/src/server/routes-open-workspace.js +1 -1
- package/dist/src/server/routes-remote.d.ts +2 -0
- package/dist/src/server/routes-remote.js +166 -0
- package/dist/src/server/routes-runtime.js +6 -6
- package/dist/src/server/routes-settings.js +16 -16
- package/dist/src/server/routes-tasks.js +2 -2
- package/dist/src/server/routes-team-memory.d.ts +2 -0
- package/dist/src/server/routes-team-memory.js +154 -0
- package/dist/src/server/routes-team-recall.d.ts +2 -0
- package/dist/src/server/routes-team-recall.js +119 -0
- package/dist/src/server/routes-team.js +31 -9
- package/dist/src/server/routes-ui.js +11 -1
- package/dist/src/server/routes-workflow-schedules.js +3 -3
- package/dist/src/server/routes-workflows.js +5 -5
- package/dist/src/server/routes-workspace-memory-dreams.d.ts +2 -0
- package/dist/src/server/routes-workspace-memory-dreams.js +105 -0
- package/dist/src/server/routes-workspace-memory.d.ts +2 -0
- package/dist/src/server/routes-workspace-memory.js +215 -0
- package/dist/src/server/routes-workspaces.js +9 -9
- package/dist/src/server/routes.js +10 -0
- package/dist/src/server/runtime-database.d.ts +1 -0
- package/dist/src/server/runtime-database.js +27 -2
- package/dist/src/server/runtime-restart-policy.d.ts +3 -1
- package/dist/src/server/runtime-restart-policy.js +2 -1
- package/dist/src/server/runtime-store-contract.d.ts +37 -0
- package/dist/src/server/runtime-store-dream.d.ts +23 -0
- package/dist/src/server/runtime-store-dream.js +16 -0
- package/dist/src/server/runtime-store-helpers.d.ts +20 -0
- package/dist/src/server/runtime-store-helpers.js +81 -7
- package/dist/src/server/runtime-store-memory.d.ts +33 -0
- package/dist/src/server/runtime-store-memory.js +37 -0
- package/dist/src/server/runtime-store-remote.d.ts +5 -0
- package/dist/src/server/runtime-store-remote.js +45 -0
- package/dist/src/server/runtime-store-workflows.js +2 -0
- package/dist/src/server/runtime-store.js +14 -3
- package/dist/src/server/session-capture-claude.d.ts +1 -1
- package/dist/src/server/session-capture-claude.js +7 -4
- package/dist/src/server/session-capture-codex.js +4 -5
- package/dist/src/server/session-capture-gemini.js +4 -5
- package/dist/src/server/session-capture-opencode.d.ts +4 -4
- package/dist/src/server/session-capture-opencode.js +20 -12
- package/dist/src/server/session-capture-qwen.d.ts +5 -0
- package/dist/src/server/session-capture-qwen.js +104 -0
- package/dist/src/server/session-capture.d.ts +17 -0
- package/dist/src/server/session-capture.js +16 -0
- package/dist/src/server/sqlite-schema-v23.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v23.js +43 -0
- package/dist/src/server/sqlite-schema-v24.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v24.js +34 -0
- package/dist/src/server/sqlite-schema-v25.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v25.js +127 -0
- package/dist/src/server/sqlite-schema-v26.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v26.js +56 -0
- package/dist/src/server/sqlite-schema-v27.d.ts +6 -0
- package/dist/src/server/sqlite-schema-v27.js +92 -0
- package/dist/src/server/sqlite-schema-v28.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v28.js +19 -0
- package/dist/src/server/sqlite-schema-v29.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v29.js +27 -0
- package/dist/src/server/sqlite-schema-v30.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v30.js +27 -0
- package/dist/src/server/sqlite-schema-v31.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v31.js +30 -0
- package/dist/src/server/sqlite-schema.d.ts +1 -1
- package/dist/src/server/sqlite-schema.js +49 -1
- package/dist/src/server/startup-command-parser.js +5 -1
- package/dist/src/server/tasks-file-watcher.d.ts +2 -0
- package/dist/src/server/tasks-file-watcher.js +15 -6
- package/dist/src/server/tasks-file.js +30 -5
- package/dist/src/server/tasks-websocket-server.js +4 -0
- package/dist/src/server/team-authz.d.ts +1 -1
- package/dist/src/server/team-authz.js +13 -1
- package/dist/src/server/team-list-enrichment.js +3 -1
- package/dist/src/server/team-memory-digest.d.ts +52 -0
- package/dist/src/server/team-memory-digest.js +200 -0
- package/dist/src/server/team-memory-dream-applier.d.ts +5 -0
- package/dist/src/server/team-memory-dream-applier.js +234 -0
- package/dist/src/server/team-memory-dream-http-serializers.d.ts +13 -0
- package/dist/src/server/team-memory-dream-http-serializers.js +12 -0
- package/dist/src/server/team-memory-dream-ops.d.ts +40 -0
- package/dist/src/server/team-memory-dream-ops.js +153 -0
- package/dist/src/server/team-memory-dream-reverter.d.ts +22 -0
- package/dist/src/server/team-memory-dream-reverter.js +221 -0
- package/dist/src/server/team-memory-dream-run-store.d.ts +23 -0
- package/dist/src/server/team-memory-dream-run-store.js +211 -0
- package/dist/src/server/team-memory-dream-runner.d.ts +37 -0
- package/dist/src/server/team-memory-dream-runner.js +178 -0
- package/dist/src/server/team-memory-dream-scheduler.d.ts +32 -0
- package/dist/src/server/team-memory-dream-scheduler.js +115 -0
- package/dist/src/server/team-memory-dream-store.d.ts +19 -0
- package/dist/src/server/team-memory-dream-store.js +16 -0
- package/dist/src/server/team-memory-dream-types.d.ts +104 -0
- package/dist/src/server/team-memory-dream-types.js +23 -0
- package/dist/src/server/team-memory-export.d.ts +22 -0
- package/dist/src/server/team-memory-export.js +220 -0
- package/dist/src/server/team-memory-feature.d.ts +12 -0
- package/dist/src/server/team-memory-feature.js +12 -0
- package/dist/src/server/team-memory-http-serializers.d.ts +102 -0
- package/dist/src/server/team-memory-http-serializers.js +46 -0
- package/dist/src/server/team-memory-injection.d.ts +31 -0
- package/dist/src/server/team-memory-injection.js +49 -0
- package/dist/src/server/team-memory-store.d.ts +116 -0
- package/dist/src/server/team-memory-store.js +513 -0
- package/dist/src/server/team-operations.d.ts +5 -1
- package/dist/src/server/team-operations.js +46 -16
- package/dist/src/server/team-recall-store.d.ts +38 -0
- package/dist/src/server/team-recall-store.js +205 -0
- package/dist/src/server/terminal-input-profile.d.ts +1 -1
- package/dist/src/server/terminal-input-profile.js +18 -0
- package/dist/src/server/terminal-ws-server.js +6 -0
- package/dist/src/server/ui-auth-helpers.d.ts +1 -1
- package/dist/src/server/ui-auth-helpers.js +7 -1
- package/dist/src/server/ui-auth.d.ts +3 -0
- package/dist/src/server/ui-auth.js +21 -1
- package/dist/src/server/workflow-cli-policy.d.ts +2 -3
- package/dist/src/server/workflow-cli-policy.js +3 -3
- package/dist/src/server/workflow-runner.d.ts +1 -0
- package/dist/src/server/workflow-runner.js +9 -4
- package/dist/src/server/workspace-path-validation.js +6 -2
- package/dist/src/server/workspace-store.d.ts +1 -1
- package/dist/src/server/workspace-store.js +35 -9
- package/dist/src/shared/fs-browse.d.ts +1 -0
- package/dist/src/shared/fs-browse.js +1 -0
- package/dist/src/shared/path-input.d.ts +12 -0
- package/dist/src/shared/path-input.js +22 -0
- package/dist/src/shared/remote-bridge-routing.d.ts +19 -0
- package/dist/src/shared/remote-bridge-routing.js +141 -0
- package/dist/src/shared/remote-crypto.d.ts +138 -0
- package/dist/src/shared/remote-crypto.js +427 -0
- package/dist/src/shared/remote-pairing-code.d.ts +7 -0
- package/dist/src/shared/remote-pairing-code.js +47 -0
- package/dist/src/shared/remote-protocol.d.ts +160 -0
- package/dist/src/shared/remote-protocol.js +526 -0
- package/dist/src/shared/team-memory.d.ts +11 -0
- package/dist/src/shared/team-memory.js +10 -0
- package/dist/src/shared/team-recall.d.ts +1 -0
- package/dist/src/shared/team-recall.js +1 -0
- package/dist/src/shared/types.d.ts +4 -5
- package/package.json +12 -5
- package/scripts/postinstall-native-artifacts.mjs +113 -0
- package/web/dist/assets/AddWorkerDialog-CbV75qUX.js +2 -0
- package/web/dist/assets/AddWorkspaceFlow-CwV-7wPx.js +1 -0
- package/web/dist/assets/FirstRunWizard-a6PWIK3x.js +1 -0
- package/web/dist/assets/MarketplaceDrawer-Dd8WIA8T.js +67 -0
- package/web/dist/assets/TaskGraphDrawer-Bk5WFIk_.js +1 -0
- package/web/dist/assets/{WhatsNewDialog-CHkZeINH.js → WhatsNewDialog-C2VZaip0.js} +1 -1
- package/web/dist/assets/WorkerModal-DucW-9YT.js +1 -0
- package/web/dist/assets/WorkflowsDrawer-Bjf4olbR.js +1 -0
- package/web/dist/assets/WorkspaceMemoryDrawer-DglCy_5f.js +1 -0
- package/web/dist/assets/WorkspaceTaskDrawer-BIWwISvA.js +1 -0
- package/web/dist/assets/index-BAiLYajK.css +1 -0
- package/web/dist/assets/index-BV2k9Dts.js +73 -0
- package/web/dist/assets/search-Bk2HQvO7.js +1 -0
- package/web/dist/assets/square-terminal-D93m9hfY.js +1 -0
- package/web/dist/cli-icons/agy.png +0 -0
- package/web/dist/cli-icons/cursor.ico +0 -0
- package/web/dist/cli-icons/grok.ico +0 -0
- package/web/dist/cli-icons/qwen.png +0 -0
- package/web/dist/index.html +8 -3
- package/web/dist/sw.js +1 -1
- package/scripts/fix-runtime-artifacts.mjs +0 -33
- package/web/dist/assets/AddWorkerDialog-BRUxpa3f.js +0 -2
- package/web/dist/assets/AddWorkspaceDialog-D56x5JCb.js +0 -1
- package/web/dist/assets/FirstRunWizard-BFVaMIsE.js +0 -1
- package/web/dist/assets/MarketplaceDrawer-DeEZ35dN.js +0 -76
- package/web/dist/assets/WorkerModal-BBCuMLIa.js +0 -1
- package/web/dist/assets/WorkspaceTaskDrawer-CpZHAcj1.js +0 -1
- package/web/dist/assets/WorkspaceTerminalPanels-7If2mDyp.js +0 -1
- package/web/dist/assets/WorkspaceTerminalPanels-DDGTF8rc.css +0 -1
- package/web/dist/assets/index-5zh61jMg.css +0 -1
- package/web/dist/assets/index-CxNL0O-C.js +0 -73
- package/web/dist/assets/path-join-7MR1s7b1.js +0 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// The loopback bridge whitelist — the "not a general localhost proxy" gate.
|
|
2
|
+
//
|
|
3
|
+
// Runs in BOTH node and browser (pure, no I/O): the daemon runs it on the
|
|
4
|
+
// OPENED Open-frame StreamMeta, AFTER the frame authenticates (M1 openNext) and
|
|
5
|
+
// BEFORE any loopback socket exists. A { ok: false } decision => the tunnel
|
|
6
|
+
// sends Reset(StreamRefused) + emits a `reject` audit row, and NEVER makes a
|
|
7
|
+
// loopback request. Removing or weakening any check here turns the tunnel into
|
|
8
|
+
// an arbitrary localhost proxy — that is exactly what these checks forbid.
|
|
9
|
+
//
|
|
10
|
+
// Invariant 1 (binding): the bridge forwards ONLY /api/* and /ws/* (terminal +
|
|
11
|
+
// tasks). Everything else — traversal, encoded tricks, absolute URLs, the UI
|
|
12
|
+
// session cookie route — is refused here.
|
|
13
|
+
import { StreamTransport } from './remote-protocol.js';
|
|
14
|
+
export const ALLOWED_HTTP_PREFIX = '/api/';
|
|
15
|
+
// Exact terminal io/control + tasks ws paths — mirrors terminal-ws-server's
|
|
16
|
+
// matchTerminalPath regex and the /ws/tasks/<id> match in that file. Path-only:
|
|
17
|
+
// the WS query (clientId/cols/rows) rides separate StreamMeta fields, never the
|
|
18
|
+
// path, so we gate the bare path and reject any query smuggled into it.
|
|
19
|
+
const WS_TERMINAL_RE = /^\/ws\/terminal\/[^/]+\/(?:io|control)$/;
|
|
20
|
+
const WS_TASKS_RE = /^\/ws\/tasks\/[^/]+$/;
|
|
21
|
+
const ALLOWED_METHODS = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']);
|
|
22
|
+
// HARDEN: /api/ui/session is UNAUTHENTICATED and unconditionally responds with
|
|
23
|
+
// Set-Cookie: hive_ui_token=<the master UI token>. The tunnel is authorized by
|
|
24
|
+
// the per-boot secret, so the phone has zero need for the UI cookie. Forwarding
|
|
25
|
+
// it would let a paired phone harvest the single credential that authenticates
|
|
26
|
+
// every /api/* route + WS upgrade. Hard-deny by exact pathname (case-folded),
|
|
27
|
+
// before prefix matching can let it through. This is layer (1) of the fix; the
|
|
28
|
+
// bridge's response-header sanitizer (remote-loopback-auth) is layer (2), and
|
|
29
|
+
// routes-ui refusing tunnel-tagged requests is layer (3).
|
|
30
|
+
const DENIED_HTTP_PATHS = new Set(['/api/ui/session']);
|
|
31
|
+
// HARDEN (trust-root defense-in-depth): the device-pairing TRUST-ROOT actions — begin a pairing,
|
|
32
|
+
// confirm/approve a device, reject a pending one — are desktop-only at the route layer
|
|
33
|
+
// (routes-remote.gateLocalDesktopOnly). The Authority Model says a phone can NEVER self-approve a
|
|
34
|
+
// new device, so we mirror /api/ui/session's layered defense: layer 1 hard-denies these here on the
|
|
35
|
+
// bridge (a forwarded frame is Reset(StreamRefused) + audited path_denied), layer 3 is the route gate.
|
|
36
|
+
// `/api/remote/pairings` (begin, POST) collides with the collection path, and confirm/reject carry a
|
|
37
|
+
// :pairingId segment, so we deny by matcher rather than an exact-set entry. A phone still uses the
|
|
38
|
+
// EQUAL-AUTHORITY remote routes (list devices, revoke, status, audit) — only the trust root is denied.
|
|
39
|
+
const isDeniedPairingPath = (pathname) => {
|
|
40
|
+
if (pathname === '/api/remote/pairings')
|
|
41
|
+
return true; // begin a pairing (POST)
|
|
42
|
+
// confirm/reject under /api/remote/pairings/:id — match the action suffix on a pairings path.
|
|
43
|
+
return (pathname.startsWith('/api/remote/pairings/') &&
|
|
44
|
+
(pathname.endsWith('/confirm') || pathname.endsWith('/reject')));
|
|
45
|
+
};
|
|
46
|
+
// Canonicalization gate for the path SHAPE (no query handling — that is the
|
|
47
|
+
// caller's policy). Reject anything non-canonical so an encoded or traversal
|
|
48
|
+
// path can never reach a route that later decodes it.
|
|
49
|
+
//
|
|
50
|
+
// A query string is allowed structurally (it is not traversal); whether a query
|
|
51
|
+
// is *permitted* on a given transport is decided in classifyOpen.
|
|
52
|
+
export function isCanonicalPath(path) {
|
|
53
|
+
if (typeof path !== 'string' || path.length === 0)
|
|
54
|
+
return false;
|
|
55
|
+
if (path[0] !== '/')
|
|
56
|
+
return false; // no absolute URLs, no authority-relative
|
|
57
|
+
if (path.includes('\0') || path.includes('\\'))
|
|
58
|
+
return false;
|
|
59
|
+
if (/%2e|%2f/i.test(path))
|
|
60
|
+
return false; // encoded . or / — refuse outright
|
|
61
|
+
if (path.includes('..'))
|
|
62
|
+
return false; // any traversal segment
|
|
63
|
+
if (path.startsWith('//'))
|
|
64
|
+
return false; // protocol-relative / authority
|
|
65
|
+
// URL parse must round-trip the pathname (ignoring any query) unchanged and
|
|
66
|
+
// not surface an injected host.
|
|
67
|
+
let u;
|
|
68
|
+
try {
|
|
69
|
+
u = new URL(path, 'http://x');
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
if (u.host !== 'x')
|
|
75
|
+
return false;
|
|
76
|
+
// pathname + search must reconstruct the input byte-for-byte: this rejects a
|
|
77
|
+
// space or any char the URL parser would normalize/strip, while still
|
|
78
|
+
// permitting a legitimate ?query.
|
|
79
|
+
return u.pathname + u.search === path;
|
|
80
|
+
}
|
|
81
|
+
// True if s contains any C0 control char (charCode 0..31). Used to keep a WS query pair from
|
|
82
|
+
// smuggling a NUL/CR/LF past the loopback-URL reattach.
|
|
83
|
+
function hasControlChar(s) {
|
|
84
|
+
for (let i = 0; i < s.length; i++) {
|
|
85
|
+
if (s.charCodeAt(i) <= 0x1f)
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
export function classifyOpen(meta) {
|
|
91
|
+
if (meta.transport === StreamTransport.Http) {
|
|
92
|
+
const m = meta.http;
|
|
93
|
+
if (!m)
|
|
94
|
+
return { ok: false, reason: 'malformed_meta' };
|
|
95
|
+
if (!isCanonicalPath(m.path))
|
|
96
|
+
return { ok: false, reason: 'path_not_canonical' };
|
|
97
|
+
// Split off the query: the prefix/deny checks run on the pathname only, so a
|
|
98
|
+
// query string can neither bypass the deny set nor break a legit /api route.
|
|
99
|
+
const queryStart = m.path.indexOf('?');
|
|
100
|
+
const pathname = queryStart === -1 ? m.path : m.path.slice(0, queryStart);
|
|
101
|
+
const lowered = pathname.toLowerCase();
|
|
102
|
+
if (DENIED_HTTP_PATHS.has(lowered) || isDeniedPairingPath(lowered)) {
|
|
103
|
+
return { ok: false, reason: 'path_denied' };
|
|
104
|
+
}
|
|
105
|
+
if (!pathname.startsWith(ALLOWED_HTTP_PREFIX)) {
|
|
106
|
+
return { ok: false, reason: 'path_not_whitelisted' };
|
|
107
|
+
}
|
|
108
|
+
if (!ALLOWED_METHODS.has(m.method))
|
|
109
|
+
return { ok: false, reason: 'bad_method' };
|
|
110
|
+
return { ok: true, transport: 'http', method: m.method, path: m.path };
|
|
111
|
+
}
|
|
112
|
+
if (meta.transport === StreamTransport.Ws) {
|
|
113
|
+
const w = meta.ws;
|
|
114
|
+
if (!w)
|
|
115
|
+
return { ok: false, reason: 'malformed_meta' };
|
|
116
|
+
if (!isCanonicalPath(w.path))
|
|
117
|
+
return { ok: false, reason: 'path_not_canonical' };
|
|
118
|
+
// WS path is path-only — a query here is non-canonical (it must ride the
|
|
119
|
+
// separate `query` meta field, reattached on the loopback URL).
|
|
120
|
+
if (w.path.includes('?'))
|
|
121
|
+
return { ok: false, reason: 'path_not_canonical' };
|
|
122
|
+
if (!WS_TERMINAL_RE.test(w.path) && !WS_TASKS_RE.test(w.path)) {
|
|
123
|
+
return { ok: false, reason: 'path_not_whitelisted' };
|
|
124
|
+
}
|
|
125
|
+
// The WS query rides separate [name,value] pairs. Reject any pair whose key/value carries a
|
|
126
|
+
// control char that could break out of the query when we URL-encode it back (defense in depth —
|
|
127
|
+
// even though we encodeURIComponent on reattach, a NUL/control char has no business in
|
|
128
|
+
// clientId/cols/rows). An empty key is also nonsense.
|
|
129
|
+
if (w.query !== undefined) {
|
|
130
|
+
for (const [k, v] of w.query) {
|
|
131
|
+
if (k.length === 0 || hasControlChar(k) || hasControlChar(v)) {
|
|
132
|
+
return { ok: false, reason: 'malformed_meta' };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return w.query !== undefined
|
|
137
|
+
? { ok: true, transport: 'ws', path: w.path, query: w.query }
|
|
138
|
+
: { ok: true, transport: 'ws', path: w.path };
|
|
139
|
+
}
|
|
140
|
+
return { ok: false, reason: 'malformed_meta' };
|
|
141
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
export declare const REMOTE_CRYPTO_VERSION = 2;
|
|
2
|
+
export declare const X25519_KEY_LEN = 32;
|
|
3
|
+
export declare const SHARED_SECRET_LEN = 32;
|
|
4
|
+
export declare const SESSION_KEY_LEN = 32;
|
|
5
|
+
export declare const NONCE_LEN = 24;
|
|
6
|
+
export declare const AEAD_TAG_LEN = 16;
|
|
7
|
+
export declare const PAIRING_SECRET_LEN = 32;
|
|
8
|
+
export declare const SESSION_SALT_LEN = 32;
|
|
9
|
+
export declare const SAS_DIGITS = 6;
|
|
10
|
+
export declare const CONN_SALT_LEN = 32;
|
|
11
|
+
export type Direction = 'd2p' | 'p2d';
|
|
12
|
+
export interface DeviceKeyPair {
|
|
13
|
+
secretKey: Uint8Array;
|
|
14
|
+
publicKey: Uint8Array;
|
|
15
|
+
}
|
|
16
|
+
export interface PairingPayload {
|
|
17
|
+
v: number;
|
|
18
|
+
gatewayUrl: string;
|
|
19
|
+
daemonId: string;
|
|
20
|
+
pairingSecret: string;
|
|
21
|
+
}
|
|
22
|
+
export interface HandshakeIds {
|
|
23
|
+
daemonId: string;
|
|
24
|
+
deviceId: string;
|
|
25
|
+
protocolVersion: number;
|
|
26
|
+
}
|
|
27
|
+
export interface SessionKeys {
|
|
28
|
+
d2p: Uint8Array;
|
|
29
|
+
p2d: Uint8Array;
|
|
30
|
+
sas: string;
|
|
31
|
+
transcriptHash: Uint8Array;
|
|
32
|
+
}
|
|
33
|
+
export interface FrameSealer {
|
|
34
|
+
direction: Direction;
|
|
35
|
+
nextSeq: number;
|
|
36
|
+
}
|
|
37
|
+
export interface FrameOpener {
|
|
38
|
+
direction: Direction;
|
|
39
|
+
lastSeq: number;
|
|
40
|
+
}
|
|
41
|
+
export declare function toBase64Url(bytes: Uint8Array): string;
|
|
42
|
+
export declare function fromBase64Url(s: string): Uint8Array;
|
|
43
|
+
export declare function encodePairingPayload(p: PairingPayload): string;
|
|
44
|
+
export declare function decodePairingPayload(s: string): PairingPayload;
|
|
45
|
+
export declare function generateDeviceKeyPair(): DeviceKeyPair;
|
|
46
|
+
export declare function serializeDeviceKeyPair(kp: DeviceKeyPair): {
|
|
47
|
+
secretKey: string;
|
|
48
|
+
publicKey: string;
|
|
49
|
+
};
|
|
50
|
+
export declare function deserializeDeviceKeyPair(s: {
|
|
51
|
+
secretKey: string;
|
|
52
|
+
publicKey: string;
|
|
53
|
+
}): DeviceKeyPair;
|
|
54
|
+
/** A fresh per-session salt. MUST be drawn anew on every (re)connect — see header comment. */
|
|
55
|
+
export declare function generateSessionSalt(): Uint8Array;
|
|
56
|
+
export declare function deriveDaemonSession(args: {
|
|
57
|
+
daemonSecretKey: Uint8Array;
|
|
58
|
+
devicePublicKey: Uint8Array;
|
|
59
|
+
daemonPublicKey: Uint8Array;
|
|
60
|
+
pairingSecret: Uint8Array;
|
|
61
|
+
sessionSalt: Uint8Array;
|
|
62
|
+
ids: HandshakeIds;
|
|
63
|
+
}): SessionKeys;
|
|
64
|
+
export declare function deriveDeviceSession(args: {
|
|
65
|
+
deviceSecretKey: Uint8Array;
|
|
66
|
+
daemonPublicKey: Uint8Array;
|
|
67
|
+
devicePublicKey: Uint8Array;
|
|
68
|
+
pairingSecret: Uint8Array;
|
|
69
|
+
sessionSalt: Uint8Array;
|
|
70
|
+
ids: HandshakeIds;
|
|
71
|
+
}): SessionKeys;
|
|
72
|
+
export interface ConnectionKeys {
|
|
73
|
+
d2p: Uint8Array;
|
|
74
|
+
p2d: Uint8Array;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A fresh 32-byte connection salt. Drawn anew on EVERY (re)connect (page reload, daemon reconnect,
|
|
78
|
+
* peer-online). Both sides contribute one (bilateral) so neither can force the result to repeat.
|
|
79
|
+
*/
|
|
80
|
+
export declare function generateConnSalt(): Uint8Array;
|
|
81
|
+
/**
|
|
82
|
+
* Derive the two per-connection AEAD keys from the persisted ROOT keys + the bilateral connection
|
|
83
|
+
* salts. Called identically on both sides — same inputs, same byte order ⇒ same ConnectionKeys, so
|
|
84
|
+
* d2p/p2d still match across the wire.
|
|
85
|
+
*
|
|
86
|
+
* The persisted keys (DeviceSession.keys / StoredDeviceSession.rootKeys) are ROOTS: they are never
|
|
87
|
+
* passed to sealFrame/openFrame. This is the ONLY consumer of the root for AEAD purposes.
|
|
88
|
+
*
|
|
89
|
+
* ikm = the PER-DIRECTION root (rootD2p→d2p, rootP2d→p2d) — no cross-direction mixing.
|
|
90
|
+
* salt = CONN_SALT_PREFIX || phoneConnSalt(32) || daemonConnSalt(32) (bilateral, fixed order).
|
|
91
|
+
* info = per-direction label || 0x00 || ctx, where ctx binds protocolVersion + ids + both salts.
|
|
92
|
+
*
|
|
93
|
+
* NOTE: the salts appear in BOTH the HKDF salt arg and the info ctx. This is deliberate
|
|
94
|
+
* defense-in-depth, not a leftover — the salt slot makes them the extract entropy, the info slot
|
|
95
|
+
* binds them into the per-direction expand context so the two directions can never coincide and a
|
|
96
|
+
* future label edit can't silently drop the salt binding. Redundant but harmless (HKDF salt and
|
|
97
|
+
* info are independent inputs). A root reused across (deviceId, daemonId, version) yields different
|
|
98
|
+
* connKeys, and the two directions stay distinct (invariant 2).
|
|
99
|
+
*/
|
|
100
|
+
export declare function deriveConnectionKeys(args: {
|
|
101
|
+
rootD2p: Uint8Array;
|
|
102
|
+
rootP2d: Uint8Array;
|
|
103
|
+
phoneConnSalt: Uint8Array;
|
|
104
|
+
daemonConnSalt: Uint8Array;
|
|
105
|
+
ids: HandshakeIds;
|
|
106
|
+
}): ConnectionKeys;
|
|
107
|
+
export declare function deriveSas(transcriptHash: Uint8Array, ids: HandshakeIds): string;
|
|
108
|
+
export declare function buildNonce(direction: Direction, streamId: number, seq: number): Uint8Array;
|
|
109
|
+
export declare function sealFrame(args: {
|
|
110
|
+
key: Uint8Array;
|
|
111
|
+
direction: Direction;
|
|
112
|
+
headerBytes: Uint8Array;
|
|
113
|
+
payload: Uint8Array;
|
|
114
|
+
}): Uint8Array;
|
|
115
|
+
export declare function openFrame(args: {
|
|
116
|
+
key: Uint8Array;
|
|
117
|
+
direction: Direction;
|
|
118
|
+
headerBytes: Uint8Array;
|
|
119
|
+
ciphertext: Uint8Array;
|
|
120
|
+
}): Uint8Array;
|
|
121
|
+
export declare function createSealer(direction: Direction): FrameSealer;
|
|
122
|
+
export declare function createOpener(direction: Direction): FrameOpener;
|
|
123
|
+
export declare function sealNext(sealer: FrameSealer, args: {
|
|
124
|
+
key: Uint8Array;
|
|
125
|
+
streamId: number;
|
|
126
|
+
headerBytes: Uint8Array;
|
|
127
|
+
payload: Uint8Array;
|
|
128
|
+
}): {
|
|
129
|
+
seq: number;
|
|
130
|
+
ciphertext: Uint8Array;
|
|
131
|
+
};
|
|
132
|
+
export declare function openNext(opener: FrameOpener, args: {
|
|
133
|
+
key: Uint8Array;
|
|
134
|
+
streamId: number;
|
|
135
|
+
headerBytes: Uint8Array;
|
|
136
|
+
ciphertext: Uint8Array;
|
|
137
|
+
seq: number;
|
|
138
|
+
}): Uint8Array;
|