chainlesschain 0.165.4 → 0.165.6

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 (214) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-C__nnraW.js → AIOps-vfoDb6HZ.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-CsKWHEZ-.js → ActionButton-Cm5f7p93.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-BhIjfjWO.js → Analytics-yRuvgWxa.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-BjuXFzWZ.js → AppLayout-BROsC4h1.js} +4 -4
  7. package/src/assets/web-panel/assets/{Artifacts-k6whDkZJ.js → Artifacts-CimHApb1.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-C4FBMYRh.js → Audit-DI8kr8zI.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-C_vRrSXk.js → BackgroundAgents-DSLzKy-n.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-CngFZeVp.js → Backup-DkH_n2r1.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-B_H9EBRm.js → BaseInput-CKSrUoRA.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-DLEC5kt7.js → Chat-ClwfMXUS.js} +3 -3
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-CjMaqfL7.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-m3ClR4xh.js → Checkbox-BYTehcQG.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-CmLSFrgp.js → Codegen-B8AMcp6J.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-DFGQteHN.js → Col-BNSogPo3.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-EUUVORHE.js → Community-JxPvGb8O.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-2PSg0Yr7.js → Compact-DF7bFAgE.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-CcKur7gB.js → Compliance-Bvhqnwrq.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DK_Fezfa.js → Cowork-BDS0CJvM.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-Dbw89xl7.js → Cron-DqvRfxoQ.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-CH66h_Ec.js → Crosschain-CDcIvnqS.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-D1igRb17.js → DID-D7jaUxU3.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-DVAsrobk.js → Dashboard-B5A18wEV.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-Dl7W8AQs.js → Dropdown-_6IVkGag.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-D5tpaEQC.js → EmailListRenderer-C9PeiKCG.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-O_VQf8gq.js → FamilyGuardDashboard-DXjuEHrX.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-BTVcgS9n.js → Federation-B4IAR9zv.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-BRnkJAIz.js → FormItemContext-CxQeSNx1.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-8OWSCkDj.js → GenericCardRenderer-B8GS-Y3N.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-WXLYTQj2.js → Git-BS8SouUX.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-BezaUhtc.js → Governance-Dq9aYb5Q.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-CuRay3Gu.js → Inference-BrCEe9AJ.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-DfWLDW1m.js → KnowledgeGraph-DybOpiY2.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-C55y2pPY.js → Logs-DBpzbRfx.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-CFyt8iUd.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-Dk08Uycg.js → Marketplace-Cc7CCSCu.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-C11fSB5V.js → McpTools-DFaIhOfu.js} +3 -3
  39. package/src/assets/web-panel/assets/{Memory-mr0eZc97.js → Memory-KCqlp9ol.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-C0iCSRbb.js → MobileBridge-Bys84ySN.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-CY-xMG8Z.js → MobileProjects-B-6qU-je.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-Bplqc_Wc.js → Mtc-BgY-fvCq.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-BlzwsoSM.js → MtcAudit-B8pCmqQJ.js} +4 -4
  44. package/src/assets/web-panel/assets/{Multisig-BBLmTty6.js → Multisig-BbOmYutA.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-DtT1uf1W.js → NLProgramming-BocLQz_M.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-DcqQAxTK.js → Notes-CSwbxEop.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-cb0kWoZe.js → NotificationSettings-CqFNSYG3.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-kw9HZQNN.js → OrderTableRenderer-C6gMfAk7.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-DlTlbkUN.js → Organization-QrDXVdMJ.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-pp1qs-oY.js → Overflow-Cxntotjt.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-BESLnyYz.js → P2P-CIdBOwNq.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-B-J1mGuf.js → PdhVaultBrowser-CT5aP8rD.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-B36PeT1_.js → Permissions-DH7OM8XU.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-CTK2ad-3.js → PersonalDataHub-CnJqbWce.js} +3 -3
  55. package/src/assets/web-panel/assets/{Pipeline-zJNGXsac.js → Pipeline-5C3HG1kH.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-b9fz0Qf0.js → Privacy-B4yIp7xK.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-DjZbZgiB.js → ProjectInit-BMvt-2M0.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-C-hHJXlD.js → ProjectSettings-CvSTEMRG.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-BKBJxEek.js +1 -0
  60. package/src/assets/web-panel/assets/Providers-BdwUqFWN.js +1 -0
  61. package/src/assets/web-panel/assets/{QrScannerModal-A0yoQ3-3.js → QrScannerModal-DtnCKlTm.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-DrFgFJNC.js → QuickAsk-sTbgwyNr.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-KC8hF1g4.js → Recommend-Bzr14gSb.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-DGTPIk6u.js → RemoteSession-C_TF5IaJ.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-QmbwM7Yk.js → Reputation-GCAM4H2K.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-BxjJylia.js → Row-Dir0r6OQ.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-B7Wda1J6.js → RssFeed-CpGnlecX.js} +2 -2
  68. package/src/assets/web-panel/assets/{Search-_plQ_YSA.js → Search-DMMamC6u.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-CD2YJZfx.js → Security-yqay5yG-.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-qQE7NmwE.js → Services-C5i2wh9G.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-DgQqkFf7.js → Skeleton-vUqk35yj.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-C4C0oFFt.js → Skills-DzcSkhot.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-Px2N2B5c.js → Sla-CB6qbYNr.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-DN6C3lQd.js → SpeechSettings-BHUI7cox.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-jm5aH_m7.js → SyncSettings-D-RrsxTJ.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-DlkJcv67.js → Tasks-DFxdOJiq.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-DyrTHUBs.js → Templates-SKAob_Aq.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-mC434PtL.js → Tenant-BPfsfHQ-.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-o0_s3NgK.js → Terminal-8iw7FpUf.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-DAGlAXOw.js → TimelineRenderer-C4Gjj5Su.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-BnulS_bs.js → Tokens-COYhN18e.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-suQbVXYM.js → Trigger-B-JzXcf6.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-DbIJAPo5.js → Trust-BJ5bg0nI.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-CQNpbIHi.js → UkeySign-BG_ByRMf.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-D8aWCERk.js → VideoEditing-B3kOnSiU.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-CuLpYfZc.js → Wallet-CcoGFnK7.js} +2 -2
  87. package/src/assets/web-panel/assets/{WebAuthn-CsfG8ZMc.js → WebAuthn-DdT8vRC_.js} +4 -4
  88. package/src/assets/web-panel/assets/{WorkflowEditor-BYsEAZbd.js → WorkflowEditor-CZu3M9N8.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-FT4Um3-I.js → chat-DxJt3Qba.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-CKbS_Wlt.js → colors-DdYfwJaB.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-CFJtzuJM.js → compact-item-BG3SUzwj.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-B9kSRW6K.js → createContext-By51z2-1.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-DorRZcI9.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-DR-x0Xpi.js → hasIn-B0apZ3KJ.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DjEP7WkH.js → index-3jWkIu5j.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-7jHr_A_k.js → index-B3rWuPMW.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DcfxhKXR.js → index-B5rLiEo0.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-tqSEHPRT.js → index-BBPf3TpM.js} +1 -1
  99. package/src/assets/web-panel/assets/index-BETiL0a3.js +1 -0
  100. package/src/assets/web-panel/assets/{index-Cr2FLUsD.js → index-BIrJfqO4.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Bt4fS2eL.js → index-BLIbGOLX.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BwR7yc2h.js → index-BP94CrBV.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BVPU43b1.js → index-BWF8_mkx.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DShSkww-.js → index-BYINoRLK.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Bzz0C1BZ.js → index-BZ_0qm4g.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-KqDaCApw.js → index-BewKYrX9.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BW-t9n-q.js → index-BfWYsB9t.js} +1 -1
  108. package/src/assets/web-panel/assets/index-BhAVHuW8.js +1 -0
  109. package/src/assets/web-panel/assets/{index-B7osrXqy.js → index-Bissefox.js} +3 -3
  110. package/src/assets/web-panel/assets/{index-DW4BWjTk.js → index-BkvcEo4p.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DdkIhfi-.js → index-Bos5_aMV.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DTxNy0ZU.js → index-C1C9i79A.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-QBSeNVO3.js → index-CAhHirVH.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-D4LfPvWA.js → index-CNIxXMmN.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-C_enk9I_.js → index-CW93tZDv.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-WE08gMJP.js → index-CWEuZWCK.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-r2VI1Ikk.js → index-Cp6e-hGw.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Dap9c3eD.js → index-CpjZhgNd.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DWdqx7zI.js → index-CzmQElZy.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BGbPnC5x.js → index-DCn8IcxV.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DiNUEpSQ.js → index-DE76D-kA.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DtkFqPVA.js → index-DaIz0NAW.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CGaXUsIs.js → index-Da_WBdLX.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-B3ZX81mK.js → index-Dz49VOyQ.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BK3TiKVq.js → index-Q2SCX-MI.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CJmIQ9XR.js → index-RD6O-0C6.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CvrCCqJt.js → index-SQqOlR3d.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BI5hP_qy.js → index-ZFbps9po.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DeSgw-WV.js → index-ZxtP8jG7.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DMGmg497.js → index-bP_VlDb4.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BfFCQHL3.js → index-kf57LJ93.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-OSzjTzGM.js → index-oxRrmFO9.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-C2JvmYRc.js → index-p1_8SbI1.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-Dd7n7VgI.js → initDefaultProps-BWJktcg8.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BwxouRw1.js → motion-BEXBzUFV.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-DObN0PG9.js → move-BUKwn2sx.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-B3nOUQc7.js → mtc-parser-FTI7btqQ.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-o9WU2Nbl.js → omit-CsLtSPH3.js} +1 -1
  139. package/src/assets/web-panel/assets/parsers-YNLm7sQ0.js +4 -0
  140. package/src/assets/web-panel/assets/{pickAttrs-CL_vvfQ_.js → pickAttrs-DWZxsT_S.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-CYrM2tyK.js → placementArrow-Bcfs43CX.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-k_06cQ96.js → responsiveObserve-a9bcC1p-.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-CPKG0pxS.js → slide-BDTzgOXN.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-BDDEeLTw.js → statusUtils-Iq1j_HUg.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-Cmb7RTh7.js → styleChecker-DAZEYxeh.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-e2IedC4N.js → useFlexGapSupport-DVkjyDrh.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-CE_gxVnm.js → useFs-wXTpNTHt.js} +1 -1
  148. package/src/assets/web-panel/assets/{usePersonalDataHub-BkMIIKMz.js → usePersonalDataHub-CtQM_aZr.js} +1 -1
  149. package/src/assets/web-panel/assets/{vnode-DW886XV1.js → vnode-hjEoG85v.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-Dwo_adaf.js → zoom-BHVpyKg_.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/command-help-index.json +3 -3
  153. package/src/commands/agent.js +14 -2
  154. package/src/commands/artifacts.js +235 -0
  155. package/src/commands/background-session.js +100 -10
  156. package/src/commands/cowork.js +86 -57
  157. package/src/commands/mcp.js +5 -0
  158. package/src/commands/plugin.js +145 -29
  159. package/src/commands/remote-control.js +295 -110
  160. package/src/commands/session-location.js +20 -1
  161. package/src/commands/session-message.js +171 -0
  162. package/src/commands/session.js +75 -69
  163. package/src/data/changelog.json +29 -3
  164. package/src/gateways/ws/message-dispatcher.js +57 -0
  165. package/src/harness/mcp-client.js +141 -10
  166. package/src/lib/artifact-access-ledger.js +1 -0
  167. package/src/lib/artifact-deletion-ledger.js +1 -0
  168. package/src/lib/artifact-recovery.js +970 -0
  169. package/src/lib/artifact-workbench-projection.js +171 -0
  170. package/src/lib/background-session-transport.js +153 -27
  171. package/src/lib/dynamic-workflow-runtime.js +408 -1
  172. package/src/lib/execution-location-target.js +4 -2
  173. package/src/lib/mcp-client.js +2 -0
  174. package/src/lib/mcp-headers-helper.js +47 -5
  175. package/src/lib/mcp-oauth.js +55 -6
  176. package/src/lib/mcp-stdio-executable-identity.js +16 -0
  177. package/src/lib/mcp-stdio-native-code-policy.js +74 -0
  178. package/src/lib/mcp-stdio-package-materialization.js +12 -3
  179. package/src/lib/mcp-tls.js +230 -0
  180. package/src/lib/output-styles.js +12 -0
  181. package/src/lib/plugin-runtime/install.js +614 -71
  182. package/src/lib/plugin-runtime/marketplace-archive-source.js +331 -0
  183. package/src/lib/plugin-runtime/marketplace-catalog.js +52 -6
  184. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1 -0
  185. package/src/lib/plugin-runtime/marketplace-source-adapter.js +163 -0
  186. package/src/lib/plugin-runtime/marketplace-source-cache.js +11 -1
  187. package/src/lib/plugin-runtime/remote-source.js +435 -55
  188. package/src/lib/plugin-security.js +393 -41
  189. package/src/lib/plugin-source-identity.js +850 -0
  190. package/src/lib/process-execution-broker/index.js +112 -0
  191. package/src/lib/process-execution-broker/platform-sandbox.js +57 -3
  192. package/src/lib/process-execution-broker/workspace-transaction.js +26 -0
  193. package/src/lib/remote-approval-bridge.js +136 -46
  194. package/src/lib/remote-control.js +217 -31
  195. package/src/lib/session-message-fabric.js +1105 -0
  196. package/src/lib/session-projection.js +171 -12
  197. package/src/lib/session-runtime-retention.js +155 -0
  198. package/src/lib/settings-loader.cjs +29 -0
  199. package/src/repl/agent-repl.js +51 -14
  200. package/src/repl/permission-prompt.js +31 -8
  201. package/src/repl/repl-keybindings.js +55 -0
  202. package/src/runtime/agent-core.js +63 -2
  203. package/src/runtime/headless-runner.js +47 -66
  204. package/src/runtime/headless-stream.js +18 -24
  205. package/src/runtime/mcp-config.js +2 -0
  206. package/src/runtime/policies/agent-policy.js +2 -0
  207. package/src/assets/web-panel/assets/ChatBubbleRenderer-xijcWkJe.js +0 -1
  208. package/src/assets/web-panel/assets/MarkdownRenderer-hDpWdiBc.js +0 -1
  209. package/src/assets/web-panel/assets/Projects-BC9KSDM6.js +0 -1
  210. package/src/assets/web-panel/assets/Providers-Dz-CAYYU.js +0 -1
  211. package/src/assets/web-panel/assets/devWarning-DJZ0gAMu.js +0 -1
  212. package/src/assets/web-panel/assets/index-Bizb4hkw.js +0 -1
  213. package/src/assets/web-panel/assets/index-OHSl-xCh.js +0 -1
  214. package/src/assets/web-panel/assets/parsers-UEvh_ShA.js +0 -4
@@ -38,8 +38,18 @@ import {
38
38
  } from "./scopes.js";
39
39
  import {
40
40
  enforcePluginPolicy,
41
+ enforcePluginSourcePolicy,
42
+ resolvePluginManagedPolicy,
41
43
  verifyPluginManifest,
42
44
  } from "../plugin-security.js";
45
+ import {
46
+ assertPluginGitTransportSafe,
47
+ canonicalizePluginSource,
48
+ normalizePluginLocalSourcePath,
49
+ parsePluginGitSource,
50
+ redactPluginSourceForDisplay,
51
+ } from "../plugin-source-identity.js";
52
+ import { assertRegistryResolutionAuthority } from "./remote-source.js";
43
53
  import {
44
54
  writePluginLock,
45
55
  buildPluginSbom,
@@ -106,12 +116,18 @@ export const _deps = {
106
116
  mkdtempSync: fs.mkdtempSync,
107
117
  randomToken: () => crypto.randomBytes(16).toString("hex"),
108
118
  spawnSync: (...args) => executionBroker.spawnSync(...args),
119
+ gitExecutable: null,
109
120
  beforeTransactionPhaseHook: null,
110
121
  transactionPhaseHook: null,
111
122
  };
112
123
 
113
124
  const pendingPluginTransactions = new WeakMap();
114
125
  const ownedPluginLifecycleLocks = new Map();
126
+ const SOURCE_POLICY_PREFLIGHT = Symbol("plugin-source-policy-preflight");
127
+ const UPDATE_MATERIALIZATION = Symbol("plugin-update-materialization");
128
+ const NULL_DEVICE = process.platform === "win32" ? "NUL" : "/dev/null";
129
+ let cachedTrustedGitExecutable = null;
130
+ let cachedTrustedSshExecutable = null;
115
131
 
116
132
  /**
117
133
  * Install a plugin from a local directory into a scope's immutable version dir.
@@ -120,8 +136,11 @@ const ownedPluginLifecycleLocks = new Map();
120
136
  * @returns {{ name, version, scope, dir, warnings }}
121
137
  */
122
138
  export function installFromDirectory(srcDir, opts = {}) {
139
+ opts = preflightManagedSource(srcDir, opts, "install", {
140
+ materializedKind: "directory",
141
+ });
123
142
  const scope = opts.scope || "user";
124
- const src = path.resolve(srcDir);
143
+ const src = path.resolve(opts.cwd || process.cwd(), srcDir);
125
144
  if (!_deps.existsSync(src)) {
126
145
  throw new Error(`source directory does not exist: ${src}`);
127
146
  }
@@ -157,7 +176,10 @@ export function installFromDirectory(srcDir, opts = {}) {
157
176
  assertInstalledNameDirectoryIdentity(name, { scope, cwd: opts.cwd });
158
177
  assertExpectedPluginIdentity(name, version, opts.expectedIdentity);
159
178
  let sourceMetadata = normalizeSourceMetadata(
160
- opts.sourceMetadata ?? { type: "local", source: src },
179
+ bindSourcePolicyAuthority(
180
+ opts.sourceMetadata ?? { type: "local", source: src },
181
+ opts,
182
+ ),
161
183
  );
162
184
  if (!sourceMetadata) {
163
185
  throw new Error("plugin source metadata has no valid source identity");
@@ -184,6 +206,13 @@ export function installFromDirectory(srcDir, opts = {}) {
184
206
  {
185
207
  name,
186
208
  source: opts.policySource || sourceMetadata?.source || null,
209
+ sourceKind:
210
+ sourceMetadata?.type === "registry"
211
+ ? "registry"
212
+ : sourceMetadata?.type === "git"
213
+ ? "git"
214
+ : null,
215
+ sourcePolicyPreflighted: true,
187
216
  action: "install",
188
217
  },
189
218
  opts.managedPolicy,
@@ -508,17 +537,34 @@ export function installFromDirectory(srcDir, opts = {}) {
508
537
  * An optional `#ref` (branch / tag / commit) pins the checkout.
509
538
  */
510
539
  export function installFromSource(source, opts = {}) {
540
+ const governedOpts = preflightManagedSource(source, opts, "install");
511
541
  return _withMaterializedSource(
512
542
  source,
513
543
  (dir, info) => {
514
544
  const sourceMetadata =
515
- normalizeSourceMetadata(opts.sourceMetadata) ||
516
545
  normalizeSourceMetadata(
517
- info
518
- ? { type: "git", source: info.url, ref: info.ref || null }
519
- : { type: "local", source: path.resolve(String(source || "")) },
546
+ bindSourcePolicyAuthority(governedOpts.sourceMetadata, governedOpts),
547
+ ) ||
548
+ normalizeSourceMetadata(
549
+ bindSourcePolicyAuthority(
550
+ info
551
+ ? { type: "git", source: info.url, ref: info.ref || null }
552
+ : {
553
+ type: "local",
554
+ source:
555
+ governedOpts[SOURCE_POLICY_PREFLIGHT]?.materialized?.path ||
556
+ path.resolve(
557
+ governedOpts.cwd || process.cwd(),
558
+ String(source || ""),
559
+ ),
560
+ },
561
+ governedOpts,
562
+ ),
520
563
  );
521
- const res = installFromDirectory(dir, { ...opts, sourceMetadata });
564
+ const res = installFromDirectory(dir, {
565
+ ...governedOpts,
566
+ sourceMetadata,
567
+ });
522
568
  if (!info) return res;
523
569
  const result = {
524
570
  ...res,
@@ -528,8 +574,166 @@ export function installFromSource(source, opts = {}) {
528
574
  transferPendingTransaction(res, result);
529
575
  return result;
530
576
  },
577
+ governedOpts,
578
+ );
579
+ }
580
+
581
+ function describeMaterializedSource(source, opts, materializedKind = null) {
582
+ if (materializedKind !== "directory") {
583
+ const git = parsePluginGitSource(source);
584
+ if (git) return { kind: "git", git };
585
+ }
586
+ return {
587
+ kind: "directory",
588
+ path: path.resolve(
589
+ opts.cwd || process.cwd(),
590
+ normalizePluginLocalSourcePath(source),
591
+ ),
592
+ };
593
+ }
594
+
595
+ function preflightManagedSource(source, opts, action, options = {}) {
596
+ if (opts[SOURCE_POLICY_PREFLIGHT]) return opts;
597
+ const materialized = describeMaterializedSource(
598
+ source,
531
599
  opts,
600
+ options.materializedKind,
532
601
  );
602
+ const registrySource =
603
+ opts.sourceMetadata?.type === "registry"
604
+ ? opts.sourceMetadata.registry || opts.sourceMetadata.source
605
+ : null;
606
+ // Policy follows the bytes that will actually be materialized. The only
607
+ // intentional indirection is a registry-backed install, whose catalog URL
608
+ // is authoritative in sourceMetadata; a caller-supplied display/legacy
609
+ // policySource must never substitute a different allowlisted identity.
610
+ const policySource = registrySource ?? source;
611
+ const policyKind = registrySource ? "registry" : materialized.kind;
612
+ const managedPolicy = resolvePluginManagedPolicy(opts);
613
+ let registryAuthority = null;
614
+ if (registrySource) {
615
+ registryAuthority = assertRegistryResolutionAuthority(
616
+ opts.registryResolutionAuthority,
617
+ {
618
+ registryUrl: registrySource,
619
+ source,
620
+ ref: materialized.git?.ref || null,
621
+ },
622
+ );
623
+ if (
624
+ opts.sourceMetadata.resolvedSource !== registryAuthority.declaredSource ||
625
+ (opts.sourceMetadata.ref || null) !== (registryAuthority.ref || null)
626
+ ) {
627
+ throw new Error(
628
+ "registry source metadata does not match materialized source",
629
+ );
630
+ }
631
+ if (
632
+ !opts.sourceMetadata.catalogAuthority ||
633
+ typeof opts.sourceMetadata.catalogAuthority !== "object" ||
634
+ Array.isArray(opts.sourceMetadata.catalogAuthority)
635
+ ) {
636
+ throw new Error("registry source metadata requires catalog authority");
637
+ }
638
+ const declaredDocumentSha256 =
639
+ opts.sourceMetadata.catalogAuthority?.registryDocumentSha256 || null;
640
+ if (
641
+ declaredDocumentSha256 &&
642
+ declaredDocumentSha256 !== registryAuthority.documentSha256
643
+ ) {
644
+ throw new Error(
645
+ "registry source authority does not match catalog document digest",
646
+ );
647
+ }
648
+ if (registryAuthority.sourceType === "archive") {
649
+ const archive = opts.sourceMetadata.catalogAuthority?.archiveSource;
650
+ if (
651
+ !archive ||
652
+ archive.archiveSha256 !== registryAuthority.archiveSha256 ||
653
+ archive.payloadSha256 !== registryAuthority.payloadSha256
654
+ ) {
655
+ throw new Error(
656
+ "registry source authority does not match archive digest authority",
657
+ );
658
+ }
659
+ }
660
+ }
661
+ // Resolve managed identity before exists/clone/cache/process work. The
662
+ // manifest name is checked again by installFromDirectory after materializing
663
+ // bytes; source policy cannot wait for that later gate.
664
+ const decision = enforcePluginSourcePolicy(policySource, managedPolicy, {
665
+ action,
666
+ cwd: opts.cwd,
667
+ kindHint: policyKind,
668
+ });
669
+ const policyIdentity = canonicalizePluginSource(policySource, {
670
+ cwd: opts.cwd,
671
+ kindHint: policyKind,
672
+ });
673
+ const identityDigest = (identity) =>
674
+ crypto
675
+ .createHash("sha256")
676
+ .update(
677
+ canonicalJson({
678
+ identity: identity.identityDigest || identity.key,
679
+ ref: identity.ref ?? null,
680
+ path: identity.path ?? null,
681
+ }),
682
+ )
683
+ .digest("hex");
684
+ const sourceAuthority = {
685
+ policyDigest: decision.policyDigest || null,
686
+ sourceDigest: identityDigest(policyIdentity),
687
+ resolvedSourceDigest: registrySource
688
+ ? registryResolvedSourceAuthorityDigest(registryAuthority)
689
+ : null,
690
+ registryDocumentSha256: registryAuthority?.documentSha256 || null,
691
+ };
692
+ const preflight = {
693
+ decision,
694
+ materialized,
695
+ sourceAuthority,
696
+ };
697
+ const boundSourceMetadata = opts.sourceMetadata
698
+ ? bindSourcePolicyAuthority(opts.sourceMetadata, {
699
+ [SOURCE_POLICY_PREFLIGHT]: preflight,
700
+ })
701
+ : null;
702
+ const normalizedSourceMetadata = boundSourceMetadata
703
+ ? normalizeSourceMetadata(boundSourceMetadata)
704
+ : null;
705
+ if (opts.sourceMetadata && !normalizedSourceMetadata) {
706
+ throw new Error("plugin source metadata is invalid");
707
+ }
708
+ return {
709
+ ...opts,
710
+ managedPolicy,
711
+ ...(normalizedSourceMetadata
712
+ ? { sourceMetadata: normalizedSourceMetadata }
713
+ : {}),
714
+ [SOURCE_POLICY_PREFLIGHT]: preflight,
715
+ };
716
+ }
717
+
718
+ function bindSourcePolicyAuthority(sourceMetadata, opts) {
719
+ const authority = opts[SOURCE_POLICY_PREFLIGHT]?.sourceAuthority;
720
+ if (!authority || !sourceMetadata) return sourceMetadata;
721
+ return {
722
+ ...sourceMetadata,
723
+ policyDigest: authority.policyDigest,
724
+ sourceDigest: authority.sourceDigest,
725
+ ...(authority.resolvedSourceDigest
726
+ ? { resolvedSourceDigest: authority.resolvedSourceDigest }
727
+ : {}),
728
+ ...(authority.registryDocumentSha256 && sourceMetadata.catalogAuthority
729
+ ? {
730
+ catalogAuthority: {
731
+ ...sourceMetadata.catalogAuthority,
732
+ registryDocumentSha256: authority.registryDocumentSha256,
733
+ },
734
+ }
735
+ : {}),
736
+ };
533
737
  }
534
738
 
535
739
  /**
@@ -538,12 +742,21 @@ export function installFromSource(source, opts = {}) {
538
742
  * Single fetch shared by installFromSource + updatePlugin.
539
743
  */
540
744
  function _withMaterializedSource(source, fn, opts = {}) {
541
- const raw = String(source || "");
542
- const asDir = path.resolve(raw);
543
- if (_deps.existsSync(asDir) && _deps.lstatSync(asDir).isDirectory()) {
544
- return fn(asDir, null);
745
+ const materialized =
746
+ opts[SOURCE_POLICY_PREFLIGHT]?.materialized ||
747
+ describeMaterializedSource(source, opts);
748
+ if (materialized.kind === "directory") {
749
+ if (
750
+ _deps.existsSync(materialized.path) &&
751
+ _deps.lstatSync(materialized.path).isDirectory()
752
+ ) {
753
+ return fn(materialized.path, null);
754
+ }
755
+ throw new Error(
756
+ `source directory does not exist: ${redactPluginSourceForDisplay(materialized.path)}`,
757
+ );
545
758
  }
546
- const git = parseGitSource(raw);
759
+ const git = materialized.git;
547
760
  if (git) {
548
761
  if (opts.offline === true) {
549
762
  const cached = readMarketplaceSourceCache(opts.sourceMetadata, {
@@ -562,7 +775,7 @@ function _withMaterializedSource(source, fn, opts = {}) {
562
775
  }
563
776
  return result;
564
777
  }
565
- const cloned = fetchGitRepo(git.url, git.ref);
778
+ const cloned = fetchGitRepo(git.url, git.ref, { cwd: opts.cwd });
566
779
  try {
567
780
  const result = fn(cloned, git);
568
781
  if (result && typeof result === "object") {
@@ -592,9 +805,7 @@ function _withMaterializedSource(source, fn, opts = {}) {
592
805
  }
593
806
  }
594
807
  }
595
- throw new Error(
596
- `source not found as a local directory or git URL: ${source}`,
597
- );
808
+ throw new Error("source is neither a local directory nor a git URL");
598
809
  }
599
810
 
600
811
  /**
@@ -606,6 +817,7 @@ function _withMaterializedSource(source, fn, opts = {}) {
606
817
  * Returns { name, version, previousVersion, updated, reinstalled }.
607
818
  */
608
819
  export function updatePlugin(source, opts = {}) {
820
+ opts = preflightManagedSource(source, opts, "upgrade");
609
821
  const scope = opts.scope || "user";
610
822
  const updateMaterialized = (dir, info) => {
611
823
  const manifest = parsePluginManifest(dir);
@@ -627,8 +839,7 @@ export function updatePlugin(source, opts = {}) {
627
839
  updatePlugin(source, {
628
840
  ...opts,
629
841
  _lifecycleLock: lifecycleLock,
630
- _materializedDir: dir,
631
- _materializedGitInfo: info,
842
+ [UPDATE_MATERIALIZATION]: { dir, info },
632
843
  }),
633
844
  );
634
845
  }
@@ -640,11 +851,21 @@ export function updatePlugin(source, opts = {}) {
640
851
  assertNoRetainedInstallRecovery(name, { scope, cwd: opts.cwd });
641
852
  assertExpectedPluginIdentity(name, version, opts.expectedIdentity);
642
853
  const sourceMetadata =
643
- normalizeSourceMetadata(opts.sourceMetadata) ||
644
854
  normalizeSourceMetadata(
645
- info
646
- ? { type: "git", source: info.url, ref: info.ref || null }
647
- : { type: "local", source: path.resolve(String(source || "")) },
855
+ bindSourcePolicyAuthority(opts.sourceMetadata, opts),
856
+ ) ||
857
+ normalizeSourceMetadata(
858
+ bindSourcePolicyAuthority(
859
+ info
860
+ ? { type: "git", source: info.url, ref: info.ref || null }
861
+ : {
862
+ type: "local",
863
+ source:
864
+ opts[SOURCE_POLICY_PREFLIGHT]?.materialized?.path ||
865
+ path.resolve(opts.cwd || process.cwd(), String(source || "")),
866
+ },
867
+ opts,
868
+ ),
648
869
  );
649
870
  const previousVersion = getActiveVersion(name, { scope, cwd: opts.cwd });
650
871
  assertReplacementApprovals({
@@ -799,10 +1020,10 @@ export function updatePlugin(source, opts = {}) {
799
1020
  transferPendingTransaction(res, result);
800
1021
  return result;
801
1022
  };
802
- if (opts._materializedDir) {
1023
+ if (opts[UPDATE_MATERIALIZATION]) {
803
1024
  return updateMaterialized(
804
- opts._materializedDir,
805
- opts._materializedGitInfo || null,
1025
+ opts[UPDATE_MATERIALIZATION].dir,
1026
+ opts[UPDATE_MATERIALIZATION].info || null,
806
1027
  );
807
1028
  }
808
1029
  return _withMaterializedSource(source, updateMaterialized, opts);
@@ -1570,19 +1791,158 @@ function rollbackRecoveredWholeNameUninstall(transaction) {
1570
1791
  * not remote-looking. `owner/repo` expands to a GitHub HTTPS URL.
1571
1792
  */
1572
1793
  export function parseGitSource(raw) {
1573
- const [loc, ref] = String(raw || "").split("#");
1574
- if (!loc) return null;
1575
- if (
1576
- /^(https?|git|ssh|file):\/\//.test(loc) ||
1577
- loc.endsWith(".git") ||
1578
- /^git@/.test(loc)
1579
- ) {
1580
- return { url: loc, ref: ref || null };
1794
+ return parsePluginGitSource(raw);
1795
+ }
1796
+
1797
+ function isPathWithin(child, root) {
1798
+ const relative = path.relative(root, child);
1799
+ return (
1800
+ relative === "" ||
1801
+ (!path.isAbsolute(relative) &&
1802
+ relative !== ".." &&
1803
+ !relative.startsWith(`..${path.sep}`))
1804
+ );
1805
+ }
1806
+
1807
+ function validateTrustedExecutable(value, cwd) {
1808
+ if (typeof value !== "string" || !path.isAbsolute(value)) return null;
1809
+ try {
1810
+ const executable = fs.realpathSync.native(value);
1811
+ const stat = fs.statSync(executable);
1812
+ if (!stat.isFile()) return null;
1813
+ if (process.platform !== "win32" && (Number(stat.mode) & 0o111) === 0) {
1814
+ return null;
1815
+ }
1816
+ if (isPathWithin(executable, path.resolve(cwd))) return null;
1817
+ return executable;
1818
+ } catch {
1819
+ return null;
1581
1820
  }
1582
- if (/^[\w.-]+\/[\w.-]+$/.test(loc)) {
1583
- return { url: `https://github.com/${loc}.git`, ref: ref || null };
1821
+ }
1822
+
1823
+ function resolveTrustedExecutable({
1824
+ cwd,
1825
+ executableName,
1826
+ configured,
1827
+ configuredLabel,
1828
+ injected = null,
1829
+ cached = null,
1830
+ }) {
1831
+ if (injected != null) {
1832
+ const trusted = validateTrustedExecutable(injected, cwd);
1833
+ if (!trusted)
1834
+ throw new Error(`configured ${configuredLabel} is not trusted`);
1835
+ return trusted;
1836
+ }
1837
+ const trustedCached = validateTrustedExecutable(cached, cwd);
1838
+ if (trustedCached) return trustedCached;
1839
+ if (configured != null) {
1840
+ const trusted = validateTrustedExecutable(configured, cwd);
1841
+ if (!trusted) {
1842
+ throw new Error(`${configuredLabel} must be a trusted absolute file`);
1843
+ }
1844
+ return trusted;
1584
1845
  }
1585
- return null;
1846
+ for (const rawEntry of String(process.env.PATH || "").split(path.delimiter)) {
1847
+ const entry = rawEntry.replace(/^"|"$/gu, "");
1848
+ if (!entry || !path.isAbsolute(entry)) continue;
1849
+ const trusted = validateTrustedExecutable(
1850
+ path.join(entry, executableName),
1851
+ cwd,
1852
+ );
1853
+ if (trusted) return trusted;
1854
+ }
1855
+ throw new Error(
1856
+ `trusted ${configuredLabel} was not found on an absolute PATH entry`,
1857
+ );
1858
+ }
1859
+
1860
+ function resolveTrustedGitExecutable(cwd) {
1861
+ const executable = resolveTrustedExecutable({
1862
+ cwd,
1863
+ executableName: process.platform === "win32" ? "git.exe" : "git",
1864
+ configured: process.env.CHAINLESSCHAIN_GIT_BIN,
1865
+ configuredLabel: "Git executable",
1866
+ injected: _deps.gitExecutable,
1867
+ cached: cachedTrustedGitExecutable,
1868
+ });
1869
+ cachedTrustedGitExecutable = executable;
1870
+ return executable;
1871
+ }
1872
+
1873
+ function resolveTrustedSshExecutable(cwd) {
1874
+ const executable = resolveTrustedExecutable({
1875
+ cwd,
1876
+ executableName: process.platform === "win32" ? "ssh.exe" : "ssh",
1877
+ configured: process.env.CHAINLESSCHAIN_SSH_BIN,
1878
+ configuredLabel: "SSH executable",
1879
+ cached: cachedTrustedSshExecutable,
1880
+ });
1881
+ cachedTrustedSshExecutable = executable;
1882
+ return executable;
1883
+ }
1884
+
1885
+ function hardenedPluginGitEnvironment() {
1886
+ const environment = {};
1887
+ for (const [key, value] of Object.entries(process.env)) {
1888
+ if (!key.toUpperCase().startsWith("GIT_")) environment[key] = value;
1889
+ }
1890
+ return {
1891
+ ...environment,
1892
+ GIT_ATTR_NOSYSTEM: "1",
1893
+ GIT_CONFIG_COUNT: "0",
1894
+ GIT_CONFIG_GLOBAL: NULL_DEVICE,
1895
+ GIT_CONFIG_NOSYSTEM: "1",
1896
+ GIT_EXTERNAL_DIFF: "",
1897
+ GIT_OPTIONAL_LOCKS: "0",
1898
+ GIT_PAGER: "",
1899
+ GIT_TERMINAL_PROMPT: "0",
1900
+ LC_ALL: "C",
1901
+ };
1902
+ }
1903
+
1904
+ function quoteSshCommandArgument(value) {
1905
+ if (process.platform === "win32") {
1906
+ return `"${String(value).replace(/"/gu, '\\"')}"`;
1907
+ }
1908
+ return `'${String(value).replace(/'/gu, `'\\''`)}'`;
1909
+ }
1910
+
1911
+ function hardenedPluginGitArgs(args, { url, cwd }) {
1912
+ const parsed = parsePluginGitSource(url);
1913
+ const sshTransport =
1914
+ /^ssh:\/\//iu.test(parsed?.url || "") ||
1915
+ (!(parsed?.url || "").includes("://") &&
1916
+ /^(?:[^@\s/:]+@)?(?:\[[^\]]+\]|[^\s/:]+):/u.test(parsed?.url || "") &&
1917
+ !/^[a-z]:[\\/]/iu.test(parsed?.url || ""));
1918
+ const sshArgs = sshTransport
1919
+ ? [
1920
+ "-c",
1921
+ `core.sshCommand=${quoteSshCommandArgument(resolveTrustedSshExecutable(cwd))} -F ${quoteSshCommandArgument(NULL_DEVICE)} -o BatchMode=yes -o ClearAllForwardings=yes -o PermitLocalCommand=no -o ProxyCommand=none -o CanonicalizeHostname=no`,
1922
+ ]
1923
+ : [];
1924
+ const fileTransport = /^file:\/\//iu.test(parsed?.url || "");
1925
+ return [
1926
+ "--no-pager",
1927
+ "-c",
1928
+ `core.hooksPath=${NULL_DEVICE}`,
1929
+ "-c",
1930
+ "core.fsmonitor=false",
1931
+ "-c",
1932
+ "core.untrackedCache=false",
1933
+ "-c",
1934
+ "credential.helper=",
1935
+ "-c",
1936
+ "diff.external=",
1937
+ "-c",
1938
+ "http.followRedirects=false",
1939
+ "-c",
1940
+ "protocol.ext.allow=never",
1941
+ "-c",
1942
+ `protocol.file.allow=${fileTransport ? "always" : "never"}`,
1943
+ ...sshArgs,
1944
+ ...args,
1945
+ ];
1586
1946
  }
1587
1947
 
1588
1948
  /**
@@ -1590,30 +1950,46 @@ export function parseGitSource(raw) {
1590
1950
  * checkout path. Uses `git` via spawn WITHOUT a shell (url/ref are argv, not a
1591
1951
  * command line — no injection). Caller removes the temp dir's parent.
1592
1952
  */
1593
- export function fetchGitRepo(url, ref) {
1953
+ export function fetchGitRepo(url, ref, options = {}) {
1594
1954
  // git argv-injection guard: a value starting with "-" is parsed by git as an
1595
1955
  // OPTION, not a URL/ref — e.g. a registry-supplied ref "-f" reaches
1596
1956
  // `git checkout <ref>` on the full-clone retry path, and an option-looking
1597
1957
  // url reaches `git clone`. Real git URLs/refs never start with "-"
1598
1958
  // (check-ref-format forbids it), so reject instead of trying to escape.
1599
1959
  if (String(url).startsWith("-")) {
1600
- throw new Error(`refusing git source that looks like an option: ${url}`);
1960
+ throw new Error("refusing git source that looks like an option");
1601
1961
  }
1602
1962
  if (ref != null && String(ref).startsWith("-")) {
1603
- throw new Error(`refusing git ref that looks like an option: ${ref}`);
1963
+ throw new Error("refusing git ref that looks like an option");
1604
1964
  }
1965
+ const git = parsePluginGitSource(ref ? `${url}#${ref}` : url);
1966
+ assertPluginGitTransportSafe(git, { allowFile: true });
1967
+ const cwd = path.resolve(options.cwd || process.cwd());
1968
+ const executable = resolveTrustedGitExecutable(cwd);
1605
1969
  const base = _deps.mkdtempSync(path.join(os.tmpdir(), "cc-plugin-git-"));
1606
1970
  const dir = path.join(base, "repo");
1607
- const run = (args) =>
1608
- _deps.spawnSync("git", args, {
1609
- encoding: "utf8",
1610
- timeout: 120000,
1611
- windowsHide: true,
1612
- origin: "plugin:install-git",
1613
- policy: "allow",
1614
- scope: "plugin-install",
1615
- shell: false,
1616
- });
1971
+ const run = (args) => {
1972
+ const hardenedArgs = hardenedPluginGitArgs(args, { url, cwd });
1973
+ const auditRedactArgIndexes = hardenedArgs.flatMap((argument, index) =>
1974
+ argument === url || (ref != null && argument === ref) ? [index] : [],
1975
+ );
1976
+ try {
1977
+ return _deps.spawnSync(executable, hardenedArgs, {
1978
+ cwd: base,
1979
+ encoding: "utf8",
1980
+ env: hardenedPluginGitEnvironment(),
1981
+ timeout: 120000,
1982
+ windowsHide: true,
1983
+ origin: "plugin:install-git",
1984
+ policy: "allow",
1985
+ scope: "plugin-install",
1986
+ shell: false,
1987
+ auditRedactArgIndexes,
1988
+ });
1989
+ } catch (error) {
1990
+ return { error, status: null, stdout: "", stderr: "" };
1991
+ }
1992
+ };
1617
1993
 
1618
1994
  // Reduce host-specific checkout differences before the v2 inventory hashes
1619
1995
  // the materialized staged bytes. Repository .gitattributes may still define
@@ -1636,6 +2012,11 @@ export function fetchGitRepo(url, ref) {
1636
2012
  if (res.error && res.error.code === "ENOENT") {
1637
2013
  throw new Error("git is not installed (needed to fetch a remote plugin)");
1638
2014
  }
2015
+ if (res.error) {
2016
+ throw new Error(
2017
+ `git process failed for ${redactPluginSourceForDisplay(url)}`,
2018
+ );
2019
+ }
1639
2020
  if (res.status !== 0) {
1640
2021
  // A commit SHA can't be used with --branch/--depth; retry with a full clone
1641
2022
  // then checkout the ref explicitly.
@@ -1651,8 +2032,9 @@ export function fetchGitRepo(url, ref) {
1651
2032
  if (co.status === 0) return dir;
1652
2033
  }
1653
2034
  }
1654
- const reason = (res.stderr || "").trim() || `git exited ${res.status}`;
1655
- throw new Error(`git clone failed for ${url}: ${reason}`);
2035
+ throw new Error(
2036
+ `git clone failed for ${redactPluginSourceForDisplay(url)} (git exited ${res.status})`,
2037
+ );
1656
2038
  }
1657
2039
  return dir;
1658
2040
  }
@@ -2987,13 +3369,19 @@ function sameSourceAuthority(current, candidate) {
2987
3369
  if (current?.type !== candidate?.type) return false;
2988
3370
  if (current.type === "registry") {
2989
3371
  return Boolean(
2990
- current.registry &&
2991
- candidate.registry &&
2992
- current.registry === candidate.registry,
3372
+ current.sourceDigest &&
3373
+ candidate.sourceDigest &&
3374
+ current.sourceDigest === candidate.sourceDigest &&
3375
+ current.resolvedSourceDigest &&
3376
+ candidate.resolvedSourceDigest &&
3377
+ current.resolvedSourceDigest === candidate.resolvedSourceDigest,
2993
3378
  );
2994
3379
  }
2995
- if (current.type === "git") return current.source === candidate.source;
2996
- return current.type === "local" && current.source === candidate.source;
3380
+ return Boolean(
3381
+ current.sourceDigest &&
3382
+ candidate.sourceDigest &&
3383
+ current.sourceDigest === candidate.sourceDigest,
3384
+ );
2997
3385
  }
2998
3386
 
2999
3387
  function effectivePluginRecord(
@@ -3229,23 +3617,119 @@ function normalizeSourceMetadata(value) {
3229
3617
  type,
3230
3618
  source,
3231
3619
  ref: cleanBounded(value.ref, 256),
3620
+ sourceDigest:
3621
+ normalizeSourceAuthorityDigest(value.sourceDigest) ||
3622
+ pluginSourceAuthorityDigest(value.source, type, value.ref),
3232
3623
  };
3233
3624
  const registry = sanitizeSource(value.registry);
3234
3625
  const resolvedSource = sanitizeSource(value.resolvedSource);
3235
3626
  const packageName = cleanBounded(value.package, 256);
3627
+ const catalogAuthority =
3628
+ value.catalogAuthority != null
3629
+ ? normalizeCatalogAuthority(value.catalogAuthority)
3630
+ : null;
3236
3631
  if (registry) metadata.registry = registry;
3237
3632
  if (resolvedSource) metadata.resolvedSource = resolvedSource;
3633
+ const resolvedSourceDigest =
3634
+ normalizeSourceAuthorityDigest(value.resolvedSourceDigest) ||
3635
+ (value.resolvedSource
3636
+ ? catalogAuthority?.archiveSource
3637
+ ? archiveSourceAuthorityDigest(
3638
+ value.resolvedSource,
3639
+ catalogAuthority.archiveSource,
3640
+ )
3641
+ : pluginSourceAuthorityDigest(value.resolvedSource, "git", value.ref)
3642
+ : null);
3643
+ const policyDigest = normalizeSourceAuthorityDigest(value.policyDigest);
3644
+ if (resolvedSourceDigest) {
3645
+ metadata.resolvedSourceDigest = resolvedSourceDigest;
3646
+ }
3647
+ if (policyDigest) metadata.policyDigest = policyDigest;
3238
3648
  if (packageName) metadata.package = packageName;
3239
3649
  if (value.offline === true) metadata.offline = true;
3240
- if (value.catalogAuthority != null) {
3241
- metadata.catalogAuthority = normalizeCatalogAuthority(
3242
- value.catalogAuthority,
3243
- );
3244
- }
3650
+ if (catalogAuthority) metadata.catalogAuthority = catalogAuthority;
3245
3651
  assertCatalogRemoteArtifactBindings(metadata);
3652
+ assertCatalogArchiveSourceBindings(metadata);
3246
3653
  return metadata;
3247
3654
  }
3248
3655
 
3656
+ function assertCatalogArchiveSourceBindings(metadata) {
3657
+ const archive = metadata.catalogAuthority?.archiveSource;
3658
+ if (!archive) return;
3659
+ if (
3660
+ metadata.type !== "registry" ||
3661
+ !metadata.registry ||
3662
+ !metadata.resolvedSource
3663
+ ) {
3664
+ throw new Error(
3665
+ "marketplace archive authority requires registry and resolved source metadata",
3666
+ );
3667
+ }
3668
+ let registryOrigin;
3669
+ try {
3670
+ registryOrigin = new URL(metadata.registry).origin;
3671
+ } catch {
3672
+ throw new Error("marketplace archive registry URL is invalid");
3673
+ }
3674
+ if (
3675
+ archive.registryOrigin !== registryOrigin ||
3676
+ archive.url !== metadata.resolvedSource
3677
+ ) {
3678
+ throw new Error(
3679
+ "marketplace archive authority does not match the selected registry and source",
3680
+ );
3681
+ }
3682
+ }
3683
+
3684
+ function normalizeSourceAuthorityDigest(value) {
3685
+ const normalized = cleanBounded(value, 64)?.toLowerCase() || null;
3686
+ return normalized && /^[a-f0-9]{64}$/u.test(normalized) ? normalized : null;
3687
+ }
3688
+
3689
+ function pluginSourceAuthorityDigest(source, type, ref = null) {
3690
+ const identity = canonicalizePluginSource(source, {
3691
+ kindHint:
3692
+ type === "registry" ? "registry" : type === "local" ? "directory" : "git",
3693
+ ref: ref || undefined,
3694
+ });
3695
+ return crypto
3696
+ .createHash("sha256")
3697
+ .update(
3698
+ canonicalJson({
3699
+ identity: identity.identityDigest || identity.key,
3700
+ ref: identity.ref ?? null,
3701
+ path: identity.path ?? null,
3702
+ }),
3703
+ )
3704
+ .digest("hex");
3705
+ }
3706
+
3707
+ function archiveSourceAuthorityDigest(source, archive) {
3708
+ return crypto
3709
+ .createHash("sha256")
3710
+ .update(
3711
+ canonicalJson({
3712
+ kind: "archive",
3713
+ source: sanitizeSource(source),
3714
+ archiveSha256: archive.archiveSha256,
3715
+ payloadSha256: archive.payloadSha256,
3716
+ }),
3717
+ )
3718
+ .digest("hex");
3719
+ }
3720
+
3721
+ function registryResolvedSourceAuthorityDigest(authority) {
3722
+ if (!authority) return null;
3723
+ if (authority.sourceType === "archive") {
3724
+ return archiveSourceAuthorityDigest(authority.declaredSource, authority);
3725
+ }
3726
+ return pluginSourceAuthorityDigest(
3727
+ authority.declaredSource,
3728
+ "git",
3729
+ authority.ref,
3730
+ );
3731
+ }
3732
+
3249
3733
  function normalizeMigrationIssuedAt(value) {
3250
3734
  const date = value == null ? new Date() : new Date(String(value));
3251
3735
  if (!Number.isFinite(date.getTime())) {
@@ -3545,6 +4029,7 @@ function normalizeCatalogAuthority(value) {
3545
4029
  const remoteArtifactEvidence = normalizeRemoteArtifactEvidence(
3546
4030
  value.remoteArtifactEvidence,
3547
4031
  );
4032
+ const archiveSource = normalizeArchiveSourceAuthority(value.archiveSource);
3548
4033
  const remoteSbomPayloadComparison = assertRemoteSbomPayloadComparison(
3549
4034
  value.remoteSbomPayloadComparison,
3550
4035
  {
@@ -3651,6 +4136,7 @@ function normalizeCatalogAuthority(value) {
3651
4136
  ...(registryNetworkAuthority ? { registryNetworkAuthority } : {}),
3652
4137
  ...(artifactExpectations ? { artifactExpectations } : {}),
3653
4138
  ...(remoteArtifactEvidence ? { remoteArtifactEvidence } : {}),
4139
+ ...(archiveSource ? { archiveSource } : {}),
3654
4140
  ...(remoteSbomPayloadComparison ? { remoteSbomPayloadComparison } : {}),
3655
4141
  ...(publisherDeclaration ? { publisherDeclaration } : {}),
3656
4142
  ...(publisherAuthority ? { publisherAuthority } : {}),
@@ -3661,6 +4147,72 @@ function normalizeCatalogAuthority(value) {
3661
4147
  };
3662
4148
  }
3663
4149
 
4150
+ function normalizeArchiveSourceAuthority(value) {
4151
+ if (value == null) return null;
4152
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
4153
+ throw new Error("catalogAuthority.archiveSource must be an object");
4154
+ }
4155
+ const url = cleanBounded(value.url, 4096);
4156
+ const registryOrigin = cleanBounded(value.registryOrigin, 4096);
4157
+ const archiveSha256 = cleanBounded(value.archiveSha256, 64);
4158
+ const payloadSha256 = cleanBounded(value.payloadSha256, 64);
4159
+ let archiveUrl;
4160
+ let selectedOrigin;
4161
+ try {
4162
+ archiveUrl = new URL(url);
4163
+ selectedOrigin = new URL(registryOrigin).origin;
4164
+ } catch {
4165
+ throw new Error("catalogAuthority.archiveSource URL authority is invalid");
4166
+ }
4167
+ if (
4168
+ archiveUrl.origin !== selectedOrigin ||
4169
+ archiveUrl.username ||
4170
+ archiveUrl.password
4171
+ ) {
4172
+ throw new Error(
4173
+ "catalogAuthority.archiveSource must use the selected registry origin",
4174
+ );
4175
+ }
4176
+ if (
4177
+ value.schemaVersion !== "cc-plugin-marketplace-archive-source/v1" ||
4178
+ value.status !== "digest-verified-and-extracted" ||
4179
+ !/^[a-f0-9]{64}$/u.test(archiveSha256 || "") ||
4180
+ !/^[a-f0-9]{64}$/u.test(payloadSha256 || "")
4181
+ ) {
4182
+ throw new Error(
4183
+ "catalogAuthority.archiveSource digest authority is invalid",
4184
+ );
4185
+ }
4186
+ const compressedBytes = Number(value.compressedBytes);
4187
+ const fileCount = Number(value.fileCount);
4188
+ const totalBytes = Number(value.totalBytes);
4189
+ if (
4190
+ !Number.isSafeInteger(compressedBytes) ||
4191
+ compressedBytes <= 0 ||
4192
+ compressedBytes > 64 * 1024 * 1024 ||
4193
+ !Number.isSafeInteger(fileCount) ||
4194
+ fileCount <= 0 ||
4195
+ fileCount > 10_000 ||
4196
+ !Number.isSafeInteger(totalBytes) ||
4197
+ totalBytes <= 0 ||
4198
+ totalBytes > 256 * 1024 * 1024
4199
+ ) {
4200
+ throw new Error("catalogAuthority.archiveSource size authority is invalid");
4201
+ }
4202
+ return {
4203
+ schemaVersion: value.schemaVersion,
4204
+ status: value.status,
4205
+ url: archiveUrl.href,
4206
+ registryOrigin: selectedOrigin,
4207
+ archiveSha256,
4208
+ payloadSha256,
4209
+ compressedBytes,
4210
+ fileCount,
4211
+ totalBytes,
4212
+ fromCache: value.fromCache === true,
4213
+ };
4214
+ }
4215
+
3664
4216
  function normalizeArtifactExpectations(value) {
3665
4217
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3666
4218
  const digest = (candidate, label) => {
@@ -3928,16 +4480,7 @@ function sanitizeSource(value, preservePath = false) {
3928
4480
  const raw = cleanBounded(value, 4096);
3929
4481
  if (!raw) return null;
3930
4482
  if (preservePath) return raw;
3931
- try {
3932
- const parsed = new URL(raw);
3933
- parsed.username = "";
3934
- parsed.password = "";
3935
- parsed.search = "";
3936
- parsed.hash = "";
3937
- return parsed.toString();
3938
- } catch {
3939
- return raw;
3940
- }
4483
+ return redactPluginSourceForDisplay(raw);
3941
4484
  }
3942
4485
 
3943
4486
  function cleanBounded(value, max) {