chainlesschain 0.163.8 → 0.164.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 (228) hide show
  1. package/package.json +4 -1
  2. package/scripts/macos-mcp-launcher-build.mjs +317 -0
  3. package/scripts/macos-mcp-launcher-postinstall.sh +54 -0
  4. package/scripts/macos-mcp-launcher-preinstall.sh +61 -0
  5. package/src/assets/web-panel/assets/{AIOps-CavM7W3k.js → AIOps-D2MEn7fI.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-CUvZGabQ.js → ActionButton-lCW_4Gf0.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-DeBnWnbe.js → Analytics-Bs7i-nsS.js} +3 -3
  8. package/src/assets/web-panel/assets/{AppLayout-DbI5kxvd.js → AppLayout-CK7B6S_j.js} +5 -5
  9. package/src/assets/web-panel/assets/{Artifacts-D93bigKm.js → Artifacts-C8ILjpyo.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-CBNvfUZa.js → Audit-Bb9cNaWc.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-CJLyh_YU.js → BackgroundAgents-sC9ooXIf.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-D_0yoehD.js → Backup-D-DrsplT.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-TJxmbObW.js → BaseInput-uCNZpwSK.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-CE0dto1q.js → Chat-CsDK1uE9.js} +6 -6
  15. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZ5-yYiD.js → ChatBubbleRenderer-OqSDt50w.js} +1 -1
  16. package/src/assets/web-panel/assets/{Checkbox-Ck1RVIVo.js → Checkbox-JhTXM1-K.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-qpoyf6DK.js → Codegen-BbzZLnyD.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-DUyPWjIE.js → Col-D4VW4m4j.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-D_S1BemS.js → Community-BXrYawxV.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-BjxeK-Gc.js → Compact-DCihGy1u.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-Dd3CEJsu.js → Compliance-DlyZ-lk8.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-B4QG7-ad.js → Cowork-DvGZrhbO.js} +3 -3
  23. package/src/assets/web-panel/assets/{Cron-BtZnids5.js → Cron-DUncZi8h.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-CHPfCBFD.js → Crosschain-C_hLuUcm.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-DT8DCEOK.js → DID-b6YSUqxr.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-COQab5_U.js → Dashboard-uvM15Cfb.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-BY1PsQuU.js → Dropdown-D1-Nd4qY.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer-CsuAuaq3.js → EmailListRenderer-8ApvL6Zc.js} +1 -1
  29. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BqpinD-j.js → FamilyGuardDashboard-LYJt8811.js} +1 -1
  30. package/src/assets/web-panel/assets/{Federation-D0gkfQFv.js → Federation-BbI3HGpv.js} +1 -1
  31. package/src/assets/web-panel/assets/{FormItemContext-BqgE-axK.js → FormItemContext-DSoEKuFu.js} +1 -1
  32. package/src/assets/web-panel/assets/{GenericCardRenderer-D0hJ2KvC.js → GenericCardRenderer-B0S9OWd5.js} +1 -1
  33. package/src/assets/web-panel/assets/{Git-CmMB0uPY.js → Git-DIs9Zhf3.js} +2 -2
  34. package/src/assets/web-panel/assets/{Governance-BnVEI0Dk.js → Governance-jzZXrxcl.js} +1 -1
  35. package/src/assets/web-panel/assets/{Inference-YTAMihRP.js → Inference-JUPjf2ZI.js} +1 -1
  36. package/src/assets/web-panel/assets/{KnowledgeGraph-0ax7VHop.js → KnowledgeGraph-DxnfJvlP.js} +1 -1
  37. package/src/assets/web-panel/assets/{Logs-B9TRcokL.js → Logs-iXiz8BEE.js} +2 -2
  38. package/src/assets/web-panel/assets/MarkdownRenderer-GmB6DOr6.js +1 -0
  39. package/src/assets/web-panel/assets/{Marketplace-B6CSuD_H.js → Marketplace-Y3Xch312.js} +1 -1
  40. package/src/assets/web-panel/assets/{McpTools-CjMXvOVU.js → McpTools-D7kGGbO7.js} +3 -3
  41. package/src/assets/web-panel/assets/{Memory-CXRF71U3.js → Memory-Do_hVxzh.js} +2 -2
  42. package/src/assets/web-panel/assets/{MobileBridge-DQgNp_oS.js → MobileBridge-CtrR0Tas.js} +1 -1
  43. package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +1 -0
  44. package/src/assets/web-panel/assets/{Mtc-6hDTRPe1.js → Mtc-qo5QCTGd.js} +2 -2
  45. package/src/assets/web-panel/assets/{MtcAudit-BlVHU1DS.js → MtcAudit-cq5mnjYj.js} +2 -2
  46. package/src/assets/web-panel/assets/{Multisig-DP9pzEbV.js → Multisig-Bq0MAQHw.js} +3 -3
  47. package/src/assets/web-panel/assets/{NLProgramming-Djorh8EV.js → NLProgramming-DdWE6Vi2.js} +1 -1
  48. package/src/assets/web-panel/assets/{Notes-oLZ-JkUg.js → Notes-BxMBFcIp.js} +4 -4
  49. package/src/assets/web-panel/assets/{NotificationSettings-Cg288VH3.js → NotificationSettings-RTrAcbUe.js} +1 -1
  50. package/src/assets/web-panel/assets/{OrderTableRenderer-Bb1O3Ofr.js → OrderTableRenderer-DJbpGqzT.js} +1 -1
  51. package/src/assets/web-panel/assets/{Organization-CBdo-bHs.js → Organization-SQzIPdqC.js} +2 -2
  52. package/src/assets/web-panel/assets/{Overflow-qZdnVumx.js → Overflow-02yECh22.js} +1 -1
  53. package/src/assets/web-panel/assets/{P2P-D4kNfGNQ.js → P2P-BNg4pNCM.js} +2 -2
  54. package/src/assets/web-panel/assets/{PdhVaultBrowser-DnlMemM7.js → PdhVaultBrowser-BxPJZsD3.js} +5 -5
  55. package/src/assets/web-panel/assets/{Permissions-DHucHFBJ.js → Permissions-CLnDnPAz.js} +3 -3
  56. package/src/assets/web-panel/assets/{PersonalDataHub-BovlOIPR.js → PersonalDataHub-C5m0AjGf.js} +4 -4
  57. package/src/assets/web-panel/assets/{Pipeline-Deru3CQY.js → Pipeline-VwOf5Dhv.js} +1 -1
  58. package/src/assets/web-panel/assets/{Privacy-Bq5oTTbU.js → Privacy-CC0W2hZd.js} +1 -1
  59. package/src/assets/web-panel/assets/{ProjectInit-BfzxVWAz.js → ProjectInit-Dz893Bob.js} +2 -2
  60. package/src/assets/web-panel/assets/{ProjectSettings-CKC6XBtH.js → ProjectSettings-CxQkvl0k.js} +2 -2
  61. package/src/assets/web-panel/assets/Projects-DDAjkUkT.js +1 -0
  62. package/src/assets/web-panel/assets/{Providers-C_jx4TWX.js → Providers-DeB9IkxK.js} +1 -1
  63. package/src/assets/web-panel/assets/{QrScannerModal-BdIHeVGC.js → QrScannerModal-OCeoaswn.js} +1 -1
  64. package/src/assets/web-panel/assets/{QuickAsk-BwZwAcPD.js → QuickAsk-DenotaAj.js} +1 -1
  65. package/src/assets/web-panel/assets/{Recommend-BNTt0L-m.js → Recommend-Cies_jjI.js} +1 -1
  66. package/src/assets/web-panel/assets/{RemoteSession-Biyn2-Ro.js → RemoteSession-Dxzl5n2T.js} +3 -3
  67. package/src/assets/web-panel/assets/{Reputation-D6VpG32B.js → Reputation-CW0mvxgZ.js} +1 -1
  68. package/src/assets/web-panel/assets/{Row-D_74OvQQ.js → Row-SEd32qZU.js} +1 -1
  69. package/src/assets/web-panel/assets/{RssFeed-BTr0a-BQ.js → RssFeed-BSwnJ3Lm.js} +3 -3
  70. package/src/assets/web-panel/assets/{Search-C3MCFoNU.js → Search-xe2m8bkC.js} +1 -1
  71. package/src/assets/web-panel/assets/{Security-CqW9aJ6s.js → Security-Mq2F9Nro.js} +3 -3
  72. package/src/assets/web-panel/assets/{Services-Cw5GNaHF.js → Services-BKGw2V3P.js} +2 -2
  73. package/src/assets/web-panel/assets/{Skeleton-DZps5qeY.js → Skeleton-DW95-Rxf.js} +1 -1
  74. package/src/assets/web-panel/assets/{Skills-C6NQ1znT.js → Skills-CHgChCv_.js} +1 -1
  75. package/src/assets/web-panel/assets/{Sla-DFP8G2Sh.js → Sla-tKh6k3R7.js} +1 -1
  76. package/src/assets/web-panel/assets/{SpeechSettings-ESt6MB6v.js → SpeechSettings-D6UL3dA5.js} +1 -1
  77. package/src/assets/web-panel/assets/{SyncSettings-BHnN98aM.js → SyncSettings-D0Fw8fVL.js} +2 -2
  78. package/src/assets/web-panel/assets/{Tasks-NiSCaOf-.js → Tasks-20x1GB3D.js} +1 -1
  79. package/src/assets/web-panel/assets/{Templates-DsKFhW2C.js → Templates-DJ-lts-a.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tenant-CQ0E0ze5.js → Tenant-BFdGpbs7.js} +1 -1
  81. package/src/assets/web-panel/assets/{Terminal-D_rBu_by.js → Terminal-Xpx61vHL.js} +2 -2
  82. package/src/assets/web-panel/assets/{TimelineRenderer-D3Vm4Po4.js → TimelineRenderer-C6UIvyZQ.js} +1 -1
  83. package/src/assets/web-panel/assets/{Tokens-DNTxRH6q.js → Tokens-Dellq08f.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trigger-Dgprz1ms.js → Trigger-VKw8d0J-.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trust-B2rUnVBD.js → Trust-DNftdKT5.js} +1 -1
  86. package/src/assets/web-panel/assets/{UkeySign-DEGQNc6_.js → UkeySign-DX9VAKJI.js} +1 -1
  87. package/src/assets/web-panel/assets/{VideoEditing-jlsAx3Eh.js → VideoEditing-B2h3jO8q.js} +1 -1
  88. package/src/assets/web-panel/assets/{Wallet-8jQih1ij.js → Wallet-CjGkof7f.js} +4 -4
  89. package/src/assets/web-panel/assets/{WebAuthn-XLeO6xIB.js → WebAuthn-2Zfcu55P.js} +4 -4
  90. package/src/assets/web-panel/assets/{WorkflowEditor-De_dgDAG.js → WorkflowEditor-B9eZ8UxZ.js} +1 -1
  91. package/src/assets/web-panel/assets/{chat-CI19R_Sk.js → chat-CQNfD-YO.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-DsYTOYN3.js → colors-D4usyFT2.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-CM3-t_Qr.js → compact-item-s4TpjvjX.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-BjxlBRZv.js → createContext-BRCSvYxj.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +1 -0
  96. package/src/assets/web-panel/assets/{hasIn-RDaWX_4i.js → hasIn-AdkUbkV-.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BN6UPD2E.js → index--00U6vXZ.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CiECJi-Q.js → index-B2V-eKfK.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CnHH7bnD.js → index-B4JJCfQ2.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CjTrRt13.js → index-BIoG5yYf.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Dxge-xWl.js → index-BMcimU-2.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-9R6BxAr0.js → index-BPhkFZ1I.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BHNYPO0b.js → index-BQ3DXBtD.js} +3 -3
  104. package/src/assets/web-panel/assets/{index-Cpqm1qfU.js → index-Bad4VNvz.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-B9O9YyWE.js → index-BgiXR_-_.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-B5REnKt8.js → index-BtobShro.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-D6sm5-Xq.js → index-Bw2MEnUk.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DcPFB8qJ.js → index-C1mJtE6q.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BKJr-TZp.js → index-C484EJiS.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Drlnwr8F.js → index-C5KAQFPQ.js} +1 -1
  111. package/src/assets/web-panel/assets/index-C61cnl3K.js +1 -0
  112. package/src/assets/web-panel/assets/{index-Dsij_KsN.js → index-C7Y8Aluc.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-IeiL9RKc.js → index-CCsM_Ybx.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-B-Z8eFUJ.js → index-CJCFe2Qf.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CyOGN6a5.js → index-CJVEOa4i.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-B0PMlbMs.js → index-CJdQrZth.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-nAH87mQP.js → index-CKC1t3Ic.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BGqqJfL0.js → index-CQhAVU5t.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Cd9OJhHG.js → index-CYdoQsa1.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-D0zcy54x.js → index-Cduu2Hhr.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DIUnNOjG.js → index-CkwiSLhU.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CVQMeiwH.js → index-Cvyt-zhm.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BlT2VAEK.js → index-D2GjI2oe.js} +1 -1
  124. package/src/assets/web-panel/assets/index-D4XFHxBr.js +1 -0
  125. package/src/assets/web-panel/assets/{index-C0yx-jtQ.js → index-DARsAyUp.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-Bi46Xu4u.js → index-DVKfQ0Ux.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Cd3euP_4.js → index-DZT36ZYq.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BCnZyIMA.js → index-D_XBHopw.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-C4gkzxkw.js → index-DdCB70SS.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-B7WBt_4T.js → index-DzRjHxEv.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DX7tuWVJ.js → index-MMN9V6JF.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DoQA0-52.js → index-RfMr79MW.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DXXtGMUJ.js → index-_IRVsV9X.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-pPL66J85.js → index-nXyk8GZ9.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-DyIWssPC.js → index-s_buFMEG.js} +1 -1
  136. package/src/assets/web-panel/assets/{initDefaultProps-BTL88of4.js → initDefaultProps-Blmpk7HL.js} +1 -1
  137. package/src/assets/web-panel/assets/{motion-BUB2pXwB.js → motion-DPBJMv1n.js} +1 -1
  138. package/src/assets/web-panel/assets/{move-egCkkOMm.js → move-ButdzCM7.js} +1 -1
  139. package/src/assets/web-panel/assets/{mtc-parser-DAWhZC8c.js → mtc-parser-cyRC_QCe.js} +1 -1
  140. package/src/assets/web-panel/assets/{omit-CvOxe_6d.js → omit-1epSV7hz.js} +1 -1
  141. package/src/assets/web-panel/assets/{pickAttrs-DZUSLZ5Z.js → pickAttrs-DuPY8xJs.js} +1 -1
  142. package/src/assets/web-panel/assets/{placementArrow-BERxGNMY.js → placementArrow-6XZqarhe.js} +1 -1
  143. package/src/assets/web-panel/assets/{responsiveObserve-DZuDfZ9n.js → responsiveObserve-B40pXBWM.js} +1 -1
  144. package/src/assets/web-panel/assets/{slide-DO756sgI.js → slide-jbZ9Wu4n.js} +1 -1
  145. package/src/assets/web-panel/assets/{statusUtils-BRQlIz28.js → statusUtils-BKQQcVy4.js} +1 -1
  146. package/src/assets/web-panel/assets/{styleChecker-DCSfdybl.js → styleChecker-NiKfWjUT.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFlexGapSupport-LD8dWVve.js → useFlexGapSupport-bQUCvPeb.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFs-BX9uW2LK.js → useFs-CUGji7ph.js} +1 -1
  149. package/src/assets/web-panel/assets/{usePersonalDataHub-BPSlPuHU.js → usePersonalDataHub-4veyN2N2.js} +1 -1
  150. package/src/assets/web-panel/assets/{vnode-BmOILC9V.js → vnode-1-nLug-Z.js} +1 -1
  151. package/src/assets/web-panel/assets/{zoom-1iKowKGg.js → zoom-dhCeRDMo.js} +1 -1
  152. package/src/assets/web-panel/index.html +1 -1
  153. package/src/command-help-index.json +3 -3
  154. package/src/commands/cowork.js +174 -19
  155. package/src/commands/permissions.js +240 -16
  156. package/src/commands/plugin.js +1293 -56
  157. package/src/commands/session-location.js +276 -0
  158. package/src/commands/session.js +2 -0
  159. package/src/data/changelog.json +15 -2
  160. package/src/gateways/ws/action-protocol.js +35 -10
  161. package/src/gateways/ws/remote-session-protocol.js +24 -1
  162. package/src/gateways/ws/ws-agent-handler.js +12 -0
  163. package/src/gateways/ws/ws-server.js +48 -12
  164. package/src/harness/jsonl-session-store.js +341 -24
  165. package/src/harness/mcp-client.js +8 -1
  166. package/src/harness/remote-session-audit.js +1 -0
  167. package/src/harness/remote-session-crypto.js +210 -2
  168. package/src/harness/remote-session-registry.js +159 -6
  169. package/src/harness/session-list-index.js +233 -14
  170. package/src/lib/agent-authority.js +6 -0
  171. package/src/lib/background-agent-keeper-client.js +248 -0
  172. package/src/lib/background-agent-keeper-protocol.js +147 -0
  173. package/src/lib/background-agent-supervisor.js +520 -40
  174. package/src/lib/background-interaction-resolver.js +12 -0
  175. package/src/lib/background-turn-bootstrap-protocol.js +126 -0
  176. package/src/lib/cowork-workflow.js +336 -16
  177. package/src/lib/dynamic-workflow-facade.js +648 -0
  178. package/src/lib/execution-location-contract.js +551 -0
  179. package/src/lib/execution-location-runtime.js +130 -0
  180. package/src/lib/ide-context.js +94 -6
  181. package/src/lib/mcp-stdio-package-materialization.js +166 -14
  182. package/src/lib/packer/pkg-config-generator.js +2 -0
  183. package/src/lib/permission-authority.js +77 -0
  184. package/src/lib/permission-side-effect-center.js +495 -0
  185. package/src/lib/plugin-runtime/install.js +514 -13
  186. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +900 -0
  187. package/src/lib/plugin-runtime/marketplace-catalog.js +1181 -0
  188. package/src/lib/plugin-runtime/marketplace-impact.js +408 -0
  189. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1567 -0
  190. package/src/lib/plugin-security.js +60 -3
  191. package/src/lib/process-execution-broker/index.js +1095 -57
  192. package/src/lib/process-execution-broker/linux-bwrap-descriptor-launch.js +375 -0
  193. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +90 -16
  194. package/src/lib/process-execution-broker/linux-generic-bwrap.js +159 -18
  195. package/src/lib/process-execution-broker/macos-mcp-launcher-contract.js +278 -0
  196. package/src/lib/process-execution-broker/macos-mcp-launcher-protocol.json +23 -0
  197. package/src/lib/process-execution-broker/macos-mcp-launcher.c +1497 -0
  198. package/src/lib/process-execution-broker/mcp-fd-entry-bootstrap.js +37 -0
  199. package/src/lib/process-execution-broker/platform-sandbox.js +2330 -279
  200. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  201. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  202. package/src/lib/process-execution-broker/windows-sandbox.cs +50 -4
  203. package/src/lib/public-ide-capability-manifest.js +3 -0
  204. package/src/lib/remote-approval-bridge.js +123 -17
  205. package/src/lib/remote-membership-authority.js +632 -0
  206. package/src/lib/remote-membership-coordinator.js +2409 -0
  207. package/src/lib/remote-membership-host-store.js +1750 -0
  208. package/src/lib/scoped-permission-store.js +369 -0
  209. package/src/lib/session-anti-rollback-anchor.js +262 -5
  210. package/src/lib/sub-agent-context.js +4 -0
  211. package/src/lib/workflow-definition-contract.js +149 -0
  212. package/src/repl/agent-repl.js +41 -7
  213. package/src/repl/chat-repl.js +2 -0
  214. package/src/repl/clipboard-image.js +735 -16
  215. package/src/repl/ide-status.js +1 -1
  216. package/src/repl/prompt-interactions.js +59 -6
  217. package/src/runtime/agent-core.js +83 -3
  218. package/src/runtime/headless-runner.js +30 -25
  219. package/src/runtime/headless-stream.js +32 -25
  220. package/src/workers/background-agent-keeper.js +498 -0
  221. package/src/workers/background-agent-worker.js +358 -13
  222. package/src/workers/background-turn-bootstrap.js +143 -0
  223. package/src/assets/web-panel/assets/MarkdownRenderer-yySN5YNM.js +0 -1
  224. package/src/assets/web-panel/assets/MobileProjects-DlPwcCbq.js +0 -1
  225. package/src/assets/web-panel/assets/Projects-CMolGhae.js +0 -1
  226. package/src/assets/web-panel/assets/devWarning-S1ld2LwU.js +0 -1
  227. package/src/assets/web-panel/assets/index-Drz6yq0m.js +0 -1
  228. package/src/assets/web-panel/assets/index-uWiUs7S_.js +0 -1
@@ -0,0 +1,1567 @@
1
+ /**
2
+ * Bounded retrieval for marketplace signature, public-key, and SBOM
3
+ * documents. Registry metadata is still an assertion; this module proves
4
+ * only the exact remote document digests and signing-key SPKI fingerprint.
5
+ * The detached signature is verified against plugin manifest bytes later by
6
+ * the installer, before the plugin becomes active.
7
+ */
8
+
9
+ import crypto from "node:crypto";
10
+ import dns from "node:dns/promises";
11
+ import fs from "node:fs";
12
+ import net from "node:net";
13
+ import os from "node:os";
14
+ import path from "node:path";
15
+ import { getElectronUserDataDir } from "../paths.js";
16
+
17
+ export const PLUGIN_MARKETPLACE_REMOTE_ARTIFACT_EVIDENCE_SCHEMA =
18
+ "cc-plugin-marketplace-remote-artifact-evidence/v1";
19
+ export const MAX_MARKETPLACE_ARTIFACT_REDIRECTS = 3;
20
+ export const DEFAULT_MARKETPLACE_ARTIFACT_TIMEOUT_MS = 15_000;
21
+ export const MAX_MARKETPLACE_ARTIFACT_URL_LENGTH = 4096;
22
+ export const MARKETPLACE_REMOTE_ARTIFACT_LIMITS = Object.freeze({
23
+ signature: 16 * 1024,
24
+ publicKey: 64 * 1024,
25
+ sbom: 16 * 1024 * 1024,
26
+ });
27
+
28
+ const SHA256_RE = /^[a-f0-9]{64}$/;
29
+ const MAX_UNBOUND_CACHE_CANDIDATES = 64;
30
+ const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
31
+
32
+ export const marketplaceRemoteArtifactDependencies = {
33
+ fetch: (...args) => globalThis.fetch(...args),
34
+ resolveHostname: (hostname) =>
35
+ dns.lookup(hostname, { all: true, verbatim: true }),
36
+ };
37
+
38
+ export class MarketplaceRemoteArtifactError extends Error {
39
+ constructor(code, message, { cacheFallbackAllowed = false, cause } = {}) {
40
+ super(message, cause ? { cause } : undefined);
41
+ this.name = "MarketplaceRemoteArtifactError";
42
+ this.code = code;
43
+ this.cacheFallbackAllowed = cacheFallbackAllowed;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Fetch every complete remote-artifact declaration for one registry entry.
49
+ * Returns null when neither a signature bundle nor an SBOM was requested.
50
+ *
51
+ * The returned authority is safe to persist at
52
+ * `sourceMetadata.catalogAuthority.remoteArtifactEvidence`. Signature files
53
+ * and their temporary path deliberately live outside that authority.
54
+ */
55
+ export async function fetchPluginMarketplaceRemoteArtifacts(options = {}) {
56
+ const signatureSpec = objectValue(options.signature);
57
+ const sbomSpec = objectValue(options.sbom);
58
+ const signatureRequested = artifactWasRequested(signatureSpec, [
59
+ "url",
60
+ "publicKeyUrl",
61
+ ]);
62
+ const sbomRequested = artifactWasRequested(sbomSpec, ["url"]);
63
+ if (!signatureRequested && !sbomRequested) return null;
64
+
65
+ const registryUrl = assertRegistryUrlSafe(
66
+ options.registryUrl,
67
+ options.allowInsecure === true,
68
+ );
69
+ const registryOrigin = registryUrl.origin;
70
+ const common = {
71
+ token: normalizeBearerToken(options.token),
72
+ authorizationOrigin: registryOrigin,
73
+ registryOrigin,
74
+ allowInsecure: options.allowInsecure === true,
75
+ cacheDir: options.cacheDir,
76
+ timeoutMs: options.timeoutMs,
77
+ allowCache: options.allowCache,
78
+ offline: options.offline,
79
+ fetchImpl: options.fetchImpl,
80
+ resolveHostname: options.resolveHostname,
81
+ };
82
+
83
+ let normalizedSignature = null;
84
+ if (signatureRequested) {
85
+ normalizedSignature = normalizeSignatureSpec(signatureSpec);
86
+ }
87
+ let normalizedSbom = null;
88
+ if (sbomRequested) normalizedSbom = normalizeSbomSpec(sbomSpec);
89
+
90
+ const signaturePromise = normalizedSignature
91
+ ? fetchMarketplaceRemoteArtifact({
92
+ ...common,
93
+ kind: "signature",
94
+ url: normalizedSignature.url,
95
+ expectedSha256: normalizedSignature.sha256,
96
+ })
97
+ : Promise.resolve(null);
98
+ const publicKeyPromise = normalizedSignature
99
+ ? fetchMarketplaceRemoteArtifact({
100
+ ...common,
101
+ kind: "publicKey",
102
+ url: normalizedSignature.publicKeyUrl,
103
+ expectedSha256: normalizedSignature.publicKeyDocumentSha256,
104
+ validateBytes: (bytes) =>
105
+ verifyPublicKeyDocument(
106
+ bytes,
107
+ normalizedSignature.publicKeySha256,
108
+ normalizedSignature.algorithm,
109
+ ),
110
+ })
111
+ : Promise.resolve(null);
112
+ const sbomPromise = normalizedSbom
113
+ ? fetchMarketplaceRemoteArtifact({
114
+ ...common,
115
+ kind: "sbom",
116
+ url: normalizedSbom.url,
117
+ expectedSha256: normalizedSbom.sha256,
118
+ })
119
+ : Promise.resolve(null);
120
+
121
+ const [signatureArtifact, publicKeyArtifact, sbomArtifact] =
122
+ await Promise.all([signaturePromise, publicKeyPromise, sbomPromise]);
123
+
124
+ const signatureAuthority = signatureArtifact
125
+ ? {
126
+ status: "fetched",
127
+ url: signatureArtifact.url,
128
+ signatureSha256: signatureArtifact.sha256,
129
+ bytes: signatureArtifact.bytes.length,
130
+ fromCache: signatureArtifact.fromCache,
131
+ publicKey: {
132
+ url: publicKeyArtifact.url,
133
+ documentSha256: publicKeyArtifact.sha256,
134
+ spkiSha256: publicKeyArtifact.validation.spkiSha256,
135
+ bytes: publicKeyArtifact.bytes.length,
136
+ fromCache: publicKeyArtifact.fromCache,
137
+ },
138
+ }
139
+ : null;
140
+ const sbomAuthority = sbomArtifact
141
+ ? {
142
+ status: "digest-verified",
143
+ url: sbomArtifact.url,
144
+ format: normalizedSbom.format,
145
+ expectedDocumentSha256: normalizedSbom.sha256,
146
+ documentSha256: sbomArtifact.sha256,
147
+ bytes: sbomArtifact.bytes.length,
148
+ fromCache: sbomArtifact.fromCache,
149
+ }
150
+ : null;
151
+ const claims = {
152
+ publisherIdentityVerified: false,
153
+ signatureBytesFetched: Boolean(signatureArtifact),
154
+ publicKeyFingerprintVerified: Boolean(publicKeyArtifact),
155
+ manifestSignatureVerified: false,
156
+ sbomDocumentDigestVerified: Boolean(sbomArtifact),
157
+ sbomPayloadCompared: false,
158
+ };
159
+ const authorityWithoutDigest = {
160
+ schemaVersion: PLUGIN_MARKETPLACE_REMOTE_ARTIFACT_EVIDENCE_SCHEMA,
161
+ status: "verified",
162
+ registryOrigin,
163
+ signature: signatureAuthority,
164
+ sbom: sbomAuthority,
165
+ claims,
166
+ };
167
+ const authority = {
168
+ ...authorityWithoutDigest,
169
+ evidenceDigest: sha256Canonical(authorityWithoutDigest),
170
+ };
171
+
172
+ let materialized = null;
173
+ try {
174
+ materialized = signatureArtifact
175
+ ? materializeSignatureBundle(
176
+ signatureArtifact.bytes,
177
+ publicKeyArtifact.bytes,
178
+ )
179
+ : null;
180
+ return {
181
+ authority,
182
+ signatureFile: materialized?.signatureFile || null,
183
+ publicKeyFile: materialized?.publicKeyFile || null,
184
+ sbomBytes: sbomArtifact?.bytes || null,
185
+ cleanup: materialized?.cleanup || (() => {}),
186
+ };
187
+ } catch (error) {
188
+ materialized?.cleanup();
189
+ throw artifactError(
190
+ "TEMP_MATERIALIZATION_FAILED",
191
+ "could not materialize verified marketplace signature files",
192
+ { error, token: common.token },
193
+ );
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Fetch one bounded artifact with manual redirects and immutable offline cache
199
+ * fallback. A cache entry is addressed by both the full request URL hash and
200
+ * the fetched content digest; every read recomputes and validates that digest.
201
+ */
202
+ export async function fetchMarketplaceRemoteArtifact(options = {}) {
203
+ const kind = normalizeArtifactKind(options.kind);
204
+ const maxBytes = MARKETPLACE_REMOTE_ARTIFACT_LIMITS[kind];
205
+ const registryOrigin = normalizeTrustedRegistryOrigin(
206
+ options.registryOrigin,
207
+ options.allowInsecure === true,
208
+ );
209
+ const url = assertMarketplaceArtifactUrlSafe(options.url, {
210
+ registryOrigin,
211
+ allowLoopbackHttp: options.allowLoopbackHttp !== false,
212
+ label: kind,
213
+ });
214
+ const token = normalizeBearerToken(options.token);
215
+ const authorizationOrigin = normalizeAuthorizationOrigin(
216
+ options.authorizationOrigin,
217
+ url,
218
+ token,
219
+ options.allowInsecure,
220
+ );
221
+ const expectedSha256 = normalizeSha256(
222
+ options.expectedSha256,
223
+ `${kind} expected SHA-256`,
224
+ { optional: true },
225
+ );
226
+ const timeoutMs = normalizePositiveInteger(
227
+ options.timeoutMs,
228
+ DEFAULT_MARKETPLACE_ARTIFACT_TIMEOUT_MS,
229
+ 1,
230
+ 120_000,
231
+ "artifact timeout",
232
+ );
233
+ const allowCache = options.allowCache !== false;
234
+ const cacheDir = marketplaceArtifactCacheRoot(options.cacheDir);
235
+ const validateBytes =
236
+ typeof options.validateBytes === "function" ? options.validateBytes : null;
237
+ const hasCustomFetch = typeof options.fetchImpl === "function";
238
+ const resolveHostname =
239
+ typeof options.resolveHostname === "function"
240
+ ? options.resolveHostname
241
+ : hasCustomFetch
242
+ ? null
243
+ : marketplaceRemoteArtifactDependencies.resolveHostname;
244
+
245
+ if (options.offline === true) {
246
+ const cached = await readCachedArtifact({
247
+ url,
248
+ kind,
249
+ maxBytes,
250
+ expectedSha256,
251
+ cacheDir,
252
+ validateBytes,
253
+ });
254
+ if (cached) return cached;
255
+ throw artifactError(
256
+ "OFFLINE_CACHE_MISS",
257
+ `${kind} artifact is unavailable in the verified immutable cache`,
258
+ );
259
+ }
260
+
261
+ let networkError = null;
262
+ try {
263
+ const fetched = await fetchArtifactFromNetwork({
264
+ url,
265
+ kind,
266
+ maxBytes,
267
+ expectedSha256,
268
+ token,
269
+ authorizationOrigin,
270
+ registryOrigin,
271
+ timeoutMs,
272
+ allowLoopbackHttp: options.allowLoopbackHttp !== false,
273
+ fetchImpl:
274
+ options.fetchImpl || marketplaceRemoteArtifactDependencies.fetch,
275
+ resolveHostname,
276
+ validateBytes,
277
+ });
278
+ if (allowCache) {
279
+ writeImmutableCache(
280
+ url,
281
+ fetched.bytes,
282
+ fetched.sha256,
283
+ cacheDir,
284
+ maxBytes,
285
+ );
286
+ }
287
+ return fetched;
288
+ } catch (error) {
289
+ networkError = normalizeFetchError(error, { kind, token });
290
+ }
291
+
292
+ if (allowCache && networkError.cacheFallbackAllowed) {
293
+ try {
294
+ const cached = await readCachedArtifact({
295
+ url,
296
+ kind,
297
+ maxBytes,
298
+ expectedSha256,
299
+ cacheDir,
300
+ validateBytes,
301
+ });
302
+ if (cached) return cached;
303
+ } catch (cacheError) {
304
+ throw artifactError(
305
+ networkError.code,
306
+ `${networkError.message}; verified immutable cache rejected: ${redactMarketplaceArtifactError(
307
+ cacheError?.message || cacheError,
308
+ { token },
309
+ )}`,
310
+ { error: networkError },
311
+ );
312
+ }
313
+ }
314
+ throw networkError;
315
+ }
316
+
317
+ export function assertMarketplaceArtifactUrlSafe(
318
+ value,
319
+ {
320
+ registryOrigin = null,
321
+ allowLoopbackHttp = true,
322
+ label = "marketplace artifact",
323
+ allowInsecureRegistry = false,
324
+ allowPrivateNetwork = false,
325
+ } = {},
326
+ ) {
327
+ const raw = String(value || "");
328
+ if (raw.length > MAX_MARKETPLACE_ARTIFACT_URL_LENGTH) {
329
+ throw artifactError(
330
+ "URL_TOO_LONG",
331
+ `${label} URL exceeds ${MAX_MARKETPLACE_ARTIFACT_URL_LENGTH} characters`,
332
+ );
333
+ }
334
+ let url;
335
+ try {
336
+ url = new URL(raw);
337
+ } catch {
338
+ throw artifactError("INVALID_URL", `${label} URL is invalid`);
339
+ }
340
+ if (url.href.length > MAX_MARKETPLACE_ARTIFACT_URL_LENGTH) {
341
+ throw artifactError(
342
+ "URL_TOO_LONG",
343
+ `${label} URL exceeds ${MAX_MARKETPLACE_ARTIFACT_URL_LENGTH} characters`,
344
+ );
345
+ }
346
+ if (url.username || url.password) {
347
+ throw artifactError(
348
+ "URL_CREDENTIALS_REJECTED",
349
+ `${label} URL must not contain credentials`,
350
+ );
351
+ }
352
+ const trustedOrigin = registryOrigin
353
+ ? normalizeOriginString(registryOrigin, "registry origin")
354
+ : null;
355
+ const sameRegistryOrigin = Boolean(
356
+ trustedOrigin && url.origin === trustedOrigin,
357
+ );
358
+ if (url.protocol === "https:") {
359
+ if (
360
+ !sameRegistryOrigin &&
361
+ !allowPrivateNetwork &&
362
+ isRestrictedNetworkHostname(url.hostname)
363
+ ) {
364
+ throw artifactError(
365
+ "UNSAFE_NETWORK_TARGET",
366
+ `${label} URL must not target a private or local network address`,
367
+ );
368
+ }
369
+ if (trustedOrigin && !sameRegistryOrigin) {
370
+ // Registry metadata is untrusted. Keeping artifact requests on the exact
371
+ // user-selected registry origin avoids granting it a second network
372
+ // authority (and removes a DNS-rebinding window on cross-origin fetches).
373
+ throw artifactError(
374
+ "CROSS_ORIGIN_ARTIFACT_REJECTED",
375
+ `${label} URL must use the selected registry origin`,
376
+ );
377
+ }
378
+ return url;
379
+ }
380
+ if (url.protocol !== "http:") {
381
+ throw artifactError(
382
+ "UNSAFE_URL",
383
+ `${label} URL must use HTTPS or loopback HTTP`,
384
+ );
385
+ }
386
+ if (sameRegistryOrigin) return url;
387
+ if (!trustedOrigin && allowLoopbackHttp && isLoopbackHostname(url.hostname)) {
388
+ return url;
389
+ }
390
+ if (!trustedOrigin && allowInsecureRegistry === true) return url;
391
+ throw artifactError(
392
+ "UNSAFE_URL",
393
+ `${label} URL must use HTTPS or loopback HTTP`,
394
+ );
395
+ }
396
+
397
+ export function sanitizeMarketplaceArtifactUrl(value) {
398
+ try {
399
+ const url = new URL(String(value || ""));
400
+ url.username = "";
401
+ url.password = "";
402
+ url.search = "";
403
+ url.hash = "";
404
+ return url.toString();
405
+ } catch {
406
+ return "[URL]";
407
+ }
408
+ }
409
+
410
+ export function redactMarketplaceArtifactError(value, { token = null } = {}) {
411
+ let text = String(value == null ? "" : value).replace(/\p{Cc}/gu, " ");
412
+ if (token) text = text.split(String(token)).join("[REDACTED]");
413
+ text = text
414
+ .replace(/Bearer\s+[^\s,;]+/gi, "Bearer [REDACTED]")
415
+ .replace(
416
+ /\b(authorization|access_token|token|password|secret)=([^\s,;&]+)/gi,
417
+ "$1=[REDACTED]",
418
+ )
419
+ .replace(/https?:\/\/[^\s"'<>]+/gi, (match) =>
420
+ sanitizeMarketplaceArtifactUrl(match.replace(/[),.;]+$/g, "")),
421
+ );
422
+ return text.trim().slice(0, 2048) || "unknown error";
423
+ }
424
+
425
+ export function marketplaceRemoteArtifactCachePath(
426
+ url,
427
+ contentSha256,
428
+ cacheDir,
429
+ ) {
430
+ const parsed = assertMarketplaceArtifactUrlSafe(url, {
431
+ allowInsecureRegistry: true,
432
+ allowPrivateNetwork: true,
433
+ });
434
+ const digest = normalizeSha256(contentSha256, "cache content SHA-256");
435
+ const urlHash = sha256(parsed.href);
436
+ return path.join(
437
+ marketplaceArtifactCacheRoot(cacheDir),
438
+ urlHash,
439
+ `${digest}.bin`,
440
+ );
441
+ }
442
+
443
+ async function fetchArtifactFromNetwork({
444
+ url,
445
+ kind,
446
+ maxBytes,
447
+ expectedSha256,
448
+ token,
449
+ authorizationOrigin,
450
+ registryOrigin,
451
+ timeoutMs,
452
+ allowLoopbackHttp,
453
+ fetchImpl,
454
+ resolveHostname,
455
+ validateBytes,
456
+ }) {
457
+ const controller = new AbortController();
458
+ let timedOut = false;
459
+ const timer = setTimeout(() => {
460
+ timedOut = true;
461
+ controller.abort();
462
+ }, timeoutMs);
463
+ let current = url;
464
+ let redirectCount = 0;
465
+ try {
466
+ while (true) {
467
+ await assertMarketplaceArtifactNetworkTargetSafe(current, {
468
+ kind,
469
+ registryOrigin,
470
+ resolveHostname,
471
+ });
472
+ const headers = { Accept: acceptHeader(kind) };
473
+ if (token && current.origin === authorizationOrigin) {
474
+ headers.Authorization = `Bearer ${token}`;
475
+ }
476
+ let response;
477
+ try {
478
+ response = await fetchImpl(current.href, {
479
+ method: "GET",
480
+ headers,
481
+ redirect: "manual",
482
+ signal: controller.signal,
483
+ });
484
+ } catch (error) {
485
+ if (timedOut) {
486
+ throw artifactError(
487
+ "TIMEOUT",
488
+ `${kind} artifact fetch timed out after ${timeoutMs}ms`,
489
+ { cacheFallbackAllowed: true, error, token },
490
+ );
491
+ }
492
+ throw artifactError(
493
+ "FETCH_FAILED",
494
+ `${kind} artifact fetch failed: ${redactMarketplaceArtifactError(
495
+ error?.message || error,
496
+ { token },
497
+ )}`,
498
+ { cacheFallbackAllowed: true, error, token },
499
+ );
500
+ }
501
+
502
+ if (REDIRECT_STATUSES.has(response.status)) {
503
+ if (redirectCount >= MAX_MARKETPLACE_ARTIFACT_REDIRECTS) {
504
+ await cancelResponseBody(response);
505
+ throw artifactError(
506
+ "REDIRECT_LIMIT_EXCEEDED",
507
+ `${kind} artifact exceeded ${MAX_MARKETPLACE_ARTIFACT_REDIRECTS} redirects`,
508
+ );
509
+ }
510
+ const location = response.headers?.get?.("location");
511
+ if (!location) {
512
+ await cancelResponseBody(response);
513
+ throw artifactError(
514
+ "INVALID_REDIRECT",
515
+ `${kind} artifact redirect omitted Location`,
516
+ );
517
+ }
518
+ let next;
519
+ try {
520
+ next = new URL(location, current);
521
+ } catch {
522
+ await cancelResponseBody(response);
523
+ throw artifactError(
524
+ "INVALID_REDIRECT",
525
+ `${kind} artifact redirect Location is invalid`,
526
+ );
527
+ }
528
+ await cancelResponseBody(response);
529
+ current = assertMarketplaceArtifactUrlSafe(next.href, {
530
+ registryOrigin,
531
+ allowLoopbackHttp,
532
+ label: `${kind} redirect`,
533
+ });
534
+ redirectCount += 1;
535
+ continue;
536
+ }
537
+
538
+ if (!response.ok) {
539
+ const status = Number.isInteger(response.status)
540
+ ? response.status
541
+ : "unknown";
542
+ const statusText = redactMarketplaceArtifactError(
543
+ response.statusText || "",
544
+ { token },
545
+ );
546
+ await cancelResponseBody(response);
547
+ throw artifactError(
548
+ "HTTP_ERROR",
549
+ `${kind} artifact fetch failed: HTTP ${status}${
550
+ statusText && statusText !== "unknown error" ? ` ${statusText}` : ""
551
+ }`,
552
+ {
553
+ cacheFallbackAllowed:
554
+ Number.isInteger(response.status) && response.status >= 500,
555
+ },
556
+ );
557
+ }
558
+
559
+ let bytes;
560
+ try {
561
+ bytes = await readBoundedResponse(response, {
562
+ kind,
563
+ maxBytes,
564
+ });
565
+ } catch (error) {
566
+ if (timedOut) {
567
+ throw artifactError(
568
+ "TIMEOUT",
569
+ `${kind} artifact fetch timed out after ${timeoutMs}ms`,
570
+ { cacheFallbackAllowed: true, error, token },
571
+ );
572
+ }
573
+ throw error;
574
+ }
575
+ const digest = sha256(bytes);
576
+ if (expectedSha256 && digest !== expectedSha256) {
577
+ throw artifactError(
578
+ "DIGEST_MISMATCH",
579
+ `${kind} artifact SHA-256 mismatch (expected ${expectedSha256}, got ${digest})`,
580
+ );
581
+ }
582
+ const validation = await runByteValidator(validateBytes, bytes, kind);
583
+ return {
584
+ kind,
585
+ url: sanitizeMarketplaceArtifactUrl(url.href),
586
+ finalUrl: sanitizeMarketplaceArtifactUrl(current.href),
587
+ sha256: digest,
588
+ bytes,
589
+ fromCache: false,
590
+ redirectCount,
591
+ validation,
592
+ };
593
+ }
594
+ } finally {
595
+ clearTimeout(timer);
596
+ }
597
+ }
598
+
599
+ async function readBoundedResponse(response, { kind, maxBytes }) {
600
+ const declared = response.headers?.get?.("content-length");
601
+ if (declared != null && String(declared).trim() !== "") {
602
+ const normalized = String(declared).trim();
603
+ if (!/^\d+$/.test(normalized)) {
604
+ await cancelResponseBody(response);
605
+ throw artifactError(
606
+ "INVALID_CONTENT_LENGTH",
607
+ `${kind} artifact returned an invalid Content-Length`,
608
+ );
609
+ }
610
+ if (BigInt(normalized) > BigInt(maxBytes)) {
611
+ await cancelResponseBody(response);
612
+ throw artifactError(
613
+ "CONTENT_TOO_LARGE",
614
+ `${kind} artifact Content-Length exceeds ${maxBytes} bytes`,
615
+ );
616
+ }
617
+ }
618
+
619
+ if (!response.body) return Buffer.alloc(0);
620
+ const chunks = [];
621
+ let total = 0;
622
+ const append = (value) => {
623
+ const chunk = Buffer.from(value);
624
+ total += chunk.length;
625
+ if (total > maxBytes) {
626
+ throw artifactError(
627
+ "CONTENT_TOO_LARGE",
628
+ `${kind} artifact body exceeds ${maxBytes} bytes`,
629
+ );
630
+ }
631
+ chunks.push(chunk);
632
+ };
633
+
634
+ if (typeof response.body.getReader === "function") {
635
+ const reader = response.body.getReader();
636
+ try {
637
+ while (true) {
638
+ const { done, value } = await reader.read();
639
+ if (done) break;
640
+ append(value);
641
+ }
642
+ } catch (error) {
643
+ try {
644
+ await reader.cancel();
645
+ } catch {
646
+ // The read or size error remains authoritative.
647
+ }
648
+ throw error;
649
+ } finally {
650
+ reader.releaseLock?.();
651
+ }
652
+ } else if (Symbol.asyncIterator in Object(response.body)) {
653
+ try {
654
+ for await (const chunk of response.body) append(chunk);
655
+ } catch (error) {
656
+ await cancelResponseBody(response);
657
+ throw error;
658
+ }
659
+ } else {
660
+ await cancelResponseBody(response);
661
+ throw artifactError(
662
+ "UNREADABLE_BODY",
663
+ `${kind} artifact response body is not readable`,
664
+ );
665
+ }
666
+ return Buffer.concat(chunks, total);
667
+ }
668
+
669
+ async function cancelResponseBody(response) {
670
+ const body = response?.body;
671
+ if (!body) return;
672
+ try {
673
+ if (typeof body.cancel === "function") {
674
+ await body.cancel();
675
+ return;
676
+ }
677
+ if (typeof body.destroy === "function") {
678
+ body.destroy();
679
+ return;
680
+ }
681
+ if (typeof body.getReader === "function") {
682
+ const reader = body.getReader();
683
+ try {
684
+ await reader.cancel();
685
+ } finally {
686
+ reader.releaseLock?.();
687
+ }
688
+ }
689
+ } catch {
690
+ // A protocol/policy error is authoritative even if body cancellation fails.
691
+ }
692
+ }
693
+
694
+ async function readCachedArtifact({
695
+ url,
696
+ kind,
697
+ maxBytes,
698
+ expectedSha256,
699
+ cacheDir,
700
+ validateBytes,
701
+ }) {
702
+ const urlHash = sha256(url.href);
703
+ const directory = path.join(cacheDir, urlHash);
704
+ if (expectedSha256) {
705
+ const cachePath = path.join(directory, `${expectedSha256}.bin`);
706
+ if (!fs.existsSync(cachePath)) return null;
707
+ assertCacheReadDirectorySafe(directory, kind);
708
+ const result = await readAndVerifyCacheFile(cachePath, {
709
+ kind,
710
+ maxBytes,
711
+ expectedSha256,
712
+ validateBytes,
713
+ });
714
+ return cachedResult(url, result, kind);
715
+ }
716
+ if (!fs.existsSync(directory)) return null;
717
+ assertCacheReadDirectorySafe(directory, kind);
718
+ let entries;
719
+ try {
720
+ entries = fs
721
+ .readdirSync(directory, { withFileTypes: true })
722
+ .filter(
723
+ (entry) => entry.isFile() && /^[a-f0-9]{64}\.bin$/.test(entry.name),
724
+ )
725
+ .sort((a, b) => a.name.localeCompare(b.name));
726
+ } catch (error) {
727
+ throw artifactError(
728
+ "CACHE_READ_FAILED",
729
+ `${kind} artifact immutable cache could not be read`,
730
+ { error },
731
+ );
732
+ }
733
+ if (entries.length > MAX_UNBOUND_CACHE_CANDIDATES) {
734
+ throw artifactError(
735
+ "CACHE_AMBIGUOUS",
736
+ `${kind} artifact immutable cache has too many unbound candidates`,
737
+ );
738
+ }
739
+ const matches = [];
740
+ for (const entry of entries) {
741
+ try {
742
+ const result = await readAndVerifyCacheFile(
743
+ path.join(directory, entry.name),
744
+ {
745
+ kind,
746
+ maxBytes,
747
+ expectedSha256: entry.name.slice(0, 64),
748
+ validateBytes,
749
+ },
750
+ );
751
+ matches.push(result);
752
+ } catch {
753
+ // Invalid, corrupted, or validator-mismatched unbound entries cannot be
754
+ // cache authorities. A unique remaining valid entry is still usable.
755
+ }
756
+ }
757
+ if (matches.length === 0) return null;
758
+ if (matches.length !== 1) {
759
+ throw artifactError(
760
+ "CACHE_AMBIGUOUS",
761
+ `${kind} artifact immutable cache has multiple valid unbound candidates`,
762
+ );
763
+ }
764
+ return cachedResult(url, matches[0], kind);
765
+ }
766
+
767
+ async function readAndVerifyCacheFile(
768
+ cachePath,
769
+ { kind, maxBytes, expectedSha256, validateBytes },
770
+ ) {
771
+ const bytes = readBoundedRegularFile(cachePath, {
772
+ maxBytes,
773
+ inspectionCode: "CACHE_READ_FAILED",
774
+ invalidCode: "CACHE_INVALID",
775
+ label: `${kind} artifact immutable cache entry`,
776
+ });
777
+ const digest = sha256(bytes);
778
+ if (digest !== expectedSha256) {
779
+ throw artifactError(
780
+ "CACHE_DIGEST_MISMATCH",
781
+ `${kind} artifact immutable cache digest mismatch`,
782
+ );
783
+ }
784
+ const validation = await runByteValidator(validateBytes, bytes, kind);
785
+ return { bytes, sha256: digest, validation };
786
+ }
787
+
788
+ function cachedResult(url, result, kind) {
789
+ return {
790
+ kind,
791
+ url: sanitizeMarketplaceArtifactUrl(url.href),
792
+ finalUrl: sanitizeMarketplaceArtifactUrl(url.href),
793
+ sha256: result.sha256,
794
+ bytes: result.bytes,
795
+ fromCache: true,
796
+ redirectCount: 0,
797
+ validation: result.validation,
798
+ };
799
+ }
800
+
801
+ function writeImmutableCache(url, bytes, digest, cacheDir, maxBytes) {
802
+ const cachePath = marketplaceRemoteArtifactCachePath(
803
+ url.href,
804
+ digest,
805
+ cacheDir,
806
+ );
807
+ const directory = path.dirname(cachePath);
808
+ let tempPath = null;
809
+ let tempFd = null;
810
+ try {
811
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
812
+ assertCacheDirectorySafe(directory);
813
+ tempPath = path.join(
814
+ directory,
815
+ `.tmp-${process.pid}-${crypto.randomBytes(12).toString("hex")}`,
816
+ );
817
+ tempFd = fs.openSync(tempPath, "wx", 0o600);
818
+ fs.writeFileSync(tempFd, bytes);
819
+ fs.fsyncSync(tempFd);
820
+ fs.closeSync(tempFd);
821
+ tempFd = null;
822
+
823
+ for (let attempt = 0; attempt < 4; attempt += 1) {
824
+ try {
825
+ fs.linkSync(tempPath, cachePath);
826
+ fsyncDirectoryBestEffort(directory);
827
+ return;
828
+ } catch (error) {
829
+ if (error?.code !== "EEXIST") throw error;
830
+ }
831
+
832
+ const existing = inspectExistingCacheForPublish(cachePath, {
833
+ bytes,
834
+ digest,
835
+ maxBytes,
836
+ });
837
+ if (existing === "equal") return;
838
+ if (existing !== "truncated") {
839
+ throw artifactError(
840
+ "CACHE_WRITE_CONFLICT",
841
+ "immutable marketplace artifact cache content conflict",
842
+ );
843
+ }
844
+ removeUnchangedTruncatedCacheEntry(cachePath);
845
+ }
846
+ throw artifactError(
847
+ "CACHE_WRITE_CONFLICT",
848
+ "immutable marketplace artifact cache entry could not be published",
849
+ );
850
+ } catch (error) {
851
+ if (error instanceof MarketplaceRemoteArtifactError) throw error;
852
+ throw artifactError(
853
+ "CACHE_WRITE_FAILED",
854
+ "immutable marketplace artifact cache entry could not be published",
855
+ { error },
856
+ );
857
+ } finally {
858
+ if (tempFd != null) {
859
+ try {
860
+ fs.closeSync(tempFd);
861
+ } catch {
862
+ // The cache publication error remains authoritative.
863
+ }
864
+ }
865
+ if (tempPath) {
866
+ try {
867
+ fs.unlinkSync(tempPath);
868
+ } catch (error) {
869
+ if (error?.code !== "ENOENT") {
870
+ // Best-effort cleanup; the hidden temp is never a cache candidate.
871
+ }
872
+ }
873
+ }
874
+ }
875
+ }
876
+
877
+ function assertCacheDirectorySafe(directory) {
878
+ let stat;
879
+ try {
880
+ stat = fs.lstatSync(directory);
881
+ } catch (error) {
882
+ throw artifactError(
883
+ "CACHE_WRITE_FAILED",
884
+ "immutable marketplace artifact cache directory could not be inspected",
885
+ { error },
886
+ );
887
+ }
888
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
889
+ throw artifactError(
890
+ "CACHE_WRITE_FAILED",
891
+ "immutable marketplace artifact cache directory is not a directory",
892
+ );
893
+ }
894
+ }
895
+
896
+ function assertCacheReadDirectorySafe(directory, kind) {
897
+ let stat;
898
+ try {
899
+ stat = fs.lstatSync(directory);
900
+ } catch (error) {
901
+ throw artifactError(
902
+ "CACHE_READ_FAILED",
903
+ `${kind} artifact immutable cache directory could not be inspected`,
904
+ { error },
905
+ );
906
+ }
907
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
908
+ throw artifactError(
909
+ "CACHE_INVALID",
910
+ `${kind} artifact immutable cache directory is not a directory`,
911
+ );
912
+ }
913
+ }
914
+
915
+ function inspectExistingCacheForPublish(
916
+ cachePath,
917
+ { bytes, digest, maxBytes },
918
+ ) {
919
+ const existing = readBoundedRegularFile(cachePath, {
920
+ maxBytes,
921
+ inspectionCode: "CACHE_WRITE_CONFLICT",
922
+ invalidCode: "CACHE_WRITE_CONFLICT",
923
+ label: "immutable marketplace artifact cache entry",
924
+ });
925
+ if (existing.length === bytes.length) {
926
+ return sha256(existing) === digest && existing.equals(bytes)
927
+ ? "equal"
928
+ : "conflict";
929
+ }
930
+ return existing.length < bytes.length ? "truncated" : "conflict";
931
+ }
932
+
933
+ function removeUnchangedTruncatedCacheEntry(cachePath) {
934
+ let before;
935
+ let after;
936
+ try {
937
+ before = fs.lstatSync(cachePath, { bigint: true });
938
+ after = fs.lstatSync(cachePath, { bigint: true });
939
+ } catch (error) {
940
+ if (error?.code === "ENOENT") return;
941
+ throw artifactError(
942
+ "CACHE_WRITE_CONFLICT",
943
+ "immutable marketplace artifact cache entry could not be repaired",
944
+ { error },
945
+ );
946
+ }
947
+ if (
948
+ !before.isFile() ||
949
+ before.isSymbolicLink() ||
950
+ !after.isFile() ||
951
+ after.isSymbolicLink() ||
952
+ before.dev !== after.dev ||
953
+ before.ino !== after.ino ||
954
+ before.size !== after.size ||
955
+ before.mtimeNs !== after.mtimeNs
956
+ ) {
957
+ throw artifactError(
958
+ "CACHE_WRITE_CONFLICT",
959
+ "immutable marketplace artifact cache entry changed during repair",
960
+ );
961
+ }
962
+ try {
963
+ fs.unlinkSync(cachePath);
964
+ } catch (error) {
965
+ if (error?.code !== "ENOENT") {
966
+ throw artifactError(
967
+ "CACHE_WRITE_CONFLICT",
968
+ "immutable marketplace artifact cache entry could not be repaired",
969
+ { error },
970
+ );
971
+ }
972
+ }
973
+ }
974
+
975
+ function readBoundedRegularFile(
976
+ filePath,
977
+ { maxBytes, inspectionCode, invalidCode, label },
978
+ ) {
979
+ let descriptor = null;
980
+ try {
981
+ const linkStat = fs.lstatSync(filePath);
982
+ if (!linkStat.isFile() || linkStat.isSymbolicLink()) {
983
+ throw artifactError(invalidCode, `${label} is not a regular file`);
984
+ }
985
+ if (linkStat.size > maxBytes) {
986
+ throw artifactError(invalidCode, `${label} exceeds ${maxBytes} bytes`);
987
+ }
988
+ const noFollow = fs.constants.O_NOFOLLOW || 0;
989
+ descriptor = fs.openSync(filePath, fs.constants.O_RDONLY | noFollow);
990
+ const fileStat = fs.fstatSync(descriptor);
991
+ if (!fileStat.isFile() || fileStat.size > maxBytes) {
992
+ throw artifactError(
993
+ invalidCode,
994
+ `${label} is not a bounded regular file`,
995
+ );
996
+ }
997
+ const chunks = [];
998
+ let total = 0;
999
+ while (total <= maxBytes) {
1000
+ const chunk = Buffer.allocUnsafe(
1001
+ Math.min(64 * 1024, maxBytes + 1 - total),
1002
+ );
1003
+ const count = fs.readSync(descriptor, chunk, 0, chunk.length, null);
1004
+ if (count === 0) break;
1005
+ chunks.push(chunk.subarray(0, count));
1006
+ total += count;
1007
+ }
1008
+ if (total > maxBytes) {
1009
+ throw artifactError(invalidCode, `${label} exceeds ${maxBytes} bytes`);
1010
+ }
1011
+ return Buffer.concat(chunks, total);
1012
+ } catch (error) {
1013
+ if (error instanceof MarketplaceRemoteArtifactError) throw error;
1014
+ throw artifactError(inspectionCode, `${label} could not be read`, {
1015
+ error,
1016
+ });
1017
+ } finally {
1018
+ if (descriptor != null) {
1019
+ try {
1020
+ fs.closeSync(descriptor);
1021
+ } catch {
1022
+ // The inspection result remains authoritative.
1023
+ }
1024
+ }
1025
+ }
1026
+ }
1027
+
1028
+ function fsyncDirectoryBestEffort(directory) {
1029
+ let descriptor = null;
1030
+ try {
1031
+ descriptor = fs.openSync(directory, fs.constants.O_RDONLY);
1032
+ fs.fsyncSync(descriptor);
1033
+ } catch {
1034
+ // Some platforms/filesystems do not support directory fsync.
1035
+ } finally {
1036
+ if (descriptor != null) {
1037
+ try {
1038
+ fs.closeSync(descriptor);
1039
+ } catch {
1040
+ // Best effort only.
1041
+ }
1042
+ }
1043
+ }
1044
+ }
1045
+
1046
+ function materializeSignatureBundle(signatureBytes, publicKeyBytes) {
1047
+ const tempDir = fs.mkdtempSync(
1048
+ path.join(os.tmpdir(), "cc-marketplace-artifacts-"),
1049
+ );
1050
+ const signatureFile = path.join(tempDir, "manifest.sig");
1051
+ const publicKeyFile = path.join(tempDir, "publisher.pem");
1052
+ let cleaned = false;
1053
+ const cleanup = () => {
1054
+ if (cleaned) return;
1055
+ fs.rmSync(tempDir, {
1056
+ recursive: true,
1057
+ force: true,
1058
+ maxRetries: 5,
1059
+ retryDelay: 25,
1060
+ });
1061
+ cleaned = true;
1062
+ };
1063
+ try {
1064
+ fs.writeFileSync(signatureFile, signatureBytes, { mode: 0o600 });
1065
+ fs.writeFileSync(publicKeyFile, publicKeyBytes, { mode: 0o600 });
1066
+ return { signatureFile, publicKeyFile, cleanup };
1067
+ } catch (error) {
1068
+ cleanup();
1069
+ throw error;
1070
+ }
1071
+ }
1072
+
1073
+ function normalizeSignatureSpec(value) {
1074
+ const publicKey = objectValue(value.publicKey);
1075
+ const algorithm = cleanString(value.algorithm || "ed25519", 64).toLowerCase();
1076
+ if (algorithm !== "ed25519") {
1077
+ throw artifactError(
1078
+ "UNSUPPORTED_SIGNATURE_ALGORITHM",
1079
+ "marketplace remote signatures must use ed25519",
1080
+ );
1081
+ }
1082
+ const url = requiredUrl(value.url, "signature");
1083
+ const publicKeyUrl = requiredUrl(
1084
+ value.publicKeyUrl || publicKey?.url,
1085
+ "signature public key",
1086
+ );
1087
+ const publicKeySha256 = normalizeSha256(
1088
+ value.publicKeySha256 || publicKey?.spkiSha256,
1089
+ "signature public-key SPKI SHA-256",
1090
+ );
1091
+ return {
1092
+ algorithm,
1093
+ url,
1094
+ sha256: normalizeSha256(
1095
+ value.sha256 || value.digest,
1096
+ "signature document SHA-256",
1097
+ { optional: true },
1098
+ ),
1099
+ publicKeyUrl,
1100
+ publicKeySha256,
1101
+ publicKeyDocumentSha256: normalizeSha256(
1102
+ value.publicKeyDocumentSha256 || publicKey?.sha256,
1103
+ "public-key document SHA-256",
1104
+ { optional: true },
1105
+ ),
1106
+ };
1107
+ }
1108
+
1109
+ function normalizeSbomSpec(value) {
1110
+ return {
1111
+ url: requiredUrl(value.url, "SBOM"),
1112
+ sha256: normalizeSha256(
1113
+ value.digest || value.sha256,
1114
+ "SBOM document SHA-256",
1115
+ ),
1116
+ format: cleanString(value.format, 128) || null,
1117
+ };
1118
+ }
1119
+
1120
+ function verifyPublicKeyDocument(bytes, expectedSpkiSha256, algorithm) {
1121
+ let key;
1122
+ try {
1123
+ assertStrictSpkiPublicKeyPem(bytes);
1124
+ key = crypto.createPublicKey({ key: bytes, format: "pem", type: "spki" });
1125
+ } catch (error) {
1126
+ if (error instanceof MarketplaceRemoteArtifactError) throw error;
1127
+ throw artifactError(
1128
+ "INVALID_PUBLIC_KEY",
1129
+ "marketplace public-key document must be a strict SPKI PUBLIC KEY PEM document",
1130
+ { error },
1131
+ );
1132
+ }
1133
+ if (algorithm === "ed25519" && key.asymmetricKeyType !== "ed25519") {
1134
+ throw artifactError(
1135
+ "INVALID_PUBLIC_KEY",
1136
+ "marketplace public key is not an ed25519 key",
1137
+ );
1138
+ }
1139
+ const spkiSha256 = sha256(key.export({ type: "spki", format: "der" }));
1140
+ if (spkiSha256 !== expectedSpkiSha256) {
1141
+ throw artifactError(
1142
+ "PUBLIC_KEY_FINGERPRINT_MISMATCH",
1143
+ `marketplace public-key SPKI SHA-256 mismatch (expected ${expectedSpkiSha256}, got ${spkiSha256})`,
1144
+ );
1145
+ }
1146
+ return {
1147
+ spkiSha256,
1148
+ asymmetricKeyType: key.asymmetricKeyType,
1149
+ };
1150
+ }
1151
+
1152
+ function assertStrictSpkiPublicKeyPem(bytes) {
1153
+ const text = bytes.toString("utf8");
1154
+ if (!Buffer.from(text, "utf8").equals(bytes)) {
1155
+ throw artifactError(
1156
+ "INVALID_PUBLIC_KEY_CONTAINER",
1157
+ "marketplace public-key PEM must be valid UTF-8",
1158
+ );
1159
+ }
1160
+ if (
1161
+ !/^-----BEGIN PUBLIC KEY-----\r?\n(?:[A-Za-z0-9+/=]+\r?\n)+-----END PUBLIC KEY-----\r?\n?$/.test(
1162
+ text,
1163
+ )
1164
+ ) {
1165
+ throw artifactError(
1166
+ "INVALID_PUBLIC_KEY_CONTAINER",
1167
+ "marketplace public-key PEM must contain exactly one PUBLIC KEY block",
1168
+ );
1169
+ }
1170
+ }
1171
+
1172
+ async function runByteValidator(validateBytes, bytes, kind) {
1173
+ if (!validateBytes) return null;
1174
+ try {
1175
+ return (await validateBytes(bytes)) ?? null;
1176
+ } catch (error) {
1177
+ if (error instanceof MarketplaceRemoteArtifactError) throw error;
1178
+ throw artifactError(
1179
+ "ARTIFACT_VALIDATION_FAILED",
1180
+ `${kind} artifact validation failed: ${redactMarketplaceArtifactError(
1181
+ error?.message || error,
1182
+ )}`,
1183
+ { error },
1184
+ );
1185
+ }
1186
+ }
1187
+
1188
+ function normalizeAuthorizationOrigin(
1189
+ value,
1190
+ _artifactUrl,
1191
+ _token,
1192
+ allowInsecure,
1193
+ ) {
1194
+ if (!value) return null;
1195
+ const url = assertRegistryUrlSafe(value, allowInsecure === true);
1196
+ return url.origin;
1197
+ }
1198
+
1199
+ function normalizeTrustedRegistryOrigin(value, allowInsecure) {
1200
+ if (!value) return null;
1201
+ return assertRegistryUrlSafe(value, allowInsecure).origin;
1202
+ }
1203
+
1204
+ function assertRegistryUrlSafe(value, allowInsecure) {
1205
+ return assertMarketplaceArtifactUrlSafe(value, {
1206
+ allowLoopbackHttp: true,
1207
+ allowInsecureRegistry: allowInsecure,
1208
+ allowPrivateNetwork: true,
1209
+ label: "registry",
1210
+ });
1211
+ }
1212
+
1213
+ function normalizeOriginString(value, label) {
1214
+ const raw = String(value || "");
1215
+ if (raw.length > MAX_MARKETPLACE_ARTIFACT_URL_LENGTH) {
1216
+ throw artifactError(
1217
+ "URL_TOO_LONG",
1218
+ `${label} exceeds ${MAX_MARKETPLACE_ARTIFACT_URL_LENGTH} characters`,
1219
+ );
1220
+ }
1221
+ let url;
1222
+ try {
1223
+ url = new URL(raw);
1224
+ } catch {
1225
+ throw artifactError("INVALID_URL", `${label} is invalid`);
1226
+ }
1227
+ if (url.username || url.password) {
1228
+ throw artifactError(
1229
+ "URL_CREDENTIALS_REJECTED",
1230
+ `${label} must not contain credentials`,
1231
+ );
1232
+ }
1233
+ return url.origin;
1234
+ }
1235
+
1236
+ function requiredUrl(value, label) {
1237
+ const raw = String(value == null ? "" : value);
1238
+ if (!raw.trim()) {
1239
+ throw artifactError(
1240
+ "INCOMPLETE_ARTIFACT_DECLARATION",
1241
+ `${label} URL is required for remote artifact verification`,
1242
+ );
1243
+ }
1244
+ if (raw.length > MAX_MARKETPLACE_ARTIFACT_URL_LENGTH) {
1245
+ throw artifactError(
1246
+ "URL_TOO_LONG",
1247
+ `${label} URL exceeds ${MAX_MARKETPLACE_ARTIFACT_URL_LENGTH} characters`,
1248
+ );
1249
+ }
1250
+ return raw;
1251
+ }
1252
+
1253
+ function normalizeArtifactKind(value) {
1254
+ const kind = String(value || "");
1255
+ if (!(kind in MARKETPLACE_REMOTE_ARTIFACT_LIMITS)) {
1256
+ throw artifactError("INVALID_ARTIFACT_KIND", "artifact kind is invalid");
1257
+ }
1258
+ return kind;
1259
+ }
1260
+
1261
+ function normalizeSha256(value, label, { optional = false } = {}) {
1262
+ if (value == null || String(value).trim() === "") {
1263
+ if (optional) return null;
1264
+ throw artifactError(
1265
+ "INCOMPLETE_ARTIFACT_DECLARATION",
1266
+ `${label} is required`,
1267
+ );
1268
+ }
1269
+ const digest = String(value).trim().toLowerCase();
1270
+ if (!SHA256_RE.test(digest)) {
1271
+ throw artifactError("INVALID_DIGEST", `${label} is invalid`);
1272
+ }
1273
+ return digest;
1274
+ }
1275
+
1276
+ function normalizeBearerToken(value) {
1277
+ if (value == null || value === "") return null;
1278
+ const token = String(value);
1279
+ if (token.length > 8192 || !/^[A-Za-z0-9\-._~+/]+=*$/.test(token)) {
1280
+ throw artifactError("INVALID_TOKEN", "registry bearer token is invalid");
1281
+ }
1282
+ return token;
1283
+ }
1284
+
1285
+ function normalizePositiveInteger(value, fallback, min, max, label) {
1286
+ if (value == null) return fallback;
1287
+ const number = Number(value);
1288
+ if (!Number.isInteger(number) || number < min || number > max) {
1289
+ throw artifactError(
1290
+ "INVALID_LIMIT",
1291
+ `${label} must be an integer from ${min} to ${max}`,
1292
+ );
1293
+ }
1294
+ return number;
1295
+ }
1296
+
1297
+ function marketplaceArtifactCacheRoot(value) {
1298
+ return path.resolve(
1299
+ value ||
1300
+ path.join(getElectronUserDataDir(), "plugin-marketplace-artifact-cache"),
1301
+ );
1302
+ }
1303
+
1304
+ function artifactWasRequested(value, fields) {
1305
+ if (!value) return false;
1306
+ return fields.some((field) => {
1307
+ const candidate = value[field];
1308
+ return candidate != null && candidate !== "";
1309
+ });
1310
+ }
1311
+
1312
+ function objectValue(value) {
1313
+ return value && typeof value === "object" && !Array.isArray(value)
1314
+ ? value
1315
+ : null;
1316
+ }
1317
+
1318
+ function cleanString(value, max) {
1319
+ return String(value == null ? "" : value)
1320
+ .replace(/\p{Cc}/gu, "")
1321
+ .trim()
1322
+ .slice(0, max);
1323
+ }
1324
+
1325
+ function isLoopbackHostname(value) {
1326
+ const host = String(value || "")
1327
+ .replace(/^\[|\]$/g, "")
1328
+ .toLowerCase();
1329
+ return (
1330
+ host === "localhost" || host === "::1" || /^127(?:\.\d{1,3}){3}$/.test(host)
1331
+ );
1332
+ }
1333
+
1334
+ async function assertMarketplaceArtifactNetworkTargetSafe(
1335
+ url,
1336
+ { kind, registryOrigin, resolveHostname },
1337
+ ) {
1338
+ if (registryOrigin && url.origin === registryOrigin) return;
1339
+ if (url.protocol !== "https:") {
1340
+ // Standalone loopback HTTP is allowed only as an explicit local test/dev
1341
+ // boundary by the synchronous URL policy above.
1342
+ if (!registryOrigin && isLoopbackHostname(url.hostname)) return;
1343
+ throw artifactError(
1344
+ "UNSAFE_URL",
1345
+ `${kind} artifact cross-origin requests must use HTTPS`,
1346
+ );
1347
+ }
1348
+ if (isRestrictedNetworkHostname(url.hostname)) {
1349
+ throw artifactError(
1350
+ "UNSAFE_NETWORK_TARGET",
1351
+ `${kind} artifact must not target a private or local network address`,
1352
+ );
1353
+ }
1354
+ if (net.isIP(stripHostnameBrackets(url.hostname)) || !resolveHostname) {
1355
+ // Custom fetch implementations form an explicit test boundary unless a
1356
+ // resolver is injected. Production fetches always use the default DNS
1357
+ // resolver immediately before each request/redirect hop.
1358
+ return;
1359
+ }
1360
+
1361
+ let resolved;
1362
+ try {
1363
+ resolved = await resolveHostname(stripHostnameBrackets(url.hostname));
1364
+ } catch (error) {
1365
+ throw artifactError(
1366
+ "DNS_RESOLUTION_FAILED",
1367
+ `${kind} artifact hostname could not be resolved safely`,
1368
+ { cacheFallbackAllowed: true, error },
1369
+ );
1370
+ }
1371
+ const addresses = normalizeResolvedAddresses(resolved);
1372
+ if (addresses.length === 0) {
1373
+ throw artifactError(
1374
+ "DNS_RESOLUTION_FAILED",
1375
+ `${kind} artifact hostname did not resolve to an address`,
1376
+ { cacheFallbackAllowed: true },
1377
+ );
1378
+ }
1379
+ if (addresses.some((address) => isRestrictedNetworkAddress(address))) {
1380
+ throw artifactError(
1381
+ "UNSAFE_NETWORK_TARGET",
1382
+ `${kind} artifact hostname resolved to a private or local network address`,
1383
+ );
1384
+ }
1385
+ }
1386
+
1387
+ function normalizeResolvedAddresses(value) {
1388
+ const entries = Array.isArray(value) ? value : [value];
1389
+ return entries
1390
+ .map((entry) =>
1391
+ typeof entry === "string" ? entry : String(entry?.address || ""),
1392
+ )
1393
+ .filter((address) => net.isIP(stripHostnameBrackets(address)) !== 0);
1394
+ }
1395
+
1396
+ function isRestrictedNetworkHostname(value) {
1397
+ const hostname = stripHostnameBrackets(value).toLowerCase();
1398
+ if (
1399
+ hostname === "localhost" ||
1400
+ hostname === "localhost.localdomain" ||
1401
+ hostname === "metadata" ||
1402
+ hostname === "instance-data" ||
1403
+ hostname === "metadata.google.internal" ||
1404
+ hostname === "metadata.azure.internal" ||
1405
+ hostname.endsWith(".localhost")
1406
+ ) {
1407
+ return true;
1408
+ }
1409
+ return net.isIP(hostname) !== 0 && isRestrictedNetworkAddress(hostname);
1410
+ }
1411
+
1412
+ function isRestrictedNetworkAddress(value) {
1413
+ const address = stripHostnameBrackets(value).toLowerCase();
1414
+ const family = net.isIP(address);
1415
+ if (family === 4) return isRestrictedIpv4(address);
1416
+ if (family !== 6) return true;
1417
+ const bytes = parseIpv6Bytes(address);
1418
+ if (!bytes) return true;
1419
+ const allZeroPrefix = (length) =>
1420
+ bytes.subarray(0, length).every((part) => part === 0);
1421
+ if (
1422
+ bytes.every((part) => part === 0) ||
1423
+ (allZeroPrefix(15) && bytes[15] === 1) ||
1424
+ (bytes[0] & 0xfe) === 0xfc ||
1425
+ (bytes[0] === 0xfe && (bytes[1] & 0xc0) === 0x80) ||
1426
+ bytes[0] === 0xff ||
1427
+ (bytes[0] === 0x20 &&
1428
+ bytes[1] === 0x01 &&
1429
+ bytes[2] === 0x0d &&
1430
+ bytes[3] === 0xb8)
1431
+ ) {
1432
+ return true;
1433
+ }
1434
+ const mappedIpv4 =
1435
+ (allZeroPrefix(10) && bytes[10] === 0xff && bytes[11] === 0xff) ||
1436
+ allZeroPrefix(12) ||
1437
+ (bytes[0] === 0x00 &&
1438
+ bytes[1] === 0x64 &&
1439
+ bytes[2] === 0xff &&
1440
+ bytes[3] === 0x9b &&
1441
+ bytes.subarray(4, 12).every((part) => part === 0));
1442
+ if (mappedIpv4) return isRestrictedIpv4Bytes(bytes.subarray(12));
1443
+ if (bytes[0] === 0x20 && bytes[1] === 0x02) {
1444
+ return isRestrictedIpv4Bytes(bytes.subarray(2, 6));
1445
+ }
1446
+ return false;
1447
+ }
1448
+
1449
+ function parseIpv6Bytes(value) {
1450
+ let address = String(value).toLowerCase();
1451
+ if (address.includes(".")) {
1452
+ const separator = address.lastIndexOf(":");
1453
+ const ipv4 = address.slice(separator + 1);
1454
+ if (net.isIP(ipv4) !== 4) return null;
1455
+ const octets = ipv4.split(".").map(Number);
1456
+ address = `${address.slice(0, separator)}:${(
1457
+ (octets[0] << 8) |
1458
+ octets[1]
1459
+ ).toString(16)}:${((octets[2] << 8) | octets[3]).toString(16)}`;
1460
+ }
1461
+ const halves = address.split("::");
1462
+ if (halves.length > 2) return null;
1463
+ const left = halves[0] ? halves[0].split(":") : [];
1464
+ const right = halves.length === 2 && halves[1] ? halves[1].split(":") : [];
1465
+ const missing = 8 - left.length - right.length;
1466
+ if (
1467
+ missing < 0 ||
1468
+ (halves.length === 1 && missing !== 0) ||
1469
+ left.concat(right).some((part) => !/^[a-f0-9]{1,4}$/.test(part))
1470
+ ) {
1471
+ return null;
1472
+ }
1473
+ const groups = [...left, ...Array(missing).fill("0"), ...right];
1474
+ const bytes = Buffer.alloc(16);
1475
+ groups.forEach((group, index) =>
1476
+ bytes.writeUInt16BE(parseInt(group, 16), index * 2),
1477
+ );
1478
+ return bytes;
1479
+ }
1480
+
1481
+ function isRestrictedIpv4Bytes(bytes) {
1482
+ return isRestrictedIpv4(Array.from(bytes).join("."));
1483
+ }
1484
+
1485
+ function isRestrictedIpv4(value) {
1486
+ const octets = value.split(".").map(Number);
1487
+ if (octets.length !== 4 || octets.some((part) => part < 0 || part > 255)) {
1488
+ return true;
1489
+ }
1490
+ const [a, b, c] = octets;
1491
+ return (
1492
+ a === 0 ||
1493
+ a === 10 ||
1494
+ a === 127 ||
1495
+ (a === 100 && b >= 64 && b <= 127) ||
1496
+ (a === 169 && b === 254) ||
1497
+ (a === 172 && b >= 16 && b <= 31) ||
1498
+ (a === 192 && b === 0 && (c === 0 || c === 2)) ||
1499
+ (a === 192 && b === 168) ||
1500
+ (a === 198 && (b === 18 || b === 19)) ||
1501
+ (a === 198 && b === 51 && c === 100) ||
1502
+ (a === 203 && b === 0 && c === 113) ||
1503
+ a >= 224
1504
+ );
1505
+ }
1506
+
1507
+ function stripHostnameBrackets(value) {
1508
+ return String(value || "").replace(/^\[|\]$/g, "");
1509
+ }
1510
+
1511
+ function acceptHeader(kind) {
1512
+ if (kind === "sbom") {
1513
+ return "application/json, application/xml;q=0.9, application/octet-stream;q=0.8";
1514
+ }
1515
+ if (kind === "publicKey") {
1516
+ return "application/x-pem-file, application/octet-stream;q=0.8";
1517
+ }
1518
+ return "application/octet-stream";
1519
+ }
1520
+
1521
+ function normalizeFetchError(error, { kind, token }) {
1522
+ if (error instanceof MarketplaceRemoteArtifactError) return error;
1523
+ return artifactError(
1524
+ "FETCH_FAILED",
1525
+ `${kind} artifact fetch failed: ${redactMarketplaceArtifactError(
1526
+ error?.message || error,
1527
+ { token },
1528
+ )}`,
1529
+ { cacheFallbackAllowed: true, error, token },
1530
+ );
1531
+ }
1532
+
1533
+ function artifactError(
1534
+ code,
1535
+ message,
1536
+ { cacheFallbackAllowed = false, error = null, token = null } = {},
1537
+ ) {
1538
+ const safeCause = error
1539
+ ? new Error(
1540
+ redactMarketplaceArtifactError(error?.message || error, { token }),
1541
+ )
1542
+ : undefined;
1543
+ return new MarketplaceRemoteArtifactError(
1544
+ code,
1545
+ redactMarketplaceArtifactError(message, { token }),
1546
+ { cacheFallbackAllowed, cause: safeCause },
1547
+ );
1548
+ }
1549
+
1550
+ function sha256(value) {
1551
+ return crypto.createHash("sha256").update(value).digest("hex");
1552
+ }
1553
+
1554
+ function sha256Canonical(value) {
1555
+ return sha256(canonicalJson(value));
1556
+ }
1557
+
1558
+ function canonicalJson(value) {
1559
+ if (value == null || typeof value !== "object") return JSON.stringify(value);
1560
+ if (Array.isArray(value)) {
1561
+ return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
1562
+ }
1563
+ return `{${Object.keys(value)
1564
+ .sort()
1565
+ .map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
1566
+ .join(",")}}`;
1567
+ }