gitspace 0.2.0-rc.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.
Files changed (318) hide show
  1. package/.claude/settings.local.json +21 -0
  2. package/.gitspace/bundle.json +50 -0
  3. package/.gitspace/select/01-status.sh +40 -0
  4. package/.gitspace/setup/01-install-deps.sh +12 -0
  5. package/.gitspace/setup/02-typecheck.sh +16 -0
  6. package/AGENTS.md +439 -0
  7. package/CLAUDE.md +1 -0
  8. package/LICENSE +25 -0
  9. package/README.md +607 -0
  10. package/bin/gssh +62 -0
  11. package/bun.lock +647 -0
  12. package/docs/CONNECTION.md +623 -0
  13. package/docs/GATEWAY-WORKER.md +319 -0
  14. package/docs/GETTING-STARTED.md +448 -0
  15. package/docs/GITSPACE-PLATFORM.md +1819 -0
  16. package/docs/INFRASTRUCTURE.md +1347 -0
  17. package/docs/PROTOCOL.md +619 -0
  18. package/docs/QUICKSTART.md +174 -0
  19. package/docs/RELAY.md +327 -0
  20. package/docs/REMOTE-DESIGN.md +549 -0
  21. package/docs/ROADMAP.md +564 -0
  22. package/docs/SITE_DOCS_FIGMA_MAKE.md +1167 -0
  23. package/docs/STACK-DESIGN.md +588 -0
  24. package/docs/UNIFIED_ARCHITECTURE.md +292 -0
  25. package/experiments/pty-benchmark.ts +148 -0
  26. package/experiments/pty-latency.ts +100 -0
  27. package/experiments/router/client.ts +199 -0
  28. package/experiments/router/protocol.ts +74 -0
  29. package/experiments/router/router.ts +217 -0
  30. package/experiments/router/session.ts +180 -0
  31. package/experiments/router/test.ts +133 -0
  32. package/experiments/socket-bandwidth.ts +77 -0
  33. package/homebrew/gitspace.rb +45 -0
  34. package/landing-page/ATTRIBUTIONS.md +3 -0
  35. package/landing-page/README.md +11 -0
  36. package/landing-page/bun.lock +801 -0
  37. package/landing-page/guidelines/Guidelines.md +61 -0
  38. package/landing-page/index.html +37 -0
  39. package/landing-page/package.json +90 -0
  40. package/landing-page/postcss.config.mjs +15 -0
  41. package/landing-page/public/_redirects +1 -0
  42. package/landing-page/public/favicon.png +0 -0
  43. package/landing-page/src/app/App.tsx +53 -0
  44. package/landing-page/src/app/components/figma/ImageWithFallback.tsx +27 -0
  45. package/landing-page/src/app/components/ui/accordion.tsx +66 -0
  46. package/landing-page/src/app/components/ui/alert-dialog.tsx +157 -0
  47. package/landing-page/src/app/components/ui/alert.tsx +66 -0
  48. package/landing-page/src/app/components/ui/aspect-ratio.tsx +11 -0
  49. package/landing-page/src/app/components/ui/avatar.tsx +53 -0
  50. package/landing-page/src/app/components/ui/badge.tsx +46 -0
  51. package/landing-page/src/app/components/ui/breadcrumb.tsx +109 -0
  52. package/landing-page/src/app/components/ui/button.tsx +57 -0
  53. package/landing-page/src/app/components/ui/calendar.tsx +75 -0
  54. package/landing-page/src/app/components/ui/card.tsx +92 -0
  55. package/landing-page/src/app/components/ui/carousel.tsx +241 -0
  56. package/landing-page/src/app/components/ui/chart.tsx +353 -0
  57. package/landing-page/src/app/components/ui/checkbox.tsx +32 -0
  58. package/landing-page/src/app/components/ui/collapsible.tsx +33 -0
  59. package/landing-page/src/app/components/ui/command.tsx +177 -0
  60. package/landing-page/src/app/components/ui/context-menu.tsx +252 -0
  61. package/landing-page/src/app/components/ui/dialog.tsx +135 -0
  62. package/landing-page/src/app/components/ui/drawer.tsx +132 -0
  63. package/landing-page/src/app/components/ui/dropdown-menu.tsx +257 -0
  64. package/landing-page/src/app/components/ui/form.tsx +168 -0
  65. package/landing-page/src/app/components/ui/hover-card.tsx +44 -0
  66. package/landing-page/src/app/components/ui/input-otp.tsx +77 -0
  67. package/landing-page/src/app/components/ui/input.tsx +21 -0
  68. package/landing-page/src/app/components/ui/label.tsx +24 -0
  69. package/landing-page/src/app/components/ui/menubar.tsx +276 -0
  70. package/landing-page/src/app/components/ui/navigation-menu.tsx +168 -0
  71. package/landing-page/src/app/components/ui/pagination.tsx +127 -0
  72. package/landing-page/src/app/components/ui/popover.tsx +48 -0
  73. package/landing-page/src/app/components/ui/progress.tsx +31 -0
  74. package/landing-page/src/app/components/ui/radio-group.tsx +45 -0
  75. package/landing-page/src/app/components/ui/resizable.tsx +56 -0
  76. package/landing-page/src/app/components/ui/scroll-area.tsx +58 -0
  77. package/landing-page/src/app/components/ui/select.tsx +189 -0
  78. package/landing-page/src/app/components/ui/separator.tsx +28 -0
  79. package/landing-page/src/app/components/ui/sheet.tsx +139 -0
  80. package/landing-page/src/app/components/ui/sidebar.tsx +726 -0
  81. package/landing-page/src/app/components/ui/skeleton.tsx +13 -0
  82. package/landing-page/src/app/components/ui/slider.tsx +63 -0
  83. package/landing-page/src/app/components/ui/sonner.tsx +25 -0
  84. package/landing-page/src/app/components/ui/switch.tsx +31 -0
  85. package/landing-page/src/app/components/ui/table.tsx +116 -0
  86. package/landing-page/src/app/components/ui/tabs.tsx +66 -0
  87. package/landing-page/src/app/components/ui/textarea.tsx +18 -0
  88. package/landing-page/src/app/components/ui/toggle-group.tsx +73 -0
  89. package/landing-page/src/app/components/ui/toggle.tsx +47 -0
  90. package/landing-page/src/app/components/ui/tooltip.tsx +61 -0
  91. package/landing-page/src/app/components/ui/use-mobile.ts +21 -0
  92. package/landing-page/src/app/components/ui/utils.ts +6 -0
  93. package/landing-page/src/components/docs/DocsContent.tsx +718 -0
  94. package/landing-page/src/components/docs/DocsSidebar.tsx +84 -0
  95. package/landing-page/src/components/landing/CTA.tsx +59 -0
  96. package/landing-page/src/components/landing/Comparison.tsx +84 -0
  97. package/landing-page/src/components/landing/FaultyTerminal.tsx +424 -0
  98. package/landing-page/src/components/landing/Features.tsx +201 -0
  99. package/landing-page/src/components/landing/Hero.tsx +142 -0
  100. package/landing-page/src/components/landing/Pricing.tsx +140 -0
  101. package/landing-page/src/components/landing/Roadmap.tsx +86 -0
  102. package/landing-page/src/components/landing/Security.tsx +81 -0
  103. package/landing-page/src/components/landing/TerminalWindow.tsx +27 -0
  104. package/landing-page/src/components/landing/UseCases.tsx +55 -0
  105. package/landing-page/src/components/landing/Workflow.tsx +101 -0
  106. package/landing-page/src/components/layout/DashboardNavbar.tsx +37 -0
  107. package/landing-page/src/components/layout/Footer.tsx +55 -0
  108. package/landing-page/src/components/layout/LandingNavbar.tsx +82 -0
  109. package/landing-page/src/components/ui/badge.tsx +39 -0
  110. package/landing-page/src/components/ui/breadcrumb.tsx +115 -0
  111. package/landing-page/src/components/ui/button.tsx +57 -0
  112. package/landing-page/src/components/ui/card.tsx +79 -0
  113. package/landing-page/src/components/ui/mock-terminal.tsx +68 -0
  114. package/landing-page/src/components/ui/separator.tsx +28 -0
  115. package/landing-page/src/lib/utils.ts +6 -0
  116. package/landing-page/src/main.tsx +10 -0
  117. package/landing-page/src/pages/Dashboard.tsx +133 -0
  118. package/landing-page/src/pages/DocsPage.tsx +79 -0
  119. package/landing-page/src/pages/LandingPage.tsx +31 -0
  120. package/landing-page/src/pages/TerminalView.tsx +106 -0
  121. package/landing-page/src/styles/fonts.css +0 -0
  122. package/landing-page/src/styles/index.css +3 -0
  123. package/landing-page/src/styles/tailwind.css +4 -0
  124. package/landing-page/src/styles/theme.css +181 -0
  125. package/landing-page/vite.config.ts +19 -0
  126. package/npm/darwin-arm64/bin/gssh +0 -0
  127. package/npm/darwin-arm64/package.json +20 -0
  128. package/package.json +74 -0
  129. package/scripts/build.ts +284 -0
  130. package/scripts/release.ts +140 -0
  131. package/src/__tests__/test-utils.ts +298 -0
  132. package/src/commands/__tests__/serve-messages.test.ts +190 -0
  133. package/src/commands/access.ts +298 -0
  134. package/src/commands/add.ts +452 -0
  135. package/src/commands/auth.ts +364 -0
  136. package/src/commands/connect.ts +287 -0
  137. package/src/commands/directory.ts +16 -0
  138. package/src/commands/host.ts +396 -0
  139. package/src/commands/identity.ts +184 -0
  140. package/src/commands/list.ts +200 -0
  141. package/src/commands/relay.ts +315 -0
  142. package/src/commands/remove.ts +241 -0
  143. package/src/commands/serve.ts +1493 -0
  144. package/src/commands/share.ts +456 -0
  145. package/src/commands/status.ts +125 -0
  146. package/src/commands/switch.ts +353 -0
  147. package/src/commands/tmux.ts +317 -0
  148. package/src/core/__tests__/access.test.ts +240 -0
  149. package/src/core/access.ts +277 -0
  150. package/src/core/bundle.ts +342 -0
  151. package/src/core/config.ts +510 -0
  152. package/src/core/git.ts +317 -0
  153. package/src/core/github.ts +151 -0
  154. package/src/core/identity.ts +631 -0
  155. package/src/core/linear.ts +225 -0
  156. package/src/core/shell.ts +161 -0
  157. package/src/core/trusted-relays.ts +315 -0
  158. package/src/index.ts +821 -0
  159. package/src/lib/remote-session/index.ts +7 -0
  160. package/src/lib/remote-session/protocol.ts +267 -0
  161. package/src/lib/remote-session/session-handler.ts +581 -0
  162. package/src/lib/remote-session/workspace-scanner.ts +167 -0
  163. package/src/lib/tmux-lite/README.md +81 -0
  164. package/src/lib/tmux-lite/cli.ts +796 -0
  165. package/src/lib/tmux-lite/crypto/__tests__/helpers/handshake-runner.ts +349 -0
  166. package/src/lib/tmux-lite/crypto/__tests__/helpers/mock-relay.ts +291 -0
  167. package/src/lib/tmux-lite/crypto/__tests__/helpers/test-identities.ts +142 -0
  168. package/src/lib/tmux-lite/crypto/__tests__/integration/authorization.integration.test.ts +339 -0
  169. package/src/lib/tmux-lite/crypto/__tests__/integration/e2e-communication.integration.test.ts +477 -0
  170. package/src/lib/tmux-lite/crypto/__tests__/integration/error-handling.integration.test.ts +499 -0
  171. package/src/lib/tmux-lite/crypto/__tests__/integration/handshake.integration.test.ts +371 -0
  172. package/src/lib/tmux-lite/crypto/__tests__/integration/security.integration.test.ts +573 -0
  173. package/src/lib/tmux-lite/crypto/access-control.test.ts +512 -0
  174. package/src/lib/tmux-lite/crypto/access-control.ts +320 -0
  175. package/src/lib/tmux-lite/crypto/frames.test.ts +262 -0
  176. package/src/lib/tmux-lite/crypto/frames.ts +141 -0
  177. package/src/lib/tmux-lite/crypto/handshake.ts +894 -0
  178. package/src/lib/tmux-lite/crypto/identity.test.ts +220 -0
  179. package/src/lib/tmux-lite/crypto/identity.ts +286 -0
  180. package/src/lib/tmux-lite/crypto/index.ts +51 -0
  181. package/src/lib/tmux-lite/crypto/invites.test.ts +381 -0
  182. package/src/lib/tmux-lite/crypto/invites.ts +215 -0
  183. package/src/lib/tmux-lite/crypto/keyexchange.ts +435 -0
  184. package/src/lib/tmux-lite/crypto/keys.test.ts +58 -0
  185. package/src/lib/tmux-lite/crypto/keys.ts +47 -0
  186. package/src/lib/tmux-lite/crypto/secretbox.test.ts +169 -0
  187. package/src/lib/tmux-lite/crypto/secretbox.ts +124 -0
  188. package/src/lib/tmux-lite/handshake-handler.ts +451 -0
  189. package/src/lib/tmux-lite/protocol.test.ts +307 -0
  190. package/src/lib/tmux-lite/protocol.ts +266 -0
  191. package/src/lib/tmux-lite/relay-client.ts +506 -0
  192. package/src/lib/tmux-lite/server.ts +1250 -0
  193. package/src/lib/tmux-lite/shell-integration.sh +37 -0
  194. package/src/lib/tmux-lite/terminal-queries.test.ts +54 -0
  195. package/src/lib/tmux-lite/terminal-queries.ts +49 -0
  196. package/src/relay/__tests__/e2e-flow.test.ts +1284 -0
  197. package/src/relay/__tests__/helpers/auth.ts +354 -0
  198. package/src/relay/__tests__/helpers/ports.ts +51 -0
  199. package/src/relay/__tests__/protocol-validation.test.ts +265 -0
  200. package/src/relay/authorization.ts +303 -0
  201. package/src/relay/embedded-assets.generated.d.ts +15 -0
  202. package/src/relay/identity.ts +352 -0
  203. package/src/relay/index.ts +57 -0
  204. package/src/relay/pipes.test.ts +427 -0
  205. package/src/relay/pipes.ts +195 -0
  206. package/src/relay/protocol.ts +804 -0
  207. package/src/relay/registries.test.ts +437 -0
  208. package/src/relay/registries.ts +593 -0
  209. package/src/relay/server.test.ts +1323 -0
  210. package/src/relay/server.ts +1092 -0
  211. package/src/relay/signing.ts +238 -0
  212. package/src/relay/types.ts +69 -0
  213. package/src/serve/client-session-manager.ts +622 -0
  214. package/src/serve/daemon.ts +497 -0
  215. package/src/serve/pty-session.ts +236 -0
  216. package/src/serve/types.ts +169 -0
  217. package/src/shared/components/Flow.tsx +453 -0
  218. package/src/shared/components/Flow.tui.tsx +343 -0
  219. package/src/shared/components/Flow.web.tsx +442 -0
  220. package/src/shared/components/Inbox.tsx +446 -0
  221. package/src/shared/components/Inbox.tui.tsx +262 -0
  222. package/src/shared/components/Inbox.web.tsx +329 -0
  223. package/src/shared/components/MachineList.tsx +187 -0
  224. package/src/shared/components/MachineList.tui.tsx +161 -0
  225. package/src/shared/components/MachineList.web.tsx +210 -0
  226. package/src/shared/components/ProjectList.tsx +176 -0
  227. package/src/shared/components/ProjectList.tui.tsx +109 -0
  228. package/src/shared/components/ProjectList.web.tsx +143 -0
  229. package/src/shared/components/SpacesBrowser.tsx +332 -0
  230. package/src/shared/components/SpacesBrowser.tui.tsx +163 -0
  231. package/src/shared/components/SpacesBrowser.web.tsx +221 -0
  232. package/src/shared/components/index.ts +103 -0
  233. package/src/shared/hooks/index.ts +16 -0
  234. package/src/shared/hooks/useNavigation.ts +226 -0
  235. package/src/shared/index.ts +122 -0
  236. package/src/shared/providers/LocalMachineProvider.ts +425 -0
  237. package/src/shared/providers/MachineProvider.ts +165 -0
  238. package/src/shared/providers/RemoteMachineProvider.ts +444 -0
  239. package/src/shared/providers/index.ts +26 -0
  240. package/src/shared/types.ts +145 -0
  241. package/src/tui/adapters.ts +120 -0
  242. package/src/tui/app.tsx +1816 -0
  243. package/src/tui/components/Terminal.tsx +580 -0
  244. package/src/tui/hooks/index.ts +35 -0
  245. package/src/tui/hooks/useAppState.ts +314 -0
  246. package/src/tui/hooks/useDaemonStatus.ts +174 -0
  247. package/src/tui/hooks/useInboxTUI.ts +113 -0
  248. package/src/tui/hooks/useRemoteMachines.ts +209 -0
  249. package/src/tui/index.ts +24 -0
  250. package/src/tui/state.ts +299 -0
  251. package/src/tui/terminal-bracketed-paste.test.ts +45 -0
  252. package/src/tui/terminal-bracketed-paste.ts +47 -0
  253. package/src/types/bundle.ts +112 -0
  254. package/src/types/config.ts +89 -0
  255. package/src/types/errors.ts +206 -0
  256. package/src/types/identity.ts +284 -0
  257. package/src/types/workspace-fuzzy.ts +49 -0
  258. package/src/types/workspace.ts +151 -0
  259. package/src/utils/bun-socket-writer.ts +80 -0
  260. package/src/utils/deps.ts +127 -0
  261. package/src/utils/fuzzy-match.ts +125 -0
  262. package/src/utils/logger.ts +127 -0
  263. package/src/utils/markdown.ts +254 -0
  264. package/src/utils/onboarding.ts +229 -0
  265. package/src/utils/prompts.ts +114 -0
  266. package/src/utils/run-commands.ts +112 -0
  267. package/src/utils/run-scripts.ts +142 -0
  268. package/src/utils/sanitize.ts +98 -0
  269. package/src/utils/secrets.ts +122 -0
  270. package/src/utils/shell-escape.ts +40 -0
  271. package/src/utils/utf8.ts +79 -0
  272. package/src/utils/workspace-state.ts +47 -0
  273. package/src/web/README.md +73 -0
  274. package/src/web/bun.lock +575 -0
  275. package/src/web/eslint.config.js +23 -0
  276. package/src/web/index.html +16 -0
  277. package/src/web/package.json +37 -0
  278. package/src/web/public/vite.svg +1 -0
  279. package/src/web/src/App.tsx +604 -0
  280. package/src/web/src/assets/react.svg +1 -0
  281. package/src/web/src/components/Terminal.tsx +207 -0
  282. package/src/web/src/hooks/useRelayConnection.ts +224 -0
  283. package/src/web/src/hooks/useTerminal.ts +699 -0
  284. package/src/web/src/index.css +55 -0
  285. package/src/web/src/lib/crypto/__tests__/web-terminal.test.ts +1158 -0
  286. package/src/web/src/lib/crypto/frames.ts +205 -0
  287. package/src/web/src/lib/crypto/handshake.ts +396 -0
  288. package/src/web/src/lib/crypto/identity.ts +128 -0
  289. package/src/web/src/lib/crypto/keyexchange.ts +246 -0
  290. package/src/web/src/lib/crypto/relay-signing.ts +53 -0
  291. package/src/web/src/lib/invite.ts +58 -0
  292. package/src/web/src/lib/storage/identity-store.ts +94 -0
  293. package/src/web/src/main.tsx +10 -0
  294. package/src/web/src/types/identity.ts +45 -0
  295. package/src/web/tsconfig.app.json +28 -0
  296. package/src/web/tsconfig.json +7 -0
  297. package/src/web/tsconfig.node.json +26 -0
  298. package/src/web/vite.config.ts +31 -0
  299. package/todo-security.md +92 -0
  300. package/tsconfig.json +23 -0
  301. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite +0 -0
  302. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-shm +0 -0
  303. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-wal +0 -0
  304. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite +0 -0
  305. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-shm +0 -0
  306. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-wal +0 -0
  307. package/worker/bun.lock +237 -0
  308. package/worker/package.json +22 -0
  309. package/worker/schema.sql +96 -0
  310. package/worker/src/handlers/auth.ts +451 -0
  311. package/worker/src/handlers/subdomains.ts +376 -0
  312. package/worker/src/handlers/user.ts +98 -0
  313. package/worker/src/index.ts +70 -0
  314. package/worker/src/middleware/auth.ts +152 -0
  315. package/worker/src/services/cloudflare.ts +609 -0
  316. package/worker/src/types.ts +96 -0
  317. package/worker/tsconfig.json +15 -0
  318. package/worker/wrangler.toml +26 -0
@@ -0,0 +1,477 @@
1
+ /**
2
+ * Integration tests for end-to-end encrypted communication
3
+ *
4
+ * Tests frame encryption/decryption after handshake establishment.
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach } from "bun:test";
8
+ import {
9
+ createClientHello,
10
+ processServerHello,
11
+ createClientAuth,
12
+ } from "../../handshake.js";
13
+ import {
14
+ HandshakeHandler,
15
+ type EstablishedSession,
16
+ } from "../../../handshake-handler.js";
17
+ import { AccessControlList } from "../../access-control.js";
18
+ import { createFrame, openFrame, MASTER_STREAM_ID } from "../../index.js";
19
+ import {
20
+ createTestIdentityPair,
21
+ toPublicIdentity,
22
+ createIdentityFixtures,
23
+ } from "../helpers/test-identities.js";
24
+ import { runCompleteHandshake, verifyKeyPairing } from "../helpers/handshake-runner.js";
25
+ import type { X3DHResponseMessage, SessionKeys } from "../../../../../types/identity.js";
26
+
27
+ describe("E2E Communication Integration", () => {
28
+ describe("frame encryption after handshake", () => {
29
+ it("should encrypt and decrypt frames with session keys", async () => {
30
+ const { client, machine } = createTestIdentityPair();
31
+ const accessList = new AccessControlList();
32
+ accessList.addEntry(toPublicIdentity(client));
33
+
34
+ const result = await runCompleteHandshake(
35
+ client,
36
+ machine,
37
+ accessList,
38
+ { type: "access_list" }
39
+ );
40
+
41
+ expect(result.success).toBe(true);
42
+ const clientKeys = result.clientKeys!;
43
+ const machineKeys = result.machineSession!.sessionKeys;
44
+
45
+ // Client encrypts with sendKey
46
+ const message = Buffer.from("Hello, machine!");
47
+ const encrypted = createFrame(
48
+ MASTER_STREAM_ID,
49
+ message,
50
+ Buffer.from(clientKeys.sendKey)
51
+ );
52
+
53
+ // Machine decrypts with receiveKey (which equals client's sendKey)
54
+ const decrypted = openFrame(encrypted, Buffer.from(machineKeys.receiveKey));
55
+
56
+ expect(decrypted).not.toBeNull();
57
+ expect(decrypted!.streamId).toBe(MASTER_STREAM_ID);
58
+ expect(decrypted!.data.toString()).toBe("Hello, machine!");
59
+ });
60
+
61
+ it("should handle bidirectional communication", async () => {
62
+ const { client, machine } = createTestIdentityPair();
63
+ const accessList = new AccessControlList();
64
+ accessList.addEntry(toPublicIdentity(client));
65
+
66
+ const result = await runCompleteHandshake(
67
+ client,
68
+ machine,
69
+ accessList,
70
+ { type: "access_list" }
71
+ );
72
+
73
+ expect(result.success).toBe(true);
74
+ const clientKeys = result.clientKeys!;
75
+ const machineKeys = result.machineSession!.sessionKeys;
76
+
77
+ // Client -> Machine
78
+ const clientMsg = Buffer.from("Request from client");
79
+ const encryptedReq = createFrame(
80
+ MASTER_STREAM_ID,
81
+ clientMsg,
82
+ Buffer.from(clientKeys.sendKey)
83
+ );
84
+ const decryptedReq = openFrame(
85
+ encryptedReq,
86
+ Buffer.from(machineKeys.receiveKey)
87
+ );
88
+
89
+ expect(decryptedReq?.data.toString()).toBe("Request from client");
90
+
91
+ // Machine -> Client
92
+ const machineMsg = Buffer.from("Response from machine");
93
+ const encryptedRes = createFrame(
94
+ MASTER_STREAM_ID,
95
+ machineMsg,
96
+ Buffer.from(machineKeys.sendKey)
97
+ );
98
+ const decryptedRes = openFrame(
99
+ encryptedRes,
100
+ Buffer.from(clientKeys.receiveKey)
101
+ );
102
+
103
+ expect(decryptedRes?.data.toString()).toBe("Response from machine");
104
+ });
105
+
106
+ it("should fail to decrypt with wrong key", async () => {
107
+ const { client, machine } = createTestIdentityPair();
108
+ const accessList = new AccessControlList();
109
+ accessList.addEntry(toPublicIdentity(client));
110
+
111
+ const result = await runCompleteHandshake(
112
+ client,
113
+ machine,
114
+ accessList,
115
+ { type: "access_list" }
116
+ );
117
+
118
+ expect(result.success).toBe(true);
119
+ const clientKeys = result.clientKeys!;
120
+
121
+ // Encrypt with sendKey
122
+ const message = Buffer.from("Secret message");
123
+ const encrypted = createFrame(
124
+ MASTER_STREAM_ID,
125
+ message,
126
+ Buffer.from(clientKeys.sendKey)
127
+ );
128
+
129
+ // Try to decrypt with wrong key (receiveKey instead of sendKey)
130
+ const wrongDecrypt = openFrame(
131
+ encrypted,
132
+ Buffer.from(clientKeys.receiveKey)
133
+ );
134
+
135
+ expect(wrongDecrypt).toBeNull();
136
+ });
137
+
138
+ it("should fail to decrypt with random key", async () => {
139
+ const { client, machine } = createTestIdentityPair();
140
+ const accessList = new AccessControlList();
141
+ accessList.addEntry(toPublicIdentity(client));
142
+
143
+ const result = await runCompleteHandshake(
144
+ client,
145
+ machine,
146
+ accessList,
147
+ { type: "access_list" }
148
+ );
149
+
150
+ expect(result.success).toBe(true);
151
+ const clientKeys = result.clientKeys!;
152
+
153
+ // Encrypt normally
154
+ const message = Buffer.from("Secret message");
155
+ const encrypted = createFrame(
156
+ MASTER_STREAM_ID,
157
+ message,
158
+ Buffer.from(clientKeys.sendKey)
159
+ );
160
+
161
+ // Try to decrypt with random key
162
+ const randomKey = Buffer.alloc(32);
163
+ for (let i = 0; i < 32; i++) {
164
+ randomKey[i] = Math.floor(Math.random() * 256);
165
+ }
166
+
167
+ const wrongDecrypt = openFrame(encrypted, randomKey);
168
+ expect(wrongDecrypt).toBeNull();
169
+ });
170
+ });
171
+
172
+ describe("stream IDs", () => {
173
+ it("should handle different stream IDs", async () => {
174
+ const { client, machine } = createTestIdentityPair();
175
+ const accessList = new AccessControlList();
176
+ accessList.addEntry(toPublicIdentity(client));
177
+
178
+ const result = await runCompleteHandshake(
179
+ client,
180
+ machine,
181
+ accessList,
182
+ { type: "access_list" }
183
+ );
184
+
185
+ expect(result.success).toBe(true);
186
+ const clientKeys = result.clientKeys!;
187
+ const machineKeys = result.machineSession!.sessionKeys;
188
+
189
+ // Send on different streams
190
+ const stream0 = createFrame(0, Buffer.from("stream 0"), Buffer.from(clientKeys.sendKey));
191
+ const stream1 = createFrame(1, Buffer.from("stream 1"), Buffer.from(clientKeys.sendKey));
192
+ const stream255 = createFrame(255, Buffer.from("stream 255"), Buffer.from(clientKeys.sendKey));
193
+
194
+ const dec0 = openFrame(stream0, Buffer.from(machineKeys.receiveKey));
195
+ const dec1 = openFrame(stream1, Buffer.from(machineKeys.receiveKey));
196
+ const dec255 = openFrame(stream255, Buffer.from(machineKeys.receiveKey));
197
+
198
+ expect(dec0?.streamId).toBe(0);
199
+ expect(dec0?.data.toString()).toBe("stream 0");
200
+
201
+ expect(dec1?.streamId).toBe(1);
202
+ expect(dec1?.data.toString()).toBe("stream 1");
203
+
204
+ expect(dec255?.streamId).toBe(255);
205
+ expect(dec255?.data.toString()).toBe("stream 255");
206
+ });
207
+ });
208
+
209
+ describe("message integrity", () => {
210
+ it("should detect tampered ciphertext", async () => {
211
+ const { client, machine } = createTestIdentityPair();
212
+ const accessList = new AccessControlList();
213
+ accessList.addEntry(toPublicIdentity(client));
214
+
215
+ const result = await runCompleteHandshake(
216
+ client,
217
+ machine,
218
+ accessList,
219
+ { type: "access_list" }
220
+ );
221
+
222
+ expect(result.success).toBe(true);
223
+ const clientKeys = result.clientKeys!;
224
+ const machineKeys = result.machineSession!.sessionKeys;
225
+
226
+ // Encrypt a message
227
+ const message = Buffer.from("Sensitive data");
228
+ const encrypted = createFrame(
229
+ MASTER_STREAM_ID,
230
+ message,
231
+ Buffer.from(clientKeys.sendKey)
232
+ );
233
+
234
+ // Tamper with the ciphertext
235
+ const tampered = Buffer.from(encrypted);
236
+ tampered[10] ^= 0xff; // Flip bits in the middle
237
+
238
+ // Should fail authentication
239
+ const decrypted = openFrame(tampered, Buffer.from(machineKeys.receiveKey));
240
+ expect(decrypted).toBeNull();
241
+ });
242
+
243
+ it("should detect truncated message", async () => {
244
+ const { client, machine } = createTestIdentityPair();
245
+ const accessList = new AccessControlList();
246
+ accessList.addEntry(toPublicIdentity(client));
247
+
248
+ const result = await runCompleteHandshake(
249
+ client,
250
+ machine,
251
+ accessList,
252
+ { type: "access_list" }
253
+ );
254
+
255
+ expect(result.success).toBe(true);
256
+ const clientKeys = result.clientKeys!;
257
+ const machineKeys = result.machineSession!.sessionKeys;
258
+
259
+ // Encrypt a message
260
+ const message = Buffer.from("Complete message");
261
+ const encrypted = createFrame(
262
+ MASTER_STREAM_ID,
263
+ message,
264
+ Buffer.from(clientKeys.sendKey)
265
+ );
266
+
267
+ // Truncate the message
268
+ const truncated = encrypted.subarray(0, encrypted.length - 10);
269
+
270
+ // Should fail
271
+ const decrypted = openFrame(truncated, Buffer.from(machineKeys.receiveKey));
272
+ expect(decrypted).toBeNull();
273
+ });
274
+ });
275
+
276
+ describe("session isolation", () => {
277
+ it("should not decrypt messages from different sessions", async () => {
278
+ const fixtures = createIdentityFixtures();
279
+ const accessList = new AccessControlList();
280
+ accessList.addEntry(fixtures.alicePublic);
281
+ accessList.addEntry(fixtures.bobPublic);
282
+
283
+ // Alice's session
284
+ const aliceResult = await runCompleteHandshake(
285
+ fixtures.alice,
286
+ fixtures.machine,
287
+ accessList,
288
+ { type: "access_list" }
289
+ );
290
+
291
+ // Bob's session
292
+ const bobResult = await runCompleteHandshake(
293
+ fixtures.bob,
294
+ fixtures.machine,
295
+ accessList,
296
+ { type: "access_list" }
297
+ );
298
+
299
+ expect(aliceResult.success).toBe(true);
300
+ expect(bobResult.success).toBe(true);
301
+
302
+ const aliceKeys = aliceResult.clientKeys!;
303
+ const bobKeys = bobResult.clientKeys!;
304
+
305
+ // Alice encrypts a message
306
+ const aliceMsg = Buffer.from("Alice's secret");
307
+ const aliceEncrypted = createFrame(
308
+ MASTER_STREAM_ID,
309
+ aliceMsg,
310
+ Buffer.from(aliceKeys.sendKey)
311
+ );
312
+
313
+ // Bob should NOT be able to decrypt Alice's message
314
+ const bobAttempt = openFrame(
315
+ aliceEncrypted,
316
+ Buffer.from(bobKeys.receiveKey)
317
+ );
318
+
319
+ expect(bobAttempt).toBeNull();
320
+ });
321
+
322
+ it("should isolate keys between repeated handshakes", async () => {
323
+ const { client, machine } = createTestIdentityPair();
324
+ const accessList = new AccessControlList();
325
+ accessList.addEntry(toPublicIdentity(client));
326
+
327
+ // First session
328
+ const result1 = await runCompleteHandshake(
329
+ client,
330
+ machine,
331
+ accessList,
332
+ { type: "access_list" }
333
+ );
334
+
335
+ // Second session
336
+ const result2 = await runCompleteHandshake(
337
+ client,
338
+ machine,
339
+ accessList,
340
+ { type: "access_list" }
341
+ );
342
+
343
+ expect(result1.success).toBe(true);
344
+ expect(result2.success).toBe(true);
345
+
346
+ const keys1 = result1.clientKeys!;
347
+ const keys2 = result2.clientKeys!;
348
+
349
+ // Encrypt with first session's keys
350
+ const message = Buffer.from("Session 1 message");
351
+ const encrypted = createFrame(
352
+ MASTER_STREAM_ID,
353
+ message,
354
+ Buffer.from(keys1.sendKey)
355
+ );
356
+
357
+ // Should NOT decrypt with second session's keys
358
+ const wrongSession = openFrame(
359
+ encrypted,
360
+ Buffer.from(keys2.receiveKey)
361
+ );
362
+
363
+ expect(wrongSession).toBeNull();
364
+ });
365
+ });
366
+
367
+ describe("large messages", () => {
368
+ it("should handle large payloads", async () => {
369
+ const { client, machine } = createTestIdentityPair();
370
+ const accessList = new AccessControlList();
371
+ accessList.addEntry(toPublicIdentity(client));
372
+
373
+ const result = await runCompleteHandshake(
374
+ client,
375
+ machine,
376
+ accessList,
377
+ { type: "access_list" }
378
+ );
379
+
380
+ expect(result.success).toBe(true);
381
+ const clientKeys = result.clientKeys!;
382
+ const machineKeys = result.machineSession!.sessionKeys;
383
+
384
+ // Create a large message (64KB)
385
+ const largeMessage = Buffer.alloc(64 * 1024);
386
+ for (let i = 0; i < largeMessage.length; i++) {
387
+ largeMessage[i] = i % 256;
388
+ }
389
+
390
+ const encrypted = createFrame(
391
+ MASTER_STREAM_ID,
392
+ largeMessage,
393
+ Buffer.from(clientKeys.sendKey)
394
+ );
395
+
396
+ const decrypted = openFrame(
397
+ encrypted,
398
+ Buffer.from(machineKeys.receiveKey)
399
+ );
400
+
401
+ expect(decrypted).not.toBeNull();
402
+ expect(decrypted!.data.length).toBe(largeMessage.length);
403
+ expect(Buffer.compare(decrypted!.data, largeMessage)).toBe(0);
404
+ });
405
+
406
+ it("should handle empty messages", async () => {
407
+ const { client, machine } = createTestIdentityPair();
408
+ const accessList = new AccessControlList();
409
+ accessList.addEntry(toPublicIdentity(client));
410
+
411
+ const result = await runCompleteHandshake(
412
+ client,
413
+ machine,
414
+ accessList,
415
+ { type: "access_list" }
416
+ );
417
+
418
+ expect(result.success).toBe(true);
419
+ const clientKeys = result.clientKeys!;
420
+ const machineKeys = result.machineSession!.sessionKeys;
421
+
422
+ // Empty message
423
+ const emptyMessage = Buffer.alloc(0);
424
+
425
+ const encrypted = createFrame(
426
+ MASTER_STREAM_ID,
427
+ emptyMessage,
428
+ Buffer.from(clientKeys.sendKey)
429
+ );
430
+
431
+ const decrypted = openFrame(
432
+ encrypted,
433
+ Buffer.from(machineKeys.receiveKey)
434
+ );
435
+
436
+ expect(decrypted).not.toBeNull();
437
+ expect(decrypted!.data.length).toBe(0);
438
+ });
439
+ });
440
+
441
+ describe("multiple messages", () => {
442
+ it("should handle many messages in sequence", async () => {
443
+ const { client, machine } = createTestIdentityPair();
444
+ const accessList = new AccessControlList();
445
+ accessList.addEntry(toPublicIdentity(client));
446
+
447
+ const result = await runCompleteHandshake(
448
+ client,
449
+ machine,
450
+ accessList,
451
+ { type: "access_list" }
452
+ );
453
+
454
+ expect(result.success).toBe(true);
455
+ const clientKeys = result.clientKeys!;
456
+ const machineKeys = result.machineSession!.sessionKeys;
457
+
458
+ // Send 100 messages
459
+ for (let i = 0; i < 100; i++) {
460
+ const message = Buffer.from(`Message ${i}`);
461
+ const encrypted = createFrame(
462
+ MASTER_STREAM_ID,
463
+ message,
464
+ Buffer.from(clientKeys.sendKey)
465
+ );
466
+
467
+ const decrypted = openFrame(
468
+ encrypted,
469
+ Buffer.from(machineKeys.receiveKey)
470
+ );
471
+
472
+ expect(decrypted).not.toBeNull();
473
+ expect(decrypted!.data.toString()).toBe(`Message ${i}`);
474
+ }
475
+ });
476
+ });
477
+ });