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
@@ -0,0 +1,850 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const CONTROL_RE = /[\u0000-\u001f\u007f]/u;
8
+ const CONTROL_GLOBAL_RE = /[\u0000-\u001f\u007f]/gu;
9
+ const OWNER_RE = /^[a-z0-9](?:[a-z0-9._-]{0,99})$/iu;
10
+ const SUPPORTED_GIT_SCHEMES = new Set([
11
+ "http:",
12
+ "https:",
13
+ "git:",
14
+ "ssh:",
15
+ "file:",
16
+ ]);
17
+ const SOURCE_KINDS = new Set([
18
+ "github",
19
+ "git",
20
+ "url",
21
+ "registry",
22
+ "file",
23
+ "directory",
24
+ ]);
25
+ const SCP_RE = /^(?:([^@\s/:]+)@)?(\[[^\]]+\]|[^\s/:]+):(.+)$/u;
26
+
27
+ function sha256(value) {
28
+ return createHash("sha256").update(String(value)).digest("hex");
29
+ }
30
+
31
+ function normalizeSshPrincipal(raw, { decodePercent = true } = {}) {
32
+ if (raw == null || raw === "") return null;
33
+ let decoded;
34
+ try {
35
+ decoded = decodePercent ? decodeURIComponent(String(raw)) : String(raw);
36
+ } catch {
37
+ throw invalid("SSH principal has invalid percent encoding");
38
+ }
39
+ if (
40
+ decoded !== decoded.trim() ||
41
+ /\s/u.test(decoded) ||
42
+ /[@:/\\]/u.test(decoded)
43
+ ) {
44
+ throw invalid("SSH principal contains whitespace or ambiguous separators");
45
+ }
46
+ const principal = clean(decoded, "SSH principal", 256);
47
+ return principal;
48
+ }
49
+
50
+ function invalid(message) {
51
+ const error = new Error(
52
+ `managed plugin source policy is invalid: ${message}`,
53
+ );
54
+ error.code = "PLUGIN_SOURCE_POLICY_INVALID";
55
+ return error;
56
+ }
57
+
58
+ function clean(value, label = "source", max = 4096) {
59
+ if (typeof value !== "string") throw invalid(`${label} must be a string`);
60
+ const result = value.trim();
61
+ if (!result || result.length > max || CONTROL_RE.test(result)) {
62
+ throw invalid(`${label} is empty, too long, or contains control bytes`);
63
+ }
64
+ return result;
65
+ }
66
+
67
+ function splitRef(raw) {
68
+ const source = clean(raw);
69
+ const index = source.indexOf("#");
70
+ if (index < 0) return { locator: source, ref: null };
71
+ if (source.indexOf("#", index + 1) >= 0) {
72
+ throw invalid("source contains more than one unescaped ref separator");
73
+ }
74
+ const locator = clean(source.slice(0, index), "source locator");
75
+ const rawRef = source.slice(index + 1);
76
+ return {
77
+ locator,
78
+ ref: rawRef ? clean(rawRef, "source ref", 256) : null,
79
+ };
80
+ }
81
+
82
+ function isExplicitPath(locator) {
83
+ return /^(?:\.{0,2}[\\/]|[a-z]:|\\\\)/iu.test(locator);
84
+ }
85
+
86
+ function normalizeHost(raw) {
87
+ const host = clean(raw, "repository host", 253)
88
+ .replace(/^\[|\]$/gu, "")
89
+ .toLowerCase();
90
+ const withoutRootDot = host.replace(/\.+$/u, "");
91
+ if (!withoutRootDot) throw invalid("repository host is invalid");
92
+ try {
93
+ const authority = withoutRootDot.includes(":")
94
+ ? `[${withoutRootDot}]`
95
+ : withoutRootDot;
96
+ const normalized = new URL(`http://${authority}/`).hostname
97
+ .replace(/^\[|\]$/gu, "")
98
+ .replace(/\.+$/u, "")
99
+ .toLowerCase();
100
+ const mapped = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/iu.exec(
101
+ normalized,
102
+ );
103
+ if (mapped) {
104
+ const high = Number.parseInt(mapped[1], 16);
105
+ const low = Number.parseInt(mapped[2], 16);
106
+ return [high >>> 8, high & 0xff, low >>> 8, low & 0xff].join(".");
107
+ }
108
+ return normalized;
109
+ } catch {
110
+ throw invalid("repository host is invalid");
111
+ }
112
+ }
113
+
114
+ function normalizePort(scheme, port) {
115
+ const value = String(port || "");
116
+ const defaults = { http: "80", https: "443", ssh: "22", git: "9418" };
117
+ return defaults[scheme] === value ? "" : value;
118
+ }
119
+
120
+ function formatAuthority(host, port = "") {
121
+ const renderedHost = host.includes(":") ? `[${host}]` : host;
122
+ return port ? `${renderedHost}:${port}` : renderedHost;
123
+ }
124
+
125
+ function normalizeUrlPathSegment(segment) {
126
+ return String(segment).replace(/%([0-9a-f]{2})/giu, (_match, hex) => {
127
+ const character = String.fromCharCode(Number.parseInt(hex, 16));
128
+ return /[a-z0-9._~-]/iu.test(character)
129
+ ? character
130
+ : `%${hex.toUpperCase()}`;
131
+ });
132
+ }
133
+
134
+ function normalizeUrlPathname(pathname) {
135
+ return String(pathname)
136
+ .split("/")
137
+ .map((segment) => normalizeUrlPathSegment(segment))
138
+ .join("/");
139
+ }
140
+
141
+ function decodeRepoSegments(
142
+ host,
143
+ rawPath,
144
+ { decodePercent = true, normalizeUrlPercent = false } = {},
145
+ ) {
146
+ const hosted = host === "github.com" || host === "gitlab.com";
147
+ const segments = String(rawPath || "")
148
+ .replace(/^\/+|\/+$/gu, "")
149
+ .split("/")
150
+ .map((segment) => {
151
+ let decoded;
152
+ try {
153
+ decoded = decodeURIComponent(segment);
154
+ } catch {
155
+ throw invalid("repository path has invalid percent encoding");
156
+ }
157
+ if (
158
+ !decoded ||
159
+ decoded === "." ||
160
+ decoded === ".." ||
161
+ /[\\/]/u.test(decoded) ||
162
+ CONTROL_RE.test(decoded)
163
+ ) {
164
+ throw invalid("repository path is ambiguous");
165
+ }
166
+ const identitySegment = decodePercent
167
+ ? decoded
168
+ : normalizeUrlPercent
169
+ ? normalizeUrlPathSegment(segment)
170
+ : segment;
171
+ return hosted ? identitySegment.toLowerCase() : identitySegment;
172
+ });
173
+ if (hosted && segments.length > 0) {
174
+ segments[segments.length - 1] = segments[segments.length - 1].replace(
175
+ /\.git$/iu,
176
+ "",
177
+ );
178
+ }
179
+ if (!segments.at(-1) || segments.at(-1) === "." || segments.at(-1) === "..") {
180
+ throw invalid("repository path is invalid");
181
+ }
182
+ if (host === "github.com" && segments.length !== 2) {
183
+ throw invalid("GitHub repository identity must be owner/repo");
184
+ }
185
+ if (host === "gitlab.com" && segments.length < 2) {
186
+ throw invalid("GitLab repository identity must include namespace/repo");
187
+ }
188
+ if (segments.length < 1) throw invalid("repository path is invalid");
189
+ return segments;
190
+ }
191
+
192
+ function repositoryIdentity({
193
+ scheme,
194
+ host,
195
+ port = "",
196
+ rawPath,
197
+ query = "",
198
+ principal = null,
199
+ pathMode = "absolute",
200
+ decodePercent = true,
201
+ normalizeUrlPercent = false,
202
+ ref = null,
203
+ subpath = null,
204
+ }) {
205
+ const normalizedHost = normalizeHost(host);
206
+ const normalizedPort = normalizePort(scheme, port);
207
+ const authority = formatAuthority(normalizedHost, normalizedPort);
208
+ const segments = decodeRepoSegments(normalizedHost, rawPath, {
209
+ decodePercent,
210
+ normalizeUrlPercent,
211
+ });
212
+ const kind =
213
+ normalizedHost === "github.com"
214
+ ? "github"
215
+ : normalizedHost === "gitlab.com"
216
+ ? "gitlab"
217
+ : "git";
218
+ const repoPath = segments.join("/");
219
+ const principalDigest = principal ? sha256(principal) : null;
220
+ const principalLabel = principalDigest
221
+ ? `[principal:${principalDigest}]@`
222
+ : "";
223
+ const canonicalPathMode =
224
+ normalizedHost === "github.com" || normalizedHost === "gitlab.com"
225
+ ? "absolute"
226
+ : pathMode;
227
+ const canonical = JSON.stringify({
228
+ kind: "git",
229
+ scheme,
230
+ authority,
231
+ principal,
232
+ pathMode: canonicalPathMode,
233
+ repoPath,
234
+ query,
235
+ });
236
+ return {
237
+ kind,
238
+ key: `${kind}:${scheme}://${principalLabel}${authority}/${canonicalPathMode === "relative" ? "[relative]/" : ""}${repoPath.replace(/\?.*$/u, "?[REDACTED]")}${query ? "?[REDACTED]" : ""}`,
239
+ identityDigest: sha256(canonical),
240
+ scheme,
241
+ authority,
242
+ host: normalizedHost,
243
+ port: normalizedPort || null,
244
+ owner: kind === "github" ? segments[0] : null,
245
+ principalDigest,
246
+ pathMode: canonicalPathMode,
247
+ ref,
248
+ path: subpath,
249
+ };
250
+ }
251
+
252
+ function ownerWildcard(raw, { ref = null, path: subpath = null } = {}) {
253
+ if (ref != null || subpath != null) {
254
+ throw invalid("GitHub owner wildcards cannot include ref or path");
255
+ }
256
+ const match = /^([^/]+)\/\*$/u.exec(raw);
257
+ if (!match || !OWNER_RE.test(match[1])) {
258
+ throw invalid(`invalid GitHub owner wildcard ${JSON.stringify(raw)}`);
259
+ }
260
+ const owner = match[1].toLowerCase();
261
+ return {
262
+ kind: "github-owner",
263
+ key: `github-owner:${owner}/*`,
264
+ scheme: null,
265
+ authority: "github.com",
266
+ host: "github.com",
267
+ port: null,
268
+ owner,
269
+ ref: null,
270
+ path: null,
271
+ };
272
+ }
273
+
274
+ function parsedUrl(raw, { stripCredentials = true } = {}) {
275
+ let url;
276
+ try {
277
+ url = new URL(raw);
278
+ } catch {
279
+ throw invalid("source URL is invalid");
280
+ }
281
+ if (stripCredentials) {
282
+ url.username = "";
283
+ url.password = "";
284
+ }
285
+ url.hash = "";
286
+ return url;
287
+ }
288
+
289
+ function assertRawUrlPathUnambiguous(raw) {
290
+ const match = /^[a-z][a-z0-9+.-]*:\/\/[^/?#]*(\/[^?#]*)?/iu.exec(raw);
291
+ const rawPath = match?.[1] || "/";
292
+ for (const segment of rawPath.split("/")) {
293
+ if (!segment) continue;
294
+ let decoded;
295
+ try {
296
+ decoded = decodeURIComponent(segment);
297
+ } catch {
298
+ throw invalid("repository path has invalid percent encoding");
299
+ }
300
+ if (
301
+ decoded === "." ||
302
+ decoded === ".." ||
303
+ /[\\/]/u.test(decoded) ||
304
+ CONTROL_RE.test(decoded)
305
+ ) {
306
+ throw invalid("repository path is ambiguous");
307
+ }
308
+ }
309
+ }
310
+
311
+ function urlAuthority(url) {
312
+ const host = normalizeHost(url.hostname);
313
+ return {
314
+ host,
315
+ port: url.port || "",
316
+ authority: formatAuthority(host, url.port || ""),
317
+ };
318
+ }
319
+
320
+ function exactUrl(raw, subpath = null) {
321
+ assertRawUrlPathUnambiguous(raw);
322
+ const url = parsedUrl(raw, { stripCredentials: false });
323
+ if (!url.hostname || !["http:", "https:"].includes(url.protocol)) {
324
+ throw invalid("URL marketplace source must use http(s)");
325
+ }
326
+ if (url.username || url.password) {
327
+ throw invalid("URL marketplace credentials are not supported");
328
+ }
329
+ const { host, port, authority } = urlAuthority(url);
330
+ const scheme = url.protocol.slice(0, -1).toLowerCase();
331
+ const pathname = normalizeUrlPathname(url.pathname);
332
+ const canonical = `url:${scheme}://${authority}${pathname}${url.search}`;
333
+ return {
334
+ kind: "url",
335
+ key: `url:${scheme}://${authority}${pathname}${url.search ? "?[REDACTED]" : ""}`,
336
+ identityDigest: createHash("sha256").update(canonical).digest("hex"),
337
+ scheme,
338
+ authority,
339
+ host,
340
+ port: port || null,
341
+ owner: null,
342
+ ref: null,
343
+ path: subpath,
344
+ };
345
+ }
346
+
347
+ function fileUrlIdentity(url, ref, subpath) {
348
+ if (url.search) throw invalid("file URL query parameters are not supported");
349
+ const host = url.hostname ? normalizeHost(url.hostname) : "";
350
+ const authority = host ? formatAuthority(host, url.port || "") : "";
351
+ let localPath;
352
+ try {
353
+ localPath = fileURLToPath(url);
354
+ } catch {
355
+ throw invalid("file URL path is invalid");
356
+ }
357
+ const normalizedPath = normalizeLocalIdentityPath(localPath);
358
+ const canonical = `file://${authority}${normalizedPath}`;
359
+ return {
360
+ kind: "file-url",
361
+ key: `file://${authority}${normalizedPath.replace(/\\/gu, "/")}`,
362
+ identityDigest: createHash("sha256").update(canonical).digest("hex"),
363
+ scheme: "file",
364
+ authority,
365
+ host: host || null,
366
+ port: url.port || null,
367
+ owner: null,
368
+ ref,
369
+ path: subpath,
370
+ };
371
+ }
372
+
373
+ function fromGitLocation(raw, ref, subpath) {
374
+ const scp = SCP_RE.exec(raw);
375
+ if (scp && !raw.includes("://") && !/^[a-z]:[\\/]/iu.test(raw)) {
376
+ if (!scp[1]) {
377
+ throw invalid("SSH Git sources require an explicit principal");
378
+ }
379
+ const host = normalizeHost(scp[2]);
380
+ return repositoryIdentity({
381
+ scheme: "ssh",
382
+ host,
383
+ rawPath: scp[3],
384
+ principal: normalizeSshPrincipal(scp[1], { decodePercent: false }),
385
+ pathMode: scp[3].startsWith("/") ? "absolute" : "relative",
386
+ decodePercent: false,
387
+ ref,
388
+ subpath,
389
+ });
390
+ }
391
+ assertRawUrlPathUnambiguous(raw);
392
+ const url = parsedUrl(raw, { stripCredentials: false });
393
+ if (!SUPPORTED_GIT_SCHEMES.has(url.protocol)) {
394
+ throw invalid("git source URL uses an unsupported protocol");
395
+ }
396
+ if (url.protocol === "file:") {
397
+ return fileUrlIdentity(url, ref, subpath);
398
+ }
399
+ if (!url.hostname) throw invalid("source URL has no host");
400
+ if (url.protocol === "ssh:" && !url.username) {
401
+ throw invalid("SSH Git sources require an explicit principal");
402
+ }
403
+ const { host, port } = urlAuthority(url);
404
+ return repositoryIdentity({
405
+ scheme: url.protocol.slice(0, -1).toLowerCase(),
406
+ host,
407
+ port,
408
+ rawPath: url.pathname,
409
+ query: url.search,
410
+ pathMode: "absolute",
411
+ // Git transports percent-decode URL path segments before handing the
412
+ // repository path to upload-pack. Decode every validated segment here so
413
+ // reserved spellings such as `%3A` cannot bypass an exact source rule.
414
+ decodePercent: true,
415
+ principal:
416
+ url.protocol === "ssh:" ? normalizeSshPrincipal(url.username) : null,
417
+ ref,
418
+ subpath,
419
+ });
420
+ }
421
+
422
+ function fromPath(raw, ref, subpath, cwd = process.cwd()) {
423
+ const absolute = path.resolve(cwd, normalizePluginLocalSourcePath(raw));
424
+ const normalized = normalizeLocalIdentityPath(absolute);
425
+ return {
426
+ kind: "path",
427
+ key: `path:${normalized.replace(/\\/gu, "/")}`,
428
+ scheme: null,
429
+ authority: null,
430
+ host: null,
431
+ port: null,
432
+ owner: null,
433
+ ref,
434
+ path: subpath,
435
+ };
436
+ }
437
+
438
+ function expandWindowsShortNameAncestors(value) {
439
+ let ancestor = path.normalize(value);
440
+ const suffix = [];
441
+ while (true) {
442
+ try {
443
+ return path.join(fs.realpathSync.native(ancestor), ...suffix);
444
+ } catch {
445
+ const parent = path.dirname(ancestor);
446
+ if (parent === ancestor) {
447
+ throw invalid(
448
+ "Windows short-name and trailing-dot/space path aliases are not supported",
449
+ );
450
+ }
451
+ suffix.unshift(path.basename(ancestor));
452
+ ancestor = parent;
453
+ }
454
+ }
455
+ }
456
+
457
+ const WINDOWS_SHORT_NAME_SEGMENT_RE = /~[0-9]+(?:\.[^.]*)?$/iu;
458
+
459
+ function windowsPathSegments(value) {
460
+ return path
461
+ .normalize(value)
462
+ .replace(/^[a-z]:/iu, "")
463
+ .split(/[\\/]/u);
464
+ }
465
+
466
+ function unsafeWindowsPathSegment(value) {
467
+ if (value === "" || value === ".") return null;
468
+ return windowsPathSegments(value).find(
469
+ (segment) =>
470
+ WINDOWS_SHORT_NAME_SEGMENT_RE.test(segment) || /[ .]$/u.test(segment),
471
+ );
472
+ }
473
+
474
+ function canonicalizeAmbientWindowsTempPath(value) {
475
+ const normalized = path.normalize(value);
476
+ const ambientTempRoot = path.resolve(os.tmpdir());
477
+ const relative = path.relative(ambientTempRoot, normalized);
478
+ const insideAmbientTemp =
479
+ relative === "" ||
480
+ (!path.isAbsolute(relative) &&
481
+ relative !== ".." &&
482
+ !relative.startsWith(`..${path.sep}`));
483
+ if (!insideAmbientTemp) return normalized;
484
+
485
+ const ambientHasShortName = windowsPathSegments(ambientTempRoot).some(
486
+ (segment) => WINDOWS_SHORT_NAME_SEGMENT_RE.test(segment),
487
+ );
488
+ if (!ambientHasShortName) return normalized;
489
+
490
+ // GitHub's Windows runners can expose the OS-selected TEMP root via an 8.3
491
+ // segment such as RUNNER~1. Never preserve that alternate spelling in a
492
+ // managed identity: resolve only the trusted ambient root, then append the
493
+ // still-lexical caller suffix. The caller's target is never stat'ed/read.
494
+ if (unsafeWindowsPathSegment(relative)) {
495
+ throw invalid(
496
+ "Windows short-name and trailing-dot/space path aliases are not supported",
497
+ );
498
+ }
499
+ let canonicalAmbientTempRoot;
500
+ try {
501
+ canonicalAmbientTempRoot = path.normalize(
502
+ fs.realpathSync.native(ambientTempRoot),
503
+ );
504
+ } catch {
505
+ throw invalid("Windows ambient temp root identity cannot be resolved");
506
+ }
507
+ if (
508
+ !path.isAbsolute(canonicalAmbientTempRoot) ||
509
+ /^\\\\\?\\/u.test(canonicalAmbientTempRoot) ||
510
+ /^\\\?\?\\/u.test(canonicalAmbientTempRoot) ||
511
+ unsafeWindowsPathSegment(canonicalAmbientTempRoot)
512
+ ) {
513
+ throw invalid("Windows ambient temp root identity is not canonical");
514
+ }
515
+ return relative
516
+ ? path.resolve(canonicalAmbientTempRoot, relative)
517
+ : canonicalAmbientTempRoot;
518
+ }
519
+
520
+ function normalizeLocalIdentityPath(value) {
521
+ const raw = String(value);
522
+ if (
523
+ process.platform === "win32" &&
524
+ (/^\\\\\?\\/u.test(raw) || /^\\\?\?\\/u.test(raw))
525
+ ) {
526
+ throw invalid("Windows extended namespace paths are not supported");
527
+ }
528
+ let identityPath = raw;
529
+ if (process.platform === "win32") {
530
+ // Canonicalize only the trusted ambient temp root first. This handles
531
+ // hosted-runner roots such as RUNNER~1 without touching the caller's
532
+ // target before the managed-policy decision.
533
+ identityPath = canonicalizeAmbientWindowsTempPath(raw);
534
+ const segments = identityPath.replace(/^[a-z]:/iu, "").split(/[\\/]/u);
535
+ if (segments.some((segment) => /[ .]$/u.test(segment))) {
536
+ throw invalid(
537
+ "Windows short-name and trailing-dot/space path aliases are not supported",
538
+ );
539
+ }
540
+ if (segments.some((segment) => /~[0-9]+(?:\.[^.]*)?$/iu.test(segment))) {
541
+ // Outside the ambient temp-root exception, resolve the longest existing
542
+ // ancestor and rebuild any not-yet-created safe suffix from its long
543
+ // spelling. An alias in a nonexistent suffix remains visible below and
544
+ // fails closed, while real aliases cannot mint a second authority.
545
+ identityPath = expandWindowsShortNameAncestors(identityPath);
546
+ const resolvedSegments = identityPath
547
+ .replace(/^[a-z]:/iu, "")
548
+ .split(/[\\/]/u);
549
+ if (
550
+ resolvedSegments.some(
551
+ (segment) =>
552
+ /~[0-9]+(?:\.[^.]*)?$/iu.test(segment) || /[ .]$/u.test(segment),
553
+ )
554
+ ) {
555
+ throw invalid(
556
+ "Windows short-name and trailing-dot/space path aliases are not supported",
557
+ );
558
+ }
559
+ }
560
+ }
561
+ const normalized = path.normalize(identityPath);
562
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
563
+ }
564
+
565
+ /** Exact local-path spelling shared by policy identity and materialization. */
566
+ export function normalizePluginLocalSourcePath(raw) {
567
+ if (typeof raw !== "string") throw invalid("source path must be a string");
568
+ if (raw !== raw.trim()) {
569
+ throw invalid("source path cannot have leading or trailing whitespace");
570
+ }
571
+ return clean(raw, "source path");
572
+ }
573
+
574
+ function fromString(
575
+ raw,
576
+ { policyEntry = false, cwd, ref, path: subpath, kindHint = null } = {},
577
+ ) {
578
+ if (kindHint === "url" || kindHint === "registry") {
579
+ return exactUrl(clean(raw), subpath ?? null);
580
+ }
581
+ if (kindHint === "directory" || kindHint === "file") {
582
+ return fromPath(raw, ref ?? null, subpath ?? null, cwd);
583
+ }
584
+ const cleaned = clean(raw);
585
+ if (cleaned.includes("*")) {
586
+ if (policyEntry && /^[^/]+\/\*$/u.test(cleaned)) {
587
+ return ownerWildcard(cleaned, { ref, path: subpath });
588
+ }
589
+ throw invalid("only a GitHub owner/* source wildcard is supported");
590
+ }
591
+ const git = parsePluginGitSource(cleaned);
592
+ if (git) {
593
+ if (ref != null && git.ref != null && ref !== git.ref) {
594
+ throw invalid("git source ref conflicts with its URL fragment");
595
+ }
596
+ return fromGitLocation(git.url, ref ?? git.ref, subpath ?? null);
597
+ }
598
+ if (kindHint === "git") throw invalid("git source locator is invalid");
599
+ if (isExplicitPath(cleaned)) {
600
+ return fromPath(cleaned, ref ?? null, subpath ?? null, cwd);
601
+ }
602
+ return {
603
+ kind: "name",
604
+ // Opaque legacy marketplace identifiers keep exact-string semantics.
605
+ key: `name:${cleaned}`,
606
+ scheme: null,
607
+ authority: null,
608
+ host: null,
609
+ port: null,
610
+ owner: null,
611
+ ref: ref ?? null,
612
+ path: subpath ?? null,
613
+ };
614
+ }
615
+
616
+ /** The one parser used by both install materialization and managed policy. */
617
+ export function parsePluginGitSource(raw) {
618
+ if (raw == null) return null;
619
+ if (typeof raw !== "string") throw invalid("source must be a string");
620
+ if (!raw.trim()) return null;
621
+ if (isExplicitPath(raw.trim())) return null;
622
+ const { locator, ref } = splitRef(raw);
623
+ if (/^(?:https?|git|ssh|file):\/\//iu.test(locator)) {
624
+ return { url: locator, ref };
625
+ }
626
+ if (/^[a-z][a-z0-9+.-]*:\/\//iu.test(locator)) {
627
+ throw invalid("git source URL uses an unsupported protocol");
628
+ }
629
+ if (
630
+ SCP_RE.test(locator) &&
631
+ !locator.includes("://") &&
632
+ !/^[a-z]:[\\/]/iu.test(locator)
633
+ ) {
634
+ return { url: locator, ref };
635
+ }
636
+ const hosted = /^(github\.com|gitlab\.com)\/(.+)$/iu.exec(locator);
637
+ if (hosted) {
638
+ const segments = hosted[2].split("/");
639
+ if (segments.length < 2) return null;
640
+ if (hosted[1].toLowerCase() === "github.com" && segments.length !== 2) {
641
+ return null;
642
+ }
643
+ return {
644
+ url: `https://${hosted[1].toLowerCase()}/${hosted[2].replace(/\.git$/iu, "")}.git`,
645
+ ref,
646
+ };
647
+ }
648
+ if (/^[\w.-]+\/[\w.-]+$/u.test(locator)) {
649
+ return { url: `https://github.com/${locator}.git`, ref };
650
+ }
651
+ if (locator.endsWith(".git")) return { url: locator, ref };
652
+ return null;
653
+ }
654
+
655
+ /**
656
+ * Reject Git locators whose eventual transport cannot be bound to the policy
657
+ * identity. Query credentials and HTTP userinfo would otherwise be exposed in
658
+ * argv/process listings, while a bare `repo.git` can be reinterpreted as a
659
+ * cwd-relative repository by Git. Registry entries additionally forbid file
660
+ * URLs because a remote catalog must not grant access to local bytes.
661
+ */
662
+ export function assertPluginGitTransportSafe(
663
+ git,
664
+ { allowFile = true, requireRemote = false } = {},
665
+ ) {
666
+ if (!git || typeof git.url !== "string") {
667
+ throw invalid("git source locator is invalid");
668
+ }
669
+ const locator = clean(git.url, "git source locator");
670
+ if (locator.includes("?")) {
671
+ throw invalid("git source query credentials are not supported");
672
+ }
673
+ if (locator.includes("://")) {
674
+ const url = parsedUrl(locator, { stripCredentials: false });
675
+ if (!SUPPORTED_GIT_SCHEMES.has(url.protocol)) {
676
+ throw invalid("git source URL uses an unsupported protocol");
677
+ }
678
+ if (url.protocol === "file:" && !allowFile) {
679
+ throw invalid("registry git sources cannot use file URLs");
680
+ }
681
+ if (
682
+ url.protocol !== "ssh:" &&
683
+ (url.username !== "" || url.password !== "")
684
+ ) {
685
+ throw invalid("git source URL credentials are not supported");
686
+ }
687
+ if (url.password !== "") {
688
+ throw invalid("git source URL passwords are not supported");
689
+ }
690
+ if (requireRemote && url.protocol === "file:") {
691
+ throw invalid("registry git sources must be remote repositories");
692
+ }
693
+ return git;
694
+ }
695
+ if (
696
+ SCP_RE.test(locator) &&
697
+ !/^[a-z]:[\\/]/iu.test(locator) &&
698
+ !locator.includes("#")
699
+ ) {
700
+ return git;
701
+ }
702
+ if (requireRemote) {
703
+ throw invalid("registry git sources must use a remote repository locator");
704
+ }
705
+ return git;
706
+ }
707
+
708
+ /** Safe diagnostic projection: never prints URL credentials, query, or ref. */
709
+ export function redactPluginSourceForDisplay(raw) {
710
+ const value = String(raw ?? "")
711
+ .replace(CONTROL_GLOBAL_RE, "")
712
+ .trim();
713
+ try {
714
+ const url = new URL(value);
715
+ const { authority } = urlAuthority(url);
716
+ return `${url.protocol}//${authority}${url.pathname}${url.search ? "?[REDACTED]" : ""}`;
717
+ } catch {
718
+ const hash = value.indexOf("#");
719
+ const query = value.indexOf("?");
720
+ const cut = [hash, query]
721
+ .filter((index) => index >= 0)
722
+ .sort((a, b) => a - b)[0];
723
+ const locator = (cut == null ? value : value.slice(0, cut)).replace(
724
+ /^([a-z][a-z0-9+.-]*:\/\/)(?:[^/@\s]+@)/iu,
725
+ "$1[REDACTED]@",
726
+ );
727
+ const redactedLocator = locator.replace(
728
+ /^(?:[^@\s/:]+@)(\[[^\]]+\]|[^\s/:]+):/u,
729
+ "[REDACTED]@$1:",
730
+ );
731
+ if (cut == null) return redactedLocator;
732
+ return `${redactedLocator}${cut === query ? "?[REDACTED]" : "#[REDACTED]"}`;
733
+ }
734
+ }
735
+
736
+ export function canonicalizePluginSource(source, options = {}) {
737
+ if (typeof source === "string") return fromString(source, options);
738
+ if (!source || typeof source !== "object" || Array.isArray(source)) {
739
+ throw invalid("source entry must be a string or object");
740
+ }
741
+ if (source.source && typeof source.source === "object") {
742
+ if (Object.keys(source).some((key) => key !== "source")) {
743
+ throw invalid("nested source wrappers cannot add outer constraints");
744
+ }
745
+ return canonicalizePluginSource(source.source, options);
746
+ }
747
+ const knownFields = new Set([
748
+ "source",
749
+ "type",
750
+ "url",
751
+ "registry",
752
+ "repo",
753
+ "name",
754
+ "ref",
755
+ "path",
756
+ "hostPattern",
757
+ "pathPattern",
758
+ ]);
759
+ const unknownField = Object.keys(source).find(
760
+ (field) => !knownFields.has(field),
761
+ );
762
+ if (unknownField) {
763
+ throw invalid(`unsupported marketplace source field ${unknownField}`);
764
+ }
765
+ if (source.hostPattern != null || source.pathPattern != null) {
766
+ throw invalid("unsupported marketplace source pattern");
767
+ }
768
+ if (source.type != null && !SOURCE_KINDS.has(source.type)) {
769
+ throw invalid(`unsupported marketplace source kind ${source.type}`);
770
+ }
771
+ const kind = SOURCE_KINDS.has(source.type)
772
+ ? source.type
773
+ : SOURCE_KINDS.has(source.source)
774
+ ? source.source
775
+ : null;
776
+ if (
777
+ SOURCE_KINDS.has(source.type) &&
778
+ SOURCE_KINDS.has(source.source) &&
779
+ source.type !== source.source
780
+ ) {
781
+ throw invalid("marketplace source kind declarations conflict");
782
+ }
783
+ const locatorFields = ["url", "registry", "repo", "name"].filter(
784
+ (field) => source[field] != null,
785
+ );
786
+ if (locatorFields.length > 1) {
787
+ throw invalid("marketplace source has conflicting locator fields");
788
+ }
789
+ if (
790
+ ["directory", "file"].includes(kind) &&
791
+ source.path != null &&
792
+ source.url != null
793
+ ) {
794
+ throw invalid("local marketplace source has conflicting path fields");
795
+ }
796
+ if (
797
+ !kind &&
798
+ typeof source.source === "string" &&
799
+ ["url", "repo", "path", "registry", "package"].some((field) =>
800
+ Object.prototype.hasOwnProperty.call(source, field),
801
+ )
802
+ ) {
803
+ throw invalid(`unsupported marketplace source kind ${source.source}`);
804
+ }
805
+ const constraints = {
806
+ ...options,
807
+ ref: source.ref == null ? undefined : clean(source.ref, "ref", 256),
808
+ path: source.path == null ? undefined : clean(source.path, "subpath", 1024),
809
+ };
810
+ if (kind === "github") {
811
+ const value = clean(source.repo, "GitHub repo");
812
+ if (value.includes("*")) {
813
+ if (options.policyEntry && /^[^/]+\/\*$/u.test(value)) {
814
+ return ownerWildcard(value, constraints);
815
+ }
816
+ throw invalid("GitHub source wildcards must use owner/*");
817
+ }
818
+ return repositoryIdentity({
819
+ scheme: "https",
820
+ host: "github.com",
821
+ rawPath: value,
822
+ ref: constraints.ref ?? null,
823
+ subpath: constraints.path ?? null,
824
+ });
825
+ }
826
+ if (["directory", "file"].includes(kind)) {
827
+ return fromPath(
828
+ source.path ?? source.url,
829
+ constraints.ref,
830
+ null,
831
+ options.cwd,
832
+ );
833
+ }
834
+ const locator =
835
+ source.url ??
836
+ source.registry ??
837
+ (source.source !== kind ? source.source : undefined) ??
838
+ source.repo ??
839
+ source.name;
840
+ if (kind === "url" || kind === "registry") {
841
+ if (constraints.ref != null) {
842
+ throw invalid("URL marketplace sources cannot include a git ref");
843
+ }
844
+ return exactUrl(locator, constraints.path ?? null);
845
+ }
846
+ return fromString(locator, {
847
+ ...constraints,
848
+ kindHint: kind === "git" ? "git" : constraints.kindHint,
849
+ });
850
+ }