chainlesschain 0.165.5 → 0.165.8
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.
- package/package.json +2 -1
- package/src/assets/web-panel/assets/{AIOps-CEeYTeDB.js → AIOps-B3gWmXjo.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BeRUQ3V2.js → ActionButton-G9pdT-0e.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DrV718XF.js → Analytics-Ca30J7m2.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-Cd2Frt7Q.js → AppLayout-BtVTm8PU.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CUJMlORU.js → Artifacts-BhViNa6u.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CXJcfT6p.js → Audit-DhKq-FnY.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DOv6Z7H9.js → BackgroundAgents-BL0SVbnE.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BwdDyBI0.js → Backup-BWkblnFi.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BmLn4kYH.js → BaseInput-ClaRNrF8.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-COg8pAW2.js → Chat-sZE2Z77o.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BAfKOnRe.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-BCP4uoU8.js → Checkbox-Cvhay5WM.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-pRt6Y9JI.js → Codegen-BpejL-4N.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DLaprKhS.js → Col-g2Ki6g_1.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DyATsz5f.js → Community-D2xKbNo9.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BXAaupZK.js → Compact-DbL1Pds3.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DpcJBELr.js → Compliance-DGtZCV4c.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DG4EFWyd.js → Cowork-4kQpoNsI.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-F6Oy5n2P.js → Cron-BxTYXXAN.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BOdgtalX.js → Crosschain-k0J0LT2N.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DT1dSkjH.js → DID-BaqhoPgN.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-NBUIqWzC.js → Dashboard-T4RVPIM1.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CSpHeMYB.js → Dropdown-DszOToWq.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CEG0xISr.js → EmailListRenderer-C9KJU7HF.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BTZ0BZna.js → FamilyGuardDashboard-Cmr0JnsA.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BuuCTn2V.js → Federation-BI-_2xZX.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-KAVceevk.js → FormItemContext-DsnDEPsl.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DnHpcQvW.js → GenericCardRenderer-BLncAKKK.js} +1 -1
- package/src/assets/web-panel/assets/{Git-C_B_0HwB.js → Git-BPQhGXDn.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DPSZuzuu.js → Governance-CyElbUQW.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BV-06V7q.js → Inference-CcSntBQD.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-wpDuupn6.js → KnowledgeGraph-p6e7HZ2r.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Bseca1_I.js → Logs-CNEtr-wz.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-Bj9xI0YH.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-d3kM4DDL.js → Marketplace-Dd2notAJ.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DQ_xZ23i.js → McpTools-BBsN2CSW.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-CRIeYO4L.js → Memory-Dg2uTvwx.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BsllIa9l.js → MobileBridge-r1YMRiJV.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BUuQy30X.js → MobileProjects-C-89kSm4.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BOFDIzrC.js → Mtc-CrPZcVgf.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BQg-vyvG.js → MtcAudit-sxi7hqJb.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-Bo1jrPdN.js → Multisig-CAgOMfF1.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Bmod8alw.js → NLProgramming-DafaPts3.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CGGFL8w-.js → Notes-CJM-xUW_.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BsZvCJxC.js → NotificationSettings-L0ZjfAh_.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C1-Quo_h.js → OrderTableRenderer-BB1CvCK0.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BjOeDYOi.js → Organization-DeLtgAD8.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DwVV3l5A.js → Overflow-CKC2b10z.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cu3DQT9W.js → P2P-eyf2rzY1.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Cg0gR7dH.js → PdhVaultBrowser-5upNIOW8.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CsAXxRN5.js → Permissions-B8p3W6JK.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-C-nNmnYp.js → PersonalDataHub-BnRMxTq3.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-BOnW7nNc.js → Pipeline-DjCNI1li.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-ClLSyGKt.js → Privacy-u6gXLbWT.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DuLvjjq7.js → ProjectInit-BZAw4tkM.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DQYYbDD9.js → ProjectSettings-B1Tr72q7.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Drw8MBGK.js → Projects-DO7HFuBQ.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-ckqHQe6K.js → Providers-BjCztyhj.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DW5BXk-G.js → QrScannerModal-ZWyCUbR1.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DDLnhNTp.js → QuickAsk-DhDjeW4G.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-PJQ2C91U.js → Recommend-R_CN2MuU.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-s6wmda34.js → RemoteSession-uxFcujeu.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-DC2sb-I3.js → Reputation-DX8D7rks.js} +1 -1
- package/src/assets/web-panel/assets/{Row-hPGk1eGj.js → Row-BYahQILD.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-RIvfZql0.js → RssFeed-CUSQ2tm-.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DFg1G1eR.js → Search-CnN_S8Cs.js} +1 -1
- package/src/assets/web-panel/assets/{Security-D6H5p8Dp.js → Security-DcZlh-O4.js} +4 -4
- package/src/assets/web-panel/assets/{Services-nxYiFV_w.js → Services-ZhBOjzIX.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BmgFNAdB.js → Skeleton-D5Qnq_lI.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CsaaD2av.js → Skills-BC9cZ3Be.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-yM5y9yvK.js → Sla-DauJ3E8_.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-DbNeZ_fh.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-CFVhEyyr.js → SyncSettings-DUvJbSct.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DdO02Inn.js → Tasks-BXa-5noQ.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Eo2d8Llu.js → Templates-JxaiUt_3.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-ChESOXgy.js → Tenant-BW4ERQN8.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BJ7oDxTI.js → Terminal-DltUbdtv.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-hWX0fUvz.js → TimelineRenderer-CMoZz0Tc.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DVBZQnd-.js → Tokens-D_VEV60K.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-D89cQfVq.js → Trigger-CCXTCs0R.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BIVSb4-V.js → Trust-lkGRIPcG.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B58gWopl.js → UkeySign-BfGeQ9HX.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DhHI7bmG.js → VideoEditing-BEC7pYvc.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-C4FheWS_.js → Wallet-BZdG24o0.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DnPRq-Bx.js → WebAuthn-yoEddedp.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-BerRPjpJ.js → WorkflowEditor-BXZEprOW.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BzZ52iKr.js → chat-gg0bzZal.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BI_E4BAn.js → colors-DjRVRuin.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-d50mr3yk.js → compact-item-su-dieAc.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-C6FsQ1IH.js → createContext-Ci3S8oKF.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CxbFX9uH.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DLZEMgLP.js → hasIn-D8F10heH.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-aBumtY.js → index-11wtYQiF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CjuKZBqJ.js → index-1IgxgALJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-9H_M2jju.js → index-1agckWSq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DE0V6cp-.js → index-5JqhujnR.js} +1 -1
- package/src/assets/web-panel/assets/{index-Y2-q7QLm.js → index-6Tniqk1s.js} +1 -1
- package/src/assets/web-panel/assets/{index-DoFtCVZ7.js → index-8Z-8BIlN.js} +1 -1
- package/src/assets/web-panel/assets/{index-KNGgO4Wm.js → index-BCI5v0yB.js} +1 -1
- package/src/assets/web-panel/assets/{index-DY4j5zkF.js → index-BDJntYmJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZjjdQ62.js → index-BGzixOGZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTpIOTfR.js → index-BMMOVoLD.js} +1 -1
- package/src/assets/web-panel/assets/{index-BB9SqVSt.js → index-BTFpdBtt.js} +1 -1
- package/src/assets/web-panel/assets/{index-AHr7ryWf.js → index-Ba_HyPnE.js} +1 -1
- package/src/assets/web-panel/assets/{index-Pvf30m8Q.js → index-BbF1IHof.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRN5TRTQ.js → index-BgT9su_e.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUaEHfQ0.js → index-BzPB6kGZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdKt9fOd.js → index-C6ygzplr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dec0Kf_Z.js → index-CC3ZDjzY.js} +1 -1
- package/src/assets/web-panel/assets/{index-IPkwSAW_.js → index-CEmXRhAE.js} +1 -1
- package/src/assets/web-panel/assets/index-CJGOlduY.js +1 -0
- package/src/assets/web-panel/assets/{index-C3k8kevG.js → index-CMPqd5Cv.js} +1 -1
- package/src/assets/web-panel/assets/{index-A5CDWP7y.js → index-COWd8uGx.js} +1 -1
- package/src/assets/web-panel/assets/{index-CO8DAF7U.js → index-CPIYSp4H.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVzuawCX.js → index-CR0h6seT.js} +1 -1
- package/src/assets/web-panel/assets/{index-Co7s9LsB.js → index-CUmDhC4j.js} +1 -1
- package/src/assets/web-panel/assets/index-CkBCN5yH.js +1 -0
- package/src/assets/web-panel/assets/{index-CLoTQr0W.js → index-CusgZPtc.js} +1 -1
- package/src/assets/web-panel/assets/{index-B81mo1Et.js → index-DBAETXft.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcSUl9fA.js → index-DIJ2qFOC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Q5Tn735s.js → index-Dbq4NMET.js} +1 -1
- package/src/assets/web-panel/assets/{index-GXE0D8Nx.js → index-DkMEF5VT.js} +1 -1
- package/src/assets/web-panel/assets/{index-TWG-_D1M.js → index-DkfQR3hM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAE3hwev.js → index-DnLucOn9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgkoH3_g.js → index-DugB_aKa.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRGx3w6T.js → index-DyLUarhZ.js} +3 -3
- package/src/assets/web-panel/assets/{index-BeM4hfht.js → index-T6_6ygJZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DsscwxPE.js → index-Tu8eWqh-.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bmr_vxXD.js → index-W7XUgdVL.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoNZlzhQ.js → index-dHZinfko.js} +1 -1
- package/src/assets/web-panel/assets/{index-DqkbHHg8.js → index-jyNgp5FI.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-D-4Hzgej.js → initDefaultProps-Cv4p8e4w.js} +1 -1
- package/src/assets/web-panel/assets/{motion-D4Wl4xU7.js → motion-B8EwmMOw.js} +1 -1
- package/src/assets/web-panel/assets/{move-Cd5eQvL0.js → move-B8R9F8TK.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-B4ilslCW.js → mtc-parser-DoFYz5Bp.js} +1 -1
- package/src/assets/web-panel/assets/{omit-Ckarx-tf.js → omit-CHWYLBjI.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-QJ0bpCmc.js → pickAttrs-CXuPDO77.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CKHm-tq8.js → placementArrow-BaAtUUon.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CV6a_w_Y.js → responsiveObserve-DCc4zcDD.js} +1 -1
- package/src/assets/web-panel/assets/{slide-mHpeQFgk.js → slide-B_lx89oZ.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DIaQNQWA.js → statusUtils-DE8DPmR0.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-PJpzavV8.js → styleChecker-1ktXOsL_.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BH7zTw8S.js → useFlexGapSupport-xH89-wjh.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BDNG3JDd.js → useFs-DscvfW5p.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CY2VWdUg.js → usePersonalDataHub-RdMKQta7.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-D0kw9jhz.js → vnode-Z3wTni2T.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B5g7TN7r.js → zoom-B0iChCsm.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/commands/agent.js +105 -62
- package/src/commands/background-session.js +100 -10
- package/src/commands/git.js +39 -0
- package/src/commands/mcp.js +5 -0
- package/src/commands/plugin.js +160 -29
- package/src/commands/remote-control.js +295 -110
- package/src/commands/session-group.js +131 -0
- package/src/commands/session-location.js +48 -0
- package/src/commands/session-message.js +171 -0
- package/src/commands/session.js +22 -0
- package/src/commands/sync-providers.js +4 -1
- package/src/commands/update.js +1 -0
- package/src/data/changelog.json +41 -2
- package/src/gateways/ws/ws-agent-handler.js +20 -12
- package/src/harness/jsonl-session-store.js +279 -37
- package/src/harness/mcp-client.js +832 -91
- package/src/harness/session-index.js +15 -4
- package/src/lib/agent-worktree.js +8 -1
- package/src/lib/auto-mode-safety-classifier.js +3 -17
- package/src/lib/background-agent-keeper-protocol.js +26 -6
- package/src/lib/background-agent-supervisor.js +17 -1
- package/src/lib/background-session-transport.js +153 -27
- package/src/lib/browser-evidence.js +1405 -0
- package/src/lib/chrome-connector.js +465 -79
- package/src/lib/claude-project-auto-memory.js +481 -0
- package/src/lib/claude-project-storage-layout.js +104 -0
- package/src/lib/cloud/cloud-client.js +40 -0
- package/src/lib/execution-location-local-supervisor.mjs +226 -0
- package/src/lib/execution-location-runner-lifecycle.js +991 -0
- package/src/lib/execution-location-target-preflight.js +333 -0
- package/src/lib/execution-location-target.js +783 -13
- package/src/lib/file-lines.js +6 -1
- package/src/lib/gateway-client-resilience.js +571 -0
- package/src/lib/gitlab-mr-worktree.js +176 -0
- package/src/lib/hooks-v2-producers.js +119 -15
- package/src/lib/hooks-v2-workspace-context.js +44 -1
- package/src/lib/host-resource-budget.js +295 -0
- package/src/lib/ide-context.js +15 -2
- package/src/lib/llm-config-defaults.js +43 -0
- package/src/lib/mcp-client.js +2 -0
- package/src/lib/mcp-headers-helper.js +47 -5
- package/src/lib/mcp-lifecycle-authority.js +887 -0
- package/src/lib/mcp-oauth.js +156 -26
- package/src/lib/mcp-tls.js +330 -0
- package/src/lib/output-styles.js +12 -0
- package/src/lib/paths.js +260 -32
- package/src/lib/permission-request.js +48 -0
- package/src/lib/plugin-runtime/bin.js +3 -1
- package/src/lib/plugin-runtime/hooks.js +3 -1
- package/src/lib/plugin-runtime/install.js +730 -71
- package/src/lib/plugin-runtime/lsp.js +4 -4
- package/src/lib/plugin-runtime/marketplace-archive-source.js +366 -0
- package/src/lib/plugin-runtime/marketplace-catalog.js +63 -6
- package/src/lib/plugin-runtime/marketplace-command-descriptor.js +343 -0
- package/src/lib/plugin-runtime/marketplace-command-source.js +580 -0
- package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1 -0
- package/src/lib/plugin-runtime/marketplace-source-adapter.js +188 -0
- package/src/lib/plugin-runtime/marketplace-source-cache.js +11 -1
- package/src/lib/plugin-runtime/mcp.js +3 -1
- package/src/lib/plugin-runtime/monitors.js +3 -1
- package/src/lib/plugin-runtime/remote-source.js +583 -58
- package/src/lib/plugin-runtime/settings.js +3 -1
- package/src/lib/plugin-runtime/trust.js +274 -13
- package/src/lib/plugin-security.js +393 -41
- package/src/lib/plugin-source-identity.js +850 -0
- package/src/lib/process-execution-broker/credential-agent.js +210 -41
- package/src/lib/process-execution-broker/index.js +15 -1
- package/src/lib/process-execution-broker/linux-cgroup-v2.js +391 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +50 -28
- package/src/lib/project-mcp-trust.js +165 -10
- package/src/lib/remote-approval-bridge.js +136 -46
- package/src/lib/remote-control.js +217 -31
- package/src/lib/resume-session.js +9 -6
- package/src/lib/session-core-singletons.js +26 -11
- package/src/lib/session-host-lease.js +52 -16
- package/src/lib/session-lifecycle-scope.js +153 -0
- package/src/lib/session-message-fabric.js +1252 -0
- package/src/lib/session-projection.js +286 -3
- package/src/lib/session-runtime-retention.js +155 -0
- package/src/lib/session-store-guard.js +3 -1
- package/src/lib/session-workbench-store.js +466 -0
- package/src/lib/settings-loader.cjs +29 -0
- package/src/lib/spellcheck.js +211 -0
- package/src/lib/sub-agent-context.js +9 -9
- package/src/lib/sync-cli-db.js +485 -129
- package/src/lib/sync-credentials.js +31 -20
- package/src/lib/web-fetch.js +112 -40
- package/src/lib/web-search.js +127 -61
- package/src/lib/workspace-trust.js +793 -0
- package/src/repl/agent-repl.js +175 -19
- package/src/repl/permission-prompt.js +29 -8
- package/src/repl/repl-keybindings.js +55 -0
- package/src/runtime/agent-core.js +362 -20
- package/src/runtime/bootstrap.js +37 -13
- package/src/runtime/coding-agent-contract-shared.cjs +11 -4
- package/src/runtime/headless-runner.js +281 -102
- package/src/runtime/headless-stream.js +199 -30
- package/src/runtime/mcp-config.js +298 -6
- package/src/runtime/policies/agent-policy.js +10 -0
- package/src/runtime/status-diagnostics-lite.js +9 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-4gGkdGgA.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-B4RLp2zs.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-DPy1XMYh.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CagEcIex.js +0 -1
- package/src/assets/web-panel/assets/index-pzT9PpOU.js +0 -1
- package/src/assets/web-panel/assets/index-z1kFadaX.js +0 -1
|
@@ -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
|
-
|
|
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,
|
|
@@ -297,6 +326,10 @@ export function installFromDirectory(srcDir, opts = {}) {
|
|
|
297
326
|
// and may ship its own `.plugin-lock.json` or provenance metadata.
|
|
298
327
|
_deps.rmSync(path.join(staged, LOCK_FILENAME), { force: true });
|
|
299
328
|
_deps.rmSync(path.join(staged, SOURCE_METADATA_FILENAME), { force: true });
|
|
329
|
+
assertStagedRegistryCommandPayload(
|
|
330
|
+
staged,
|
|
331
|
+
opts[SOURCE_POLICY_PREFLIGHT]?.registryAuthority,
|
|
332
|
+
);
|
|
300
333
|
|
|
301
334
|
const remoteSbomPayloadComparison = buildRemoteSbomPayloadComparison({
|
|
302
335
|
remoteArtifactEvidence:
|
|
@@ -508,17 +541,34 @@ export function installFromDirectory(srcDir, opts = {}) {
|
|
|
508
541
|
* An optional `#ref` (branch / tag / commit) pins the checkout.
|
|
509
542
|
*/
|
|
510
543
|
export function installFromSource(source, opts = {}) {
|
|
544
|
+
const governedOpts = preflightManagedSource(source, opts, "install");
|
|
511
545
|
return _withMaterializedSource(
|
|
512
546
|
source,
|
|
513
547
|
(dir, info) => {
|
|
514
548
|
const sourceMetadata =
|
|
515
|
-
normalizeSourceMetadata(opts.sourceMetadata) ||
|
|
516
549
|
normalizeSourceMetadata(
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
550
|
+
bindSourcePolicyAuthority(governedOpts.sourceMetadata, governedOpts),
|
|
551
|
+
) ||
|
|
552
|
+
normalizeSourceMetadata(
|
|
553
|
+
bindSourcePolicyAuthority(
|
|
554
|
+
info
|
|
555
|
+
? { type: "git", source: info.url, ref: info.ref || null }
|
|
556
|
+
: {
|
|
557
|
+
type: "local",
|
|
558
|
+
source:
|
|
559
|
+
governedOpts[SOURCE_POLICY_PREFLIGHT]?.materialized?.path ||
|
|
560
|
+
path.resolve(
|
|
561
|
+
governedOpts.cwd || process.cwd(),
|
|
562
|
+
String(source || ""),
|
|
563
|
+
),
|
|
564
|
+
},
|
|
565
|
+
governedOpts,
|
|
566
|
+
),
|
|
520
567
|
);
|
|
521
|
-
const res = installFromDirectory(dir, {
|
|
568
|
+
const res = installFromDirectory(dir, {
|
|
569
|
+
...governedOpts,
|
|
570
|
+
sourceMetadata,
|
|
571
|
+
});
|
|
522
572
|
if (!info) return res;
|
|
523
573
|
const result = {
|
|
524
574
|
...res,
|
|
@@ -528,8 +578,180 @@ export function installFromSource(source, opts = {}) {
|
|
|
528
578
|
transferPendingTransaction(res, result);
|
|
529
579
|
return result;
|
|
530
580
|
},
|
|
581
|
+
governedOpts,
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function describeMaterializedSource(source, opts, materializedKind = null) {
|
|
586
|
+
if (materializedKind !== "directory") {
|
|
587
|
+
const git = parsePluginGitSource(source);
|
|
588
|
+
if (git) return { kind: "git", git };
|
|
589
|
+
}
|
|
590
|
+
return {
|
|
591
|
+
kind: "directory",
|
|
592
|
+
path: path.resolve(
|
|
593
|
+
opts.cwd || process.cwd(),
|
|
594
|
+
normalizePluginLocalSourcePath(source),
|
|
595
|
+
),
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function preflightManagedSource(source, opts, action, options = {}) {
|
|
600
|
+
if (opts[SOURCE_POLICY_PREFLIGHT]) return opts;
|
|
601
|
+
const materialized = describeMaterializedSource(
|
|
602
|
+
source,
|
|
531
603
|
opts,
|
|
604
|
+
options.materializedKind,
|
|
532
605
|
);
|
|
606
|
+
const registrySource =
|
|
607
|
+
opts.sourceMetadata?.type === "registry"
|
|
608
|
+
? opts.sourceMetadata.registry || opts.sourceMetadata.source
|
|
609
|
+
: null;
|
|
610
|
+
// Policy follows the bytes that will actually be materialized. The only
|
|
611
|
+
// intentional indirection is a registry-backed install, whose catalog URL
|
|
612
|
+
// is authoritative in sourceMetadata; a caller-supplied display/legacy
|
|
613
|
+
// policySource must never substitute a different allowlisted identity.
|
|
614
|
+
const policySource = registrySource ?? source;
|
|
615
|
+
const policyKind = registrySource ? "registry" : materialized.kind;
|
|
616
|
+
const managedPolicy = resolvePluginManagedPolicy(opts);
|
|
617
|
+
let registryAuthority = null;
|
|
618
|
+
if (registrySource) {
|
|
619
|
+
registryAuthority = assertRegistryResolutionAuthority(
|
|
620
|
+
opts.registryResolutionAuthority,
|
|
621
|
+
{
|
|
622
|
+
registryUrl: registrySource,
|
|
623
|
+
source,
|
|
624
|
+
ref: materialized.git?.ref || null,
|
|
625
|
+
},
|
|
626
|
+
);
|
|
627
|
+
if (
|
|
628
|
+
opts.sourceMetadata.resolvedSource !== registryAuthority.declaredSource ||
|
|
629
|
+
(opts.sourceMetadata.ref || null) !== (registryAuthority.ref || null)
|
|
630
|
+
) {
|
|
631
|
+
throw new Error(
|
|
632
|
+
"registry source metadata does not match materialized source",
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
if (
|
|
636
|
+
!opts.sourceMetadata.catalogAuthority ||
|
|
637
|
+
typeof opts.sourceMetadata.catalogAuthority !== "object" ||
|
|
638
|
+
Array.isArray(opts.sourceMetadata.catalogAuthority)
|
|
639
|
+
) {
|
|
640
|
+
throw new Error("registry source metadata requires catalog authority");
|
|
641
|
+
}
|
|
642
|
+
const declaredDocumentSha256 =
|
|
643
|
+
opts.sourceMetadata.catalogAuthority?.registryDocumentSha256 || null;
|
|
644
|
+
if (
|
|
645
|
+
declaredDocumentSha256 &&
|
|
646
|
+
declaredDocumentSha256 !== registryAuthority.documentSha256
|
|
647
|
+
) {
|
|
648
|
+
throw new Error(
|
|
649
|
+
"registry source authority does not match catalog document digest",
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
if (registryAuthority.sourceType === "archive") {
|
|
653
|
+
const archive = opts.sourceMetadata.catalogAuthority?.archiveSource;
|
|
654
|
+
if (
|
|
655
|
+
!archive ||
|
|
656
|
+
archive.archiveSha256 !== registryAuthority.archiveSha256 ||
|
|
657
|
+
archive.payloadSha256 !== registryAuthority.payloadSha256
|
|
658
|
+
) {
|
|
659
|
+
throw new Error(
|
|
660
|
+
"registry source authority does not match archive digest authority",
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (registryAuthority.sourceType === "command") {
|
|
665
|
+
const command = opts.sourceMetadata.catalogAuthority?.commandSource;
|
|
666
|
+
if (
|
|
667
|
+
!command ||
|
|
668
|
+
command.descriptorIdentity !== registryAuthority.declaredSource ||
|
|
669
|
+
command.payloadSha256 !== registryAuthority.payloadSha256 ||
|
|
670
|
+
command.mode !== registryAuthority.commandMode
|
|
671
|
+
) {
|
|
672
|
+
throw new Error(
|
|
673
|
+
"registry source authority does not match command source authority",
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
// Resolve managed identity before exists/clone/cache/process work. The
|
|
679
|
+
// manifest name is checked again by installFromDirectory after materializing
|
|
680
|
+
// bytes; source policy cannot wait for that later gate.
|
|
681
|
+
const decision = enforcePluginSourcePolicy(policySource, managedPolicy, {
|
|
682
|
+
action,
|
|
683
|
+
cwd: opts.cwd,
|
|
684
|
+
kindHint: policyKind,
|
|
685
|
+
});
|
|
686
|
+
const policyIdentity = canonicalizePluginSource(policySource, {
|
|
687
|
+
cwd: opts.cwd,
|
|
688
|
+
kindHint: policyKind,
|
|
689
|
+
});
|
|
690
|
+
const identityDigest = (identity) =>
|
|
691
|
+
crypto
|
|
692
|
+
.createHash("sha256")
|
|
693
|
+
.update(
|
|
694
|
+
canonicalJson({
|
|
695
|
+
identity: identity.identityDigest || identity.key,
|
|
696
|
+
ref: identity.ref ?? null,
|
|
697
|
+
path: identity.path ?? null,
|
|
698
|
+
}),
|
|
699
|
+
)
|
|
700
|
+
.digest("hex");
|
|
701
|
+
const sourceAuthority = {
|
|
702
|
+
policyDigest: decision.policyDigest || null,
|
|
703
|
+
sourceDigest: identityDigest(policyIdentity),
|
|
704
|
+
resolvedSourceDigest: registrySource
|
|
705
|
+
? registryResolvedSourceAuthorityDigest(registryAuthority)
|
|
706
|
+
: null,
|
|
707
|
+
registryDocumentSha256: registryAuthority?.documentSha256 || null,
|
|
708
|
+
};
|
|
709
|
+
const preflight = {
|
|
710
|
+
decision,
|
|
711
|
+
materialized,
|
|
712
|
+
sourceAuthority,
|
|
713
|
+
registryAuthority,
|
|
714
|
+
};
|
|
715
|
+
const boundSourceMetadata = opts.sourceMetadata
|
|
716
|
+
? bindSourcePolicyAuthority(opts.sourceMetadata, {
|
|
717
|
+
[SOURCE_POLICY_PREFLIGHT]: preflight,
|
|
718
|
+
})
|
|
719
|
+
: null;
|
|
720
|
+
const normalizedSourceMetadata = boundSourceMetadata
|
|
721
|
+
? normalizeSourceMetadata(boundSourceMetadata)
|
|
722
|
+
: null;
|
|
723
|
+
if (opts.sourceMetadata && !normalizedSourceMetadata) {
|
|
724
|
+
throw new Error("plugin source metadata is invalid");
|
|
725
|
+
}
|
|
726
|
+
return {
|
|
727
|
+
...opts,
|
|
728
|
+
managedPolicy,
|
|
729
|
+
...(normalizedSourceMetadata
|
|
730
|
+
? { sourceMetadata: normalizedSourceMetadata }
|
|
731
|
+
: {}),
|
|
732
|
+
[SOURCE_POLICY_PREFLIGHT]: preflight,
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function bindSourcePolicyAuthority(sourceMetadata, opts) {
|
|
737
|
+
const authority = opts[SOURCE_POLICY_PREFLIGHT]?.sourceAuthority;
|
|
738
|
+
if (!authority || !sourceMetadata) return sourceMetadata;
|
|
739
|
+
return {
|
|
740
|
+
...sourceMetadata,
|
|
741
|
+
policyDigest: authority.policyDigest,
|
|
742
|
+
sourceDigest: authority.sourceDigest,
|
|
743
|
+
...(authority.resolvedSourceDigest
|
|
744
|
+
? { resolvedSourceDigest: authority.resolvedSourceDigest }
|
|
745
|
+
: {}),
|
|
746
|
+
...(authority.registryDocumentSha256 && sourceMetadata.catalogAuthority
|
|
747
|
+
? {
|
|
748
|
+
catalogAuthority: {
|
|
749
|
+
...sourceMetadata.catalogAuthority,
|
|
750
|
+
registryDocumentSha256: authority.registryDocumentSha256,
|
|
751
|
+
},
|
|
752
|
+
}
|
|
753
|
+
: {}),
|
|
754
|
+
};
|
|
533
755
|
}
|
|
534
756
|
|
|
535
757
|
/**
|
|
@@ -538,12 +760,21 @@ export function installFromSource(source, opts = {}) {
|
|
|
538
760
|
* Single fetch shared by installFromSource + updatePlugin.
|
|
539
761
|
*/
|
|
540
762
|
function _withMaterializedSource(source, fn, opts = {}) {
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
763
|
+
const materialized =
|
|
764
|
+
opts[SOURCE_POLICY_PREFLIGHT]?.materialized ||
|
|
765
|
+
describeMaterializedSource(source, opts);
|
|
766
|
+
if (materialized.kind === "directory") {
|
|
767
|
+
if (
|
|
768
|
+
_deps.existsSync(materialized.path) &&
|
|
769
|
+
_deps.lstatSync(materialized.path).isDirectory()
|
|
770
|
+
) {
|
|
771
|
+
return fn(materialized.path, null);
|
|
772
|
+
}
|
|
773
|
+
throw new Error(
|
|
774
|
+
`source directory does not exist: ${redactPluginSourceForDisplay(materialized.path)}`,
|
|
775
|
+
);
|
|
545
776
|
}
|
|
546
|
-
const git =
|
|
777
|
+
const git = materialized.git;
|
|
547
778
|
if (git) {
|
|
548
779
|
if (opts.offline === true) {
|
|
549
780
|
const cached = readMarketplaceSourceCache(opts.sourceMetadata, {
|
|
@@ -562,7 +793,7 @@ function _withMaterializedSource(source, fn, opts = {}) {
|
|
|
562
793
|
}
|
|
563
794
|
return result;
|
|
564
795
|
}
|
|
565
|
-
const cloned = fetchGitRepo(git.url, git.ref);
|
|
796
|
+
const cloned = fetchGitRepo(git.url, git.ref, { cwd: opts.cwd });
|
|
566
797
|
try {
|
|
567
798
|
const result = fn(cloned, git);
|
|
568
799
|
if (result && typeof result === "object") {
|
|
@@ -592,9 +823,7 @@ function _withMaterializedSource(source, fn, opts = {}) {
|
|
|
592
823
|
}
|
|
593
824
|
}
|
|
594
825
|
}
|
|
595
|
-
throw new Error(
|
|
596
|
-
`source not found as a local directory or git URL: ${source}`,
|
|
597
|
-
);
|
|
826
|
+
throw new Error("source is neither a local directory nor a git URL");
|
|
598
827
|
}
|
|
599
828
|
|
|
600
829
|
/**
|
|
@@ -606,6 +835,7 @@ function _withMaterializedSource(source, fn, opts = {}) {
|
|
|
606
835
|
* Returns { name, version, previousVersion, updated, reinstalled }.
|
|
607
836
|
*/
|
|
608
837
|
export function updatePlugin(source, opts = {}) {
|
|
838
|
+
opts = preflightManagedSource(source, opts, "upgrade");
|
|
609
839
|
const scope = opts.scope || "user";
|
|
610
840
|
const updateMaterialized = (dir, info) => {
|
|
611
841
|
const manifest = parsePluginManifest(dir);
|
|
@@ -627,8 +857,7 @@ export function updatePlugin(source, opts = {}) {
|
|
|
627
857
|
updatePlugin(source, {
|
|
628
858
|
...opts,
|
|
629
859
|
_lifecycleLock: lifecycleLock,
|
|
630
|
-
|
|
631
|
-
_materializedGitInfo: info,
|
|
860
|
+
[UPDATE_MATERIALIZATION]: { dir, info },
|
|
632
861
|
}),
|
|
633
862
|
);
|
|
634
863
|
}
|
|
@@ -640,11 +869,21 @@ export function updatePlugin(source, opts = {}) {
|
|
|
640
869
|
assertNoRetainedInstallRecovery(name, { scope, cwd: opts.cwd });
|
|
641
870
|
assertExpectedPluginIdentity(name, version, opts.expectedIdentity);
|
|
642
871
|
const sourceMetadata =
|
|
643
|
-
normalizeSourceMetadata(opts.sourceMetadata) ||
|
|
644
872
|
normalizeSourceMetadata(
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
873
|
+
bindSourcePolicyAuthority(opts.sourceMetadata, opts),
|
|
874
|
+
) ||
|
|
875
|
+
normalizeSourceMetadata(
|
|
876
|
+
bindSourcePolicyAuthority(
|
|
877
|
+
info
|
|
878
|
+
? { type: "git", source: info.url, ref: info.ref || null }
|
|
879
|
+
: {
|
|
880
|
+
type: "local",
|
|
881
|
+
source:
|
|
882
|
+
opts[SOURCE_POLICY_PREFLIGHT]?.materialized?.path ||
|
|
883
|
+
path.resolve(opts.cwd || process.cwd(), String(source || "")),
|
|
884
|
+
},
|
|
885
|
+
opts,
|
|
886
|
+
),
|
|
648
887
|
);
|
|
649
888
|
const previousVersion = getActiveVersion(name, { scope, cwd: opts.cwd });
|
|
650
889
|
assertReplacementApprovals({
|
|
@@ -799,10 +1038,10 @@ export function updatePlugin(source, opts = {}) {
|
|
|
799
1038
|
transferPendingTransaction(res, result);
|
|
800
1039
|
return result;
|
|
801
1040
|
};
|
|
802
|
-
if (opts
|
|
1041
|
+
if (opts[UPDATE_MATERIALIZATION]) {
|
|
803
1042
|
return updateMaterialized(
|
|
804
|
-
opts.
|
|
805
|
-
opts.
|
|
1043
|
+
opts[UPDATE_MATERIALIZATION].dir,
|
|
1044
|
+
opts[UPDATE_MATERIALIZATION].info || null,
|
|
806
1045
|
);
|
|
807
1046
|
}
|
|
808
1047
|
return _withMaterializedSource(source, updateMaterialized, opts);
|
|
@@ -1570,19 +1809,158 @@ function rollbackRecoveredWholeNameUninstall(transaction) {
|
|
|
1570
1809
|
* not remote-looking. `owner/repo` expands to a GitHub HTTPS URL.
|
|
1571
1810
|
*/
|
|
1572
1811
|
export function parseGitSource(raw) {
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1812
|
+
return parsePluginGitSource(raw);
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
function isPathWithin(child, root) {
|
|
1816
|
+
const relative = path.relative(root, child);
|
|
1817
|
+
return (
|
|
1818
|
+
relative === "" ||
|
|
1819
|
+
(!path.isAbsolute(relative) &&
|
|
1820
|
+
relative !== ".." &&
|
|
1821
|
+
!relative.startsWith(`..${path.sep}`))
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
function validateTrustedExecutable(value, cwd) {
|
|
1826
|
+
if (typeof value !== "string" || !path.isAbsolute(value)) return null;
|
|
1827
|
+
try {
|
|
1828
|
+
const executable = fs.realpathSync.native(value);
|
|
1829
|
+
const stat = fs.statSync(executable);
|
|
1830
|
+
if (!stat.isFile()) return null;
|
|
1831
|
+
if (process.platform !== "win32" && (Number(stat.mode) & 0o111) === 0) {
|
|
1832
|
+
return null;
|
|
1833
|
+
}
|
|
1834
|
+
if (isPathWithin(executable, path.resolve(cwd))) return null;
|
|
1835
|
+
return executable;
|
|
1836
|
+
} catch {
|
|
1837
|
+
return null;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
function resolveTrustedExecutable({
|
|
1842
|
+
cwd,
|
|
1843
|
+
executableName,
|
|
1844
|
+
configured,
|
|
1845
|
+
configuredLabel,
|
|
1846
|
+
injected = null,
|
|
1847
|
+
cached = null,
|
|
1848
|
+
}) {
|
|
1849
|
+
if (injected != null) {
|
|
1850
|
+
const trusted = validateTrustedExecutable(injected, cwd);
|
|
1851
|
+
if (!trusted)
|
|
1852
|
+
throw new Error(`configured ${configuredLabel} is not trusted`);
|
|
1853
|
+
return trusted;
|
|
1854
|
+
}
|
|
1855
|
+
const trustedCached = validateTrustedExecutable(cached, cwd);
|
|
1856
|
+
if (trustedCached) return trustedCached;
|
|
1857
|
+
if (configured != null) {
|
|
1858
|
+
const trusted = validateTrustedExecutable(configured, cwd);
|
|
1859
|
+
if (!trusted) {
|
|
1860
|
+
throw new Error(`${configuredLabel} must be a trusted absolute file`);
|
|
1861
|
+
}
|
|
1862
|
+
return trusted;
|
|
1581
1863
|
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1864
|
+
for (const rawEntry of String(process.env.PATH || "").split(path.delimiter)) {
|
|
1865
|
+
const entry = rawEntry.replace(/^"|"$/gu, "");
|
|
1866
|
+
if (!entry || !path.isAbsolute(entry)) continue;
|
|
1867
|
+
const trusted = validateTrustedExecutable(
|
|
1868
|
+
path.join(entry, executableName),
|
|
1869
|
+
cwd,
|
|
1870
|
+
);
|
|
1871
|
+
if (trusted) return trusted;
|
|
1584
1872
|
}
|
|
1585
|
-
|
|
1873
|
+
throw new Error(
|
|
1874
|
+
`trusted ${configuredLabel} was not found on an absolute PATH entry`,
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
function resolveTrustedGitExecutable(cwd) {
|
|
1879
|
+
const executable = resolveTrustedExecutable({
|
|
1880
|
+
cwd,
|
|
1881
|
+
executableName: process.platform === "win32" ? "git.exe" : "git",
|
|
1882
|
+
configured: process.env.CHAINLESSCHAIN_GIT_BIN,
|
|
1883
|
+
configuredLabel: "Git executable",
|
|
1884
|
+
injected: _deps.gitExecutable,
|
|
1885
|
+
cached: cachedTrustedGitExecutable,
|
|
1886
|
+
});
|
|
1887
|
+
cachedTrustedGitExecutable = executable;
|
|
1888
|
+
return executable;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
function resolveTrustedSshExecutable(cwd) {
|
|
1892
|
+
const executable = resolveTrustedExecutable({
|
|
1893
|
+
cwd,
|
|
1894
|
+
executableName: process.platform === "win32" ? "ssh.exe" : "ssh",
|
|
1895
|
+
configured: process.env.CHAINLESSCHAIN_SSH_BIN,
|
|
1896
|
+
configuredLabel: "SSH executable",
|
|
1897
|
+
cached: cachedTrustedSshExecutable,
|
|
1898
|
+
});
|
|
1899
|
+
cachedTrustedSshExecutable = executable;
|
|
1900
|
+
return executable;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
function hardenedPluginGitEnvironment() {
|
|
1904
|
+
const environment = {};
|
|
1905
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
1906
|
+
if (!key.toUpperCase().startsWith("GIT_")) environment[key] = value;
|
|
1907
|
+
}
|
|
1908
|
+
return {
|
|
1909
|
+
...environment,
|
|
1910
|
+
GIT_ATTR_NOSYSTEM: "1",
|
|
1911
|
+
GIT_CONFIG_COUNT: "0",
|
|
1912
|
+
GIT_CONFIG_GLOBAL: NULL_DEVICE,
|
|
1913
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
1914
|
+
GIT_EXTERNAL_DIFF: "",
|
|
1915
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
1916
|
+
GIT_PAGER: "",
|
|
1917
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
1918
|
+
LC_ALL: "C",
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
function quoteSshCommandArgument(value) {
|
|
1923
|
+
if (process.platform === "win32") {
|
|
1924
|
+
return `"${String(value).replace(/"/gu, '\\"')}"`;
|
|
1925
|
+
}
|
|
1926
|
+
return `'${String(value).replace(/'/gu, `'\\''`)}'`;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
function hardenedPluginGitArgs(args, { url, cwd }) {
|
|
1930
|
+
const parsed = parsePluginGitSource(url);
|
|
1931
|
+
const sshTransport =
|
|
1932
|
+
/^ssh:\/\//iu.test(parsed?.url || "") ||
|
|
1933
|
+
(!(parsed?.url || "").includes("://") &&
|
|
1934
|
+
/^(?:[^@\s/:]+@)?(?:\[[^\]]+\]|[^\s/:]+):/u.test(parsed?.url || "") &&
|
|
1935
|
+
!/^[a-z]:[\\/]/iu.test(parsed?.url || ""));
|
|
1936
|
+
const sshArgs = sshTransport
|
|
1937
|
+
? [
|
|
1938
|
+
"-c",
|
|
1939
|
+
`core.sshCommand=${quoteSshCommandArgument(resolveTrustedSshExecutable(cwd))} -F ${quoteSshCommandArgument(NULL_DEVICE)} -o BatchMode=yes -o ClearAllForwardings=yes -o PermitLocalCommand=no -o ProxyCommand=none -o CanonicalizeHostname=no`,
|
|
1940
|
+
]
|
|
1941
|
+
: [];
|
|
1942
|
+
const fileTransport = /^file:\/\//iu.test(parsed?.url || "");
|
|
1943
|
+
return [
|
|
1944
|
+
"--no-pager",
|
|
1945
|
+
"-c",
|
|
1946
|
+
`core.hooksPath=${NULL_DEVICE}`,
|
|
1947
|
+
"-c",
|
|
1948
|
+
"core.fsmonitor=false",
|
|
1949
|
+
"-c",
|
|
1950
|
+
"core.untrackedCache=false",
|
|
1951
|
+
"-c",
|
|
1952
|
+
"credential.helper=",
|
|
1953
|
+
"-c",
|
|
1954
|
+
"diff.external=",
|
|
1955
|
+
"-c",
|
|
1956
|
+
"http.followRedirects=false",
|
|
1957
|
+
"-c",
|
|
1958
|
+
"protocol.ext.allow=never",
|
|
1959
|
+
"-c",
|
|
1960
|
+
`protocol.file.allow=${fileTransport ? "always" : "never"}`,
|
|
1961
|
+
...sshArgs,
|
|
1962
|
+
...args,
|
|
1963
|
+
];
|
|
1586
1964
|
}
|
|
1587
1965
|
|
|
1588
1966
|
/**
|
|
@@ -1590,30 +1968,46 @@ export function parseGitSource(raw) {
|
|
|
1590
1968
|
* checkout path. Uses `git` via spawn WITHOUT a shell (url/ref are argv, not a
|
|
1591
1969
|
* command line — no injection). Caller removes the temp dir's parent.
|
|
1592
1970
|
*/
|
|
1593
|
-
export function fetchGitRepo(url, ref) {
|
|
1971
|
+
export function fetchGitRepo(url, ref, options = {}) {
|
|
1594
1972
|
// git argv-injection guard: a value starting with "-" is parsed by git as an
|
|
1595
1973
|
// OPTION, not a URL/ref — e.g. a registry-supplied ref "-f" reaches
|
|
1596
1974
|
// `git checkout <ref>` on the full-clone retry path, and an option-looking
|
|
1597
1975
|
// url reaches `git clone`. Real git URLs/refs never start with "-"
|
|
1598
1976
|
// (check-ref-format forbids it), so reject instead of trying to escape.
|
|
1599
1977
|
if (String(url).startsWith("-")) {
|
|
1600
|
-
throw new Error(
|
|
1978
|
+
throw new Error("refusing git source that looks like an option");
|
|
1601
1979
|
}
|
|
1602
1980
|
if (ref != null && String(ref).startsWith("-")) {
|
|
1603
|
-
throw new Error(
|
|
1981
|
+
throw new Error("refusing git ref that looks like an option");
|
|
1604
1982
|
}
|
|
1983
|
+
const git = parsePluginGitSource(ref ? `${url}#${ref}` : url);
|
|
1984
|
+
assertPluginGitTransportSafe(git, { allowFile: true });
|
|
1985
|
+
const cwd = path.resolve(options.cwd || process.cwd());
|
|
1986
|
+
const executable = resolveTrustedGitExecutable(cwd);
|
|
1605
1987
|
const base = _deps.mkdtempSync(path.join(os.tmpdir(), "cc-plugin-git-"));
|
|
1606
1988
|
const dir = path.join(base, "repo");
|
|
1607
|
-
const run = (args) =>
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1989
|
+
const run = (args) => {
|
|
1990
|
+
const hardenedArgs = hardenedPluginGitArgs(args, { url, cwd });
|
|
1991
|
+
const auditRedactArgIndexes = hardenedArgs.flatMap((argument, index) =>
|
|
1992
|
+
argument === url || (ref != null && argument === ref) ? [index] : [],
|
|
1993
|
+
);
|
|
1994
|
+
try {
|
|
1995
|
+
return _deps.spawnSync(executable, hardenedArgs, {
|
|
1996
|
+
cwd: base,
|
|
1997
|
+
encoding: "utf8",
|
|
1998
|
+
env: hardenedPluginGitEnvironment(),
|
|
1999
|
+
timeout: 120000,
|
|
2000
|
+
windowsHide: true,
|
|
2001
|
+
origin: "plugin:install-git",
|
|
2002
|
+
policy: "allow",
|
|
2003
|
+
scope: "plugin-install",
|
|
2004
|
+
shell: false,
|
|
2005
|
+
auditRedactArgIndexes,
|
|
2006
|
+
});
|
|
2007
|
+
} catch (error) {
|
|
2008
|
+
return { error, status: null, stdout: "", stderr: "" };
|
|
2009
|
+
}
|
|
2010
|
+
};
|
|
1617
2011
|
|
|
1618
2012
|
// Reduce host-specific checkout differences before the v2 inventory hashes
|
|
1619
2013
|
// the materialized staged bytes. Repository .gitattributes may still define
|
|
@@ -1636,6 +2030,11 @@ export function fetchGitRepo(url, ref) {
|
|
|
1636
2030
|
if (res.error && res.error.code === "ENOENT") {
|
|
1637
2031
|
throw new Error("git is not installed (needed to fetch a remote plugin)");
|
|
1638
2032
|
}
|
|
2033
|
+
if (res.error) {
|
|
2034
|
+
throw new Error(
|
|
2035
|
+
`git process failed for ${redactPluginSourceForDisplay(url)}`,
|
|
2036
|
+
);
|
|
2037
|
+
}
|
|
1639
2038
|
if (res.status !== 0) {
|
|
1640
2039
|
// A commit SHA can't be used with --branch/--depth; retry with a full clone
|
|
1641
2040
|
// then checkout the ref explicitly.
|
|
@@ -1651,8 +2050,9 @@ export function fetchGitRepo(url, ref) {
|
|
|
1651
2050
|
if (co.status === 0) return dir;
|
|
1652
2051
|
}
|
|
1653
2052
|
}
|
|
1654
|
-
|
|
1655
|
-
|
|
2053
|
+
throw new Error(
|
|
2054
|
+
`git clone failed for ${redactPluginSourceForDisplay(url)} (git exited ${res.status})`,
|
|
2055
|
+
);
|
|
1656
2056
|
}
|
|
1657
2057
|
return dir;
|
|
1658
2058
|
}
|
|
@@ -2987,13 +3387,19 @@ function sameSourceAuthority(current, candidate) {
|
|
|
2987
3387
|
if (current?.type !== candidate?.type) return false;
|
|
2988
3388
|
if (current.type === "registry") {
|
|
2989
3389
|
return Boolean(
|
|
2990
|
-
current.
|
|
2991
|
-
candidate.
|
|
2992
|
-
current.
|
|
3390
|
+
current.sourceDigest &&
|
|
3391
|
+
candidate.sourceDigest &&
|
|
3392
|
+
current.sourceDigest === candidate.sourceDigest &&
|
|
3393
|
+
current.resolvedSourceDigest &&
|
|
3394
|
+
candidate.resolvedSourceDigest &&
|
|
3395
|
+
current.resolvedSourceDigest === candidate.resolvedSourceDigest,
|
|
2993
3396
|
);
|
|
2994
3397
|
}
|
|
2995
|
-
|
|
2996
|
-
|
|
3398
|
+
return Boolean(
|
|
3399
|
+
current.sourceDigest &&
|
|
3400
|
+
candidate.sourceDigest &&
|
|
3401
|
+
current.sourceDigest === candidate.sourceDigest,
|
|
3402
|
+
);
|
|
2997
3403
|
}
|
|
2998
3404
|
|
|
2999
3405
|
function effectivePluginRecord(
|
|
@@ -3229,23 +3635,160 @@ function normalizeSourceMetadata(value) {
|
|
|
3229
3635
|
type,
|
|
3230
3636
|
source,
|
|
3231
3637
|
ref: cleanBounded(value.ref, 256),
|
|
3638
|
+
sourceDigest:
|
|
3639
|
+
normalizeSourceAuthorityDigest(value.sourceDigest) ||
|
|
3640
|
+
pluginSourceAuthorityDigest(value.source, type, value.ref),
|
|
3232
3641
|
};
|
|
3233
3642
|
const registry = sanitizeSource(value.registry);
|
|
3234
3643
|
const resolvedSource = sanitizeSource(value.resolvedSource);
|
|
3235
3644
|
const packageName = cleanBounded(value.package, 256);
|
|
3645
|
+
const catalogAuthority =
|
|
3646
|
+
value.catalogAuthority != null
|
|
3647
|
+
? normalizeCatalogAuthority(value.catalogAuthority)
|
|
3648
|
+
: null;
|
|
3236
3649
|
if (registry) metadata.registry = registry;
|
|
3237
3650
|
if (resolvedSource) metadata.resolvedSource = resolvedSource;
|
|
3651
|
+
const resolvedSourceDigest =
|
|
3652
|
+
normalizeSourceAuthorityDigest(value.resolvedSourceDigest) ||
|
|
3653
|
+
(value.resolvedSource
|
|
3654
|
+
? catalogAuthority?.archiveSource
|
|
3655
|
+
? archiveSourceAuthorityDigest(
|
|
3656
|
+
value.resolvedSource,
|
|
3657
|
+
catalogAuthority.archiveSource,
|
|
3658
|
+
)
|
|
3659
|
+
: catalogAuthority?.commandSource
|
|
3660
|
+
? commandSourceAuthorityDigest(
|
|
3661
|
+
value.resolvedSource,
|
|
3662
|
+
catalogAuthority.commandSource,
|
|
3663
|
+
)
|
|
3664
|
+
: pluginSourceAuthorityDigest(
|
|
3665
|
+
value.resolvedSource,
|
|
3666
|
+
"git",
|
|
3667
|
+
value.ref,
|
|
3668
|
+
)
|
|
3669
|
+
: null);
|
|
3670
|
+
const policyDigest = normalizeSourceAuthorityDigest(value.policyDigest);
|
|
3671
|
+
if (resolvedSourceDigest) {
|
|
3672
|
+
metadata.resolvedSourceDigest = resolvedSourceDigest;
|
|
3673
|
+
}
|
|
3674
|
+
if (policyDigest) metadata.policyDigest = policyDigest;
|
|
3238
3675
|
if (packageName) metadata.package = packageName;
|
|
3239
3676
|
if (value.offline === true) metadata.offline = true;
|
|
3240
|
-
if (
|
|
3241
|
-
metadata.catalogAuthority = normalizeCatalogAuthority(
|
|
3242
|
-
value.catalogAuthority,
|
|
3243
|
-
);
|
|
3244
|
-
}
|
|
3677
|
+
if (catalogAuthority) metadata.catalogAuthority = catalogAuthority;
|
|
3245
3678
|
assertCatalogRemoteArtifactBindings(metadata);
|
|
3679
|
+
assertCatalogArchiveSourceBindings(metadata);
|
|
3680
|
+
assertCatalogCommandSourceBindings(metadata);
|
|
3246
3681
|
return metadata;
|
|
3247
3682
|
}
|
|
3248
3683
|
|
|
3684
|
+
function assertCatalogArchiveSourceBindings(metadata) {
|
|
3685
|
+
const archive = metadata.catalogAuthority?.archiveSource;
|
|
3686
|
+
if (!archive) return;
|
|
3687
|
+
if (
|
|
3688
|
+
metadata.type !== "registry" ||
|
|
3689
|
+
!metadata.registry ||
|
|
3690
|
+
!metadata.resolvedSource
|
|
3691
|
+
) {
|
|
3692
|
+
throw new Error(
|
|
3693
|
+
"marketplace archive authority requires registry and resolved source metadata",
|
|
3694
|
+
);
|
|
3695
|
+
}
|
|
3696
|
+
let registryOrigin;
|
|
3697
|
+
try {
|
|
3698
|
+
registryOrigin = new URL(metadata.registry).origin;
|
|
3699
|
+
} catch {
|
|
3700
|
+
throw new Error("marketplace archive registry URL is invalid");
|
|
3701
|
+
}
|
|
3702
|
+
if (
|
|
3703
|
+
archive.registryOrigin !== registryOrigin ||
|
|
3704
|
+
archive.url !== metadata.resolvedSource
|
|
3705
|
+
) {
|
|
3706
|
+
throw new Error(
|
|
3707
|
+
"marketplace archive authority does not match the selected registry and source",
|
|
3708
|
+
);
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3712
|
+
function assertCatalogCommandSourceBindings(metadata) {
|
|
3713
|
+
const command = metadata.catalogAuthority?.commandSource;
|
|
3714
|
+
if (!command) return;
|
|
3715
|
+
if (
|
|
3716
|
+
metadata.type !== "registry" ||
|
|
3717
|
+
!metadata.registry ||
|
|
3718
|
+
metadata.resolvedSource !== command.descriptorIdentity
|
|
3719
|
+
) {
|
|
3720
|
+
throw new Error(
|
|
3721
|
+
"marketplace command authority requires matching registry source metadata",
|
|
3722
|
+
);
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
|
|
3726
|
+
function normalizeSourceAuthorityDigest(value) {
|
|
3727
|
+
const normalized = cleanBounded(value, 64)?.toLowerCase() || null;
|
|
3728
|
+
return normalized && /^[a-f0-9]{64}$/u.test(normalized) ? normalized : null;
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3731
|
+
function pluginSourceAuthorityDigest(source, type, ref = null) {
|
|
3732
|
+
const identity = canonicalizePluginSource(source, {
|
|
3733
|
+
kindHint:
|
|
3734
|
+
type === "registry" ? "registry" : type === "local" ? "directory" : "git",
|
|
3735
|
+
ref: ref || undefined,
|
|
3736
|
+
});
|
|
3737
|
+
return crypto
|
|
3738
|
+
.createHash("sha256")
|
|
3739
|
+
.update(
|
|
3740
|
+
canonicalJson({
|
|
3741
|
+
identity: identity.identityDigest || identity.key,
|
|
3742
|
+
ref: identity.ref ?? null,
|
|
3743
|
+
path: identity.path ?? null,
|
|
3744
|
+
}),
|
|
3745
|
+
)
|
|
3746
|
+
.digest("hex");
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3749
|
+
function archiveSourceAuthorityDigest(source, archive) {
|
|
3750
|
+
return crypto
|
|
3751
|
+
.createHash("sha256")
|
|
3752
|
+
.update(
|
|
3753
|
+
canonicalJson({
|
|
3754
|
+
kind: "archive",
|
|
3755
|
+
source: sanitizeSource(source),
|
|
3756
|
+
archiveSha256: archive.archiveSha256,
|
|
3757
|
+
payloadSha256: archive.payloadSha256,
|
|
3758
|
+
}),
|
|
3759
|
+
)
|
|
3760
|
+
.digest("hex");
|
|
3761
|
+
}
|
|
3762
|
+
|
|
3763
|
+
function commandSourceAuthorityDigest(source, command) {
|
|
3764
|
+
return crypto
|
|
3765
|
+
.createHash("sha256")
|
|
3766
|
+
.update(
|
|
3767
|
+
canonicalJson({
|
|
3768
|
+
kind: "command",
|
|
3769
|
+
descriptorIdentity: sanitizeSource(source),
|
|
3770
|
+
mode: command.mode || command.commandMode || null,
|
|
3771
|
+
payloadSha256: command.payloadSha256 || null,
|
|
3772
|
+
}),
|
|
3773
|
+
)
|
|
3774
|
+
.digest("hex");
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
function registryResolvedSourceAuthorityDigest(authority) {
|
|
3778
|
+
if (!authority) return null;
|
|
3779
|
+
if (authority.sourceType === "archive") {
|
|
3780
|
+
return archiveSourceAuthorityDigest(authority.declaredSource, authority);
|
|
3781
|
+
}
|
|
3782
|
+
if (authority.sourceType === "command") {
|
|
3783
|
+
return commandSourceAuthorityDigest(authority.declaredSource, authority);
|
|
3784
|
+
}
|
|
3785
|
+
return pluginSourceAuthorityDigest(
|
|
3786
|
+
authority.declaredSource,
|
|
3787
|
+
"git",
|
|
3788
|
+
authority.ref,
|
|
3789
|
+
);
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3249
3792
|
function normalizeMigrationIssuedAt(value) {
|
|
3250
3793
|
const date = value == null ? new Date() : new Date(String(value));
|
|
3251
3794
|
if (!Number.isFinite(date.getTime())) {
|
|
@@ -3545,6 +4088,13 @@ function normalizeCatalogAuthority(value) {
|
|
|
3545
4088
|
const remoteArtifactEvidence = normalizeRemoteArtifactEvidence(
|
|
3546
4089
|
value.remoteArtifactEvidence,
|
|
3547
4090
|
);
|
|
4091
|
+
const archiveSource = normalizeArchiveSourceAuthority(value.archiveSource);
|
|
4092
|
+
const commandSource = normalizeCommandSourceAuthority(value.commandSource);
|
|
4093
|
+
if (archiveSource && commandSource) {
|
|
4094
|
+
throw new Error(
|
|
4095
|
+
"catalogAuthority cannot combine archive and command source authority",
|
|
4096
|
+
);
|
|
4097
|
+
}
|
|
3548
4098
|
const remoteSbomPayloadComparison = assertRemoteSbomPayloadComparison(
|
|
3549
4099
|
value.remoteSbomPayloadComparison,
|
|
3550
4100
|
{
|
|
@@ -3651,6 +4201,8 @@ function normalizeCatalogAuthority(value) {
|
|
|
3651
4201
|
...(registryNetworkAuthority ? { registryNetworkAuthority } : {}),
|
|
3652
4202
|
...(artifactExpectations ? { artifactExpectations } : {}),
|
|
3653
4203
|
...(remoteArtifactEvidence ? { remoteArtifactEvidence } : {}),
|
|
4204
|
+
...(archiveSource ? { archiveSource } : {}),
|
|
4205
|
+
...(commandSource ? { commandSource } : {}),
|
|
3654
4206
|
...(remoteSbomPayloadComparison ? { remoteSbomPayloadComparison } : {}),
|
|
3655
4207
|
...(publisherDeclaration ? { publisherDeclaration } : {}),
|
|
3656
4208
|
...(publisherAuthority ? { publisherAuthority } : {}),
|
|
@@ -3661,6 +4213,108 @@ function normalizeCatalogAuthority(value) {
|
|
|
3661
4213
|
};
|
|
3662
4214
|
}
|
|
3663
4215
|
|
|
4216
|
+
function normalizeArchiveSourceAuthority(value) {
|
|
4217
|
+
if (value == null) return null;
|
|
4218
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4219
|
+
throw new Error("catalogAuthority.archiveSource must be an object");
|
|
4220
|
+
}
|
|
4221
|
+
const url = cleanBounded(value.url, 4096);
|
|
4222
|
+
const registryOrigin = cleanBounded(value.registryOrigin, 4096);
|
|
4223
|
+
const archiveSha256 = cleanBounded(value.archiveSha256, 64);
|
|
4224
|
+
const payloadSha256 = cleanBounded(value.payloadSha256, 64);
|
|
4225
|
+
let archiveUrl;
|
|
4226
|
+
let selectedOrigin;
|
|
4227
|
+
try {
|
|
4228
|
+
archiveUrl = new URL(url);
|
|
4229
|
+
selectedOrigin = new URL(registryOrigin).origin;
|
|
4230
|
+
} catch {
|
|
4231
|
+
throw new Error("catalogAuthority.archiveSource URL authority is invalid");
|
|
4232
|
+
}
|
|
4233
|
+
if (
|
|
4234
|
+
archiveUrl.origin !== selectedOrigin ||
|
|
4235
|
+
archiveUrl.username ||
|
|
4236
|
+
archiveUrl.password
|
|
4237
|
+
) {
|
|
4238
|
+
throw new Error(
|
|
4239
|
+
"catalogAuthority.archiveSource must use the selected registry origin",
|
|
4240
|
+
);
|
|
4241
|
+
}
|
|
4242
|
+
if (
|
|
4243
|
+
value.schemaVersion !== "cc-plugin-marketplace-archive-source/v1" ||
|
|
4244
|
+
value.status !== "digest-verified-and-extracted" ||
|
|
4245
|
+
!/^[a-f0-9]{64}$/u.test(archiveSha256 || "") ||
|
|
4246
|
+
!/^[a-f0-9]{64}$/u.test(payloadSha256 || "")
|
|
4247
|
+
) {
|
|
4248
|
+
throw new Error(
|
|
4249
|
+
"catalogAuthority.archiveSource digest authority is invalid",
|
|
4250
|
+
);
|
|
4251
|
+
}
|
|
4252
|
+
const compressedBytes = Number(value.compressedBytes);
|
|
4253
|
+
const fileCount = Number(value.fileCount);
|
|
4254
|
+
const totalBytes = Number(value.totalBytes);
|
|
4255
|
+
if (
|
|
4256
|
+
!Number.isSafeInteger(compressedBytes) ||
|
|
4257
|
+
compressedBytes <= 0 ||
|
|
4258
|
+
compressedBytes > 64 * 1024 * 1024 ||
|
|
4259
|
+
!Number.isSafeInteger(fileCount) ||
|
|
4260
|
+
fileCount <= 0 ||
|
|
4261
|
+
fileCount > 10_000 ||
|
|
4262
|
+
!Number.isSafeInteger(totalBytes) ||
|
|
4263
|
+
totalBytes <= 0 ||
|
|
4264
|
+
totalBytes > 256 * 1024 * 1024
|
|
4265
|
+
) {
|
|
4266
|
+
throw new Error("catalogAuthority.archiveSource size authority is invalid");
|
|
4267
|
+
}
|
|
4268
|
+
return {
|
|
4269
|
+
schemaVersion: value.schemaVersion,
|
|
4270
|
+
status: value.status,
|
|
4271
|
+
url: archiveUrl.href,
|
|
4272
|
+
registryOrigin: selectedOrigin,
|
|
4273
|
+
archiveSha256,
|
|
4274
|
+
payloadSha256,
|
|
4275
|
+
compressedBytes,
|
|
4276
|
+
fileCount,
|
|
4277
|
+
totalBytes,
|
|
4278
|
+
fromCache: value.fromCache === true,
|
|
4279
|
+
};
|
|
4280
|
+
}
|
|
4281
|
+
|
|
4282
|
+
function normalizeCommandSourceAuthority(value) {
|
|
4283
|
+
if (value == null) return null;
|
|
4284
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4285
|
+
throw new Error("catalogAuthority.commandSource must be an object");
|
|
4286
|
+
}
|
|
4287
|
+
const descriptorIdentity = cleanBounded(value.descriptorIdentity, 128);
|
|
4288
|
+
const payloadSha256 = cleanBounded(value.payloadSha256, 64)?.toLowerCase();
|
|
4289
|
+
const mode = value.mode;
|
|
4290
|
+
const fileCount = Number(value.fileCount);
|
|
4291
|
+
const totalBytes = Number(value.totalBytes);
|
|
4292
|
+
if (
|
|
4293
|
+
value.schemaVersion !== "cc-plugin-marketplace-command-source/v1" ||
|
|
4294
|
+
value.status !== "descriptor-executed-and-verified" ||
|
|
4295
|
+
!/^command:[a-f0-9]{32}$/u.test(descriptorIdentity || "") ||
|
|
4296
|
+
!/^[a-f0-9]{64}$/u.test(payloadSha256 || "") ||
|
|
4297
|
+
!["copy", "link"].includes(mode) ||
|
|
4298
|
+
!Number.isSafeInteger(fileCount) ||
|
|
4299
|
+
fileCount <= 0 ||
|
|
4300
|
+
fileCount > 10_000 ||
|
|
4301
|
+
!Number.isSafeInteger(totalBytes) ||
|
|
4302
|
+
totalBytes <= 0 ||
|
|
4303
|
+
totalBytes > 256 * 1024 * 1024
|
|
4304
|
+
) {
|
|
4305
|
+
throw new Error("catalogAuthority.commandSource authority is invalid");
|
|
4306
|
+
}
|
|
4307
|
+
return {
|
|
4308
|
+
schemaVersion: value.schemaVersion,
|
|
4309
|
+
status: value.status,
|
|
4310
|
+
descriptorIdentity,
|
|
4311
|
+
mode,
|
|
4312
|
+
payloadSha256,
|
|
4313
|
+
fileCount,
|
|
4314
|
+
totalBytes,
|
|
4315
|
+
};
|
|
4316
|
+
}
|
|
4317
|
+
|
|
3664
4318
|
function normalizeArtifactExpectations(value) {
|
|
3665
4319
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
3666
4320
|
const digest = (candidate, label) => {
|
|
@@ -3928,16 +4582,7 @@ function sanitizeSource(value, preservePath = false) {
|
|
|
3928
4582
|
const raw = cleanBounded(value, 4096);
|
|
3929
4583
|
if (!raw) return null;
|
|
3930
4584
|
if (preservePath) return raw;
|
|
3931
|
-
|
|
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
|
-
}
|
|
4585
|
+
return redactPluginSourceForDisplay(raw);
|
|
3941
4586
|
}
|
|
3942
4587
|
|
|
3943
4588
|
function cleanBounded(value, max) {
|
|
@@ -4075,6 +4720,20 @@ function writeDurableFileAtomic(directory, target, value) {
|
|
|
4075
4720
|
}
|
|
4076
4721
|
}
|
|
4077
4722
|
|
|
4723
|
+
function assertStagedRegistryCommandPayload(staged, authority) {
|
|
4724
|
+
if (authority?.sourceType !== "command") return;
|
|
4725
|
+
const payload = buildMarketplacePayloadSbom(staged);
|
|
4726
|
+
if (
|
|
4727
|
+
payload.digest !== authority.payloadSha256 ||
|
|
4728
|
+
payload.fileCount !== authority.fileCount ||
|
|
4729
|
+
payload.totalBytes !== authority.totalBytes
|
|
4730
|
+
) {
|
|
4731
|
+
throw new Error(
|
|
4732
|
+
"registry command source payload changed before immutable installation",
|
|
4733
|
+
);
|
|
4734
|
+
}
|
|
4735
|
+
}
|
|
4736
|
+
|
|
4078
4737
|
/**
|
|
4079
4738
|
* Copy `src` → `dst`, refusing to write outside `root` and skipping symlinks
|
|
4080
4739
|
* (which could later resolve outside the plugin sandbox). Directories recurse.
|