chainlesschain 0.166.78 → 0.166.80

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 (166) hide show
  1. package/README.md +8 -16
  2. package/package.json +1 -1
  3. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  4. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  5. package/src/assets/web-panel/assets/{AIOps-DSyZEY2G.js → AIOps-C5NtVVBN.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-DhNyZ577.js → ActionButton-CE9-HsoR.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-HMMpxmyi.js → Analytics-DRO6sI0Z.js} +3 -3
  8. package/src/assets/web-panel/assets/{AppLayout-CwKMM5Ie.js → AppLayout-CuVJiyC_.js} +3 -3
  9. package/src/assets/web-panel/assets/{Artifacts-CNzDmph7.js → Artifacts-GNahHsyZ.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-DxWpUW3V.js → Audit-DWvTomfm.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-C1R2ST-P.js → BackgroundAgents-k4TuA3ec.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-B6biTaEV.js → Backup-BxqN-65D.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-CVnkD2Z5.js → BaseInput-CYYLvwwD.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-Dbewl4Je.js → Chat-CoDmZ9yb.js} +6 -6
  15. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DVZ6SwC5.js → ChatBubbleRenderer-ChTIMWch.js} +1 -1
  16. package/src/assets/web-panel/assets/{Checkbox-BqqiF1JE.js → Checkbox-CRVlfbkN.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-BVMkGumc.js → Codegen-BttMy7rW.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-DmiRHy96.js → Col-DsdlE4QC.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-BDlpygqA.js → Community-BulheyO-.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-B2NME_m-.js → Compact-BC_7wMxW.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-BlzDLE-R.js → Compliance-BA3PDNLK.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-C8raPybB.js → Cowork-B6Y3L-GP.js} +4 -4
  23. package/src/assets/web-panel/assets/{Cron-DA8Py0zP.js → Cron-DLk_CG77.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-DU2XyHqB.js → Crosschain-CvsKV-90.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-Df098fT_.js → DID-CD8K9G3F.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-D1rT702x.js → Dashboard-CXNE434f.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-BweXGo1O.js → Dropdown-C_P5mttB.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer-B9ShbgJH.js → EmailListRenderer-MxZvItNA.js} +1 -1
  29. package/src/assets/web-panel/assets/EvolutionSettings-DRoJQrDb.js +1 -0
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DrznZFVs.js → FamilyGuardDashboard-CBrz9T6T.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-DQZ2adIA.js → Federation-DKlGgjNs.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-DN30Dowh.js → FormItemContext-CteicPS5.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-DRdt0j1e.js → GenericCardRenderer-Bs10mDna.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-wwRgStzk.js → Git-CXwzFBIh.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-CI4Uw0Nh.js → Governance-CYzcwfwf.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-5TuE2AJI.js → Inference-pYpCF1ws.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-oXyvmFSR.js → KnowledgeGraph-B0YJnOpj.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-ewudSWuv.js → Logs-D-hJWfq-.js} +2 -2
  39. package/src/assets/web-panel/assets/{MarkdownRenderer-Cz5nyufm.js → MarkdownRenderer-BPQlDtB7.js} +1 -1
  40. package/src/assets/web-panel/assets/{Marketplace-DcZeWFrs.js → Marketplace-D5ZmI-si.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-BcV9p1RS.js → McpTools-DByD6jY1.js} +2 -2
  42. package/src/assets/web-panel/assets/{Memory-CMhYvugt.js → Memory--lsjJd7Y.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-5RP2uwvT.js → MobileBridge-GQ3vGZO9.js} +1 -1
  44. package/src/assets/web-panel/assets/{MobileProjects-BIuM9Ze4.js → MobileProjects-Dh2wq-NU.js} +1 -1
  45. package/src/assets/web-panel/assets/{Mtc-DmBszTDe.js → Mtc-uQFoRe_Z.js} +2 -2
  46. package/src/assets/web-panel/assets/{MtcAudit-Czdp5ZGt.js → MtcAudit-BUBYbvuz.js} +2 -2
  47. package/src/assets/web-panel/assets/{Multisig-DRWUuNr-.js → Multisig-D790SLqM.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-IRQQN0-c.js → NLProgramming-DcV9gSqn.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-CGhD1lW9.js → Notes-sVl7Y_vt.js} +3 -3
  50. package/src/assets/web-panel/assets/{NotificationSettings-BtyPgQAh.js → NotificationSettings-BHRPqBZ3.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer-yUaqwCty.js → OrderTableRenderer-B029pIMF.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-C3kTVn5i.js → Organization-BKrF-dtp.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-C2-0iZhq.js → Overflow-DOAGxhhi.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-CISoEUxA.js → P2P-7uQ4F7lh.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-NfTVtvKQ.js → PdhVaultBrowser-BegpATva.js} +5 -5
  56. package/src/assets/web-panel/assets/{Permissions-CfK0jJhm.js → Permissions-C0qK0tAi.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-OsVxxh_j.js → PersonalDataHub-DCnfXKyb.js} +4 -4
  58. package/src/assets/web-panel/assets/{Pipeline-DEmK_Ch2.js → Pipeline-D8wdVvao.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-D61s73b0.js → Privacy-C-dGmb9y.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-DkLF5gHb.js → ProjectInit-ChKvrsfc.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-BA7odTrI.js → ProjectSettings-CaWnzNBi.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-CdI1GcAG.js → Projects-2uNBqS05.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-D9BYyP1r.js → Providers-DTufbN-S.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-CXMItUDD.js → QrScannerModal-CGv6QJMC.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-8-tF9ffZ.js → QuickAsk-BfNcUCfG.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-C2QGWOSi.js → Recommend-B0s5YT2K.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-B1M_v1lx.js → RemoteSession-YCs5wa8P.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-D0WZHgv1.js → Reputation-DBC__Ylq.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-D6nf9-c-.js → Row-D_NT1ROe.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-ClZ6M-mN.js → RssFeed-Dd9ktNRw.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-ZWkt8vuO.js → Search-BsmlT4PY.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-BUlQut2m.js → Security-DN-_e3oM.js} +3 -3
  73. package/src/assets/web-panel/assets/{Services-Cd_3xJOc.js → Services-Bm_KqSDz.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-C8e2-cHW.js → Skeleton-BYldeYI0.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-DucVRJp1.js → Skills-h7XAXSls.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-Csr5pUwI.js → Sla-4ArxAaMJ.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-CXvyaRAT.js → SpeechSettings-B639ejZr.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-KdsIcV5h.js → SyncSettings-DR6RRo1O.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-B-eHmJm9.js → Tasks-DysrrJVE.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-BglVeaLK.js → Templates-DyyB9L9f.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-BBbQ8qan.js → Tenant-XbmWbUl1.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-Ca_pCVBi.js → Terminal-C5GeciJG.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-YD6XG_vZ.js → TimelineRenderer-Db-NjeVy.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-2lRoeVli.js → Tokens-Btm9pYvJ.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-C-3XNAU9.js → Trigger-BhJ35b0V.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-k-1PxdLG.js → Trust-C5T4GJky.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-FG-qW646.js → UkeySign-CqWxh5U0.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-BNOe1cmd.js → VideoEditing-DKyI9zg1.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-D5C3i2Kw.js → Wallet-kgvyQOtT.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-BOpztqd3.js → WebAuthn-1wWucdJt.js} +4 -4
  91. package/src/assets/web-panel/assets/{WorkflowEditor-BpVrpUEv.js → WorkflowEditor-rHVIbLME.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-DIuMHb38.js → chat-DV8ZtfWQ.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-CTFJPyzQ.js → colors-zznFfEpj.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-CONfmJcW.js → compact-item-CWk10Qsu.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-RkW5CRrz.js → createContext-CtoMW-kY.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-_Wlo1cZJ.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-CW4XOjDC.js → hasIn-ieyHKAZZ.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Akmg1WdH.js → index--6zQrvYc.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BMg2JFmW.js → index-0GSB8LOT.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-YaeGscSw.js → index-14ADEHo7.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-7wMSBCU7.js → index-2TTLREp7.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-asWsB34i.js → index-9fgmdiB_.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-Bf19fKef.js → index-B2WI4Jse.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BFs1Tznc.js → index-B9w6Zr_O.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-B54qoVff.js → index-BG9KlVJl.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CEZWQthY.js → index-BGYCtfL-.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-YlMnFNOi.js → index-Bf9069uM.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DidrMvOx.js → index-BrpyZMUe.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CBjb2A5R.js → index-CBS8OtL2.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-B9XoBoty.js → index-CGZGQTF5.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DWY7YwDu.js → index-CLeX8Qp7.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DP5pPedy.js → index-CdSnkhrR.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DSlXL6Ju.js → index-CewbVIM8.js} +1 -1
  114. package/src/assets/web-panel/assets/index-Cj8Mcdok.js +1 -0
  115. package/src/assets/web-panel/assets/{index-gkjiFjQd.js → index-CjkiJdIX.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CFGLf88d.js → index-Cn71LASJ.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-D-lpTPS8.js → index-Cqpfzkch.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DAESljiJ.js → index-CrBful_u.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Cvmc3f25.js → index-CxSgg3Xc.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CixqfdRk.js → index-D7yq9nAq.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BRZsPxNn.js → index-D9jmNLfO.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DWCJnn2O.js → index-DNlvUg0O.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-D59GbWCN.js → index-DPwOEcUt.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BKgaRT0b.js → index-DR1Uw5as.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Dj3K5kHs.js → index-DRuSuXJh.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CM2X25Er.js → index-DSnQWQ4L.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BM7bOUvu.js → index-DdMho2Wr.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CLUa68JH.js → index-DjNjO-VL.js} +3 -3
  129. package/src/assets/web-panel/assets/{index-CkRojEip.js → index-DmtyRKy8.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-B9aS2VLu.js → index-Dz1TqS34.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-B8r2t0Ch.js → index-G9bqTRu6.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BkAbfBF-.js → index-McW2cdbg.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CX4T2uKN.js → index-esCpkxIZ.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-DDHUx4Yz.js → index-fmwnODwg.js} +1 -1
  135. package/src/assets/web-panel/assets/index-vira2aS_.js +1 -0
  136. package/src/assets/web-panel/assets/{index-DZTwP364.js → index-zY56IXHj.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-BOr4igh3.js → initDefaultProps-CVk4LKJK.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-CK469n2j.js → motion-IAABGXc_.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-DzjudlYQ.js → move-B9HSYeZc.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-BQaKUxC6.js → mtc-parser-CJfDtM89.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-MciovCFD.js → omit-D5dYnom7.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-DDPJ4zAz.js → pickAttrs-xbUkaZdY.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-Cznyb-jI.js → placementArrow-CIUU5NKN.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-AJK3SLzJ.js → responsiveObserve-BZpgHE8P.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-CPQ28wu7.js → slide-x_8rsSKk.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-CFjzvqf_.js → statusUtils-BZyDnVJs.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-BPyFfTuR.js → styleChecker-DusfWp6J.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-Dq7N6R05.js → useFlexGapSupport-022JzYAL.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-C3GS0uTc.js → useFs-DRDxX7-3.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-BhgIVzP_.js → usePersonalDataHub-DFrCp7m_.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-4ZD9-MDr.js → vnode-bCuSFOmr.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-Bx4MRVNW.js → zoom-sCpZ0sw7.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/data/changelog.json +26 -0
  155. package/src/lib/agent-sandbox.js +319 -2
  156. package/src/lib/lsp/code-intelligence.js +7 -1
  157. package/src/lib/sandbox-docker-egress.js +507 -0
  158. package/src/lib/sandbox-egress-authority-monitor.js +137 -0
  159. package/src/lib/sandbox-egress-proxy.js +167 -10
  160. package/src/lib/sandbox-egress-worker-thread.js +38 -0
  161. package/src/lib/sandbox-egress-worker.js +196 -0
  162. package/src/runtime/agent-core.js +211 -25
  163. package/src/assets/web-panel/assets/EvolutionSettings-D75_iBoc.js +0 -1
  164. package/src/assets/web-panel/assets/devWarning-DaEBln_q.js +0 -1
  165. package/src/assets/web-panel/assets/index-DG1mcYTa.js +0 -1
  166. package/src/assets/web-panel/assets/index-DRs5Pe8u.js +0 -1
@@ -0,0 +1,507 @@
1
+ /** Linux-only Docker network enclosure. The host policy proxy owns the UDS.
2
+ * This module does not grant domain policy authority or claim Docker attestation.
3
+ */
4
+ import crypto from "node:crypto";
5
+ import fs from "node:fs";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import broker from "./process-execution-broker/index.js";
9
+
10
+ const PORT = 3128;
11
+ const LABEL = "chainless.egress.owner";
12
+ const CLEANUP_TIMEOUT_MS = 5_000;
13
+ const RECONCILE_ATTEMPTS = 3;
14
+ const RELAY = `const net=require('node:net');
15
+ const sockets=new Set();
16
+ const server=net.createServer(c=>{
17
+ const u=net.connect('/egress.sock'); sockets.add(c); sockets.add(u);
18
+ const close=()=>{c.destroy();u.destroy();sockets.delete(c);sockets.delete(u)};
19
+ c.on('error',close);u.on('error',close);c.on('close',close);u.on('close',close);
20
+ c.pipe(u);u.pipe(c);
21
+ });
22
+ server.on('error',()=>process.exit(71));
23
+ const preflight=net.connect('/egress.sock');
24
+ preflight.on('error',()=>process.exit(72));
25
+ preflight.on('connect',()=>{preflight.destroy();server.listen(${PORT},'127.0.0.1',()=>console.log('EGRESS_READY'))});
26
+ process.on('SIGTERM',()=>{for(const s of sockets)s.destroy();server.close(()=>process.exit(0))});`;
27
+
28
+ // Default-deny, deliberately narrower than Docker's general purpose profile.
29
+ // No socketpair, io_uring, namespace/mount, ptrace or cross-process FD imports.
30
+ const ORDINARY_SYSCALLS =
31
+ `read write readv writev pread64 pwrite64 close close_range
32
+ fstat newfstatat stat lstat statx access faccessat faccessat2 open openat getdents getdents64
33
+ lseek dup dup2 dup3 fcntl ioctl pipe pipe2 poll ppoll select pselect6 epoll_create
34
+ epoll_create1 epoll_ctl epoll_wait epoll_pwait epoll_pwait2 eventfd eventfd2
35
+ mmap mprotect munmap mremap madvise brk futex futex_waitv set_robust_list get_robust_list
36
+ rt_sigaction rt_sigprocmask rt_sigreturn rt_sigsuspend sigaltstack rt_sigpending
37
+ rt_sigtimedwait rt_sigqueueinfo rt_tgsigqueueinfo kill tkill tgkill
38
+ clock_gettime clock_getres clock_nanosleep nanosleep gettimeofday time times
39
+ getpid getppid gettid getuid geteuid getgid getegid getgroups uname sysinfo
40
+ getcwd chdir fchdir readlink readlinkat getrandom arch_prctl set_tid_address rseq
41
+ prlimit64 getrlimit getrusage sched_getaffinity sched_yield sched_getparam sched_getscheduler
42
+ exit exit_group wait4 waitid execve execveat fork vfork
43
+ connect bind listen accept accept4 getsockname getpeername getsockopt setsockopt
44
+ sendto recvfrom sendmsg shutdown
45
+ mkdir mkdirat rmdir unlink unlinkat rename renameat renameat2 link linkat symlink symlinkat
46
+ chmod fchmod fchmodat umask truncate ftruncate fsync fdatasync flock utime utimes utimensat
47
+ fadvise64 statfs fstatfs restart_syscall`
48
+ .trim()
49
+ .split(/\s+/);
50
+
51
+ export function dockerEgressSeccompProfile(arch = process.arch) {
52
+ const architecture = { x64: "SCMP_ARCH_X86_64", arm64: "SCMP_ARCH_AARCH64" }[
53
+ arch
54
+ ];
55
+ if (!architecture) throw new Error("Unsupported Docker egress architecture");
56
+ return {
57
+ defaultAction: "SCMP_ACT_ERRNO",
58
+ defaultErrnoRet: 1,
59
+ architectures: [architecture],
60
+ syscalls: [
61
+ { names: ORDINARY_SYSCALLS, action: "SCMP_ACT_ALLOW" },
62
+ // clone3 returns ENOSYS so libc can use the inspectable clone syscall.
63
+ { names: ["clone3"], action: "SCMP_ACT_ERRNO", errnoRet: 38 },
64
+ {
65
+ names: ["clone"],
66
+ action: "SCMP_ACT_ALLOW",
67
+ args: [
68
+ {
69
+ index: 0,
70
+ value: 0x7e020080,
71
+ valueTwo: 0,
72
+ op: "SCMP_CMP_MASKED_EQ",
73
+ },
74
+ ],
75
+ },
76
+ ...[2, 10].map((family) => ({
77
+ names: ["socket"],
78
+ action: "SCMP_ACT_ALLOW",
79
+ args: [
80
+ { index: 0, value: family, op: "SCMP_CMP_EQ" },
81
+ { index: 1, value: 0xf, valueTwo: 1, op: "SCMP_CMP_MASKED_EQ" },
82
+ ],
83
+ })),
84
+ ],
85
+ };
86
+ }
87
+
88
+ function docker(args, timeout = 30_000, capture = false, auditContext = null) {
89
+ return new Promise((resolve, reject) =>
90
+ broker.execFile(
91
+ "docker",
92
+ args,
93
+ {
94
+ origin: "sandbox:docker-egress",
95
+ policy: "allow",
96
+ shell: false,
97
+ encoding: "utf8",
98
+ timeout,
99
+ maxBuffer: 4 * 1024 * 1024,
100
+ windowsHide: true,
101
+ requirePersistentAudit: true,
102
+ auditContext,
103
+ auditRedactArgIndexes:
104
+ args.includes("create") && args.includes("/bin/sh")
105
+ ? [args.length - 1]
106
+ : [],
107
+ },
108
+ (error, stdout, stderr) => {
109
+ if (error) {
110
+ error.stdout = stdout;
111
+ error.stderr = stderr;
112
+ reject(error);
113
+ } else
114
+ resolve(
115
+ capture
116
+ ? { stdout: String(stdout), stderr: String(stderr) }
117
+ : String(stdout).trim(),
118
+ );
119
+ },
120
+ ),
121
+ );
122
+ }
123
+
124
+ function checkedImage(value) {
125
+ if (
126
+ typeof value !== "string" ||
127
+ !/^[a-zA-Z0-9][a-zA-Z0-9./:_-]*@sha256:[a-f0-9]{64}$/.test(value)
128
+ ) {
129
+ throw new Error("Docker egress requires a pinned image digest");
130
+ }
131
+ return value;
132
+ }
133
+
134
+ /** Internal dependency seam permits lifecycle tests without claiming Docker runs. */
135
+ export async function startDockerEgressSession(options, deps = {}) {
136
+ const platform = deps.platform || process.platform;
137
+ if (platform !== "linux")
138
+ throw new Error("Docker egress requires a Linux host");
139
+ const runDocker = deps.docker || docker;
140
+ const io = deps.fs || fs;
141
+ const relayImage = checkedImage(options.relayImage);
142
+ const targetImage = checkedImage(options.targetImage || options.relayImage);
143
+ const endpoint = await runDocker(
144
+ ["context", "inspect", "--format", "{{.Endpoints.docker.Host}}"],
145
+ undefined,
146
+ false,
147
+ options.auditContext || null,
148
+ );
149
+ // Explicit -H also prevents DOCKER_HOST/DOCKER_CONTEXT from changing authority.
150
+ if (!/^unix:\/\/\//.test(endpoint))
151
+ throw new Error("Docker egress requires a local Unix Docker daemon");
152
+ const command = (args, timeout, capture) =>
153
+ runDocker(
154
+ ["--host", endpoint, ...args],
155
+ timeout,
156
+ capture,
157
+ options.auditContext || null,
158
+ );
159
+ const info = JSON.parse(await command(["info", "--format", "{{json .}}"]));
160
+ if (info.OSType !== "linux")
161
+ throw new Error("Docker egress requires a Linux daemon");
162
+ const workspace = io.realpathSync(options.workspaceRoot);
163
+ const socket = io.realpathSync(options.brokerSocketPath);
164
+ const socketStat = io.statSync(socket);
165
+ const ownerUid = deps.uid ?? process.getuid?.() ?? 1000;
166
+ const socketParent = io.statSync(path.dirname(socket));
167
+ if (
168
+ socketStat.uid !== ownerUid ||
169
+ socketParent.uid !== ownerUid ||
170
+ (socketParent.mode & 0o077) !== 0 ||
171
+ !socketParent.isDirectory()
172
+ ) {
173
+ throw new Error(
174
+ "Docker egress broker socket requires a private owned parent directory",
175
+ );
176
+ }
177
+ if (!io.statSync(workspace).isDirectory() || !socketStat.isSocket()) {
178
+ throw new Error(
179
+ "Docker egress requires a workspace and a listening Unix socket",
180
+ );
181
+ }
182
+ if ([workspace, socket].some((p) => p.includes(",") || p.includes("\n"))) {
183
+ throw new Error("Docker mount paths contain unsupported characters");
184
+ }
185
+ const token = crypto.randomBytes(16).toString("hex");
186
+ const relayName = `cc-egress-${token}`;
187
+ const targetName = `cc-target-${token}`;
188
+ const stateDir = io.mkdtempSync(
189
+ path.join(options.tempRoot || os.tmpdir(), "cc-egress-"),
190
+ );
191
+ const profilePath = path.join(stateDir, "target-seccomp.json");
192
+ const seccompProfile = dockerEgressSeccompProfile(deps.arch);
193
+ io.writeFileSync(profilePath, JSON.stringify(seccompProfile), {
194
+ mode: 0o600,
195
+ });
196
+ let closed = false;
197
+ let closing;
198
+ let active = false;
199
+ let relayId;
200
+ const containers = new Set([relayName, targetName]);
201
+ const uncertainCreations = new Set();
202
+ const pendingCreations = new Set();
203
+ const recoveryPath = path.join(stateDir, "cleanup-state.json");
204
+ const pause =
205
+ deps.pause || ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
206
+ function persistRecoveryState() {
207
+ io.writeFileSync(
208
+ recoveryPath,
209
+ JSON.stringify({
210
+ ownerLabel: `${LABEL}=${token}`,
211
+ containers: [...containers],
212
+ uncertainCreations: [...uncertainCreations],
213
+ cleanupRequired: true,
214
+ }),
215
+ { mode: 0o600 },
216
+ );
217
+ }
218
+ persistRecoveryState();
219
+ async function createOwned(name, args) {
220
+ if (closed) throw new Error("Docker egress session closed during launch");
221
+ // A lost CLI response does not cancel the daemon's create operation.
222
+ uncertainCreations.add(name);
223
+ persistRecoveryState();
224
+ const creation = Promise.resolve().then(() => command(args));
225
+ pendingCreations.add(creation);
226
+ try {
227
+ const id = await creation;
228
+ if (!/^[a-f0-9]{64}$/.test(id))
229
+ throw new Error("Invalid Docker container identity");
230
+ uncertainCreations.delete(name);
231
+ persistRecoveryState();
232
+ return id;
233
+ } finally {
234
+ pendingCreations.delete(creation);
235
+ }
236
+ }
237
+ const user = `${process.getuid?.() ?? 1000}:${process.getgid?.() ?? 1000}`;
238
+ const common = [
239
+ "--label",
240
+ `${LABEL}=${token}`,
241
+ "--cap-drop",
242
+ "ALL",
243
+ "--security-opt",
244
+ "no-new-privileges",
245
+ "--user",
246
+ user,
247
+ "--read-only",
248
+ "--pids-limit",
249
+ "128",
250
+ "--memory",
251
+ "512m",
252
+ "--tmpfs",
253
+ "/tmp:rw,nosuid,nodev,size=64m",
254
+ ];
255
+ async function removeOwned(name) {
256
+ let identity;
257
+ for (let attempt = 0; attempt < RECONCILE_ATTEMPTS; attempt += 1) {
258
+ try {
259
+ identity = JSON.parse(
260
+ await command(["inspect", name], CLEANUP_TIMEOUT_MS),
261
+ );
262
+ break;
263
+ } catch (error) {
264
+ if (!/No such (object|container)/i.test(error.stderr || error.message))
265
+ throw error;
266
+ if (!uncertainCreations.has(name)) return;
267
+ if (attempt + 1 < RECONCILE_ATTEMPTS) await pause(100);
268
+ }
269
+ }
270
+ if (!identity) {
271
+ const error = new Error(
272
+ "Docker container creation outcome remains unknown",
273
+ );
274
+ error.code = "ERR_DOCKER_EGRESS_CREATE_UNRESOLVED";
275
+ error.containerName = name;
276
+ throw error;
277
+ }
278
+ if (identity[0]?.Config?.Labels?.[LABEL] !== token)
279
+ throw new Error("Docker egress cleanup ownership mismatch");
280
+ // The creation has now been observed even if the subsequent rm loses its
281
+ // reply. A later absent result can therefore establish successful cleanup.
282
+ uncertainCreations.delete(name);
283
+ await command(["rm", "-f", identity[0].Id], CLEANUP_TIMEOUT_MS);
284
+ }
285
+ function close() {
286
+ if (!closing) {
287
+ closed = true;
288
+ closing = (async () => {
289
+ // Join admitted creates before inspecting their names. No new create
290
+ // may be admitted once closed is set.
291
+ await Promise.allSettled([...pendingCreations]);
292
+ const results = await Promise.allSettled(
293
+ [...containers].reverse().map(removeOwned),
294
+ );
295
+ const failures = results.filter(
296
+ (result) => result.status === "rejected",
297
+ );
298
+ if (failures.length) {
299
+ persistRecoveryState();
300
+ const error = new AggregateError(
301
+ failures.map((result) => result.reason),
302
+ "Docker egress cleanup is incomplete",
303
+ );
304
+ error.code = "ERR_DOCKER_EGRESS_CLEANUP_INCOMPLETE";
305
+ error.cleanupStatePath = recoveryPath;
306
+ throw error;
307
+ }
308
+ io.rmSync(stateDir, { recursive: true, force: true });
309
+ })();
310
+ const attempt = closing;
311
+ void attempt.catch(() => {
312
+ if (closing === attempt) closing = null;
313
+ });
314
+ }
315
+ return closing;
316
+ }
317
+ try {
318
+ relayId = await createOwned(relayName, [
319
+ "create",
320
+ "--name",
321
+ relayName,
322
+ ...common,
323
+ "--network",
324
+ "none",
325
+ "--mount",
326
+ `type=bind,source=${socket},target=/egress.sock,readonly`,
327
+ "--entrypoint",
328
+ "node",
329
+ relayImage,
330
+ "-e",
331
+ RELAY,
332
+ ]);
333
+ await command(["start", relayId]);
334
+ // Probe the full relay -> UDS -> policy proxy protocol path. This reserved
335
+ // name cannot designate a public origin; status depends on the policy.
336
+ await command([
337
+ "exec",
338
+ relayId,
339
+ "node",
340
+ "-e",
341
+ `const net=require('node:net');let tries=0;const deadline=setTimeout(()=>process.exit(1),10000);function check(){const s=net.connect(${PORT},'127.0.0.1');let bytes='';s.on('connect',()=>s.write('GET http://blocked.invalid/ HTTP/1.1\\r\\nHost: blocked.invalid\\r\\nConnection: close\\r\\n\\r\\n'));s.on('data',c=>{bytes+=c;if(/^HTTP\\/1\\.[01] [1-5][0-9]{2} [^\\r\\n]*\\r\\n/.test(bytes)){clearTimeout(deadline);s.destroy();process.exit(0)}});s.on('error',()=>{if(++tries>50)process.exit(1);setTimeout(check,100)})}check()`,
342
+ ]);
343
+ return {
344
+ relayId,
345
+ close,
346
+ async run(shellCommand, runOptions = {}) {
347
+ if (closed || active)
348
+ throw new Error("Docker egress session is closed or already running");
349
+ if (typeof shellCommand !== "string")
350
+ throw new TypeError("A shell command is required");
351
+ if (
352
+ runOptions.beforeStart !== undefined &&
353
+ typeof runOptions.beforeStart !== "function"
354
+ )
355
+ throw new TypeError("beforeStart must be a trusted function");
356
+ active = true;
357
+ let executionError;
358
+ let result;
359
+ try {
360
+ const relay = JSON.parse(await command(["inspect", relayId]))[0];
361
+ if (closed)
362
+ throw new Error("Docker egress session closed during launch");
363
+ if (
364
+ !relay?.State?.Running ||
365
+ relay.HostConfig?.NetworkMode !== "none"
366
+ )
367
+ throw new Error("Docker egress relay is unavailable");
368
+ const targetId = await createOwned(targetName, [
369
+ "create",
370
+ "--name",
371
+ targetName,
372
+ ...common,
373
+ "--network",
374
+ `container:${relayId}`,
375
+ "--security-opt",
376
+ `seccomp=${profilePath}`,
377
+ "--mount",
378
+ `type=bind,source=${workspace},target=/workspace`,
379
+ "--workdir",
380
+ "/workspace",
381
+ ...[
382
+ "HTTP_PROXY",
383
+ "HTTPS_PROXY",
384
+ "ALL_PROXY",
385
+ "http_proxy",
386
+ "https_proxy",
387
+ "all_proxy",
388
+ ].flatMap((key) => ["--env", `${key}=http://127.0.0.1:${PORT}`]),
389
+ "--env",
390
+ "NO_PROXY=",
391
+ "--env",
392
+ "no_proxy=",
393
+ ...[
394
+ "CLAUDECODE",
395
+ "CC_SESSION_ID",
396
+ "CLAUDE_CODE_SESSION_ID",
397
+ ].flatMap((key) =>
398
+ runOptions.env?.[key] == null
399
+ ? []
400
+ : ["--env", `${key}=${runOptions.env[key]}`],
401
+ ),
402
+ "--entrypoint",
403
+ "/bin/sh",
404
+ targetImage,
405
+ "-lc",
406
+ shellCommand,
407
+ ]);
408
+ if (closed) {
409
+ throw new Error("Docker egress session closed during launch");
410
+ }
411
+ const targetBeforeStart = JSON.parse(
412
+ await command(["inspect", targetId]),
413
+ )[0];
414
+ const hostConfig = targetBeforeStart?.HostConfig;
415
+ const securityOptions = hostConfig?.SecurityOpt || [];
416
+ const seccompMatched = securityOptions.some((entry) => {
417
+ if (entry === `seccomp=${profilePath}`) return true;
418
+ if (!entry.startsWith("seccomp={")) return false;
419
+ try {
420
+ return (
421
+ JSON.stringify(JSON.parse(entry.slice("seccomp=".length))) ===
422
+ JSON.stringify(seccompProfile)
423
+ );
424
+ } catch {
425
+ return false;
426
+ }
427
+ });
428
+ const missing = [
429
+ ["network", hostConfig?.NetworkMode === `container:${relayId}`],
430
+ ["readonly-root", hostConfig?.ReadonlyRootfs === true],
431
+ ["capabilities", hostConfig?.CapDrop?.includes("ALL")],
432
+ [
433
+ "no-new-privileges",
434
+ securityOptions.some((entry) =>
435
+ entry.startsWith("no-new-privileges"),
436
+ ),
437
+ ],
438
+ ["seccomp", seccompMatched],
439
+ ]
440
+ .filter(([, passed]) => !passed)
441
+ .map(([name]) => name);
442
+ if (missing.length) {
443
+ throw new Error(
444
+ `Docker target isolation options were not applied: ${missing.join(", ")}`,
445
+ );
446
+ }
447
+ // The caller can revalidate its live permission and network
448
+ // authority after every asynchronous create step and before start.
449
+ await runOptions.beforeStart?.();
450
+ if (closed) {
451
+ throw new Error("Docker egress session closed during launch");
452
+ }
453
+ let output;
454
+ let attachError;
455
+ try {
456
+ output = await command(
457
+ ["start", "--attach", targetId],
458
+ runOptions.timeoutMs || 120_000,
459
+ true,
460
+ );
461
+ } catch (error) {
462
+ attachError = error;
463
+ }
464
+ const target = JSON.parse(await command(["inspect", targetId]))[0];
465
+ if (
466
+ attachError &&
467
+ (attachError.killed ||
468
+ !Number.isInteger(attachError.code) ||
469
+ target?.State?.Running !== false ||
470
+ !Number.isInteger(target?.State?.ExitCode))
471
+ )
472
+ throw attachError;
473
+ if (
474
+ target?.State?.Running !== false ||
475
+ !Number.isInteger(target?.State?.ExitCode)
476
+ )
477
+ throw new Error("Docker target has no observed terminal state");
478
+ result = {
479
+ stdout:
480
+ typeof output === "string"
481
+ ? output
482
+ : (output?.stdout ?? String(attachError?.stdout || "")),
483
+ stderr: output?.stderr ?? String(attachError?.stderr || ""),
484
+ exitCode: target.State.ExitCode,
485
+ };
486
+ } catch (error) {
487
+ executionError = error;
488
+ }
489
+ try {
490
+ await close();
491
+ } catch (cleanupError) {
492
+ if (executionError) executionError.cleanupError = cleanupError;
493
+ else executionError = cleanupError;
494
+ }
495
+ if (executionError) throw executionError;
496
+ return result;
497
+ },
498
+ };
499
+ } catch (error) {
500
+ try {
501
+ await close();
502
+ } catch (cleanupError) {
503
+ error.cleanupError = cleanupError;
504
+ }
505
+ throw error;
506
+ }
507
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Recheck the live shell authority while a Docker egress session exists. This
3
+ * is a bounded poll, not a lossless revision subscription: changes that are
4
+ * reverted between checks cannot be observed.
5
+ */
6
+ export function createDockerEgressAuthorityMonitor({
7
+ revalidate,
8
+ abortProxy,
9
+ intervalMs = 500,
10
+ checkTimeoutMs = 5_000,
11
+ }) {
12
+ if (typeof revalidate !== "function" || typeof abortProxy !== "function")
13
+ throw new TypeError("Docker egress authority callbacks are required");
14
+ let revoked = null;
15
+ let session = null;
16
+ let timer = null;
17
+ let checking = null;
18
+ let stopped = false;
19
+ const cleanupTasks = new Set();
20
+
21
+ function trackCleanup(operation) {
22
+ try {
23
+ const task = Promise.resolve(operation());
24
+ cleanupTasks.add(task);
25
+ void task.catch((error) => {
26
+ if (revoked && !revoked.cleanupError) revoked.cleanupError = error;
27
+ });
28
+ void task.finally(() => cleanupTasks.delete(task)).catch(() => {});
29
+ } catch (error) {
30
+ if (revoked && !revoked.cleanupError) revoked.cleanupError = error;
31
+ }
32
+ }
33
+
34
+ function revoke(cause) {
35
+ if (revoked) return revoked;
36
+ revoked =
37
+ cause instanceof Error
38
+ ? cause
39
+ : new Error("Docker egress authority was revoked");
40
+ if (!revoked.code) revoked.code = "CC_DOCKER_EGRESS_AUTHORITY_CHANGED";
41
+ clearTimeout(timer);
42
+ // Latch first, then cut the broker before closing the target containers.
43
+ trackCleanup(abortProxy);
44
+ if (session) trackCleanup(() => session.close());
45
+ return revoked;
46
+ }
47
+
48
+ function assertAuthorized() {
49
+ if (revoked) throw revoked;
50
+ }
51
+
52
+ function attachSession(value) {
53
+ session = value;
54
+ if (revoked) {
55
+ trackCleanup(() => value.close());
56
+ throw revoked;
57
+ }
58
+ }
59
+
60
+ function checkNow() {
61
+ assertAuthorized();
62
+ if (checking) return checking;
63
+ let timeout;
64
+ const deadline = new Promise((_, reject) => {
65
+ timeout = setTimeout(() => {
66
+ reject(
67
+ Object.assign(new Error("Docker egress authority check timed out"), {
68
+ code: "CC_DOCKER_EGRESS_AUTHORITY_TIMEOUT",
69
+ }),
70
+ );
71
+ }, checkTimeoutMs);
72
+ timeout.unref?.();
73
+ });
74
+ const current = Promise.race([Promise.resolve().then(revalidate), deadline])
75
+ .then((accepted) => {
76
+ if (accepted === false)
77
+ throw new Error("Docker egress authority check rejected execution");
78
+ assertAuthorized();
79
+ })
80
+ .catch((error) => {
81
+ throw revoke(error);
82
+ })
83
+ .finally(() => {
84
+ clearTimeout(timeout);
85
+ if (checking === current) checking = null;
86
+ });
87
+ checking = current;
88
+ return current;
89
+ }
90
+
91
+ async function checkFresh() {
92
+ // A check already in flight may have sampled authority before a dispatch
93
+ // or receipt boundary. Drain it, then make a new observation.
94
+ if (checking) await checking;
95
+ assertAuthorized();
96
+ await checkNow();
97
+ }
98
+
99
+ function schedule() {
100
+ if (stopped || revoked) return;
101
+ timer = setTimeout(async () => {
102
+ if (stopped || revoked) return;
103
+ try {
104
+ await checkNow();
105
+ } catch {
106
+ // revoke() already latched the error and started teardown.
107
+ }
108
+ schedule();
109
+ }, intervalMs);
110
+ timer.unref?.();
111
+ }
112
+
113
+ return {
114
+ get revocationError() {
115
+ return revoked;
116
+ },
117
+ start: schedule,
118
+ checkNow,
119
+ checkFresh,
120
+ assertAuthorized,
121
+ attachSession,
122
+ revoke,
123
+ async finish() {
124
+ stopped = true;
125
+ clearTimeout(timer);
126
+ await checkFresh();
127
+ assertAuthorized();
128
+ },
129
+ stop() {
130
+ stopped = true;
131
+ clearTimeout(timer);
132
+ },
133
+ async awaitCleanup() {
134
+ await Promise.allSettled([...cleanupTasks]);
135
+ },
136
+ };
137
+ }