@cuna_labs/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (624) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +9 -0
  3. package/README.md +169 -0
  4. package/THIRD_PARTY_NOTICES.md +29 -0
  5. package/dist/api/capability-evidence.d.ts +19 -0
  6. package/dist/api/capability-evidence.d.ts.map +1 -0
  7. package/dist/api/capability-evidence.js +38 -0
  8. package/dist/api/capability-evidence.js.map +1 -0
  9. package/dist/api/client.d.ts +167 -0
  10. package/dist/api/client.d.ts.map +1 -0
  11. package/dist/api/client.js +937 -0
  12. package/dist/api/client.js.map +1 -0
  13. package/dist/api/contracts.d.ts +345 -0
  14. package/dist/api/contracts.d.ts.map +1 -0
  15. package/dist/api/contracts.js +976 -0
  16. package/dist/api/contracts.js.map +1 -0
  17. package/dist/api/execution-workspaces.d.ts +17 -0
  18. package/dist/api/execution-workspaces.d.ts.map +1 -0
  19. package/dist/api/execution-workspaces.js +46 -0
  20. package/dist/api/execution-workspaces.js.map +1 -0
  21. package/dist/api/http.d.ts +76 -0
  22. package/dist/api/http.d.ts.map +1 -0
  23. package/dist/api/http.js +838 -0
  24. package/dist/api/http.js.map +1 -0
  25. package/dist/api/managed-executions.d.ts +37 -0
  26. package/dist/api/managed-executions.d.ts.map +1 -0
  27. package/dist/api/managed-executions.js +80 -0
  28. package/dist/api/managed-executions.js.map +1 -0
  29. package/dist/api/observer-v2-schema.d.ts +206 -0
  30. package/dist/api/observer-v2-schema.d.ts.map +1 -0
  31. package/dist/api/observer-v2-schema.js +264 -0
  32. package/dist/api/observer-v2-schema.js.map +1 -0
  33. package/dist/api/observer-v2.d.ts +33 -0
  34. package/dist/api/observer-v2.d.ts.map +1 -0
  35. package/dist/api/observer-v2.js +60 -0
  36. package/dist/api/observer-v2.js.map +1 -0
  37. package/dist/api/owner-observe-grants-v2-schema.d.ts +678 -0
  38. package/dist/api/owner-observe-grants-v2-schema.d.ts.map +1 -0
  39. package/dist/api/owner-observe-grants-v2-schema.js +887 -0
  40. package/dist/api/owner-observe-grants-v2-schema.js.map +1 -0
  41. package/dist/api/owner-observe-grants-v2.d.ts +268 -0
  42. package/dist/api/owner-observe-grants-v2.d.ts.map +1 -0
  43. package/dist/api/owner-observe-grants-v2.js +604 -0
  44. package/dist/api/owner-observe-grants-v2.js.map +1 -0
  45. package/dist/api/provider-v2-schema.d.ts +293 -0
  46. package/dist/api/provider-v2-schema.d.ts.map +1 -0
  47. package/dist/api/provider-v2-schema.js +381 -0
  48. package/dist/api/provider-v2-schema.js.map +1 -0
  49. package/dist/api/provider-v2.d.ts +55 -0
  50. package/dist/api/provider-v2.d.ts.map +1 -0
  51. package/dist/api/provider-v2.js +54 -0
  52. package/dist/api/provider-v2.js.map +1 -0
  53. package/dist/api/remote-workspace.d.ts +29 -0
  54. package/dist/api/remote-workspace.d.ts.map +1 -0
  55. package/dist/api/remote-workspace.js +62 -0
  56. package/dist/api/remote-workspace.js.map +1 -0
  57. package/dist/api/supervisor-live-update.d.ts +248 -0
  58. package/dist/api/supervisor-live-update.d.ts.map +1 -0
  59. package/dist/api/supervisor-live-update.js +434 -0
  60. package/dist/api/supervisor-live-update.js.map +1 -0
  61. package/dist/api/terminal-reason.d.ts +5 -0
  62. package/dist/api/terminal-reason.d.ts.map +1 -0
  63. package/dist/api/terminal-reason.js +12 -0
  64. package/dist/api/terminal-reason.js.map +1 -0
  65. package/dist/auth/browser-handoff.d.ts +59 -0
  66. package/dist/auth/browser-handoff.d.ts.map +1 -0
  67. package/dist/auth/browser-handoff.js +33 -0
  68. package/dist/auth/browser-handoff.js.map +1 -0
  69. package/dist/auth/browser.d.ts +11 -0
  70. package/dist/auth/browser.d.ts.map +1 -0
  71. package/dist/auth/browser.js +47 -0
  72. package/dist/auth/browser.js.map +1 -0
  73. package/dist/auth/human-client.d.ts +39 -0
  74. package/dist/auth/human-client.d.ts.map +1 -0
  75. package/dist/auth/human-client.js +66 -0
  76. package/dist/auth/human-client.js.map +1 -0
  77. package/dist/auth/human-contracts.d.ts +52 -0
  78. package/dist/auth/human-contracts.d.ts.map +1 -0
  79. package/dist/auth/human-contracts.js +229 -0
  80. package/dist/auth/human-contracts.js.map +1 -0
  81. package/dist/auth/human-session.d.ts +48 -0
  82. package/dist/auth/human-session.d.ts.map +1 -0
  83. package/dist/auth/human-session.js +902 -0
  84. package/dist/auth/human-session.js.map +1 -0
  85. package/dist/auth/onboarding-state.d.ts +31 -0
  86. package/dist/auth/onboarding-state.d.ts.map +1 -0
  87. package/dist/auth/onboarding-state.js +52 -0
  88. package/dist/auth/onboarding-state.js.map +1 -0
  89. package/dist/auth/pkce.d.ts +15 -0
  90. package/dist/auth/pkce.d.ts.map +1 -0
  91. package/dist/auth/pkce.js +19 -0
  92. package/dist/auth/pkce.js.map +1 -0
  93. package/dist/bin/cuna.d.ts +3 -0
  94. package/dist/bin/cuna.d.ts.map +1 -0
  95. package/dist/bin/cuna.js +4 -0
  96. package/dist/bin/cuna.js.map +1 -0
  97. package/dist/build-identity.d.ts +20 -0
  98. package/dist/build-identity.d.ts.map +1 -0
  99. package/dist/build-identity.js +77 -0
  100. package/dist/build-identity.js.map +1 -0
  101. package/dist/cli/command-help.d.ts +9 -0
  102. package/dist/cli/command-help.d.ts.map +1 -0
  103. package/dist/cli/command-help.js +324 -0
  104. package/dist/cli/command-help.js.map +1 -0
  105. package/dist/cli/help.d.ts +40 -0
  106. package/dist/cli/help.d.ts.map +1 -0
  107. package/dist/cli/help.js +256 -0
  108. package/dist/cli/help.js.map +1 -0
  109. package/dist/cli/output.d.ts +28 -0
  110. package/dist/cli/output.d.ts.map +1 -0
  111. package/dist/cli/output.js +95 -0
  112. package/dist/cli/output.js.map +1 -0
  113. package/dist/cli/parser.d.ts +65 -0
  114. package/dist/cli/parser.d.ts.map +1 -0
  115. package/dist/cli/parser.js +294 -0
  116. package/dist/cli/parser.js.map +1 -0
  117. package/dist/cli/process-entrypoint.d.ts +18 -0
  118. package/dist/cli/process-entrypoint.d.ts.map +1 -0
  119. package/dist/cli/process-entrypoint.js +26 -0
  120. package/dist/cli/process-entrypoint.js.map +1 -0
  121. package/dist/cli/run.d.ts +114 -0
  122. package/dist/cli/run.d.ts.map +1 -0
  123. package/dist/cli/run.js +1969 -0
  124. package/dist/cli/run.js.map +1 -0
  125. package/dist/commands/commands.d.ts +74 -0
  126. package/dist/commands/commands.d.ts.map +1 -0
  127. package/dist/commands/commands.js +2814 -0
  128. package/dist/commands/commands.js.map +1 -0
  129. package/dist/config/config.d.ts +86 -0
  130. package/dist/config/config.d.ts.map +1 -0
  131. package/dist/config/config.js +323 -0
  132. package/dist/config/config.js.map +1 -0
  133. package/dist/config/infra-contract-witness.d.ts +29 -0
  134. package/dist/config/infra-contract-witness.d.ts.map +1 -0
  135. package/dist/config/infra-contract-witness.js +30 -0
  136. package/dist/config/infra-contract-witness.js.map +1 -0
  137. package/dist/core/browser-url.d.ts +2 -0
  138. package/dist/core/browser-url.d.ts.map +1 -0
  139. package/dist/core/browser-url.js +20 -0
  140. package/dist/core/browser-url.js.map +1 -0
  141. package/dist/core/deployed-wire-compatibility.d.ts +15 -0
  142. package/dist/core/deployed-wire-compatibility.d.ts.map +1 -0
  143. package/dist/core/deployed-wire-compatibility.js +15 -0
  144. package/dist/core/deployed-wire-compatibility.js.map +1 -0
  145. package/dist/core/errors.d.ts +59 -0
  146. package/dist/core/errors.d.ts.map +1 -0
  147. package/dist/core/errors.js +82 -0
  148. package/dist/core/errors.js.map +1 -0
  149. package/dist/core/exit-codes.d.ts +68 -0
  150. package/dist/core/exit-codes.d.ts.map +1 -0
  151. package/dist/core/exit-codes.js +84 -0
  152. package/dist/core/exit-codes.js.map +1 -0
  153. package/dist/core/instant.d.ts +60 -0
  154. package/dist/core/instant.d.ts.map +1 -0
  155. package/dist/core/instant.js +90 -0
  156. package/dist/core/instant.js.map +1 -0
  157. package/dist/core/namespace.d.ts +171 -0
  158. package/dist/core/namespace.d.ts.map +1 -0
  159. package/dist/core/namespace.js +227 -0
  160. package/dist/core/namespace.js.map +1 -0
  161. package/dist/core/observation-budget.d.ts +185 -0
  162. package/dist/core/observation-budget.d.ts.map +1 -0
  163. package/dist/core/observation-budget.js +209 -0
  164. package/dist/core/observation-budget.js.map +1 -0
  165. package/dist/core/product-web.d.ts +81 -0
  166. package/dist/core/product-web.d.ts.map +1 -0
  167. package/dist/core/product-web.js +87 -0
  168. package/dist/core/product-web.js.map +1 -0
  169. package/dist/core/validation.d.ts +90 -0
  170. package/dist/core/validation.d.ts.map +1 -0
  171. package/dist/core/validation.js +199 -0
  172. package/dist/core/validation.js.map +1 -0
  173. package/dist/credentials/contracts.d.ts +82 -0
  174. package/dist/credentials/contracts.d.ts.map +1 -0
  175. package/dist/credentials/contracts.js +2 -0
  176. package/dist/credentials/contracts.js.map +1 -0
  177. package/dist/credentials/errors.d.ts +19 -0
  178. package/dist/credentials/errors.d.ts.map +1 -0
  179. package/dist/credentials/errors.js +16 -0
  180. package/dist/credentials/errors.js.map +1 -0
  181. package/dist/credentials/index.d.ts +6 -0
  182. package/dist/credentials/index.d.ts.map +1 -0
  183. package/dist/credentials/index.js +6 -0
  184. package/dist/credentials/index.js.map +1 -0
  185. package/dist/credentials/local-session.d.ts +101 -0
  186. package/dist/credentials/local-session.d.ts.map +1 -0
  187. package/dist/credentials/local-session.js +839 -0
  188. package/dist/credentials/local-session.js.map +1 -0
  189. package/dist/credentials/secret-material.d.ts +17 -0
  190. package/dist/credentials/secret-material.d.ts.map +1 -0
  191. package/dist/credentials/secret-material.js +49 -0
  192. package/dist/credentials/secret-material.js.map +1 -0
  193. package/dist/credentials/vault.d.ts +41 -0
  194. package/dist/credentials/vault.d.ts.map +1 -0
  195. package/dist/credentials/vault.js +719 -0
  196. package/dist/credentials/vault.js.map +1 -0
  197. package/dist/daemon/endpoint.d.ts +29 -0
  198. package/dist/daemon/endpoint.d.ts.map +1 -0
  199. package/dist/daemon/endpoint.js +66 -0
  200. package/dist/daemon/endpoint.js.map +1 -0
  201. package/dist/daemon/journal.d.ts +62 -0
  202. package/dist/daemon/journal.d.ts.map +1 -0
  203. package/dist/daemon/journal.js +299 -0
  204. package/dist/daemon/journal.js.map +1 -0
  205. package/dist/daemon/lease.d.ts +25 -0
  206. package/dist/daemon/lease.d.ts.map +1 -0
  207. package/dist/daemon/lease.js +94 -0
  208. package/dist/daemon/lease.js.map +1 -0
  209. package/dist/daemon/lifecycle.d.ts +14 -0
  210. package/dist/daemon/lifecycle.d.ts.map +1 -0
  211. package/dist/daemon/lifecycle.js +29 -0
  212. package/dist/daemon/lifecycle.js.map +1 -0
  213. package/dist/daemon/protocol.d.ts +65 -0
  214. package/dist/daemon/protocol.d.ts.map +1 -0
  215. package/dist/daemon/protocol.js +194 -0
  216. package/dist/daemon/protocol.js.map +1 -0
  217. package/dist/daemon/supervisor.d.ts +29 -0
  218. package/dist/daemon/supervisor.d.ts.map +1 -0
  219. package/dist/daemon/supervisor.js +87 -0
  220. package/dist/daemon/supervisor.js.map +1 -0
  221. package/dist/daemon/views.d.ts +31 -0
  222. package/dist/daemon/views.d.ts.map +1 -0
  223. package/dist/daemon/views.js +86 -0
  224. package/dist/daemon/views.js.map +1 -0
  225. package/dist/index.d.ts +38 -0
  226. package/dist/index.d.ts.map +1 -0
  227. package/dist/index.js +38 -0
  228. package/dist/index.js.map +1 -0
  229. package/dist/journey/api-effects.d.ts +59 -0
  230. package/dist/journey/api-effects.d.ts.map +1 -0
  231. package/dist/journey/api-effects.js +386 -0
  232. package/dist/journey/api-effects.js.map +1 -0
  233. package/dist/journey/derived-identity.d.ts +91 -0
  234. package/dist/journey/derived-identity.d.ts.map +1 -0
  235. package/dist/journey/derived-identity.js +123 -0
  236. package/dist/journey/derived-identity.js.map +1 -0
  237. package/dist/journey/index.d.ts +6 -0
  238. package/dist/journey/index.d.ts.map +1 -0
  239. package/dist/journey/index.js +6 -0
  240. package/dist/journey/index.js.map +1 -0
  241. package/dist/journey/intent.d.ts +36 -0
  242. package/dist/journey/intent.d.ts.map +1 -0
  243. package/dist/journey/intent.js +191 -0
  244. package/dist/journey/intent.js.map +1 -0
  245. package/dist/journey/orchestrator.d.ts +140 -0
  246. package/dist/journey/orchestrator.d.ts.map +1 -0
  247. package/dist/journey/orchestrator.js +415 -0
  248. package/dist/journey/orchestrator.js.map +1 -0
  249. package/dist/journey/provider-launch-intent.d.ts +12 -0
  250. package/dist/journey/provider-launch-intent.d.ts.map +1 -0
  251. package/dist/journey/provider-launch-intent.js +40 -0
  252. package/dist/journey/provider-launch-intent.js.map +1 -0
  253. package/dist/journey/remote-workspace.d.ts +34 -0
  254. package/dist/journey/remote-workspace.d.ts.map +1 -0
  255. package/dist/journey/remote-workspace.js +91 -0
  256. package/dist/journey/remote-workspace.js.map +1 -0
  257. package/dist/journey/root-entry.d.ts +56 -0
  258. package/dist/journey/root-entry.d.ts.map +1 -0
  259. package/dist/journey/root-entry.js +19 -0
  260. package/dist/journey/root-entry.js.map +1 -0
  261. package/dist/journey/selection.d.ts +165 -0
  262. package/dist/journey/selection.d.ts.map +1 -0
  263. package/dist/journey/selection.js +565 -0
  264. package/dist/journey/selection.js.map +1 -0
  265. package/dist/journey/session-failure.d.ts +4 -0
  266. package/dist/journey/session-failure.d.ts.map +1 -0
  267. package/dist/journey/session-failure.js +28 -0
  268. package/dist/journey/session-failure.js.map +1 -0
  269. package/dist/journey/workspace-effects.d.ts +30 -0
  270. package/dist/journey/workspace-effects.d.ts.map +1 -0
  271. package/dist/journey/workspace-effects.js +348 -0
  272. package/dist/journey/workspace-effects.js.map +1 -0
  273. package/dist/local-actions/broker.d.ts +32 -0
  274. package/dist/local-actions/broker.d.ts.map +1 -0
  275. package/dist/local-actions/broker.js +400 -0
  276. package/dist/local-actions/broker.js.map +1 -0
  277. package/dist/local-actions/browser-action.d.ts +78 -0
  278. package/dist/local-actions/browser-action.d.ts.map +1 -0
  279. package/dist/local-actions/browser-action.js +307 -0
  280. package/dist/local-actions/browser-action.js.map +1 -0
  281. package/dist/local-actions/callback-relay.d.ts +45 -0
  282. package/dist/local-actions/callback-relay.d.ts.map +1 -0
  283. package/dist/local-actions/callback-relay.js +265 -0
  284. package/dist/local-actions/callback-relay.js.map +1 -0
  285. package/dist/local-actions/clipboard.d.ts +3 -0
  286. package/dist/local-actions/clipboard.d.ts.map +1 -0
  287. package/dist/local-actions/clipboard.js +18 -0
  288. package/dist/local-actions/clipboard.js.map +1 -0
  289. package/dist/local-actions/contracts.d.ts +63 -0
  290. package/dist/local-actions/contracts.d.ts.map +1 -0
  291. package/dist/local-actions/contracts.js +18 -0
  292. package/dist/local-actions/contracts.js.map +1 -0
  293. package/dist/local-actions/desktop-presentation.d.ts +141 -0
  294. package/dist/local-actions/desktop-presentation.d.ts.map +1 -0
  295. package/dist/local-actions/desktop-presentation.js +253 -0
  296. package/dist/local-actions/desktop-presentation.js.map +1 -0
  297. package/dist/local-actions/device-selection.d.ts +43 -0
  298. package/dist/local-actions/device-selection.d.ts.map +1 -0
  299. package/dist/local-actions/device-selection.js +154 -0
  300. package/dist/local-actions/device-selection.js.map +1 -0
  301. package/dist/local-actions/executor.d.ts +49 -0
  302. package/dist/local-actions/executor.d.ts.map +1 -0
  303. package/dist/local-actions/executor.js +329 -0
  304. package/dist/local-actions/executor.js.map +1 -0
  305. package/dist/local-actions/file-transfers.d.ts +141 -0
  306. package/dist/local-actions/file-transfers.d.ts.map +1 -0
  307. package/dist/local-actions/file-transfers.js +446 -0
  308. package/dist/local-actions/file-transfers.js.map +1 -0
  309. package/dist/local-actions/foreground-notifications.d.ts +45 -0
  310. package/dist/local-actions/foreground-notifications.d.ts.map +1 -0
  311. package/dist/local-actions/foreground-notifications.js +163 -0
  312. package/dist/local-actions/foreground-notifications.js.map +1 -0
  313. package/dist/local-actions/git-signing.d.ts +39 -0
  314. package/dist/local-actions/git-signing.d.ts.map +1 -0
  315. package/dist/local-actions/git-signing.js +135 -0
  316. package/dist/local-actions/git-signing.js.map +1 -0
  317. package/dist/local-actions/index.d.ts +19 -0
  318. package/dist/local-actions/index.d.ts.map +1 -0
  319. package/dist/local-actions/index.js +19 -0
  320. package/dist/local-actions/index.js.map +1 -0
  321. package/dist/local-actions/local-service-actions.d.ts +87 -0
  322. package/dist/local-actions/local-service-actions.d.ts.map +1 -0
  323. package/dist/local-actions/local-service-actions.js +347 -0
  324. package/dist/local-actions/local-service-actions.js.map +1 -0
  325. package/dist/local-actions/loopback.d.ts +5 -0
  326. package/dist/local-actions/loopback.d.ts.map +1 -0
  327. package/dist/local-actions/loopback.js +15 -0
  328. package/dist/local-actions/loopback.js.map +1 -0
  329. package/dist/local-actions/policy-store.d.ts +7 -0
  330. package/dist/local-actions/policy-store.d.ts.map +1 -0
  331. package/dist/local-actions/policy-store.js +70 -0
  332. package/dist/local-actions/policy-store.js.map +1 -0
  333. package/dist/local-actions/policy.d.ts +21 -0
  334. package/dist/local-actions/policy.d.ts.map +1 -0
  335. package/dist/local-actions/policy.js +43 -0
  336. package/dist/local-actions/policy.js.map +1 -0
  337. package/dist/local-actions/private-port-forward.d.ts +36 -0
  338. package/dist/local-actions/private-port-forward.d.ts.map +1 -0
  339. package/dist/local-actions/private-port-forward.js +154 -0
  340. package/dist/local-actions/private-port-forward.js.map +1 -0
  341. package/dist/local-actions/provider-mcp-config.d.ts +101 -0
  342. package/dist/local-actions/provider-mcp-config.d.ts.map +1 -0
  343. package/dist/local-actions/provider-mcp-config.js +166 -0
  344. package/dist/local-actions/provider-mcp-config.js.map +1 -0
  345. package/dist/local-actions/providers.d.ts +12 -0
  346. package/dist/local-actions/providers.d.ts.map +1 -0
  347. package/dist/local-actions/providers.js +42 -0
  348. package/dist/local-actions/providers.js.map +1 -0
  349. package/dist/local-actions/schemas.d.ts +4 -0
  350. package/dist/local-actions/schemas.d.ts.map +1 -0
  351. package/dist/local-actions/schemas.js +314 -0
  352. package/dist/local-actions/schemas.js.map +1 -0
  353. package/dist/local-actions/sensitive-consent.d.ts +32 -0
  354. package/dist/local-actions/sensitive-consent.d.ts.map +1 -0
  355. package/dist/local-actions/sensitive-consent.js +47 -0
  356. package/dist/local-actions/sensitive-consent.js.map +1 -0
  357. package/dist/machines/command-launch.d.ts +17 -0
  358. package/dist/machines/command-launch.d.ts.map +1 -0
  359. package/dist/machines/command-launch.js +52 -0
  360. package/dist/machines/command-launch.js.map +1 -0
  361. package/dist/machines/execution-receipt.d.ts +18 -0
  362. package/dist/machines/execution-receipt.d.ts.map +1 -0
  363. package/dist/machines/execution-receipt.js +87 -0
  364. package/dist/machines/execution-receipt.js.map +1 -0
  365. package/dist/machines/executions-screen.d.ts +6 -0
  366. package/dist/machines/executions-screen.d.ts.map +1 -0
  367. package/dist/machines/executions-screen.js +410 -0
  368. package/dist/machines/executions-screen.js.map +1 -0
  369. package/dist/machines/explorer.d.ts +75 -0
  370. package/dist/machines/explorer.d.ts.map +1 -0
  371. package/dist/machines/explorer.js +1726 -0
  372. package/dist/machines/explorer.js.map +1 -0
  373. package/dist/machines/live-supervisor-update.d.ts +325 -0
  374. package/dist/machines/live-supervisor-update.d.ts.map +1 -0
  375. package/dist/machines/live-supervisor-update.js +513 -0
  376. package/dist/machines/live-supervisor-update.js.map +1 -0
  377. package/dist/machines/machine-first.d.ts +123 -0
  378. package/dist/machines/machine-first.d.ts.map +1 -0
  379. package/dist/machines/machine-first.js +119 -0
  380. package/dist/machines/machine-first.js.map +1 -0
  381. package/dist/machines/opencode-supervisor.d.ts +51 -0
  382. package/dist/machines/opencode-supervisor.d.ts.map +1 -0
  383. package/dist/machines/opencode-supervisor.js +124 -0
  384. package/dist/machines/opencode-supervisor.js.map +1 -0
  385. package/dist/machines/pagination.d.ts +4 -0
  386. package/dist/machines/pagination.d.ts.map +1 -0
  387. package/dist/machines/pagination.js +7 -0
  388. package/dist/machines/pagination.js.map +1 -0
  389. package/dist/machines/provider-availability.d.ts +42 -0
  390. package/dist/machines/provider-availability.d.ts.map +1 -0
  391. package/dist/machines/provider-availability.js +82 -0
  392. package/dist/machines/provider-availability.js.map +1 -0
  393. package/dist/machines/provider-screen.d.ts +12 -0
  394. package/dist/machines/provider-screen.d.ts.map +1 -0
  395. package/dist/machines/provider-screen.js +129 -0
  396. package/dist/machines/provider-screen.js.map +1 -0
  397. package/dist/machines/session-actionability.d.ts +86 -0
  398. package/dist/machines/session-actionability.d.ts.map +1 -0
  399. package/dist/machines/session-actionability.js +125 -0
  400. package/dist/machines/session-actionability.js.map +1 -0
  401. package/dist/machines/session-visibility.d.ts +139 -0
  402. package/dist/machines/session-visibility.d.ts.map +1 -0
  403. package/dist/machines/session-visibility.js +109 -0
  404. package/dist/machines/session-visibility.js.map +1 -0
  405. package/dist/platform/adapter.d.ts +62 -0
  406. package/dist/platform/adapter.d.ts.map +1 -0
  407. package/dist/platform/adapter.js +240 -0
  408. package/dist/platform/adapter.js.map +1 -0
  409. package/dist/platform/support.d.ts +12 -0
  410. package/dist/platform/support.d.ts.map +1 -0
  411. package/dist/platform/support.js +25 -0
  412. package/dist/platform/support.js.map +1 -0
  413. package/dist/pty/contracts.d.ts +45 -0
  414. package/dist/pty/contracts.d.ts.map +1 -0
  415. package/dist/pty/contracts.js +2 -0
  416. package/dist/pty/contracts.js.map +1 -0
  417. package/dist/pty/evidence-gate.d.ts +14 -0
  418. package/dist/pty/evidence-gate.d.ts.map +1 -0
  419. package/dist/pty/evidence-gate.js +35 -0
  420. package/dist/pty/evidence-gate.js.map +1 -0
  421. package/dist/pty/index.d.ts +5 -0
  422. package/dist/pty/index.d.ts.map +1 -0
  423. package/dist/pty/index.js +5 -0
  424. package/dist/pty/index.js.map +1 -0
  425. package/dist/pty/node-host-terminal.d.ts +14 -0
  426. package/dist/pty/node-host-terminal.d.ts.map +1 -0
  427. package/dist/pty/node-host-terminal.js +173 -0
  428. package/dist/pty/node-host-terminal.js.map +1 -0
  429. package/dist/pty/node-process.d.ts +26 -0
  430. package/dist/pty/node-process.d.ts.map +1 -0
  431. package/dist/pty/node-process.js +102 -0
  432. package/dist/pty/node-process.js.map +1 -0
  433. package/dist/runtime/api-terminal-control-plane.d.ts +7 -0
  434. package/dist/runtime/api-terminal-control-plane.d.ts.map +1 -0
  435. package/dist/runtime/api-terminal-control-plane.js +95 -0
  436. package/dist/runtime/api-terminal-control-plane.js.map +1 -0
  437. package/dist/runtime/backoff.d.ts +17 -0
  438. package/dist/runtime/backoff.d.ts.map +1 -0
  439. package/dist/runtime/backoff.js +61 -0
  440. package/dist/runtime/backoff.js.map +1 -0
  441. package/dist/runtime/boundary.d.ts +171 -0
  442. package/dist/runtime/boundary.d.ts.map +1 -0
  443. package/dist/runtime/boundary.js +1858 -0
  444. package/dist/runtime/boundary.js.map +1 -0
  445. package/dist/runtime/capability-gate.d.ts +25 -0
  446. package/dist/runtime/capability-gate.d.ts.map +1 -0
  447. package/dist/runtime/capability-gate.js +77 -0
  448. package/dist/runtime/capability-gate.js.map +1 -0
  449. package/dist/runtime/contracts.d.ts +25 -0
  450. package/dist/runtime/contracts.d.ts.map +1 -0
  451. package/dist/runtime/contracts.js +66 -0
  452. package/dist/runtime/contracts.js.map +1 -0
  453. package/dist/runtime/errors.d.ts +20 -0
  454. package/dist/runtime/errors.d.ts.map +1 -0
  455. package/dist/runtime/errors.js +22 -0
  456. package/dist/runtime/errors.js.map +1 -0
  457. package/dist/runtime/index.d.ts +9 -0
  458. package/dist/runtime/index.d.ts.map +1 -0
  459. package/dist/runtime/index.js +9 -0
  460. package/dist/runtime/index.js.map +1 -0
  461. package/dist/runtime/node-foreground-session.d.ts +50 -0
  462. package/dist/runtime/node-foreground-session.d.ts.map +1 -0
  463. package/dist/runtime/node-foreground-session.js +431 -0
  464. package/dist/runtime/node-foreground-session.js.map +1 -0
  465. package/dist/runtime/node-websocket-connector.d.ts +6 -0
  466. package/dist/runtime/node-websocket-connector.d.ts.map +1 -0
  467. package/dist/runtime/node-websocket-connector.js +344 -0
  468. package/dist/runtime/node-websocket-connector.js.map +1 -0
  469. package/dist/runtime/observer-screen.d.ts +6 -0
  470. package/dist/runtime/observer-screen.d.ts.map +1 -0
  471. package/dist/runtime/observer-screen.js +144 -0
  472. package/dist/runtime/observer-screen.js.map +1 -0
  473. package/dist/runtime/owner-grant-operations.d.ts +136 -0
  474. package/dist/runtime/owner-grant-operations.d.ts.map +1 -0
  475. package/dist/runtime/owner-grant-operations.js +216 -0
  476. package/dist/runtime/owner-grant-operations.js.map +1 -0
  477. package/dist/runtime/owner-grants-screen.d.ts +40 -0
  478. package/dist/runtime/owner-grants-screen.d.ts.map +1 -0
  479. package/dist/runtime/owner-grants-screen.js +987 -0
  480. package/dist/runtime/owner-grants-screen.js.map +1 -0
  481. package/dist/runtime/terminal-transport.d.ts +85 -0
  482. package/dist/runtime/terminal-transport.d.ts.map +1 -0
  483. package/dist/runtime/terminal-transport.js +152 -0
  484. package/dist/runtime/terminal-transport.js.map +1 -0
  485. package/dist/sync/continuous-sync-supervisor.d.ts +107 -0
  486. package/dist/sync/continuous-sync-supervisor.d.ts.map +1 -0
  487. package/dist/sync/continuous-sync-supervisor.js +1117 -0
  488. package/dist/sync/continuous-sync-supervisor.js.map +1 -0
  489. package/dist/sync/index.d.ts +9 -0
  490. package/dist/sync/index.d.ts.map +1 -0
  491. package/dist/sync/index.js +9 -0
  492. package/dist/sync/index.js.map +1 -0
  493. package/dist/sync/journal.d.ts +57 -0
  494. package/dist/sync/journal.d.ts.map +1 -0
  495. package/dist/sync/journal.js +685 -0
  496. package/dist/sync/journal.js.map +1 -0
  497. package/dist/sync/revisions.d.ts +118 -0
  498. package/dist/sync/revisions.d.ts.map +1 -0
  499. package/dist/sync/revisions.js +364 -0
  500. package/dist/sync/revisions.js.map +1 -0
  501. package/dist/sync/supervisor.d.ts +107 -0
  502. package/dist/sync/supervisor.d.ts.map +1 -0
  503. package/dist/sync/supervisor.js +246 -0
  504. package/dist/sync/supervisor.js.map +1 -0
  505. package/dist/sync/workspace-sync-client.d.ts +18 -0
  506. package/dist/sync/workspace-sync-client.d.ts.map +1 -0
  507. package/dist/sync/workspace-sync-client.js +150 -0
  508. package/dist/sync/workspace-sync-client.js.map +1 -0
  509. package/dist/sync/workspace-sync-coordinator.d.ts +76 -0
  510. package/dist/sync/workspace-sync-coordinator.d.ts.map +1 -0
  511. package/dist/sync/workspace-sync-coordinator.js +621 -0
  512. package/dist/sync/workspace-sync-coordinator.js.map +1 -0
  513. package/dist/sync/workspace-sync-product-service.d.ts +79 -0
  514. package/dist/sync/workspace-sync-product-service.d.ts.map +1 -0
  515. package/dist/sync/workspace-sync-product-service.js +398 -0
  516. package/dist/sync/workspace-sync-product-service.js.map +1 -0
  517. package/dist/sync/workspace-sync-protocol.d.ts +145 -0
  518. package/dist/sync/workspace-sync-protocol.d.ts.map +1 -0
  519. package/dist/sync/workspace-sync-protocol.js +347 -0
  520. package/dist/sync/workspace-sync-protocol.js.map +1 -0
  521. package/dist/terminal/appbar.d.ts +40 -0
  522. package/dist/terminal/appbar.d.ts.map +1 -0
  523. package/dist/terminal/appbar.js +55 -0
  524. package/dist/terminal/appbar.js.map +1 -0
  525. package/dist/terminal/cell-width.d.ts +3 -0
  526. package/dist/terminal/cell-width.d.ts.map +1 -0
  527. package/dist/terminal/cell-width.js +53 -0
  528. package/dist/terminal/cell-width.js.map +1 -0
  529. package/dist/terminal/codec.d.ts +107 -0
  530. package/dist/terminal/codec.d.ts.map +1 -0
  531. package/dist/terminal/codec.js +538 -0
  532. package/dist/terminal/codec.js.map +1 -0
  533. package/dist/terminal/foreground.d.ts +135 -0
  534. package/dist/terminal/foreground.d.ts.map +1 -0
  535. package/dist/terminal/foreground.js +2097 -0
  536. package/dist/terminal/foreground.js.map +1 -0
  537. package/dist/terminal/generated/terminal-wire-v1.d.ts +26 -0
  538. package/dist/terminal/generated/terminal-wire-v1.d.ts.map +1 -0
  539. package/dist/terminal/generated/terminal-wire-v1.js +27 -0
  540. package/dist/terminal/generated/terminal-wire-v1.js.map +1 -0
  541. package/dist/terminal/mode.d.ts +33 -0
  542. package/dist/terminal/mode.d.ts.map +1 -0
  543. package/dist/terminal/mode.js +99 -0
  544. package/dist/terminal/mode.js.map +1 -0
  545. package/dist/terminal/passthrough.d.ts +47 -0
  546. package/dist/terminal/passthrough.d.ts.map +1 -0
  547. package/dist/terminal/passthrough.js +666 -0
  548. package/dist/terminal/passthrough.js.map +1 -0
  549. package/dist/terminal/resume.d.ts +58 -0
  550. package/dist/terminal/resume.d.ts.map +1 -0
  551. package/dist/terminal/resume.js +185 -0
  552. package/dist/terminal/resume.js.map +1 -0
  553. package/dist/terminal/viewport.d.ts +93 -0
  554. package/dist/terminal/viewport.d.ts.map +1 -0
  555. package/dist/terminal/viewport.js +266 -0
  556. package/dist/terminal/viewport.js.map +1 -0
  557. package/dist/terminal/workbench.d.ts +32 -0
  558. package/dist/terminal/workbench.d.ts.map +1 -0
  559. package/dist/terminal/workbench.js +316 -0
  560. package/dist/terminal/workbench.js.map +1 -0
  561. package/dist/terminal/xterm-vte.d.ts +49 -0
  562. package/dist/terminal/xterm-vte.d.ts.map +1 -0
  563. package/dist/terminal/xterm-vte.js +602 -0
  564. package/dist/terminal/xterm-vte.js.map +1 -0
  565. package/dist/version.d.ts +3 -0
  566. package/dist/version.d.ts.map +1 -0
  567. package/dist/version.js +3 -0
  568. package/dist/version.js.map +1 -0
  569. package/dist/workspace/binding-path.d.ts +17 -0
  570. package/dist/workspace/binding-path.d.ts.map +1 -0
  571. package/dist/workspace/binding-path.js +91 -0
  572. package/dist/workspace/binding-path.js.map +1 -0
  573. package/dist/workspace/binding-store.d.ts +107 -0
  574. package/dist/workspace/binding-store.d.ts.map +1 -0
  575. package/dist/workspace/binding-store.js +783 -0
  576. package/dist/workspace/binding-store.js.map +1 -0
  577. package/dist/workspace/binding.d.ts +35 -0
  578. package/dist/workspace/binding.d.ts.map +1 -0
  579. package/dist/workspace/binding.js +70 -0
  580. package/dist/workspace/binding.js.map +1 -0
  581. package/dist/workspace/errors.d.ts +4 -0
  582. package/dist/workspace/errors.d.ts.map +1 -0
  583. package/dist/workspace/errors.js +19 -0
  584. package/dist/workspace/errors.js.map +1 -0
  585. package/dist/workspace/exclusion.d.ts +21 -0
  586. package/dist/workspace/exclusion.d.ts.map +1 -0
  587. package/dist/workspace/exclusion.js +243 -0
  588. package/dist/workspace/exclusion.js.map +1 -0
  589. package/dist/workspace/index.d.ts +8 -0
  590. package/dist/workspace/index.d.ts.map +1 -0
  591. package/dist/workspace/index.js +8 -0
  592. package/dist/workspace/index.js.map +1 -0
  593. package/dist/workspace/lifecycle.d.ts +56 -0
  594. package/dist/workspace/lifecycle.d.ts.map +1 -0
  595. package/dist/workspace/lifecycle.js +94 -0
  596. package/dist/workspace/lifecycle.js.map +1 -0
  597. package/dist/workspace/manifest.d.ts +47 -0
  598. package/dist/workspace/manifest.d.ts.map +1 -0
  599. package/dist/workspace/manifest.js +243 -0
  600. package/dist/workspace/manifest.js.map +1 -0
  601. package/dist/workspace/paths.d.ts +14 -0
  602. package/dist/workspace/paths.d.ts.map +1 -0
  603. package/dist/workspace/paths.js +67 -0
  604. package/dist/workspace/paths.js.map +1 -0
  605. package/dist/workspace/schema.d.ts +15 -0
  606. package/dist/workspace/schema.d.ts.map +1 -0
  607. package/dist/workspace/schema.js +33 -0
  608. package/dist/workspace/schema.js.map +1 -0
  609. package/dist/workspace/selection-screen.d.ts +4 -0
  610. package/dist/workspace/selection-screen.d.ts.map +1 -0
  611. package/dist/workspace/selection-screen.js +216 -0
  612. package/dist/workspace/selection-screen.js.map +1 -0
  613. package/dist/workspace/selection-service.d.ts +16 -0
  614. package/dist/workspace/selection-service.d.ts.map +1 -0
  615. package/dist/workspace/selection-service.js +65 -0
  616. package/dist/workspace/selection-service.js.map +1 -0
  617. package/node_modules/@xterm/headless/README.md +31 -0
  618. package/node_modules/@xterm/headless/lib-headless/xterm-headless.js +2 -0
  619. package/node_modules/@xterm/headless/lib-headless/xterm-headless.js.map +1 -0
  620. package/node_modules/@xterm/headless/lib-headless/xterm-headless.mjs +46 -0
  621. package/node_modules/@xterm/headless/lib-headless/xterm-headless.mjs.map +7 -0
  622. package/node_modules/@xterm/headless/package.json +29 -0
  623. package/node_modules/@xterm/headless/typings/xterm-headless.d.ts +1385 -0
  624. package/package.json +75 -0
@@ -0,0 +1,839 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import { chmod, lstat, mkdir, open, readFile, realpath, rename, unlink } from "node:fs/promises";
3
+ import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
4
+ import { basename, dirname, resolve } from "node:path";
5
+ import { execFile } from "node:child_process";
6
+ import { createServer } from "node:net";
7
+ import { promisify } from "node:util";
8
+ import { setTimeout as delay } from "node:timers/promises";
9
+ import { performance } from "node:perf_hooks";
10
+ import { CREDENTIAL_BACKEND_PROTOCOL } from "./contracts.js";
11
+ import { CredentialBoundaryError, credentialFailure } from "./errors.js";
12
+ const KEY_BYTES = 32;
13
+ const NONCE_BYTES = 12;
14
+ const TAG_BYTES = 16;
15
+ const MAX_SESSION_BYTES = 96 * 1024;
16
+ const execFileAsync = promisify(execFile);
17
+ const WINDOWS_SYSTEM32 = "C:\\Windows\\System32";
18
+ const WINDOWS_ACL_PATH_ENVIRONMENT = "CUNA_SESSION_ACL_PATH_V1";
19
+ const WINDOWS_ACL_PATHS_ENVIRONMENT = "CUNA_SESSION_ACL_PATHS_V1";
20
+ const WINDOWS_ACL_CHILD_ENVIRONMENT_NAMES = new Set([
21
+ WINDOWS_ACL_PATH_ENVIRONMENT,
22
+ WINDOWS_ACL_PATHS_ENVIRONMENT,
23
+ // This name is intentionally stripped even though no current command uses
24
+ // it: a stale inherited value must not travel into the constrained child.
25
+ "CUNA_SESSION_ACL_SID_V1",
26
+ ]);
27
+ // The batched inspection reads a newline-separated path list. Win32 paths
28
+ // cannot legally contain control characters; refuse rather than misparse.
29
+ const WINDOWS_ACL_BATCH_PATH_SEPARATOR = "\n";
30
+ const WINDOWS_ACL_BATCH_MAX_PATHS = 8;
31
+ // Windows ACL verification may invoke several bounded OS helpers while an
32
+ // operation owns the physical lock. Keep a finite deadline, but make it longer
33
+ // than that legitimate critical section so a second Cuna process can observe a
34
+ // committed revision instead of failing spuriously while the first is healthy.
35
+ const LOCK_TIMEOUT_MS = 90_000;
36
+ const LOCK_RETRY_MS = 20;
37
+ /**
38
+ * These programs are deliberately static. Dynamic values are supplied through
39
+ * a child-only environment instead of being interpolated into `-Command`.
40
+ * This keeps the Restricted-policy-compatible invocation while avoiding
41
+ * encoded/dynamically generated PowerShell source.
42
+ */
43
+ export const WINDOWS_ACL_COMMAND_PROGRAMS = Object.freeze({
44
+ inspect: [
45
+ "$ErrorActionPreference = 'Stop'",
46
+ "Import-Module 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Security\\Microsoft.PowerShell.Security.psd1' -ErrorAction Stop",
47
+ "$path = $env:CUNA_SESSION_ACL_PATH_V1",
48
+ "if ([string]::IsNullOrWhiteSpace($path)) { throw 'Cuna session ACL path is unavailable.' }",
49
+ "$current = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value",
50
+ "$sddl = (Get-Acl -LiteralPath $path).Sddl",
51
+ "[Console]::WriteLine('CUNA_CURRENT_SID=' + $current)",
52
+ "[Console]::WriteLine('CUNA_SDDL=' + $sddl)",
53
+ ].join("; "),
54
+ // One spawn inspects several paths. A path that cannot be inspected (most
55
+ // often: it does not exist yet) emits no line; the caller treats a missing
56
+ // index as "not inspected", never as "safe", and falls back to `inspect`.
57
+ inspectMany: [
58
+ "$ErrorActionPreference = 'Stop'",
59
+ "Import-Module 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Security\\Microsoft.PowerShell.Security.psd1' -ErrorAction Stop",
60
+ "$list = $env:CUNA_SESSION_ACL_PATHS_V1",
61
+ "if ([string]::IsNullOrWhiteSpace($list)) { throw 'Cuna session ACL paths are unavailable.' }",
62
+ "$paths = $list.Split([char]10)",
63
+ "$current = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value",
64
+ "[Console]::WriteLine('CUNA_CURRENT_SID=' + $current)",
65
+ "for ($i = 0; $i -lt $paths.Length; $i++) {",
66
+ " $path = $paths[$i]",
67
+ " if ([string]::IsNullOrWhiteSpace($path)) { continue }",
68
+ " try { $sddl = (Get-Acl -LiteralPath $path).Sddl } catch { continue }",
69
+ " [Console]::WriteLine('CUNA_SDDL:' + $i + '=' + $sddl)",
70
+ "}",
71
+ // A caught per-path error leaves `$?` false, which `-Command` would report
72
+ // as exit code 1 and the parent would read as a failed batch.
73
+ "exit 0",
74
+ ].join("; "),
75
+ reconcileFile: [
76
+ "$ErrorActionPreference = 'Stop'",
77
+ "Import-Module 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Security\\Microsoft.PowerShell.Security.psd1' -ErrorAction Stop",
78
+ "$path = $env:CUNA_SESSION_ACL_PATH_V1",
79
+ "if ([string]::IsNullOrWhiteSpace($path)) { throw 'Cuna session ACL path is unavailable.' }",
80
+ "$beforeCurrent = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value",
81
+ "$beforeSddl = (Get-Acl -LiteralPath $path).Sddl",
82
+ "$beforeOwner = [regex]::Match($beforeSddl, '^O:(S-1-[0-9-]+)(?:G:|D:|S:)').Groups[1].Value",
83
+ "if ([string]::IsNullOrWhiteSpace($beforeOwner) -or $beforeOwner -ne $beforeCurrent) { throw 'Cuna session ACL owner is not the current user.' }",
84
+ "$daclStart = $beforeSddl.IndexOf('D:')",
85
+ "$saclStart = if ($daclStart -lt 0) { -1 } else { $beforeSddl.IndexOf('S:', $daclStart + 2) }",
86
+ "$beforeDacl = if ($daclStart -lt 0) { '' } elseif ($saclStart -lt 0) { $beforeSddl.Substring($daclStart) } else { $beforeSddl.Substring($daclStart, $saclStart - $daclStart) }",
87
+ "$expectedAce = '(A;;FA;;;' + $beforeCurrent + ')'",
88
+ "$exact = $beforeDacl -ceq ('D:PAI' + $expectedAce) -or $beforeDacl -ceq ('D:P' + $expectedAce)",
89
+ "$repaired = 0",
90
+ "if (-not $exact) {",
91
+ " $currentBeforeWrite = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value",
92
+ " if ($currentBeforeWrite -ne $beforeCurrent) { throw 'Cuna session ACL SID changed.' }",
93
+ " $sid = [System.Security.Principal.SecurityIdentifier]::new($beforeCurrent)",
94
+ " $acl = [System.Security.AccessControl.FileSecurity]::new()",
95
+ " $acl.SetOwner($sid)",
96
+ " $acl.SetAccessRuleProtection($true, $false)",
97
+ " $rule = [System.Security.AccessControl.FileSystemAccessRule]::new($sid, [System.Security.AccessControl.FileSystemRights]::FullControl, [System.Security.AccessControl.InheritanceFlags]::None, [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow)",
98
+ " $acl.AddAccessRule($rule)",
99
+ " Set-Acl -LiteralPath $path -AclObject $acl",
100
+ " $repaired = 1",
101
+ "}",
102
+ "$afterCurrent = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value",
103
+ "$afterSddl = (Get-Acl -LiteralPath $path).Sddl",
104
+ "[Console]::WriteLine('CUNA_BEFORE_CURRENT_SID=' + $beforeCurrent)",
105
+ "[Console]::WriteLine('CUNA_BEFORE_SDDL=' + $beforeSddl)",
106
+ "[Console]::WriteLine('CUNA_AFTER_CURRENT_SID=' + $afterCurrent)",
107
+ "[Console]::WriteLine('CUNA_AFTER_SDDL=' + $afterSddl)",
108
+ "[Console]::WriteLine('CUNA_REPAIRED=' + $repaired)",
109
+ ].join("; "),
110
+ reconcileDirectory: [
111
+ "$ErrorActionPreference = 'Stop'",
112
+ "Import-Module 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Security\\Microsoft.PowerShell.Security.psd1' -ErrorAction Stop",
113
+ "$path = $env:CUNA_SESSION_ACL_PATH_V1",
114
+ "if ([string]::IsNullOrWhiteSpace($path)) { throw 'Cuna session ACL path is unavailable.' }",
115
+ "$beforeCurrent = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value",
116
+ "$beforeSddl = (Get-Acl -LiteralPath $path).Sddl",
117
+ "$beforeOwner = [regex]::Match($beforeSddl, '^O:(S-1-[0-9-]+)(?:G:|D:|S:)').Groups[1].Value",
118
+ "if ([string]::IsNullOrWhiteSpace($beforeOwner) -or $beforeOwner -ne $beforeCurrent) { throw 'Cuna session ACL owner is not the current user.' }",
119
+ "$daclStart = $beforeSddl.IndexOf('D:')",
120
+ "$saclStart = if ($daclStart -lt 0) { -1 } else { $beforeSddl.IndexOf('S:', $daclStart + 2) }",
121
+ "$beforeDacl = if ($daclStart -lt 0) { '' } elseif ($saclStart -lt 0) { $beforeSddl.Substring($daclStart) } else { $beforeSddl.Substring($daclStart, $saclStart - $daclStart) }",
122
+ "$expectedAce = '(A;OICI;FA;;;' + $beforeCurrent + ')'",
123
+ "$exact = $beforeDacl -ceq ('D:PAI' + $expectedAce) -or $beforeDacl -ceq ('D:P' + $expectedAce)",
124
+ "$repaired = 0",
125
+ "if (-not $exact) {",
126
+ " $currentBeforeWrite = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value",
127
+ " if ($currentBeforeWrite -ne $beforeCurrent) { throw 'Cuna session ACL SID changed.' }",
128
+ " $sid = [System.Security.Principal.SecurityIdentifier]::new($beforeCurrent)",
129
+ " $acl = [System.Security.AccessControl.DirectorySecurity]::new()",
130
+ " $acl.SetOwner($sid)",
131
+ " $acl.SetAccessRuleProtection($true, $false)",
132
+ " $rule = [System.Security.AccessControl.FileSystemAccessRule]::new($sid, [System.Security.AccessControl.FileSystemRights]::FullControl, [System.Security.AccessControl.InheritanceFlags]::ObjectInherit -bor [System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow)",
133
+ " $acl.AddAccessRule($rule)",
134
+ " Set-Acl -LiteralPath $path -AclObject $acl",
135
+ " $repaired = 1",
136
+ "}",
137
+ "$afterCurrent = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value",
138
+ "$afterSddl = (Get-Acl -LiteralPath $path).Sddl",
139
+ "[Console]::WriteLine('CUNA_BEFORE_CURRENT_SID=' + $beforeCurrent)",
140
+ "[Console]::WriteLine('CUNA_BEFORE_SDDL=' + $beforeSddl)",
141
+ "[Console]::WriteLine('CUNA_AFTER_CURRENT_SID=' + $afterCurrent)",
142
+ "[Console]::WriteLine('CUNA_AFTER_SDDL=' + $afterSddl)",
143
+ "[Console]::WriteLine('CUNA_REPAIRED=' + $repaired)",
144
+ ].join("; "),
145
+ });
146
+ function windowsAclScopeKey(path, directory) {
147
+ return `${directory ? "d" : "f"}:${path}`;
148
+ }
149
+ /**
150
+ * Pure-JavaScript session persistence for the durable browser login code.
151
+ * The random AES key and ciphertext are separate current-user-only files.
152
+ * Their separation is not a backup boundary: a copied profile containing both
153
+ * files, or compromise of the same OS account, can decrypt the session.
154
+ */
155
+ export class LocalEncryptedSessionBackend {
156
+ backendId = "cuna-local-aes256gcm-v1";
157
+ platform;
158
+ #sessionFile;
159
+ #keyFile;
160
+ #clock;
161
+ #lockTimeoutMs;
162
+ #windowsAcl;
163
+ #aclScope;
164
+ constructor(input) {
165
+ this.#sessionFile = exactJsonPath(input.sessionFile, "session");
166
+ this.#keyFile = exactKeyPath(input.keyFile);
167
+ if (this.#sessionFile === this.#keyFile || dirname(this.#sessionFile) !== dirname(this.#keyFile)) {
168
+ throw credentialFailure("credential_backend_unverified", "The encrypted session paths are invalid.");
169
+ }
170
+ this.platform = input.platform;
171
+ this.#clock = input.clock ?? Date.now;
172
+ this.#lockTimeoutMs = input.lockTimeoutMs ?? LOCK_TIMEOUT_MS;
173
+ this.#windowsAcl = input.windowsAcl ?? WINDOWS_ACL_AUTHORITY;
174
+ if (!Number.isSafeInteger(this.#lockTimeoutMs) || this.#lockTimeoutMs < 10 || this.#lockTimeoutMs > 120_000) {
175
+ throw credentialFailure("credential_backend_unverified", "The encrypted session lock timeout is invalid.");
176
+ }
177
+ }
178
+ async probe() {
179
+ const now = this.#clock();
180
+ if (!Number.isSafeInteger(now) || now < 0)
181
+ throw credentialFailure("credential_backend_unverified", "The session clock is not trustworthy.");
182
+ try {
183
+ await this.#withProcessLock(async () => {
184
+ for (const [file, maximum] of [[this.#keyFile, KEY_BYTES], [this.#sessionFile, MAX_SESSION_BYTES]]) {
185
+ try {
186
+ await this.#assertSafeIfPresent(file, maximum);
187
+ }
188
+ catch (error) {
189
+ if (error.code !== "ENOENT")
190
+ throw error;
191
+ }
192
+ }
193
+ });
194
+ return evidence(this, now, "verified");
195
+ }
196
+ catch {
197
+ return evidence(this, now, "unavailable", "encrypted_session_permissions_unverified");
198
+ }
199
+ }
200
+ async read(_target) {
201
+ return await this.#withProcessLock(async () => this.#readUnlocked());
202
+ }
203
+ async #readUnlocked() {
204
+ let encoded;
205
+ try {
206
+ await this.#assertSafeIfPresent(this.#sessionFile, MAX_SESSION_BYTES);
207
+ encoded = await readFile(this.#sessionFile);
208
+ }
209
+ catch (error) {
210
+ if (error.code === "ENOENT") {
211
+ // A prior delete may have stopped after removing the ciphertext. Retry
212
+ // the other half so a new process converges to zero local residue.
213
+ await this.#deleteFiles([this.#keyFile]);
214
+ return undefined;
215
+ }
216
+ throw localSessionReadFailure("ciphertext", error);
217
+ }
218
+ let key;
219
+ try {
220
+ await this.#assertSafeIfPresent(this.#keyFile, KEY_BYTES);
221
+ key = await readFile(this.#keyFile);
222
+ if (key.byteLength !== KEY_BYTES) {
223
+ key.fill(0);
224
+ throw credentialFailure("credential_corrupt", "The encrypted local session key file is invalid.");
225
+ }
226
+ }
227
+ catch (error) {
228
+ encoded.fill(0);
229
+ // Only an observed absence proves the key can never decrypt this
230
+ // ciphertext. An ACL, metadata, or transient OS failure is not evidence
231
+ // that either file is corrupt; deleting a valid durable login code in
232
+ // that case would turn a fail-closed read into destructive data loss.
233
+ if (error.code === "ENOENT") {
234
+ await this.#deleteFiles([this.#sessionFile, this.#keyFile]);
235
+ throw credentialFailure("credential_corrupt", "The encrypted local session key is unavailable.", { cause: error });
236
+ }
237
+ throw localSessionReadFailure("key", error);
238
+ }
239
+ try {
240
+ return decrypt(encoded, key);
241
+ }
242
+ finally {
243
+ key.fill(0);
244
+ encoded.fill(0);
245
+ }
246
+ }
247
+ async replace(_target, protectedValue) {
248
+ if (protectedValue.byteLength < 1 || protectedValue.byteLength > 32 * 1024) {
249
+ throw credentialFailure("credential_corrupt", "The encrypted session payload is invalid.");
250
+ }
251
+ await this.#withProcessLock(async () => this.#replaceUnlocked(protectedValue));
252
+ }
253
+ async #replaceUnlocked(protectedValue) {
254
+ const key = await this.#loadOrCreateKey();
255
+ const record = encrypt(protectedValue, key);
256
+ key.fill(0);
257
+ await this.#writeRestricted(this.#sessionFile, Buffer.from(JSON.stringify(record), "utf8"));
258
+ }
259
+ async compareAndSwap(_target, expectedSha256, protectedValue) {
260
+ validateExpectedDigest(expectedSha256);
261
+ if (protectedValue.byteLength < 1 || protectedValue.byteLength > 32 * 1024) {
262
+ throw credentialFailure("credential_corrupt", "The encrypted session payload is invalid.");
263
+ }
264
+ return await this.#withProcessLock(async () => {
265
+ const observed = await this.#protectedValueDigest(_target);
266
+ if (observed !== expectedSha256)
267
+ return "conflict";
268
+ await this.#replaceUnlocked(protectedValue);
269
+ return "replaced";
270
+ });
271
+ }
272
+ async compareAndDelete(_target, expectedSha256) {
273
+ validateExpectedDigest(expectedSha256);
274
+ return await this.#withProcessLock(async () => {
275
+ const observed = await this.#protectedValueDigest(_target);
276
+ if (observed === null)
277
+ return "absent";
278
+ if (observed !== expectedSha256)
279
+ return "conflict";
280
+ return await this.#deleteFiles([this.#sessionFile, this.#keyFile]);
281
+ });
282
+ }
283
+ async delete(_target) {
284
+ return await this.#withProcessLock(async () => this.#deleteFiles([this.#sessionFile, this.#keyFile]));
285
+ }
286
+ async withRefreshLock(_target, operation) {
287
+ return await this.#withNamedProcessLock("refresh", operation);
288
+ }
289
+ async #deleteFiles(files) {
290
+ let deleted = false;
291
+ let failure;
292
+ for (const file of files) {
293
+ try {
294
+ await unlink(file);
295
+ deleted = true;
296
+ }
297
+ catch (error) {
298
+ if (error.code !== "ENOENT" && failure === undefined)
299
+ failure = error;
300
+ }
301
+ }
302
+ if (failure !== undefined) {
303
+ throw credentialFailure("credential_backend_failure", "The encrypted local session could not be removed.", { retryable: true, cause: failure });
304
+ }
305
+ return deleted ? "deleted" : "absent";
306
+ }
307
+ async #protectedValueDigest(target) {
308
+ void target;
309
+ const bytes = await this.#readUnlocked();
310
+ if (bytes === undefined)
311
+ return null;
312
+ try {
313
+ return createHash("sha256").update(bytes).digest("hex");
314
+ }
315
+ finally {
316
+ bytes.fill(0);
317
+ }
318
+ }
319
+ async #withProcessLock(operation) {
320
+ return await this.#withNamedProcessLock("storage", operation);
321
+ }
322
+ async #withNamedProcessLock(namespace, operation) {
323
+ // Canonicalize only after the directory exists and its no-reparse/ACL
324
+ // boundary has been checked. Lexical aliases (junctions, symlinks, 8.3
325
+ // names) must never create a second lock namespace for the same bytes.
326
+ await this.#ensureDirectory({ verifyExistingWindowsAcl: false });
327
+ const authority = await processLockAuthority(this.#sessionFile, this.platform, namespace);
328
+ const deadline = performance.now() + this.#lockTimeoutMs;
329
+ let server;
330
+ for (;;) {
331
+ try {
332
+ server = await listenForLock(authority);
333
+ break;
334
+ }
335
+ catch (error) {
336
+ if (error.code !== "EADDRINUSE" || performance.now() >= deadline) {
337
+ throw credentialFailure("credential_backend_failure", "The encrypted local session is busy and could not be locked safely.", { retryable: true, safeDetails: { reason: "process_lock_unavailable" } });
338
+ }
339
+ await delay(LOCK_RETRY_MS);
340
+ }
341
+ }
342
+ // Nested locks (refresh around storage) each get their own scope; the
343
+ // innermost operation is the unit of ACL freshness.
344
+ const previousScope = this.#aclScope;
345
+ this.#aclScope = { batched: false, failure: undefined, entries: new Map() };
346
+ try {
347
+ // The initial check is required before resolving the physical lock
348
+ // authority. Re-check once after acquiring it: this keeps the prior
349
+ // boundary against a directory replacement between those steps. Key
350
+ // loading follows immediately, while atomic replacement retains its
351
+ // own final boundary check before changing ciphertext.
352
+ await this.#ensureDirectory();
353
+ if (!sameProcessLockAuthority(authority, await processLockAuthority(this.#sessionFile, this.platform, namespace))) {
354
+ throw credentialFailure("credential_backend_failure", "The encrypted local session path changed while acquiring its lock.", { retryable: true, safeDetails: { reason: "process_lock_authority_changed" } });
355
+ }
356
+ return await operation();
357
+ }
358
+ finally {
359
+ this.#aclScope = previousScope;
360
+ await closeServer(server);
361
+ }
362
+ }
363
+ /**
364
+ * One ACL observation per path per store operation. The first request in an
365
+ * operation inspects the directory, the key and the ciphertext in a single
366
+ * OS invocation when the authority supports it; every later request in the
367
+ * same operation is served from that observation. A path the batch could
368
+ * not inspect is inspected on its own, and a batch failure is raised at the
369
+ * first enforcement site rather than silently retried.
370
+ */
371
+ async #inspectWindowsAcl(path, directory) {
372
+ const scope = this.#aclScope;
373
+ if (scope === undefined)
374
+ return await this.#windowsAcl.inspectOwnerOnly(path, directory);
375
+ const key = windowsAclScopeKey(path, directory);
376
+ const cached = scope.entries.get(key);
377
+ if (cached !== undefined)
378
+ return cached;
379
+ const inspectMany = this.#windowsAcl.inspectManyOwnerOnly;
380
+ if (!scope.batched && inspectMany !== undefined) {
381
+ scope.batched = true;
382
+ const requests = [
383
+ { path: dirname(this.#sessionFile), directory: true },
384
+ { path: this.#keyFile, directory: false },
385
+ { path: this.#sessionFile, directory: false },
386
+ ];
387
+ try {
388
+ const results = await inspectMany.call(this.#windowsAcl, requests);
389
+ if (results.length !== requests.length)
390
+ throw new Error("Windows ACL batch inspection is misaligned");
391
+ requests.forEach((request, index) => {
392
+ const result = results[index];
393
+ if (result !== undefined)
394
+ scope.entries.set(windowsAclScopeKey(request.path, request.directory), result);
395
+ });
396
+ }
397
+ catch (error) {
398
+ scope.failure = error;
399
+ }
400
+ const batched = scope.entries.get(key);
401
+ if (batched !== undefined)
402
+ return batched;
403
+ }
404
+ if (scope.failure !== undefined)
405
+ throw scope.failure;
406
+ const inspection = await this.#windowsAcl.inspectOwnerOnly(path, directory);
407
+ scope.entries.set(key, inspection);
408
+ return inspection;
409
+ }
410
+ async #assertWindowsOwnerOnlyAcl(path, directory) {
411
+ assertWindowsOwnerOnlyInspection(await this.#inspectWindowsAcl(path, directory));
412
+ }
413
+ async #ensureNewWindowsOwnerOnlyAcl(path, directory, assertSafeMetadata) {
414
+ // A reconciliation rewrites the ACL; any observation of this path taken
415
+ // earlier in the same operation is stale from here on.
416
+ this.#aclScope?.entries.delete(windowsAclScopeKey(path, directory));
417
+ await ensureNewWindowsOwnerOnlyAcl(this.#windowsAcl, path, directory, assertSafeMetadata);
418
+ }
419
+ async #ensureDirectory(input = {}) {
420
+ const directory = dirname(this.#sessionFile);
421
+ const created = await mkdir(directory, { recursive: true, mode: 0o700 });
422
+ await this.#assertSafeDirectoryMetadata(directory);
423
+ if (this.platform === "win32") {
424
+ if (created === undefined) {
425
+ // An already-present directory can contain an attacker-controlled
426
+ // junction or material. It is a verification boundary, never a repair
427
+ // target. Before acquiring the physical lock we only need the metadata
428
+ // boundary to derive its authority; immediately after acquisition the
429
+ // full owner/DACL inspection below is mandatory before any operation.
430
+ if (input.verifyExistingWindowsAcl ?? true) {
431
+ // The directory check is the first enforcement site of every
432
+ // operation. A bounded helper timeout here used to escape as a raw
433
+ // child-process error and render as an internal defect (exit 70);
434
+ // it is the same retryable, fail-closed condition as at the key
435
+ // and ciphertext sites.
436
+ try {
437
+ await this.#assertWindowsOwnerOnlyAcl(directory, true);
438
+ }
439
+ catch (error) {
440
+ throw localSessionReadFailure("directory", error);
441
+ }
442
+ }
443
+ }
444
+ else {
445
+ await this.#ensureNewWindowsOwnerOnlyAcl(directory, true, async () => await this.#assertSafeDirectoryMetadata(directory));
446
+ }
447
+ }
448
+ else {
449
+ await chmod(directory, 0o700);
450
+ }
451
+ // Re-check after chmod or any Windows ACL mutation. This does not claim
452
+ // to close arbitrary filesystem races; it rejects a reparse observed at
453
+ // either checked boundary.
454
+ await this.#assertSafeDirectoryMetadata(directory);
455
+ }
456
+ async #assertSafeDirectoryMetadata(directory) {
457
+ const metadata = await lstat(directory);
458
+ if (!metadata.isDirectory() || metadata.isSymbolicLink() || (this.platform !== "win32" && (metadata.mode & 0o077) !== 0)) {
459
+ throw new Error("encrypted session directory is unsafe");
460
+ }
461
+ }
462
+ async #loadOrCreateKey() {
463
+ let created = false;
464
+ try {
465
+ const handle = await open(this.#keyFile, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, 0o600);
466
+ created = true;
467
+ const generated = randomBytes(KEY_BYTES);
468
+ try {
469
+ await handle.writeFile(generated);
470
+ await handle.sync();
471
+ }
472
+ finally {
473
+ generated.fill(0);
474
+ await handle.close();
475
+ }
476
+ await chmod(this.#keyFile, 0o600);
477
+ }
478
+ catch (error) {
479
+ if (error.code !== "EEXIST")
480
+ throw error;
481
+ }
482
+ if (created) {
483
+ await this.#assertSafeFileMetadata(this.#keyFile, KEY_BYTES);
484
+ if (this.platform === "win32") {
485
+ await this.#ensureNewWindowsOwnerOnlyAcl(this.#keyFile, false, async () => await this.#assertSafeFileMetadata(this.#keyFile, KEY_BYTES));
486
+ }
487
+ }
488
+ else {
489
+ await this.#assertSafeIfPresent(this.#keyFile, KEY_BYTES);
490
+ }
491
+ const key = await readFile(this.#keyFile);
492
+ if (key.byteLength !== KEY_BYTES) {
493
+ key.fill(0);
494
+ throw credentialFailure("credential_corrupt", "The encrypted session key file is invalid.");
495
+ }
496
+ return key;
497
+ }
498
+ async #assertSafeIfPresent(file, maximum) {
499
+ await this.#assertSafeFileMetadata(file, maximum);
500
+ if (this.platform === "win32")
501
+ await this.#assertWindowsOwnerOnlyAcl(file, false);
502
+ }
503
+ async #assertSafeFileMetadata(file, maximum) {
504
+ const metadata = await lstat(file);
505
+ if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > maximum ||
506
+ (this.platform !== "win32" && (metadata.mode & 0o077) !== 0)) {
507
+ throw new Error("encrypted session file is unsafe");
508
+ }
509
+ }
510
+ async #writeRestricted(file, bytes) {
511
+ // The lock wrapper has already completed a full owner/DACL inspection
512
+ // after acquiring the physical authority. Keep a final reparse/metadata
513
+ // boundary immediately before the atomic replacement without repeating a
514
+ // second expensive ACL subprocess in the same locked operation.
515
+ await this.#assertSafeDirectoryMetadata(dirname(file));
516
+ const temporary = `${file}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`;
517
+ let handle;
518
+ try {
519
+ handle = await open(temporary, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, 0o600);
520
+ await handle.writeFile(bytes);
521
+ await handle.sync();
522
+ await handle.close();
523
+ handle = undefined;
524
+ await chmod(temporary, 0o600);
525
+ await rename(temporary, file);
526
+ await chmod(file, 0o600);
527
+ if (this.platform === "win32") {
528
+ await this.#assertSafeFileMetadata(file, MAX_SESSION_BYTES);
529
+ await this.#ensureNewWindowsOwnerOnlyAcl(file, false, async () => await this.#assertSafeFileMetadata(file, MAX_SESSION_BYTES));
530
+ }
531
+ }
532
+ catch (error) {
533
+ try {
534
+ await handle?.close();
535
+ }
536
+ catch { }
537
+ try {
538
+ await unlink(temporary);
539
+ }
540
+ catch { }
541
+ throw credentialFailure("credential_backend_failure", "The encrypted local session could not be replaced.", { retryable: true, cause: error });
542
+ }
543
+ }
544
+ }
545
+ function validateExpectedDigest(value) {
546
+ if (value !== null && !/^[a-f0-9]{64}$/u.test(value)) {
547
+ throw credentialFailure("credential_backend_unverified", "The encrypted session comparison digest is invalid.");
548
+ }
549
+ }
550
+ /**
551
+ * An unreadable file and an invalid encrypted value are different claims. In
552
+ * particular, a bounded Windows ACL helper timeout proves neither that the
553
+ * key changed nor that its AEAD tag is invalid. Keep that condition
554
+ * retryable/fail-closed and leave the durable pair untouched; only a parsed
555
+ * invalid key or ciphertext is reported as credential corruption.
556
+ */
557
+ function localSessionReadFailure(subject, error) {
558
+ if (error instanceof CredentialBoundaryError)
559
+ return error;
560
+ if (isBoundedWindowsAclTimeout(error)) {
561
+ return credentialFailure("credential_backend_failure", `The encrypted local session ${subject} could not be verified before the bounded ACL check expired.`, { retryable: true, safeDetails: { reason: "windows_acl_inspection_timeout" }, cause: error });
562
+ }
563
+ if (isTransientFilesystemFailure(error)) {
564
+ return credentialFailure("credential_backend_failure", `The encrypted local session ${subject} could not be read from the local store.`, { retryable: true, safeDetails: { reason: `encrypted_session_${subject}_read_failed` }, cause: error });
565
+ }
566
+ return credentialFailure("credential_backend_unverified", `The encrypted local session ${subject} could not be verified safely.`, { safeDetails: { reason: `encrypted_session_${subject}_security_unverified` }, cause: error });
567
+ }
568
+ function isBoundedWindowsAclTimeout(error) {
569
+ const record = error;
570
+ return record?.code === "ETIMEDOUT" || (record?.killed === true && typeof record.signal === "string" && record.signal.length > 0);
571
+ }
572
+ function isTransientFilesystemFailure(error) {
573
+ const code = error?.code;
574
+ return code === "EAGAIN" || code === "EBUSY" || code === "EINTR" || code === "EIO" ||
575
+ code === "EMFILE" || code === "ENFILE" || code === "ENODEV" || code === "ENXIO" || code === "ESTALE";
576
+ }
577
+ async function processLockAuthority(sessionFile, platform, namespace = "storage") {
578
+ const physicalDirectory = await realpath(dirname(sessionFile));
579
+ const physicalSession = resolve(physicalDirectory, basename(sessionFile));
580
+ const canonical = platform === "win32" ? physicalSession.toLocaleLowerCase("en-US") : physicalSession;
581
+ const digest = createHash("sha256").update(`${namespace}\0${canonical}`, "utf8").digest();
582
+ if (platform === "win32")
583
+ return `\\\\.\\pipe\\cuna-session-${namespace}-${digest.toString("hex").slice(0, 40)}`;
584
+ // A loopback listener is kernel-owned and disappears on process death. A
585
+ // hash collision or unrelated listener only causes a bounded fail-closed
586
+ // refusal; it can never permit two writers.
587
+ const namespaceBase = namespace === "storage" ? 49_152 : 57_344;
588
+ return { host: "127.0.0.1", port: namespaceBase + digest.readUInt16BE(0) % 8_192 };
589
+ }
590
+ function sameProcessLockAuthority(left, right) {
591
+ return typeof left === "string" || typeof right === "string"
592
+ ? left === right
593
+ : left.host === right.host && left.port === right.port;
594
+ }
595
+ async function listenForLock(authority) {
596
+ const server = createServer((socket) => socket.destroy());
597
+ return await new Promise((resolveListen, rejectListen) => {
598
+ const onError = (error) => { server.close(); rejectListen(error); };
599
+ server.once("error", onError);
600
+ server.listen(authority, () => {
601
+ server.off("error", onError);
602
+ server.unref();
603
+ resolveListen(server);
604
+ });
605
+ });
606
+ }
607
+ async function closeServer(server) {
608
+ await new Promise((resolveClose) => server.close(() => resolveClose()));
609
+ }
610
+ export function localEncryptedSessionPaths(configDirectory, profile = "default") {
611
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(profile))
612
+ throw credentialFailure("credential_backend_unverified", "The session profile is invalid.");
613
+ const digest = createHash("sha256").update(profile, "utf8").digest("hex").slice(0, 32);
614
+ return Object.freeze({
615
+ // `sessions` existed before this AES-GCM format. It may have inherited an
616
+ // unsafe ACL, which Cuna must never repair or trust. A new format root is
617
+ // created and verified independently; older bytes remain untouched.
618
+ sessionFile: resolve(configDirectory, "sessions-v1", `session-${digest}.json`),
619
+ keyFile: resolve(configDirectory, "sessions-v1", `session-${digest}.key`),
620
+ });
621
+ }
622
+ function encrypt(plain, key) {
623
+ const nonce = randomBytes(NONCE_BYTES);
624
+ const cipher = createCipheriv("aes-256-gcm", key, nonce);
625
+ const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]);
626
+ const tag = cipher.getAuthTag();
627
+ return { version: 1, algorithm: "aes-256-gcm", nonce: nonce.toString("base64url"), tag: tag.toString("base64url"), ciphertext: ciphertext.toString("base64url") };
628
+ }
629
+ function decrypt(bytes, key) {
630
+ let value;
631
+ try {
632
+ value = JSON.parse(Buffer.from(bytes).toString("utf8"));
633
+ }
634
+ catch (error) {
635
+ throw credentialFailure("credential_corrupt", "The encrypted local session is malformed.", { cause: error });
636
+ }
637
+ if (!isRecord(value) || Object.keys(value).sort().join(",") !== "algorithm,ciphertext,nonce,tag,version" || value.version !== 1 || value.algorithm !== "aes-256-gcm" ||
638
+ typeof value.nonce !== "string" || typeof value.tag !== "string" || typeof value.ciphertext !== "string") {
639
+ throw credentialFailure("credential_corrupt", "The encrypted local session shape is invalid.");
640
+ }
641
+ const nonce = Buffer.from(value.nonce, "base64url");
642
+ const tag = Buffer.from(value.tag, "base64url");
643
+ const ciphertext = Buffer.from(value.ciphertext, "base64url");
644
+ if (nonce.byteLength !== NONCE_BYTES || tag.byteLength !== TAG_BYTES || ciphertext.byteLength < 1 || ciphertext.byteLength > 40 * 1024)
645
+ throw credentialFailure("credential_corrupt", "The encrypted local session bytes are invalid.");
646
+ try {
647
+ const decipher = createDecipheriv("aes-256-gcm", key, nonce);
648
+ decipher.setAuthTag(tag);
649
+ return new Uint8Array(Buffer.concat([decipher.update(ciphertext), decipher.final()]));
650
+ }
651
+ catch (error) {
652
+ throw credentialFailure("credential_corrupt", "The encrypted local session authentication tag is invalid.", { cause: error });
653
+ }
654
+ finally {
655
+ nonce.fill(0);
656
+ tag.fill(0);
657
+ ciphertext.fill(0);
658
+ }
659
+ }
660
+ function evidence(backend, now, status, reason) {
661
+ return { protocol: CREDENTIAL_BACKEND_PROTOCOL, backendId: backend.backendId, platform: backend.platform, status, observedAt: now, expiresAt: now + 60_000, source: status === "verified" ? "encrypted_local_file" : "probe_failed", ...(reason === undefined ? {} : { reason }) };
662
+ }
663
+ function exactJsonPath(value, label) { const path = resolve(value); if (!path.endsWith(".json"))
664
+ throw credentialFailure("credential_backend_unverified", `The encrypted ${label} path is invalid.`); return path; }
665
+ function exactKeyPath(value) { const path = resolve(value); if (!path.endsWith(".key"))
666
+ throw credentialFailure("credential_backend_unverified", "The encrypted session key path is invalid."); return path; }
667
+ function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); }
668
+ /**
669
+ * Isolates the two ACL inputs from inherited Windows environment variables.
670
+ * Windows environment-variable names are case-insensitive, so simply adding
671
+ * canonical uppercase keys would leave an attacker-controlled differently
672
+ * cased duplicate ambiguous to the child process.
673
+ */
674
+ export function isolateWindowsAclChildEnvironment(inherited, path) {
675
+ const environment = stripWindowsAclChildEnvironment(inherited);
676
+ environment[WINDOWS_ACL_PATH_ENVIRONMENT] = path;
677
+ return environment;
678
+ }
679
+ /**
680
+ * The batched variant carries a newline-separated list under its own name.
681
+ * Both isolated environments strip every ACL input name, so a single-path
682
+ * child never sees a list and a batched child never sees a single path.
683
+ */
684
+ export function isolateWindowsAclBatchChildEnvironment(inherited, paths) {
685
+ if (paths.length < 1 || paths.length > WINDOWS_ACL_BATCH_MAX_PATHS)
686
+ throw new Error("Windows ACL batch size is invalid");
687
+ for (const path of paths) {
688
+ if (path.length < 1 || [...path].some((character) => character.codePointAt(0) < 0x20))
689
+ throw new Error("Windows ACL batch path is invalid");
690
+ }
691
+ const environment = stripWindowsAclChildEnvironment(inherited);
692
+ environment[WINDOWS_ACL_PATHS_ENVIRONMENT] = paths.join(WINDOWS_ACL_BATCH_PATH_SEPARATOR);
693
+ return environment;
694
+ }
695
+ function stripWindowsAclChildEnvironment(inherited) {
696
+ const environment = Object.create(null);
697
+ for (const [name, value] of Object.entries(inherited)) {
698
+ if (WINDOWS_ACL_CHILD_ENVIRONMENT_NAMES.has(name.toLocaleUpperCase("en-US")))
699
+ continue;
700
+ if (value !== undefined)
701
+ environment[name] = value;
702
+ }
703
+ return environment;
704
+ }
705
+ const WINDOWS_ACL_AUTHORITY = Object.freeze({
706
+ inspectOwnerOnly: async (path, directory) => await inspectWindowsAcl(path, directory),
707
+ inspectManyOwnerOnly: async (requests) => await inspectWindowsAclMany(requests),
708
+ reconcileNewOwnerOnly: async (path, directory) => await reconcileNewWindowsAcl(path, directory),
709
+ });
710
+ function assertWindowsOwnerOnlyInspection(inspection) {
711
+ assertWindowsCurrentUserOwner(inspection);
712
+ if (!inspection.daclOwnerOnly) {
713
+ throw new Error("encrypted session ACL is not current-user-only");
714
+ }
715
+ }
716
+ function assertWindowsCurrentUserOwner(inspection) {
717
+ if (inspection.ownerSid !== inspection.currentSid) {
718
+ throw new Error("encrypted session owner is not the current user");
719
+ }
720
+ }
721
+ async function ensureNewWindowsOwnerOnlyAcl(authority, path, directory, assertSafeMetadata) {
722
+ // The ACL authority returns a before/after pair from a single static OS
723
+ // invocation. That avoids a slow and racy inspect -> separate-process
724
+ // repair -> inspect sequence while retaining the boundary checks on both
725
+ // sides of a possible ACL mutation.
726
+ await assertSafeMetadata();
727
+ const reconciliation = await authority.reconcileNewOwnerOnly(path, directory);
728
+ await assertSafeMetadata();
729
+ assertWindowsCurrentUserOwner(reconciliation.before);
730
+ if (reconciliation.before.currentSid !== reconciliation.after.currentSid) {
731
+ throw new Error("encrypted session current user changed during ACL reconciliation");
732
+ }
733
+ if (reconciliation.repaired !== !reconciliation.before.daclOwnerOnly) {
734
+ throw new Error("encrypted session ACL reconciliation result is inconsistent");
735
+ }
736
+ assertWindowsOwnerOnlyInspection(reconciliation.after);
737
+ }
738
+ async function inspectWindowsAcl(path, directory) {
739
+ // The program is a constant and `path` is data in a child-only environment;
740
+ // never interpolate paths into PowerShell source or encode them as commands.
741
+ const { stdout } = await execFileAsync(resolve(WINDOWS_SYSTEM32, "WindowsPowerShell", "v1.0", "powershell.exe"), ["-NoProfile", "-NonInteractive", "-Command", WINDOWS_ACL_COMMAND_PROGRAMS.inspect], {
742
+ windowsHide: true,
743
+ // PowerShell 5.1 start-up alone is 1–3 s on a loaded host and single
744
+ // spawns of 4.9–5.2 s were measured under a CPU hog; the bound must
745
+ // outlast that without becoming an effective hang.
746
+ timeout: 15_000,
747
+ maxBuffer: 16 * 1024,
748
+ encoding: "utf8",
749
+ env: isolateWindowsAclChildEnvironment(process.env, path),
750
+ });
751
+ return parseWindowsAclInspection(stdout, "CUNA_CURRENT_SID", "CUNA_SDDL", directory);
752
+ }
753
+ /**
754
+ * Batched form of `inspectWindowsAcl`: one `powershell.exe` spawn for every
755
+ * requested path. Exported so the real program and its parser can be
756
+ * exercised against real files on Windows.
757
+ */
758
+ export async function inspectWindowsAclMany(requests) {
759
+ const { stdout } = await execFileAsync(resolve(WINDOWS_SYSTEM32, "WindowsPowerShell", "v1.0", "powershell.exe"), ["-NoProfile", "-NonInteractive", "-Command", WINDOWS_ACL_COMMAND_PROGRAMS.inspectMany], {
760
+ windowsHide: true,
761
+ // PowerShell 5.1 start-up alone is 1–3 s on a loaded host and single
762
+ // spawns of 4.9–5.2 s were measured under a CPU hog; the bound must
763
+ // outlast that without becoming an effective hang.
764
+ timeout: 15_000,
765
+ maxBuffer: 64 * 1024,
766
+ encoding: "utf8",
767
+ env: isolateWindowsAclBatchChildEnvironment(process.env, requests.map((request) => request.path)),
768
+ });
769
+ return parseWindowsAclBatchInspection(stdout, requests.map((request) => request.directory));
770
+ }
771
+ /**
772
+ * Parses `inspectMany` output. Exactly one `CUNA_CURRENT_SID=` line is
773
+ * required; each `CUNA_SDDL:<index>=` line yields an inspection at that index
774
+ * and an index without a line stays `undefined`. A duplicated or out-of-range
775
+ * index is a malformed observation and rejects the whole batch.
776
+ */
777
+ export function parseWindowsAclBatchInspection(stdout, directories) {
778
+ const sidLines = stdout.match(/^CUNA_CURRENT_SID=.*$/gmu) ?? [];
779
+ const currentSid = sidLines.length === 1 ? /^CUNA_CURRENT_SID=(S-1-[0-9-]+)$/u.exec(sidLines[0])?.[1] : undefined;
780
+ if (currentSid === undefined)
781
+ throw new Error("Windows ACL inspection is unavailable");
782
+ const results = directories.map(() => undefined);
783
+ for (const line of stdout.match(/^CUNA_SDDL:.*$/gmu) ?? []) {
784
+ const match = /^CUNA_SDDL:(0|[1-9][0-9]*)=(.+)$/u.exec(line);
785
+ const index = match === null ? Number.NaN : Number(match[1]);
786
+ if (match === null || !(index < directories.length) || results[index] !== undefined) {
787
+ throw new Error("Windows ACL batch inspection is malformed");
788
+ }
789
+ results[index] = windowsAclInspectionFromSddl(currentSid, match[2], directories[index]);
790
+ }
791
+ return results;
792
+ }
793
+ async function reconcileNewWindowsAcl(path, directory) {
794
+ const program = directory
795
+ ? WINDOWS_ACL_COMMAND_PROGRAMS.reconcileDirectory
796
+ : WINDOWS_ACL_COMMAND_PROGRAMS.reconcileFile;
797
+ const { stdout } = await execFileAsync(resolve(WINDOWS_SYSTEM32, "WindowsPowerShell", "v1.0", "powershell.exe"), ["-NoProfile", "-NonInteractive", "-Command", program], {
798
+ windowsHide: true,
799
+ // PowerShell 5.1 start-up alone is 1–3 s on a loaded host and single
800
+ // spawns of 4.9–5.2 s were measured under a CPU hog; the bound must
801
+ // outlast that without becoming an effective hang.
802
+ timeout: 15_000,
803
+ maxBuffer: 32 * 1024,
804
+ encoding: "utf8",
805
+ env: isolateWindowsAclChildEnvironment(process.env, path),
806
+ });
807
+ const repaired = /^CUNA_REPAIRED=([01])$/mu.exec(stdout)?.[1];
808
+ if (repaired === undefined)
809
+ throw new Error("Windows ACL reconciliation is unavailable");
810
+ return Object.freeze({
811
+ before: parseWindowsAclInspection(stdout, "CUNA_BEFORE_CURRENT_SID", "CUNA_BEFORE_SDDL", directory),
812
+ after: parseWindowsAclInspection(stdout, "CUNA_AFTER_CURRENT_SID", "CUNA_AFTER_SDDL", directory),
813
+ repaired: repaired === "1",
814
+ });
815
+ }
816
+ function parseWindowsAclInspection(stdout, currentSidLabel, sddlLabel, directory) {
817
+ const currentSid = new RegExp(`^${currentSidLabel}=(S-1-[0-9-]+)$`, "mu").exec(stdout)?.[1];
818
+ const sddl = new RegExp(`^${sddlLabel}=(.+)$`, "mu").exec(stdout)?.[1];
819
+ if (currentSid === undefined || sddl === undefined) {
820
+ throw new Error("Windows ACL inspection is unavailable");
821
+ }
822
+ return windowsAclInspectionFromSddl(currentSid, sddl, directory);
823
+ }
824
+ function windowsAclInspectionFromSddl(currentSid, sddl, directory) {
825
+ const ownerSid = /^O:(S-1-[0-9-]+)(?:G:|D:|S:)/u.exec(sddl)?.[1];
826
+ if (ownerSid === undefined) {
827
+ throw new Error("Windows ACL inspection is unavailable");
828
+ }
829
+ const daclStart = sddl.indexOf("D:");
830
+ const saclStart = daclStart < 0 ? -1 : sddl.indexOf("S:", daclStart + 2);
831
+ const dacl = daclStart < 0 ? undefined : sddl.slice(daclStart, saclStart < 0 ? undefined : saclStart);
832
+ const expectedAce = directory ? `(A;OICI;FA;;;${currentSid})` : `(A;;FA;;;${currentSid})`;
833
+ return {
834
+ currentSid,
835
+ ownerSid,
836
+ daclOwnerOnly: dacl === `D:PAI${expectedAce}` || dacl === `D:P${expectedAce}`,
837
+ };
838
+ }
839
+ //# sourceMappingURL=local-session.js.map