chainlesschain 0.165.1 → 0.165.2
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/README.md +248 -2
- package/package.json +4 -1
- package/src/assets/web-panel/assets/{AIOps-CYxFDrcm.js → AIOps-lS1R0Cza.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CPBtdiU2.js → ActionButton-BmlgBPsj.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Dayq7Dp7.js → Analytics-CNKczv1S.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-6U7bPb2s.js → AppLayout-BuZGJARQ.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-D2Iqg_3D.js → Artifacts-Dm73Sl-O.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CdoOB7WV.js → Audit-BGMW7A5e.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DrnAVB8w.js → BackgroundAgents-BQ6M54JR.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CesMi5cy.js → Backup-DBzGP0bR.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DaX9ptax.js → BaseInput-D1mLCoOV.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-OrwOPxDS.js → Chat-BUfcN7p8.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CvPZFRxM.js → ChatBubbleRenderer-CY5m8ZHg.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DiHbcysC.js → Checkbox-Du0Yg5Pi.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CbgCkaBJ.js → Codegen-B2RXziHW.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Du5hgrh4.js → Col-DWhekOrR.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DsPOBW-p.js → Community-HQhrkon4.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BwHIFkNC.js → Compact-izHNb4uz.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BvrDQSgd.js → Compliance-BDwLy41-.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-gwAE6arx.js → Cowork-DFJhLa7K.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DW-0l4uF.js → Cron-DYY9RG7-.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-j-NsLesB.js → Crosschain-rr7tJTtv.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CibVEBOI.js → DID-D2sfHRLi.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-W4z9vSfe.js → Dashboard-q_eGdhha.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DAcrkKxG.js → Dropdown-Cufrh2Bz.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-6GXah-89.js → EmailListRenderer-B-kdv10f.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-TKDCq8Ku.js → FamilyGuardDashboard-D21XjgfG.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BXBLlcNm.js → Federation-CAwRTLZH.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BoJJBkG3.js → FormItemContext-vx3daNDF.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-okjsl46v.js → GenericCardRenderer-CRWY2wU4.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Rvs7i6nn.js → Git-Q6SwauyD.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CHSUxXXS.js → Governance-BJ1q4IQZ.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-V3BlfxsB.js → Inference-CSWjROgD.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-C_NlD4aR.js → KnowledgeGraph-fzuQsWuY.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Nn4LNxhA.js → Logs-DVF8Mnns.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-DEmjFcg-.js → Marketplace-BQP7LOA4.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DlopXipI.js → McpTools-BNlJCmNc.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-BG3ynX60.js → Memory-DLLg5WQ4.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DeRzWOfT.js → MobileBridge-CR2skNAF.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Tyd3o2A0.js → MobileProjects-BxVavObl.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BoPbOv1C.js → Mtc-B17pgqMI.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-un-rTHp7.js → MtcAudit-4UmV5cIL.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Ds0oveEB.js → Multisig-DoMEXEcU.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-D09JI-yY.js → NLProgramming-h9DMXFX4.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DTRhiKJn.js → Notes-Cihn-xX8.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BAnnNCim.js → NotificationSettings-BE6ZGa3R.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Yr-1fSYi.js → OrderTableRenderer-CnwojoSI.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-S9EAolpj.js → Organization-B2vpwmjb.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CqcLKqJ8.js → Overflow-C4z28R0-.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Dw0F9EcU.js → P2P-BVlFkwhW.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BDRwUevN.js → PdhVaultBrowser-BXbEdL5B.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-D3_c3nKB.js → Permissions-C28xs4lp.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CxdMV98x.js → PersonalDataHub-Dgl_rBjM.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-CgjNQFLI.js → Pipeline-3QUIhrKI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BHghIIht.js → Privacy-8Gh33OZ5.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-De6pqaKM.js → ProjectInit-BNmg4tZi.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-Bg4ZkNYQ.js → ProjectSettings-vgMfeCf9.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BhtAVilc.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CUnHbNf-.js → Providers-CXoOa8eX.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DA2f3Wts.js → QrScannerModal-BhfTF0oS.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-C1FeHDqp.js → QuickAsk-C746HEAj.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-B5guaUMN.js → Recommend-BSej8Eo0.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-PDADSds3.js → RemoteSession-f3gmnlG3.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-eTlRhgvK.js → Reputation-CyvybpkN.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D7AqSGY0.js → Row-DwV2PeYn.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-JiuLm4Ja.js → RssFeed-11GrzjZy.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CwruahW3.js → Search-BjKXkrAY.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CdhqaR0b.js → Security-eWiFb9Yk.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CU0ZIlcT.js → Services-CdCsMAtt.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CtduPfAb.js → Skeleton-4Q2UTKQ5.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CROXm6AY.js → Skills-v4rtYrRj.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DiZiABFD.js → Sla-CRhsFvd_.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-DBX6J1Oe.js → SyncSettings-D16zonl7.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BLyRJd3-.js → Tasks-Xxozz0i_.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BKa5qzmk.js → Templates-CXJpPtCT.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DnD3a4M6.js → Tenant-XRV0L1ZF.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DEitQeTb.js → Terminal-CYuO1i_w.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CjHggTDq.js → TimelineRenderer-BXqitWsu.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CsGT7sfl.js → Tokens-BvPrDHIy.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-yr4rFDrW.js → Trigger-DhCrSTf5.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CLvu1Sjp.js → Trust-DmsuZD6K.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B0m7yUP8.js → UkeySign-D_yrts5C.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C0VvP4lc.js → VideoEditing-B01ihPrm.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-kllBAG1M.js → Wallet-DpUjKa-N.js} +3 -3
- package/src/assets/web-panel/assets/{WebAuthn-C54UQNLx.js → WebAuthn-CL07yON6.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-B7y8qSvU.js → WorkflowEditor-CLMflnRr.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DRzTh8CJ.js → chat-DtUqe8h_.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BSMwSnCx.js → colors-DPKrgVfy.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BTg602H5.js → compact-item-BTt2GtOm.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-B6EGsdvp.js → createContext-D_VeUc1J.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DDKqJ8RD.js → hasIn-1kndiylj.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUEU48o1.js → index-2eyAXE3d.js} +1 -1
- package/src/assets/web-panel/assets/{index-rjzk9z1Q.js → index-4bna2_SR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmAubjDi.js → index-8nz_fmlj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Co7oM1qr.js → index-B9KvcS7R.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMuGd2--.js → index-BAXfRKfG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVTAl5N2.js → index-BHpBMWCh.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpTHIRfZ.js → index-BaZAbw9A.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWGax2QW.js → index-BepLZwgB.js} +1 -1
- package/src/assets/web-panel/assets/{index-DiW0sDkz.js → index-Bhudc_Ez.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGNklbP6.js → index-BlDNVgBt.js} +1 -1
- package/src/assets/web-panel/assets/{index-u2YVld9W.js → index-By5n8up9.js} +1 -1
- package/src/assets/web-panel/assets/{index-pqfux8vZ.js → index-CHG-KcS4.js} +1 -1
- package/src/assets/web-panel/assets/index-CPcM-eqo.js +1 -0
- package/src/assets/web-panel/assets/{index-BRqt4fO-.js → index-CX81qSM4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsR7Y2NG.js → index-CauoBQkH.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_JzIXUq.js → index-CcO04eDn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQlHFN_g.js → index-CtF7EN49.js} +1 -1
- package/src/assets/web-panel/assets/{index-CwPUZbMI.js → index-CzjkKUmR.js} +1 -1
- package/src/assets/web-panel/assets/{index-th2ihDc0.js → index-D95OFht7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CHfLYwcW.js → index-DFIZ_1Sf.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1GFfuqN.js → index-DG2MZQ3f.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1naNj4U.js → index-DJ8vXGOa.js} +1 -1
- package/src/assets/web-panel/assets/{index-DLdrHhML.js → index-Dbbymxpj.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBT3YeQ9.js → index-DdkplL9R.js} +1 -1
- package/src/assets/web-panel/assets/{index-BICn3Qh1.js → index-DfGl8CqG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DqFXGzzC.js → index-DfKBRdSS.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bx044qDj.js → index-DsJCDUJG.js} +1 -1
- package/src/assets/web-panel/assets/{index-XFe-1twF.js → index-Dwqmtk65.js} +1 -1
- package/src/assets/web-panel/assets/{index-C66NpWy5.js → index-DxdTdBix.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmAInUkX.js → index-FzdwcSy2.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cf23wVMs.js → index-GsoUQ2M7.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5CsBShd.js → index-brCInrzq.js} +1 -1
- package/src/assets/web-panel/assets/{index-323Hhxxt.js → index-dVc-CXY4.js} +1 -1
- package/src/assets/web-panel/assets/{index-p2HYn89m.js → index-fk2Rjacw.js} +3 -3
- package/src/assets/web-panel/assets/{index-Dfv2lKu3.js → index-l1BETrDb.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNxg61YP.js → index-m31Ka8hZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDTvsX8t.js → index-ocf06EVQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dc77VR3H.js → index-sDoMHTNA.js} +1 -1
- package/src/assets/web-panel/assets/index-xeSyVoRe.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-C3Djh9fa.js → initDefaultProps-CvyYJd7z.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BzqFmbSe.js → motion-DYZkhUdz.js} +1 -1
- package/src/assets/web-panel/assets/{move-DJo_FFaW.js → move-slnJRVT1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-_tlTiBvk.js → mtc-parser-DVc0CKmy.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CB2ZyxUg.js → omit-BCXAtHCq.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DZwIFD4u.js → pickAttrs-DBsemz_K.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DxZY0dgk.js → placementArrow-BRtkJ-nv.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CzzGt7GL.js → responsiveObserve-Cebq7EZn.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BaIK52yz.js → slide-DT7qvId2.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-3-FHu9_M.js → statusUtils-pQiITLkG.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BkRT68fK.js → styleChecker-c-19BVKQ.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DHYwKS-s.js → useFlexGapSupport-DsS5-0bE.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DVVUhV8p.js → useFs-CxGiMe5n.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CkrFKAzQ.js → usePersonalDataHub-D9btVID5.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BuBLFz0c.js → vnode-CUYFsFEN.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-kpm6LtXU.js → zoom-CXmn_35j.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/cowork.js +280 -5
- package/src/commands/plugin.js +819 -64
- package/src/commands/session-location.js +318 -7
- package/src/constants.js +2 -2
- package/src/data/changelog.json +14 -1
- package/src/gateways/ws/action-protocol.js +1 -1
- package/src/harness/jsonl-session-store.js +638 -2
- package/src/harness/prompt-compressor.js +4 -0
- package/src/lib/background-agent-keeper-client.js +29 -0
- package/src/lib/background-agent-keeper-protocol.js +4 -0
- package/src/lib/background-agent-supervisor.js +59 -29
- package/src/lib/cowork-task-runner.js +70 -0
- package/src/lib/cowork-workflow.js +42 -9
- package/src/lib/doctor-checkup.js +34 -10
- package/src/lib/dynamic-workflow-draft.js +535 -0
- package/src/lib/dynamic-workflow-facade.js +11 -11
- package/src/lib/dynamic-workflow-runtime.js +2974 -0
- package/src/lib/execution-location-contract.js +111 -7
- package/src/lib/execution-location-runtime.js +3 -1
- package/src/lib/execution-location-target.js +910 -0
- package/src/lib/llm-providers.js +1 -0
- package/src/lib/mcp-call-ledger-store.js +35 -0
- package/src/lib/mcp-call-ledger.js +30 -0
- package/src/lib/plugin-runtime/__tests__/remote-source.test.js +34 -16
- package/src/lib/plugin-runtime/install.js +4956 -657
- package/src/lib/plugin-runtime/manifest.js +3 -1
- package/src/lib/plugin-runtime/marketplace-artifact-readback.js +671 -31
- package/src/lib/plugin-runtime/marketplace-catalog.js +68 -2
- package/src/lib/plugin-runtime/marketplace-impact.js +208 -15
- package/src/lib/plugin-runtime/marketplace-network.js +317 -0
- package/src/lib/plugin-runtime/marketplace-pac-worker.js +15 -0
- package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +25 -2
- package/src/lib/plugin-runtime/marketplace-source-cache.js +359 -0
- package/src/lib/plugin-runtime/policy.js +21 -6
- package/src/lib/plugin-runtime/publisher-trust.js +332 -0
- package/src/lib/plugin-runtime/remote-source.js +323 -27
- package/src/lib/plugin-runtime/scopes.js +317 -41
- package/src/lib/plugin-runtime/transaction-journal.js +806 -0
- package/src/lib/public-ide-capability-manifest.js +1 -1
- package/src/lib/remote-membership-coordinator.js +8 -6
- package/src/lib/sub-agent-context.js +268 -2
- package/src/lib/task-model-selector.js +1 -1
- package/src/lib/with-file-lock.js +95 -5
- package/src/runtime/agent-core.js +595 -41
- package/src/workers/background-agent-keeper.js +40 -10
- package/src/assets/web-panel/assets/MarkdownRenderer-SVAgNnkt.js +0 -1
- package/src/assets/web-panel/assets/Projects-CP8vPvzs.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-B5pLcXUw.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +0 -1
- package/src/assets/web-panel/assets/index-C3QX5tOl.js +0 -1
- package/src/assets/web-panel/assets/index-xYybvEic.js +0 -1
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* only fields whose semantics are exact: manifest bytes, manifest license,
|
|
6
6
|
* the cryptographically verified signing key, and the repository-defined
|
|
7
7
|
* payload SBOM format. Persisted remote-artifact evidence is checked for
|
|
8
|
-
* internal consistency and bound back to the installed signature lock
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* internal consistency and bound back to the installed signature lock. For
|
|
9
|
+
* the repository-defined payload SBOM, the installer also persists an exact
|
|
10
|
+
* install-time comparison record; readback validates its internal bindings and
|
|
11
|
+
* compares it with a freshly computed inventory of the installed payload. The
|
|
12
|
+
* record is not a publisher or organization trust anchor.
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
import crypto from "node:crypto";
|
|
@@ -19,22 +21,67 @@ import {
|
|
|
19
21
|
readPluginLock,
|
|
20
22
|
verifyInstalledSignature,
|
|
21
23
|
} from "./signature.js";
|
|
24
|
+
import { normalizePublisherAuthority } from "./publisher-trust.js";
|
|
22
25
|
|
|
23
26
|
export const PLUGIN_MARKETPLACE_ARTIFACT_READBACK_SCHEMA =
|
|
24
27
|
"cc-plugin-marketplace-artifact-readback/v1";
|
|
25
28
|
export const PLUGIN_MARKETPLACE_PAYLOAD_SBOM_SCHEMA =
|
|
26
29
|
"cc-plugin-marketplace-payload-sbom/v1";
|
|
30
|
+
export const PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA =
|
|
31
|
+
"cc-plugin-marketplace-payload-sbom/v2";
|
|
32
|
+
export const PLUGIN_MARKETPLACE_REMOTE_SBOM_PAYLOAD_COMPARISON_SCHEMA =
|
|
33
|
+
"cc-plugin-marketplace-remote-sbom-payload-comparison/v1";
|
|
27
34
|
export const MAX_MARKETPLACE_READBACK_FILES = 10_000;
|
|
28
35
|
export const MAX_MARKETPLACE_READBACK_FILE_BYTES = 64 * 1024 * 1024;
|
|
29
36
|
export const MAX_MARKETPLACE_READBACK_TOTAL_BYTES = 1024 * 1024 * 1024;
|
|
30
37
|
|
|
31
38
|
const SOURCE_METADATA_FILENAME = ".plugin-source.json";
|
|
39
|
+
const PLUGIN_MARKETPLACE_PAYLOAD_V1_EXCLUSIONS = Object.freeze([
|
|
40
|
+
LOCK_FILENAME,
|
|
41
|
+
SOURCE_METADATA_FILENAME,
|
|
42
|
+
]);
|
|
43
|
+
export const PLUGIN_MARKETPLACE_STAGED_SOURCE_EXCLUSIONS = Object.freeze([
|
|
44
|
+
".git",
|
|
45
|
+
LOCK_FILENAME,
|
|
46
|
+
SOURCE_METADATA_FILENAME,
|
|
47
|
+
]);
|
|
32
48
|
const SHA256_RE = /^[a-f0-9]{64}$/;
|
|
33
49
|
const COMPARABLE_SBOM_FORMATS = new Set([
|
|
34
|
-
|
|
50
|
+
PLUGIN_MARKETPLACE_PAYLOAD_SBOM_SCHEMA,
|
|
51
|
+
PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA,
|
|
35
52
|
"cc-plugin-payload-sbom/v1",
|
|
36
53
|
]);
|
|
37
54
|
|
|
55
|
+
export function assertSafeInstalledPluginStructure(root, current = root) {
|
|
56
|
+
if (current === root) {
|
|
57
|
+
const rootStat = fs.lstatSync(root);
|
|
58
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
59
|
+
throw new Error("EXISTING_VERSION_UNSAFE_ENTRY: .");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
63
|
+
const absolute = path.join(current, entry.name);
|
|
64
|
+
const relative = path.relative(root, absolute).replace(/\\/g, "/");
|
|
65
|
+
if (relative === ".git" || relative.startsWith(".git/")) {
|
|
66
|
+
throw new Error(`EXISTING_VERSION_UNSAFE_ENTRY: ${relative}`);
|
|
67
|
+
}
|
|
68
|
+
if (entry.isSymbolicLink()) {
|
|
69
|
+
throw new Error(`EXISTING_VERSION_UNSAFE_ENTRY: ${relative}`);
|
|
70
|
+
}
|
|
71
|
+
if (
|
|
72
|
+
(relative === LOCK_FILENAME || relative === SOURCE_METADATA_FILENAME) &&
|
|
73
|
+
!entry.isFile()
|
|
74
|
+
) {
|
|
75
|
+
throw new Error(`EXISTING_VERSION_UNSAFE_ENTRY: ${relative}`);
|
|
76
|
+
}
|
|
77
|
+
if (entry.isDirectory()) {
|
|
78
|
+
assertSafeInstalledPluginStructure(root, absolute);
|
|
79
|
+
} else if (!entry.isFile()) {
|
|
80
|
+
throw new Error(`EXISTING_VERSION_UNSAFE_ENTRY: ${relative}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
38
85
|
export function buildPluginMarketplaceArtifactReadback({
|
|
39
86
|
root,
|
|
40
87
|
scope = null,
|
|
@@ -45,6 +92,12 @@ export function buildPluginMarketplaceArtifactReadback({
|
|
|
45
92
|
if (!root || !fs.existsSync(resolvedRoot)) {
|
|
46
93
|
throw new Error("marketplace artifact readback root does not exist");
|
|
47
94
|
}
|
|
95
|
+
const remotePayloadFormat =
|
|
96
|
+
source?.catalogAuthority?.remoteSbomPayloadComparison?.format ||
|
|
97
|
+
source?.catalogAuthority?.artifactExpectations?.sbom?.format;
|
|
98
|
+
if (isMarketplacePayloadSbomFormat(remotePayloadFormat)) {
|
|
99
|
+
assertSafeInstalledPluginStructure(resolvedRoot);
|
|
100
|
+
}
|
|
48
101
|
const manifest = parsePluginManifest(resolvedRoot);
|
|
49
102
|
if (!manifest.ok || !manifest.manifestPath) {
|
|
50
103
|
throw new Error(
|
|
@@ -63,6 +116,26 @@ export function buildPluginMarketplaceArtifactReadback({
|
|
|
63
116
|
const remoteArtifactEvidence = validateRemoteArtifactEvidence(
|
|
64
117
|
source?.catalogAuthority?.remoteArtifactEvidence,
|
|
65
118
|
);
|
|
119
|
+
const currentRemotePayloadSbom = isMarketplacePayloadSbomFormat(
|
|
120
|
+
remotePayloadFormat,
|
|
121
|
+
)
|
|
122
|
+
? buildMarketplacePayloadSbom(resolvedRoot, {
|
|
123
|
+
schemaVersion: remotePayloadFormat,
|
|
124
|
+
})
|
|
125
|
+
: payloadSbom;
|
|
126
|
+
const remoteSbomPayloadComparison = validateRemoteSbomPayloadComparison(
|
|
127
|
+
source?.catalogAuthority?.remoteSbomPayloadComparison,
|
|
128
|
+
{
|
|
129
|
+
remoteArtifactEvidence: remoteArtifactEvidence.valid
|
|
130
|
+
? {
|
|
131
|
+
...remoteArtifactEvidence.authority,
|
|
132
|
+
evidenceDigest: remoteArtifactEvidence.evidenceDigest,
|
|
133
|
+
}
|
|
134
|
+
: null,
|
|
135
|
+
expectedPayloadSha256: expectations.sbom.payloadSha256,
|
|
136
|
+
currentPayloadSbom: currentRemotePayloadSbom,
|
|
137
|
+
},
|
|
138
|
+
);
|
|
66
139
|
const actual = {
|
|
67
140
|
manifest: {
|
|
68
141
|
relativePath: path
|
|
@@ -90,10 +163,12 @@ export function buildPluginMarketplaceArtifactReadback({
|
|
|
90
163
|
Boolean(lock?.sbom) &&
|
|
91
164
|
cleanDigest(lock?.sbom?.digest) != null,
|
|
92
165
|
},
|
|
93
|
-
payloadSbom,
|
|
166
|
+
payloadSbom: currentRemotePayloadSbom,
|
|
94
167
|
};
|
|
95
168
|
actual.remoteArtifacts = summarizeRemoteArtifacts({
|
|
96
169
|
evidence: remoteArtifactEvidence,
|
|
170
|
+
payloadComparison: remoteSbomPayloadComparison,
|
|
171
|
+
payloadSbom: currentRemotePayloadSbom,
|
|
97
172
|
lock,
|
|
98
173
|
signature: actual.signature,
|
|
99
174
|
expectedRegistryOrigin,
|
|
@@ -119,6 +194,10 @@ export function buildPluginMarketplaceArtifactReadback({
|
|
|
119
194
|
expectations.sbom,
|
|
120
195
|
actual.remoteArtifacts.sbom,
|
|
121
196
|
),
|
|
197
|
+
remoteSbomPayload: compareRemoteSbomPayload(
|
|
198
|
+
expectations.sbom,
|
|
199
|
+
actual.remoteArtifacts.sbom,
|
|
200
|
+
),
|
|
122
201
|
};
|
|
123
202
|
const blockers = [];
|
|
124
203
|
if (comparisons.manifest.status === "mismatch") {
|
|
@@ -150,17 +229,51 @@ export function buildPluginMarketplaceArtifactReadback({
|
|
|
150
229
|
if (comparisons.remoteSbom.status === "mismatch") {
|
|
151
230
|
blockers.push(issue("REMOTE_SBOM_EVIDENCE_MISMATCH"));
|
|
152
231
|
}
|
|
232
|
+
if (
|
|
233
|
+
remoteSbomPayloadComparison.present &&
|
|
234
|
+
!remoteSbomPayloadComparison.valid
|
|
235
|
+
) {
|
|
236
|
+
blockers.push(
|
|
237
|
+
issue(
|
|
238
|
+
"REMOTE_SBOM_PAYLOAD_COMPARISON_INVALID",
|
|
239
|
+
remoteSbomPayloadComparison.reason,
|
|
240
|
+
),
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
if (comparisons.remoteSbomPayload.status === "mismatch") {
|
|
244
|
+
blockers.push(
|
|
245
|
+
issue(
|
|
246
|
+
(expectations.sbom.format ===
|
|
247
|
+
PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA ||
|
|
248
|
+
(expectations.sbom.format ===
|
|
249
|
+
PLUGIN_MARKETPLACE_PAYLOAD_SBOM_SCHEMA &&
|
|
250
|
+
expectations.sbom.url &&
|
|
251
|
+
expectations.sbom.documentSha256)) &&
|
|
252
|
+
!remoteSbomPayloadComparison.present
|
|
253
|
+
? "REMOTE_SBOM_PAYLOAD_COMPARISON_MISSING"
|
|
254
|
+
: "REMOTE_SBOM_PAYLOAD_MISMATCH",
|
|
255
|
+
),
|
|
256
|
+
);
|
|
257
|
+
}
|
|
153
258
|
const comparisonStatuses = [
|
|
154
259
|
comparisons.manifest.status,
|
|
155
260
|
comparisons.license.status,
|
|
156
261
|
comparisons.signature.status,
|
|
157
|
-
comparisons.sbom.status
|
|
262
|
+
comparisons.sbom.status === "unbound" &&
|
|
263
|
+
comparisons.remoteSbomPayload.status === "matched"
|
|
264
|
+
? "matched"
|
|
265
|
+
: comparisons.sbom.status,
|
|
158
266
|
...(expectations.signature.url || actual.remoteArtifacts.signature.fetched
|
|
159
267
|
? [comparisons.remoteSignature.status]
|
|
160
268
|
: []),
|
|
161
269
|
...(expectations.sbom.url || actual.remoteArtifacts.sbom.fetched
|
|
162
270
|
? [comparisons.remoteSbom.status]
|
|
163
271
|
: []),
|
|
272
|
+
...((expectations.sbom.url &&
|
|
273
|
+
isMarketplacePayloadSbomFormat(expectations.sbom.format)) ||
|
|
274
|
+
remoteSbomPayloadComparison.present
|
|
275
|
+
? [comparisons.remoteSbomPayload.status]
|
|
276
|
+
: []),
|
|
164
277
|
];
|
|
165
278
|
const status = blockers.length
|
|
166
279
|
? "failed"
|
|
@@ -176,6 +289,7 @@ export function buildPluginMarketplaceArtifactReadback({
|
|
|
176
289
|
},
|
|
177
290
|
catalogAuthority: normalizeCatalogAuthority(source?.catalogAuthority),
|
|
178
291
|
remoteArtifactEvidence,
|
|
292
|
+
remoteSbomPayloadComparison,
|
|
179
293
|
expectations,
|
|
180
294
|
actual,
|
|
181
295
|
comparisons,
|
|
@@ -188,7 +302,9 @@ export function buildPluginMarketplaceArtifactReadback({
|
|
|
188
302
|
status,
|
|
189
303
|
...authority,
|
|
190
304
|
claims: {
|
|
191
|
-
registryPublisherIdentityVerified:
|
|
305
|
+
registryPublisherIdentityVerified: Boolean(
|
|
306
|
+
source?.catalogAuthority?.publisherAuthority,
|
|
307
|
+
),
|
|
192
308
|
remoteSignatureFetched: actual.remoteArtifacts.signature.fetched === true,
|
|
193
309
|
remoteSignatureBoundToInstalledLock:
|
|
194
310
|
remoteArtifactEvidence.valid === true &&
|
|
@@ -199,24 +315,69 @@ export function buildPluginMarketplaceArtifactReadback({
|
|
|
199
315
|
remoteSbomFetched: actual.remoteArtifacts.sbom.fetched === true,
|
|
200
316
|
remoteArtifactEvidenceSelfConsistent:
|
|
201
317
|
remoteArtifactEvidence.valid === true,
|
|
202
|
-
|
|
318
|
+
manifestBytesReadFromCurrentInstall: true,
|
|
203
319
|
licenseReadFromInstalledManifest: true,
|
|
204
320
|
signatureCryptographicallyReverified: actual.signature.verified,
|
|
205
|
-
|
|
321
|
+
payloadSbomComputedFromCurrentInstall: true,
|
|
206
322
|
externalSbomDigestComparable: comparisons.sbom.comparable,
|
|
207
323
|
remoteSbomDigestVerifiedAtInstallRecorded:
|
|
208
324
|
remoteArtifactEvidence.valid === true &&
|
|
209
325
|
actual.remoteArtifacts.registryOriginMatches === true &&
|
|
210
326
|
comparisons.remoteSbom.status === "matched" &&
|
|
211
327
|
actual.remoteArtifacts.sbom.digestVerifiedAtInstallRecorded === true,
|
|
328
|
+
remoteSbomPayloadComparisonRecorded:
|
|
329
|
+
remoteSbomPayloadComparison.valid === true,
|
|
330
|
+
remoteSbomPayloadComparisonSelfConsistent:
|
|
331
|
+
remoteSbomPayloadComparison.valid === true,
|
|
332
|
+
remoteSbomRecordedPayloadMatchesCurrentInstall:
|
|
333
|
+
comparisons.remoteSbomPayload.status === "matched" &&
|
|
334
|
+
actual.remoteArtifacts.sbom.currentPayloadMatches === true,
|
|
212
335
|
},
|
|
213
336
|
};
|
|
214
337
|
}
|
|
215
338
|
|
|
216
|
-
export function buildMarketplacePayloadSbom(
|
|
339
|
+
export function buildMarketplacePayloadSbom(
|
|
340
|
+
root,
|
|
341
|
+
{ schemaVersion = PLUGIN_MARKETPLACE_PAYLOAD_SBOM_SCHEMA } = {},
|
|
342
|
+
) {
|
|
343
|
+
const exclusions = marketplacePayloadExclusions(schemaVersion);
|
|
344
|
+
const typedPayload =
|
|
345
|
+
schemaVersion === PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA;
|
|
217
346
|
const resolvedRoot = path.resolve(root);
|
|
218
347
|
const files = [];
|
|
219
348
|
let totalBytes = 0;
|
|
349
|
+
const appendFile = (relative, bytes, type = "file") => {
|
|
350
|
+
if (files.length >= MAX_MARKETPLACE_READBACK_FILES) {
|
|
351
|
+
throw new Error(
|
|
352
|
+
`marketplace artifact readback exceeds ${MAX_MARKETPLACE_READBACK_FILES} files`,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
if (bytes.length > MAX_MARKETPLACE_READBACK_FILE_BYTES) {
|
|
356
|
+
throw new Error(
|
|
357
|
+
`marketplace artifact readback file exceeds ${MAX_MARKETPLACE_READBACK_FILE_BYTES} bytes: ${relative}`,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
totalBytes += bytes.length;
|
|
361
|
+
if (totalBytes > MAX_MARKETPLACE_READBACK_TOTAL_BYTES) {
|
|
362
|
+
throw new Error(
|
|
363
|
+
`marketplace artifact readback exceeds ${MAX_MARKETPLACE_READBACK_TOTAL_BYTES} total bytes`,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
files.push(
|
|
367
|
+
typedPayload
|
|
368
|
+
? {
|
|
369
|
+
path: relative,
|
|
370
|
+
type,
|
|
371
|
+
bytes: bytes.length,
|
|
372
|
+
sha256: sha256(bytes),
|
|
373
|
+
}
|
|
374
|
+
: {
|
|
375
|
+
path: relative,
|
|
376
|
+
bytes: bytes.length,
|
|
377
|
+
sha256: sha256(bytes),
|
|
378
|
+
},
|
|
379
|
+
);
|
|
380
|
+
};
|
|
220
381
|
const walk = (dir) => {
|
|
221
382
|
const entries = fs
|
|
222
383
|
.readdirSync(dir, { withFileTypes: true })
|
|
@@ -226,19 +387,30 @@ export function buildMarketplacePayloadSbom(root) {
|
|
|
226
387
|
const relative = path
|
|
227
388
|
.relative(resolvedRoot, absolute)
|
|
228
389
|
.replace(/\\/g, "/");
|
|
229
|
-
if (relative
|
|
390
|
+
if (exclusions.includes(relative)) {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (entry.isSymbolicLink()) {
|
|
394
|
+
if (typedPayload) {
|
|
395
|
+
appendFile(
|
|
396
|
+
relative,
|
|
397
|
+
fs.readlinkSync(absolute, { encoding: "buffer" }),
|
|
398
|
+
"symlink",
|
|
399
|
+
);
|
|
400
|
+
}
|
|
230
401
|
continue;
|
|
231
402
|
}
|
|
232
|
-
if (entry.isSymbolicLink()) continue;
|
|
233
403
|
if (entry.isDirectory()) {
|
|
234
404
|
walk(absolute);
|
|
235
405
|
continue;
|
|
236
406
|
}
|
|
237
|
-
if (!entry.isFile())
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
407
|
+
if (!entry.isFile()) {
|
|
408
|
+
if (typedPayload) {
|
|
409
|
+
throw new Error(
|
|
410
|
+
`marketplace payload SBOM does not support entry type: ${relative}`,
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
continue;
|
|
242
414
|
}
|
|
243
415
|
const stat = fs.statSync(absolute);
|
|
244
416
|
if (stat.size > MAX_MARKETPLACE_READBACK_FILE_BYTES) {
|
|
@@ -246,36 +418,435 @@ export function buildMarketplacePayloadSbom(root) {
|
|
|
246
418
|
`marketplace artifact readback file exceeds ${MAX_MARKETPLACE_READBACK_FILE_BYTES} bytes: ${relative}`,
|
|
247
419
|
);
|
|
248
420
|
}
|
|
249
|
-
totalBytes += stat.size;
|
|
250
|
-
if (totalBytes > MAX_MARKETPLACE_READBACK_TOTAL_BYTES) {
|
|
251
|
-
throw new Error(
|
|
252
|
-
`marketplace artifact readback exceeds ${MAX_MARKETPLACE_READBACK_TOTAL_BYTES} total bytes`,
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
421
|
const bytes = fs.readFileSync(absolute);
|
|
256
|
-
|
|
257
|
-
path: relative,
|
|
258
|
-
bytes: bytes.length,
|
|
259
|
-
sha256: sha256(bytes),
|
|
260
|
-
});
|
|
422
|
+
appendFile(relative, bytes);
|
|
261
423
|
}
|
|
262
424
|
};
|
|
263
425
|
walk(resolvedRoot);
|
|
264
426
|
files.sort((a, b) => stableCompare(a.path, b.path));
|
|
265
427
|
return {
|
|
266
|
-
schemaVersion
|
|
428
|
+
schemaVersion,
|
|
267
429
|
digest: sha256(
|
|
268
430
|
files
|
|
269
|
-
.map((file) =>
|
|
431
|
+
.map((file) => marketplacePayloadDigestLine(file, schemaVersion))
|
|
270
432
|
.join(""),
|
|
271
433
|
),
|
|
272
434
|
fileCount: files.length,
|
|
273
435
|
totalBytes,
|
|
274
436
|
files,
|
|
275
|
-
exclusions: [
|
|
437
|
+
exclusions: [...exclusions],
|
|
276
438
|
};
|
|
277
439
|
}
|
|
278
440
|
|
|
441
|
+
export function isMarketplacePayloadSbomFormat(value) {
|
|
442
|
+
return (
|
|
443
|
+
value === PLUGIN_MARKETPLACE_PAYLOAD_SBOM_SCHEMA ||
|
|
444
|
+
value === PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function marketplacePayloadExclusions(schemaVersion) {
|
|
449
|
+
if (schemaVersion === PLUGIN_MARKETPLACE_PAYLOAD_SBOM_SCHEMA) {
|
|
450
|
+
return [...PLUGIN_MARKETPLACE_PAYLOAD_V1_EXCLUSIONS];
|
|
451
|
+
}
|
|
452
|
+
if (schemaVersion === PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA) {
|
|
453
|
+
return [...PLUGIN_MARKETPLACE_STAGED_SOURCE_EXCLUSIONS];
|
|
454
|
+
}
|
|
455
|
+
throw new Error(
|
|
456
|
+
`unsupported marketplace payload SBOM schema: ${schemaVersion}`,
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function marketplacePayloadDigestLine(file, schemaVersion) {
|
|
461
|
+
return schemaVersion === PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA
|
|
462
|
+
? `${file.type}\0${file.path}\0${file.sha256}\0${file.bytes}\n`
|
|
463
|
+
: `${file.path}\0${file.sha256}\0${file.bytes}\n`;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Parse the repository-defined remote payload SBOM without trusting any of
|
|
468
|
+
* its summary fields. The document is accepted only when its complete ordered
|
|
469
|
+
* file list reproduces the declared digest and bounded counters.
|
|
470
|
+
*/
|
|
471
|
+
export function parseMarketplacePayloadSbomDocument(bytes, { format } = {}) {
|
|
472
|
+
if (!isMarketplacePayloadSbomFormat(format)) return null;
|
|
473
|
+
if (!Buffer.isBuffer(bytes)) {
|
|
474
|
+
throw new Error("remote marketplace payload SBOM bytes are missing");
|
|
475
|
+
}
|
|
476
|
+
const text = bytes.toString("utf8");
|
|
477
|
+
if (!Buffer.from(text, "utf8").equals(bytes)) {
|
|
478
|
+
throw new Error("remote marketplace payload SBOM must be valid UTF-8");
|
|
479
|
+
}
|
|
480
|
+
let value;
|
|
481
|
+
try {
|
|
482
|
+
value = JSON.parse(text);
|
|
483
|
+
} catch {
|
|
484
|
+
throw new Error("remote marketplace payload SBOM must be valid JSON");
|
|
485
|
+
}
|
|
486
|
+
if (
|
|
487
|
+
!exactObjectKeys(value, [
|
|
488
|
+
"schemaVersion",
|
|
489
|
+
"digest",
|
|
490
|
+
"fileCount",
|
|
491
|
+
"totalBytes",
|
|
492
|
+
"files",
|
|
493
|
+
"exclusions",
|
|
494
|
+
])
|
|
495
|
+
) {
|
|
496
|
+
throw new Error("remote marketplace payload SBOM shape is invalid");
|
|
497
|
+
}
|
|
498
|
+
if (value.schemaVersion !== format) {
|
|
499
|
+
throw new Error("remote marketplace payload SBOM schema is invalid");
|
|
500
|
+
}
|
|
501
|
+
if (!Array.isArray(value.files) || !Array.isArray(value.exclusions)) {
|
|
502
|
+
throw new Error("remote marketplace payload SBOM lists are invalid");
|
|
503
|
+
}
|
|
504
|
+
if (
|
|
505
|
+
value.files.length > MAX_MARKETPLACE_READBACK_FILES ||
|
|
506
|
+
value.fileCount !== value.files.length ||
|
|
507
|
+
!Number.isSafeInteger(value.fileCount) ||
|
|
508
|
+
value.fileCount < 0
|
|
509
|
+
) {
|
|
510
|
+
throw new Error("remote marketplace payload SBOM file count is invalid");
|
|
511
|
+
}
|
|
512
|
+
const expectedExclusions = marketplacePayloadExclusions(format);
|
|
513
|
+
if (canonicalJson(value.exclusions) !== canonicalJson(expectedExclusions)) {
|
|
514
|
+
throw new Error("remote marketplace payload SBOM exclusions are invalid");
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const files = [];
|
|
518
|
+
let totalBytes = 0;
|
|
519
|
+
let previousPath = null;
|
|
520
|
+
for (const file of value.files) {
|
|
521
|
+
const expectedFileKeys =
|
|
522
|
+
format === PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA
|
|
523
|
+
? ["path", "type", "bytes", "sha256"]
|
|
524
|
+
: ["path", "bytes", "sha256"];
|
|
525
|
+
if (!exactObjectKeys(file, expectedFileKeys)) {
|
|
526
|
+
throw new Error("remote marketplace payload SBOM file shape is invalid");
|
|
527
|
+
}
|
|
528
|
+
if (
|
|
529
|
+
format === PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA &&
|
|
530
|
+
file.type !== "file" &&
|
|
531
|
+
file.type !== "symlink"
|
|
532
|
+
) {
|
|
533
|
+
throw new Error("remote marketplace payload SBOM file type is invalid");
|
|
534
|
+
}
|
|
535
|
+
const relativePath = file.path;
|
|
536
|
+
if (
|
|
537
|
+
typeof relativePath !== "string" ||
|
|
538
|
+
!relativePath ||
|
|
539
|
+
relativePath.includes("\\") ||
|
|
540
|
+
path.posix.isAbsolute(relativePath) ||
|
|
541
|
+
path.posix.normalize(relativePath) !== relativePath ||
|
|
542
|
+
relativePath === "." ||
|
|
543
|
+
relativePath.startsWith("../") ||
|
|
544
|
+
expectedExclusions.includes(relativePath) ||
|
|
545
|
+
(previousPath != null && stableCompare(previousPath, relativePath) >= 0)
|
|
546
|
+
) {
|
|
547
|
+
throw new Error("remote marketplace payload SBOM file path is invalid");
|
|
548
|
+
}
|
|
549
|
+
if (
|
|
550
|
+
!Number.isSafeInteger(file.bytes) ||
|
|
551
|
+
file.bytes < 0 ||
|
|
552
|
+
file.bytes > MAX_MARKETPLACE_READBACK_FILE_BYTES
|
|
553
|
+
) {
|
|
554
|
+
throw new Error("remote marketplace payload SBOM file size is invalid");
|
|
555
|
+
}
|
|
556
|
+
const fileSha256 = cleanDigest(file.sha256);
|
|
557
|
+
if (!fileSha256 || fileSha256 !== file.sha256) {
|
|
558
|
+
throw new Error("remote marketplace payload SBOM file digest is invalid");
|
|
559
|
+
}
|
|
560
|
+
totalBytes += file.bytes;
|
|
561
|
+
if (totalBytes > MAX_MARKETPLACE_READBACK_TOTAL_BYTES) {
|
|
562
|
+
throw new Error("remote marketplace payload SBOM total size is invalid");
|
|
563
|
+
}
|
|
564
|
+
files.push(
|
|
565
|
+
format === PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA
|
|
566
|
+
? {
|
|
567
|
+
path: relativePath,
|
|
568
|
+
type: file.type,
|
|
569
|
+
bytes: file.bytes,
|
|
570
|
+
sha256: fileSha256,
|
|
571
|
+
}
|
|
572
|
+
: { path: relativePath, bytes: file.bytes, sha256: fileSha256 },
|
|
573
|
+
);
|
|
574
|
+
previousPath = relativePath;
|
|
575
|
+
}
|
|
576
|
+
if (value.totalBytes !== totalBytes) {
|
|
577
|
+
throw new Error("remote marketplace payload SBOM total bytes mismatch");
|
|
578
|
+
}
|
|
579
|
+
const digest = sha256(
|
|
580
|
+
files.map((file) => marketplacePayloadDigestLine(file, format)).join(""),
|
|
581
|
+
);
|
|
582
|
+
if (cleanDigest(value.digest) !== digest || value.digest !== digest) {
|
|
583
|
+
throw new Error("remote marketplace payload SBOM digest mismatch");
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
schemaVersion: format,
|
|
587
|
+
digest,
|
|
588
|
+
fileCount: files.length,
|
|
589
|
+
totalBytes,
|
|
590
|
+
files,
|
|
591
|
+
exclusions: expectedExclusions,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export function buildRemoteSbomPayloadComparison({
|
|
596
|
+
remoteArtifactEvidence,
|
|
597
|
+
remoteSbomBytes,
|
|
598
|
+
installedRoot,
|
|
599
|
+
expectedSbom = null,
|
|
600
|
+
expectedPayloadSha256 = null,
|
|
601
|
+
} = {}) {
|
|
602
|
+
const remoteSbom = remoteArtifactEvidence?.sbom;
|
|
603
|
+
const expectsCanonicalRemoteSbom =
|
|
604
|
+
expectedSbom?.format === PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA;
|
|
605
|
+
const expectsComparableRemoteSbom =
|
|
606
|
+
isMarketplacePayloadSbomFormat(expectedSbom?.format) &&
|
|
607
|
+
Boolean(expectedSbom?.url && expectedSbom?.documentSha256);
|
|
608
|
+
if (!remoteSbom) {
|
|
609
|
+
if (remoteSbomBytes != null) {
|
|
610
|
+
throw new Error("remote SBOM bytes are not bound to artifact evidence");
|
|
611
|
+
}
|
|
612
|
+
if (expectsCanonicalRemoteSbom) {
|
|
613
|
+
throw new Error(
|
|
614
|
+
"marketplace payload SBOM v2 requires complete bound remote evidence before plugin activation",
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
if (expectsComparableRemoteSbom) {
|
|
618
|
+
throw new Error(
|
|
619
|
+
"repository payload SBOM evidence is required before plugin activation",
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
if (!isMarketplacePayloadSbomFormat(remoteSbom.format)) {
|
|
625
|
+
if (expectsComparableRemoteSbom) {
|
|
626
|
+
throw new Error(
|
|
627
|
+
"repository payload SBOM evidence format changed before plugin activation",
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
if (!Buffer.isBuffer(remoteSbomBytes)) {
|
|
633
|
+
throw new Error(
|
|
634
|
+
"comparable remote SBOM bytes are required before plugin activation",
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
if (remoteSbomBytes.length !== remoteSbom.bytes) {
|
|
638
|
+
throw new Error("remote SBOM byte count changed before payload comparison");
|
|
639
|
+
}
|
|
640
|
+
const documentSha256 = sha256(remoteSbomBytes);
|
|
641
|
+
if (
|
|
642
|
+
documentSha256 !== remoteSbom.documentSha256 ||
|
|
643
|
+
documentSha256 !== remoteSbom.expectedDocumentSha256
|
|
644
|
+
) {
|
|
645
|
+
throw new Error("remote SBOM bytes changed before payload comparison");
|
|
646
|
+
}
|
|
647
|
+
const remotePayload = parseMarketplacePayloadSbomDocument(remoteSbomBytes, {
|
|
648
|
+
format: remoteSbom.format,
|
|
649
|
+
});
|
|
650
|
+
if (
|
|
651
|
+
remoteSbom.format === PLUGIN_MARKETPLACE_PAYLOAD_SBOM_SCHEMA &&
|
|
652
|
+
remotePayload.files.some(
|
|
653
|
+
(file) => file.path === ".git" || file.path.startsWith(".git/"),
|
|
654
|
+
)
|
|
655
|
+
) {
|
|
656
|
+
throw new Error(
|
|
657
|
+
"legacy marketplace payload SBOM v1 cannot bind Git VCS metadata; publish the sanitized v2 payload format",
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
const installedPayload = buildMarketplacePayloadSbom(installedRoot, {
|
|
661
|
+
schemaVersion: remoteSbom.format,
|
|
662
|
+
});
|
|
663
|
+
if (canonicalJson(remotePayload) !== canonicalJson(installedPayload)) {
|
|
664
|
+
throw new Error(
|
|
665
|
+
"remote marketplace payload SBOM does not match staged plugin bytes",
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
const expectedDigest = cleanDigest(expectedPayloadSha256);
|
|
669
|
+
if (expectedPayloadSha256 != null && !expectedDigest) {
|
|
670
|
+
throw new Error("catalog payload SBOM digest is invalid");
|
|
671
|
+
}
|
|
672
|
+
if (expectedDigest && expectedDigest !== remotePayload.digest) {
|
|
673
|
+
throw new Error(
|
|
674
|
+
"remote marketplace payload SBOM does not match catalog payload digest",
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
const summarize = (value) => ({
|
|
678
|
+
schemaVersion: value.schemaVersion,
|
|
679
|
+
digest: value.digest,
|
|
680
|
+
fileCount: value.fileCount,
|
|
681
|
+
totalBytes: value.totalBytes,
|
|
682
|
+
});
|
|
683
|
+
const authority = {
|
|
684
|
+
schemaVersion: PLUGIN_MARKETPLACE_REMOTE_SBOM_PAYLOAD_COMPARISON_SCHEMA,
|
|
685
|
+
status: "matched",
|
|
686
|
+
remoteArtifactEvidenceDigest: cleanDigest(
|
|
687
|
+
remoteArtifactEvidence.evidenceDigest,
|
|
688
|
+
),
|
|
689
|
+
format: remoteSbom.format,
|
|
690
|
+
documentSha256,
|
|
691
|
+
remotePayload: summarize(remotePayload),
|
|
692
|
+
installedPayload: summarize(installedPayload),
|
|
693
|
+
};
|
|
694
|
+
if (!authority.remoteArtifactEvidenceDigest) {
|
|
695
|
+
throw new Error("remote artifact evidence digest is invalid");
|
|
696
|
+
}
|
|
697
|
+
return {
|
|
698
|
+
...authority,
|
|
699
|
+
comparisonDigest: sha256Canonical(authority),
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Validate the persisted install-time semantic comparison. This does not need
|
|
705
|
+
* the remote document bytes: its unkeyed comparison digest checks the record's
|
|
706
|
+
* internal bindings, and its installed summary is checked against a newly
|
|
707
|
+
* computed payload inventory when `currentPayloadSbom` is supplied. The record
|
|
708
|
+
* remains locally writable and is not an external trust anchor.
|
|
709
|
+
*/
|
|
710
|
+
export function validateRemoteSbomPayloadComparison(
|
|
711
|
+
value,
|
|
712
|
+
{
|
|
713
|
+
remoteArtifactEvidence = null,
|
|
714
|
+
expectedPayloadSha256 = null,
|
|
715
|
+
currentPayloadSbom = null,
|
|
716
|
+
} = {},
|
|
717
|
+
) {
|
|
718
|
+
if (value == null) {
|
|
719
|
+
return {
|
|
720
|
+
present: false,
|
|
721
|
+
valid: false,
|
|
722
|
+
authority: null,
|
|
723
|
+
currentPayloadMatches: false,
|
|
724
|
+
reason: null,
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
const invalid = (reason) => ({
|
|
728
|
+
present: true,
|
|
729
|
+
valid: false,
|
|
730
|
+
authority: null,
|
|
731
|
+
currentPayloadMatches: false,
|
|
732
|
+
reason,
|
|
733
|
+
});
|
|
734
|
+
const expectedPayloadSchema = isMarketplacePayloadSbomFormat(value?.format)
|
|
735
|
+
? value.format
|
|
736
|
+
: null;
|
|
737
|
+
if (
|
|
738
|
+
!exactObjectKeys(value, [
|
|
739
|
+
"schemaVersion",
|
|
740
|
+
"status",
|
|
741
|
+
"remoteArtifactEvidenceDigest",
|
|
742
|
+
"format",
|
|
743
|
+
"documentSha256",
|
|
744
|
+
"remotePayload",
|
|
745
|
+
"installedPayload",
|
|
746
|
+
"comparisonDigest",
|
|
747
|
+
])
|
|
748
|
+
) {
|
|
749
|
+
return invalid("comparison shape is invalid");
|
|
750
|
+
}
|
|
751
|
+
const normalizeSummary = (candidate) => {
|
|
752
|
+
if (
|
|
753
|
+
!exactObjectKeys(candidate, [
|
|
754
|
+
"schemaVersion",
|
|
755
|
+
"digest",
|
|
756
|
+
"fileCount",
|
|
757
|
+
"totalBytes",
|
|
758
|
+
]) ||
|
|
759
|
+
candidate.schemaVersion !== expectedPayloadSchema ||
|
|
760
|
+
!exactDigest(candidate.digest) ||
|
|
761
|
+
!Number.isSafeInteger(candidate.fileCount) ||
|
|
762
|
+
candidate.fileCount < 0 ||
|
|
763
|
+
candidate.fileCount > MAX_MARKETPLACE_READBACK_FILES ||
|
|
764
|
+
!Number.isSafeInteger(candidate.totalBytes) ||
|
|
765
|
+
candidate.totalBytes < 0 ||
|
|
766
|
+
candidate.totalBytes > MAX_MARKETPLACE_READBACK_TOTAL_BYTES
|
|
767
|
+
) {
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
return {
|
|
771
|
+
schemaVersion: candidate.schemaVersion,
|
|
772
|
+
digest: candidate.digest,
|
|
773
|
+
fileCount: candidate.fileCount,
|
|
774
|
+
totalBytes: candidate.totalBytes,
|
|
775
|
+
};
|
|
776
|
+
};
|
|
777
|
+
const remotePayload = normalizeSummary(value.remotePayload);
|
|
778
|
+
const installedPayload = normalizeSummary(value.installedPayload);
|
|
779
|
+
if (!remotePayload || !installedPayload) {
|
|
780
|
+
return invalid("payload summary is invalid");
|
|
781
|
+
}
|
|
782
|
+
const authority = {
|
|
783
|
+
schemaVersion: value.schemaVersion,
|
|
784
|
+
status: value.status,
|
|
785
|
+
remoteArtifactEvidenceDigest: value.remoteArtifactEvidenceDigest,
|
|
786
|
+
format: value.format,
|
|
787
|
+
documentSha256: value.documentSha256,
|
|
788
|
+
remotePayload,
|
|
789
|
+
installedPayload,
|
|
790
|
+
};
|
|
791
|
+
if (
|
|
792
|
+
authority.schemaVersion !==
|
|
793
|
+
PLUGIN_MARKETPLACE_REMOTE_SBOM_PAYLOAD_COMPARISON_SCHEMA ||
|
|
794
|
+
authority.status !== "matched" ||
|
|
795
|
+
!exactDigest(authority.remoteArtifactEvidenceDigest) ||
|
|
796
|
+
!isMarketplacePayloadSbomFormat(authority.format) ||
|
|
797
|
+
!exactDigest(authority.documentSha256)
|
|
798
|
+
) {
|
|
799
|
+
return invalid("comparison authority is invalid");
|
|
800
|
+
}
|
|
801
|
+
if (
|
|
802
|
+
!remoteArtifactEvidence?.sbom ||
|
|
803
|
+
authority.remoteArtifactEvidenceDigest !==
|
|
804
|
+
remoteArtifactEvidence.evidenceDigest ||
|
|
805
|
+
authority.documentSha256 !== remoteArtifactEvidence.sbom.documentSha256 ||
|
|
806
|
+
authority.format !== remoteArtifactEvidence.sbom.format
|
|
807
|
+
) {
|
|
808
|
+
return invalid("comparison is not bound to remote artifact evidence");
|
|
809
|
+
}
|
|
810
|
+
if (canonicalJson(remotePayload) !== canonicalJson(installedPayload)) {
|
|
811
|
+
return invalid("remote and installed payload summaries differ");
|
|
812
|
+
}
|
|
813
|
+
const expectedDigest = cleanDigest(expectedPayloadSha256);
|
|
814
|
+
if (expectedPayloadSha256 != null && !expectedDigest) {
|
|
815
|
+
return invalid("catalog payload digest is invalid");
|
|
816
|
+
}
|
|
817
|
+
if (expectedDigest && expectedDigest !== remotePayload.digest) {
|
|
818
|
+
return invalid("comparison is not bound to the catalog payload digest");
|
|
819
|
+
}
|
|
820
|
+
if (
|
|
821
|
+
!exactDigest(value.comparisonDigest) ||
|
|
822
|
+
value.comparisonDigest !== sha256Canonical(authority)
|
|
823
|
+
) {
|
|
824
|
+
return invalid("comparison digest is invalid");
|
|
825
|
+
}
|
|
826
|
+
const currentPayloadMatches = currentPayloadSbom
|
|
827
|
+
? canonicalJson(summarizeSbom(currentPayloadSbom)) ===
|
|
828
|
+
canonicalJson(installedPayload)
|
|
829
|
+
: false;
|
|
830
|
+
return {
|
|
831
|
+
present: true,
|
|
832
|
+
valid: true,
|
|
833
|
+
authority: { ...authority, comparisonDigest: value.comparisonDigest },
|
|
834
|
+
currentPayloadMatches,
|
|
835
|
+
reason: null,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
export function assertRemoteSbomPayloadComparison(value, options = {}) {
|
|
840
|
+
const validation = validateRemoteSbomPayloadComparison(value, options);
|
|
841
|
+
if (!validation.present) return null;
|
|
842
|
+
if (!validation.valid) {
|
|
843
|
+
throw new Error(
|
|
844
|
+
`catalogAuthority.remoteSbomPayloadComparison is invalid: ${validation.reason}`,
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
return validation.authority;
|
|
848
|
+
}
|
|
849
|
+
|
|
279
850
|
function normalizeExpectations(value) {
|
|
280
851
|
const raw = value && typeof value === "object" ? value : {};
|
|
281
852
|
return {
|
|
@@ -467,6 +1038,8 @@ function normalizeRemoteArtifactClaims(value) {
|
|
|
467
1038
|
|
|
468
1039
|
function summarizeRemoteArtifacts({
|
|
469
1040
|
evidence,
|
|
1041
|
+
payloadComparison,
|
|
1042
|
+
payloadSbom,
|
|
470
1043
|
lock,
|
|
471
1044
|
signature,
|
|
472
1045
|
expectedRegistryOrigin,
|
|
@@ -558,6 +1131,14 @@ function summarizeRemoteArtifacts({
|
|
|
558
1131
|
documentSha256: cleanDigest(remoteSbom?.documentSha256),
|
|
559
1132
|
expectedDocumentSha256: cleanDigest(remoteSbom?.expectedDocumentSha256),
|
|
560
1133
|
digestVerifiedAtInstallRecorded: sbomDigestVerified,
|
|
1134
|
+
payloadComparisonPresent: payloadComparison.present,
|
|
1135
|
+
payloadComparisonValid: payloadComparison.valid,
|
|
1136
|
+
payloadComparisonDigest:
|
|
1137
|
+
payloadComparison.authority?.comparisonDigest || null,
|
|
1138
|
+
remotePayload: payloadComparison.authority?.remotePayload || null,
|
|
1139
|
+
installedPayload: payloadComparison.authority?.installedPayload || null,
|
|
1140
|
+
currentPayload: summarizeSbom(payloadSbom),
|
|
1141
|
+
currentPayloadMatches: payloadComparison.currentPayloadMatches,
|
|
561
1142
|
currentDocumentBytesAvailable: false,
|
|
562
1143
|
currentDocumentRehashed: false,
|
|
563
1144
|
registryOriginComparable,
|
|
@@ -651,6 +1232,57 @@ function compareRemoteSbom(expected, actual) {
|
|
|
651
1232
|
};
|
|
652
1233
|
}
|
|
653
1234
|
|
|
1235
|
+
function compareRemoteSbomPayload(expected, actual) {
|
|
1236
|
+
if (!expected.url) {
|
|
1237
|
+
return expected.format === PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA
|
|
1238
|
+
? { status: "mismatch", comparable: true, expected, actual }
|
|
1239
|
+
: { status: "not-declared", comparable: false, expected, actual };
|
|
1240
|
+
}
|
|
1241
|
+
if (!isMarketplacePayloadSbomFormat(expected.format)) {
|
|
1242
|
+
return { status: "not-comparable", comparable: false, expected, actual };
|
|
1243
|
+
}
|
|
1244
|
+
if (actual.evidencePresent && !actual.evidenceValid) {
|
|
1245
|
+
return { status: "mismatch", comparable: true, expected, actual };
|
|
1246
|
+
}
|
|
1247
|
+
const comparisonRequired =
|
|
1248
|
+
expected.format === PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA ||
|
|
1249
|
+
(expected.format === PLUGIN_MARKETPLACE_PAYLOAD_SBOM_SCHEMA &&
|
|
1250
|
+
Boolean(expected.url && expected.documentSha256));
|
|
1251
|
+
if (!actual.fetched) {
|
|
1252
|
+
return comparisonRequired
|
|
1253
|
+
? { status: "mismatch", comparable: true, expected, actual }
|
|
1254
|
+
: { status: "not-observed", comparable: false, expected, actual };
|
|
1255
|
+
}
|
|
1256
|
+
if (!actual.payloadComparisonPresent) {
|
|
1257
|
+
return comparisonRequired
|
|
1258
|
+
? { status: "mismatch", comparable: true, expected, actual }
|
|
1259
|
+
: { status: "not-observed", comparable: false, expected, actual };
|
|
1260
|
+
}
|
|
1261
|
+
if (!actual.registryOriginComparable) {
|
|
1262
|
+
return { status: "unbound", comparable: false, expected, actual };
|
|
1263
|
+
}
|
|
1264
|
+
const catalogPayloadMatches =
|
|
1265
|
+
!expected.payloadSha256 ||
|
|
1266
|
+
expected.payloadSha256 === actual.remotePayload?.digest;
|
|
1267
|
+
return {
|
|
1268
|
+
status:
|
|
1269
|
+
actual.payloadComparisonValid &&
|
|
1270
|
+
actual.currentPayloadMatches &&
|
|
1271
|
+
catalogPayloadMatches &&
|
|
1272
|
+
actual.registryOriginMatches
|
|
1273
|
+
? "matched"
|
|
1274
|
+
: "mismatch",
|
|
1275
|
+
comparable: true,
|
|
1276
|
+
expected: {
|
|
1277
|
+
url: expected.url,
|
|
1278
|
+
format: expected.format,
|
|
1279
|
+
documentSha256: expected.documentSha256,
|
|
1280
|
+
payloadSha256: expected.payloadSha256,
|
|
1281
|
+
},
|
|
1282
|
+
actual,
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
|
|
654
1286
|
function normalizeCatalogAuthority(value) {
|
|
655
1287
|
if (!value || typeof value !== "object") return null;
|
|
656
1288
|
return {
|
|
@@ -659,6 +1291,7 @@ function normalizeCatalogAuthority(value) {
|
|
|
659
1291
|
candidateDigest: cleanDigest(value.candidateDigest),
|
|
660
1292
|
selectionDigest: cleanDigest(value.selectionDigest),
|
|
661
1293
|
updateImpactDigest: cleanDigest(value.updateImpactDigest),
|
|
1294
|
+
publisherAuthority: normalizePublisherAuthority(value.publisherAuthority),
|
|
662
1295
|
};
|
|
663
1296
|
}
|
|
664
1297
|
|
|
@@ -888,6 +1521,13 @@ function sha256Canonical(value) {
|
|
|
888
1521
|
return sha256(canonicalJson(value));
|
|
889
1522
|
}
|
|
890
1523
|
|
|
1524
|
+
function exactObjectKeys(value, expectedKeys) {
|
|
1525
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1526
|
+
const keys = Object.keys(value).sort(stableCompare);
|
|
1527
|
+
const expected = [...expectedKeys].sort(stableCompare);
|
|
1528
|
+
return canonicalJson(keys) === canonicalJson(expected);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
891
1531
|
function canonicalJson(value) {
|
|
892
1532
|
if (value == null || typeof value !== "object") return JSON.stringify(value);
|
|
893
1533
|
if (Array.isArray(value)) {
|