chainlesschain 0.166.65 → 0.166.68
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 +20 -12
- package/package.json +4 -4
- package/src/assets/web-panel/assets/{AIOps-Byz17CTN.js → AIOps-CXkSNOGY.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-C5LiW2-J.js → ActionButton-ysMR6gTs.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D1INcvZF.js → Analytics-WGZ0sWuw.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DZzHO1VV.js → AppLayout-Cs2iRlQG.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-WfYQL6Ia.js → Artifacts-5IdfxWz0.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DohBoGWO.js → Audit-U9V434QX.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-Chtk-Hde.js → BackgroundAgents-BvFwxPlC.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-B813PuMz.js → Backup-C9yTr9hO.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DaDdWWgK.js → BaseInput-BdsSwUgE.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-D4pJ6oZ7.js → Chat-BB_2oBgH.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-Bpf1hDUl.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-Clo9TvO6.js → Checkbox-v6M5m7dK.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-uejLg8Ih.js → Codegen-CtouWSIz.js} +1 -1
- package/src/assets/web-panel/assets/{Col-B7nbx-iJ.js → Col-CqQJ4ORW.js} +1 -1
- package/src/assets/web-panel/assets/{Community-FEyjPK1j.js → Community-BO__QCOy.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-B6Fk0Kn6.js → Compact-D0Kq4xlH.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BQG8goUl.js → Compliance-BSLKKBjA.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CbBeEfHp.js → Cowork-VsujXW5o.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DPzlFcEZ.js → Cron-j8DbLGi5.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-B8pWJrbg.js → Crosschain-DWpYH1yN.js} +1 -1
- package/src/assets/web-panel/assets/{DID-gGQHUlFN.js → DID-mLaRxLQh.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-C2TMFHrn.js → Dashboard-Cff033Jr.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BVPxGbll.js → Dropdown-B1Upt6fP.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CNPxs0hd.js → EmailListRenderer-CPtb8joV.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-_XTiB7q1.js → EvolutionSettings-CVh4Uv4g.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-UyE3VWtI.js → FamilyGuardDashboard-CrD1PhbF.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CLt6FHK-.js → Federation-ftxrvqW5.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CdNQPRPw.js → FormItemContext-9O80slC1.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BJ0AMhrh.js → GenericCardRenderer-DaYvYIj9.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CQnvMfOM.js → Git-BKZh0YE5.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-deYsHC-o.js → Governance-vWbN873j.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-qaSck43z.js → Inference-BN6RR-l8.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-De0aJ3Ls.js → KnowledgeGraph-x49x9m7Q.js} +1 -1
- package/src/assets/web-panel/assets/Logs-Co2EPLsJ.js +2 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-D7wIB-c7.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Byz3xCBN.js → Marketplace-DpPu6XQp.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DOZUUW6z.js → McpTools-BR0ksiax.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-C33wC2hA.js → Memory-BXznJ5-C.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-zZe-T_6M.js → MobileBridge-BHSOpfv7.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CRnUIume.js → MobileProjects-DdWmgIup.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-C1xlRVAN.js → Mtc-D9Rk_IGT.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-D0U07bIi.js → MtcAudit--i8q1lKc.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DF8uvV3h.js → Multisig-BHnNG7R9.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-bBPGAFlt.js → NLProgramming-Duo1ij2_.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BHAaOsgg.js → Notes-B44Le73p.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Cl0fG_rA.js → NotificationSettings-C3oy_7Gf.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BEPAs8Zv.js → OrderTableRenderer-Da_73SI5.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BAxSwgk5.js → Organization-ChsSwKxT.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-D68KubAu.js → Overflow-DR5G_cCn.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-bhdT17zk.js → P2P-yfaobqow.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-DgimH6Jp.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-Bq8fvkak.js → Permissions-DhinFvtE.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BYd2JEic.js → PersonalDataHub-CfeNVic1.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-Csg_ja69.js → Pipeline-CGKO26Nk.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-lwxwLj4M.js → Privacy-CxxfYEJh.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BdBZkH8z.js → ProjectInit-CZsaAab9.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-ClWgFy-K.js → ProjectSettings-BUm_6clQ.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Bu-qj8N1.js → Projects-Capr8bVq.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DEjxrxw3.js → Providers-D28VqPja.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-eccy-8Vi.js → QrScannerModal-Cj200vUb.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Vx3uDvFg.js → QuickAsk-BGpnEiam.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CfUivNps.js → Recommend-Ix79Tpni.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BU4gk7GI.js → RemoteSession-BdGAFbIk.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-CbPzdZ0C.js → Reputation-B1Lis0Wk.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CYLrgfKA.js → Row-Ba_okwOW.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DbY1oyuv.js → RssFeed-DDbEuI-m.js} +3 -3
- package/src/assets/web-panel/assets/{Search-QeW41K5r.js → Search-D89wAkMy.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CJxf1kLZ.js → Security-CJKCKAU-.js} +3 -3
- package/src/assets/web-panel/assets/{Services-CFO9ySZc.js → Services-mitEc1XV.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CQFjeQN0.js → Skeleton-CnBjLDfX.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CLLB4INW.js → Skills-DSzDYvH3.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DxeLIJk0.js → Sla-CglJ_4p3.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-BicWJS7f.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-C6NOkcQK.js → SyncSettings-Cq7o6fmr.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-LmVPWDfQ.js → Tasks-DOxFOBpU.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BdEfUoVB.js → Templates-bY0wSbQM.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-IUlESacN.js → Tenant-BAyyl0qX.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B9pDe7sn.js → Terminal-BGBhSpqc.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-ZCwB-Pki.js → TimelineRenderer-BvW5lXYz.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-4qk16LX1.js → Tokens-Ua_LIx8-.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-jXNyargn.js → Trigger-BGwb2KL1.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-D13PydtT.js → Trust-CD2IEHvj.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-yPgws6mS.js → UkeySign-D5LetK6P.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B329WjpG.js → VideoEditing-oP7ehFgR.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-T8VM-Ttm.js → Wallet-BBtu4-xa.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-XuRibrpP.js → WebAuthn-iSPRU4Ht.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-D1Gwz3YR.js → WorkflowEditor-DuEDhQmB.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DjydtnT-.js → chat-DGDgqvvm.js} +1 -1
- package/src/assets/web-panel/assets/{colors-gFtBvPrr.js → colors-DGQMWa1-.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CndxhGfx.js → compact-item-COVznBL4.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DnfGpd0L.js → createContext-DIxVeHXI.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CjR9h-Z_.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CmT5hRVW.js → hasIn-DkxZO3E9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGlmPbl_.js → index-0ToBFsvW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsRKmb6k.js → index-2JZizEPc.js} +1 -1
- package/src/assets/web-panel/assets/{index-C14jryXi.js → index-BAL2p7YC.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0WFjxWs.js → index-BAlrovUm.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlTcxT_L.js → index-BBw2MLIe.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjcR-CwS.js → index-BHH95jcm.js} +1 -1
- package/src/assets/web-panel/assets/{index-DreYqebI.js → index-BOTshZNx.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaEPE2DC.js → index-BQm7ntcn.js} +1 -1
- package/src/assets/web-panel/assets/{index-BNHdO6Jr.js → index-BUqwlzc8.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3HvYeA3.js → index-BY6MSbWT.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUlubR4l.js → index-BY9Ossof.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dbpk75MO.js → index-BgM-3a1w.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqpR0Des.js → index-Bv0_u0ze.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjY-3sMA.js → index-C3XeNS82.js} +3 -3
- package/src/assets/web-panel/assets/{index-DV6NrfJx.js → index-C3bN6751.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuAvYTmY.js → index-CNRRrP3-.js} +1 -1
- package/src/assets/web-panel/assets/{index-BW3nWiu0.js → index-CV0KnNqR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZLDbPTa.js → index-CcNGQWum.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5na0B7G.js → index-Ci34vbh0.js} +1 -1
- package/src/assets/web-panel/assets/{index-DoBKSzqu.js → index-ClQVcrFt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CuOx4Pbe.js → index-Cligb-Dl.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgMKElUh.js → index-CokrhbY2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFif4Cev.js → index-CpZxDiVK.js} +1 -1
- package/src/assets/web-panel/assets/{index-HHP1zzo7.js → index-CyMHF2Bi.js} +1 -1
- package/src/assets/web-panel/assets/{index-_-wGLtAg.js → index-DDwPBGdA.js} +1 -1
- package/src/assets/web-panel/assets/{index-ktN-K1Tf.js → index-DU1ArKl5.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4Mqhmas.js → index-DV0H83eS.js} +1 -1
- package/src/assets/web-panel/assets/index-D_nxBMX5.js +1 -0
- package/src/assets/web-panel/assets/{index-BXNUTiW8.js → index-DaWQSp5a.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKyuE_Y2.js → index-DmduvOYT.js} +1 -1
- package/src/assets/web-panel/assets/{index-DV2PR3Fa.js → index-ECoDhyH3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVM_w30N.js → index-ZcqTvPnt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRqz8TtI.js → index-b2_M6o20.js} +1 -1
- package/src/assets/web-panel/assets/{index-CB27echm.js → index-b_0qTo8O.js} +1 -1
- package/src/assets/web-panel/assets/{index-D88-8bsB.js → index-dfYkGovV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGbKeg8G.js → index-jo7DbU0w.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2d7358Y.js → index-usmG13jq.js} +1 -1
- package/src/assets/web-panel/assets/{index-zQf599Uq.js → index-vXrktF3e.js} +1 -1
- package/src/assets/web-panel/assets/index-z_cTUMX-.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-KV3V-PNH.js → initDefaultProps-CkSeiBwL.js} +1 -1
- package/src/assets/web-panel/assets/{motion-PFGQg7_X.js → motion-CSzhDP0s.js} +1 -1
- package/src/assets/web-panel/assets/{move-Dnl79wq4.js → move-mgCt4iC6.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-Bwl5JH9V.js → mtc-parser-BEZBLcKt.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DCcTtM1l.js → omit-BsHctMEr.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Cl6BjjDf.js → pickAttrs-1BHamnGW.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-fdqhyo42.js → placementArrow--Vb4Bd7l.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DYQpmcDW.js → responsiveObserve-DD33SqCm.js} +1 -1
- package/src/assets/web-panel/assets/{slide-C4l-VfO4.js → slide-DfrLtxgK.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DYDw_dyZ.js → statusUtils-D9Pe_gXW.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BEsubSMu.js → styleChecker-0NY5iMun.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Cj82ggQ_.js → useFlexGapSupport-DSsZstwg.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-B7L1eJ0V.js → useFs-BU8HyH59.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DVlJVD4d.js → usePersonalDataHub-D_xttink.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BBAbRN_Y.js → vnode-39VhX2lM.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BYn48q-I.js → zoom-D5KQ8Pks.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/data/changelog.json +20 -7
- package/src/lib/dependency-install-policy.js +1 -1
- package/src/lib/evolution/browser-download-action-authority.js +747 -0
- package/src/lib/evolution/browser-download-artifact-disposal-authority.js +436 -0
- package/src/lib/evolution/browser-filesystem-quarantine-custody.js +2453 -0
- package/src/lib/evolution/browser-filesystem-quarantine-disposal.js +36 -0
- package/src/lib/evolution/browser-keyboard-action-authority.js +560 -0
- package/src/lib/evolution/browser-navigation-action-authority.js +546 -0
- package/src/lib/evolution/browser-quarantine-lock-maintenance-authority.js +573 -0
- package/src/lib/evolution/browser-quarantine-operator-revocation-authority.js +442 -0
- package/src/lib/evolution/browser-quarantine-retention-authority.js +503 -0
- package/src/lib/evolution/browser-quarantine-retention-scheduler.js +207 -0
- package/src/lib/evolution/browser-quarantined-download-executor.js +525 -0
- package/src/lib/evolution/browser-tab-open-action-authority.js +491 -0
- package/src/lib/evolution/browser-vision-action-authority.js +444 -0
- package/src/lib/evolution/browser-vision-observation-authority.js +304 -0
- package/src/lib/evolution/evolution-deployment-loader.js +202 -1
- package/src/lib/evolution/evolution-eval-child-evidence-ledger-adapter.js +5 -0
- package/src/lib/evolution/evolution-eval-gate.js +2 -1
- package/src/lib/evolution/evolution-eval-process-supervisor-worker.mjs +71 -9
- package/src/lib/evolution/evolution-eval-process-supervisor.js +276 -9
- package/src/lib/evolution/evolution-workbench-metrics.js +81 -21
- package/src/lib/evolution/pm-exploration-benchmark.js +747 -0
- package/src/lib/evolution/pm-exploration-budget-executor.js +106 -9
- package/src/lib/evolution/pm-exploration-egress-authority.js +382 -0
- package/src/lib/evolution/pm-exploration-evidence-bundle.js +149 -44
- package/src/lib/evolution/pm-exploration-execution-host.js +1254 -8
- package/src/lib/evolution/pm-exploration-process-grader.js +458 -0
- package/src/lib/evolution/pm-exploration-process-reviewer.js +456 -0
- package/src/lib/evolution/pm-exploration-process-runner.js +500 -0
- package/src/lib/evolution/pm-exploration-receipts.js +211 -11
- package/src/lib/evolution/pm-exploration-recovery-snapshot-store.js +465 -46
- package/src/lib/evolution/pm-exploration-transition-committer.js +185 -34
- package/src/lib/evolution/skill-outcome-index-authority.js +7 -3
- package/src/lib/governance-coverage.js +1 -1
- package/src/lib/headless-manifest.js +1 -1
- package/src/lib/protocol-replay.js +1 -1
- package/src/lib/skill-outcome-transcript-authority.js +45 -5
- package/src/runtime/agent-core.js +17 -0
- package/src/assets/web-panel/assets/ChatBubbleRenderer-Cf3LLgDc.js +0 -1
- package/src/assets/web-panel/assets/Logs-ZtS98jCm.js +0 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-CTsYo9NN.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-DqaO6wgH.js +0 -7
- package/src/assets/web-panel/assets/SpeechSettings-D4FA9KYI.js +0 -1
- package/src/assets/web-panel/assets/devWarning-D60SbiD-.js +0 -1
- package/src/assets/web-panel/assets/index-C0yR4_7h.js +0 -1
- package/src/assets/web-panel/assets/index-D4xuvo03.js +0 -1
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { types } from "node:util";
|
|
3
|
+
|
|
4
|
+
export const BROWSER_DOWNLOAD_ACTION_AUTHORITY_DESCRIPTOR_SCHEMA =
|
|
5
|
+
"chainlesschain.browser-download-action-authority-descriptor/v1";
|
|
6
|
+
export const BROWSER_DOWNLOAD_ACTION_REQUEST_SCHEMA =
|
|
7
|
+
"chainlesschain.browser-download-action-request/v1";
|
|
8
|
+
export const BROWSER_DOWNLOAD_ACTION_RECEIPT_SCHEMA =
|
|
9
|
+
"chainlesschain.browser-download-action-receipt/v1";
|
|
10
|
+
export const BROWSER_DOWNLOAD_ACTION_EXECUTION_SCHEMA =
|
|
11
|
+
"chainlesschain.browser-download-action-execution/v1";
|
|
12
|
+
export const BROWSER_DOWNLOAD_ARTIFACT_SCHEMA =
|
|
13
|
+
"chainlesschain.browser-download-artifact/v1";
|
|
14
|
+
export const BROWSER_DOWNLOAD_ACTION_OUTCOME_REQUEST_SCHEMA =
|
|
15
|
+
"chainlesschain.browser-download-action-outcome-request/v1";
|
|
16
|
+
export const BROWSER_DOWNLOAD_ACTION_OUTCOME_ACK_SCHEMA =
|
|
17
|
+
"chainlesschain.browser-download-action-outcome-ack/v1";
|
|
18
|
+
|
|
19
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
20
|
+
const ID = /^[A-Za-z0-9._:-]{1,128}$/u;
|
|
21
|
+
const ARTIFACT_REF = /^quarantine:[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/u;
|
|
22
|
+
const CONTENT_TYPE =
|
|
23
|
+
/^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}\/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$/u;
|
|
24
|
+
const MAX_BYTES = 100 * 1024 * 1024;
|
|
25
|
+
const authorities = new WeakMap();
|
|
26
|
+
|
|
27
|
+
function canonical(value) {
|
|
28
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
29
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
30
|
+
return `{${Object.keys(value)
|
|
31
|
+
.sort()
|
|
32
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
|
|
33
|
+
.join(",")}}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function digest(domain, value) {
|
|
37
|
+
return `sha256:${createHash("sha256")
|
|
38
|
+
.update(`${domain}\0`)
|
|
39
|
+
.update(canonical(value))
|
|
40
|
+
.digest("hex")}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function exact(value, keys, label) {
|
|
44
|
+
if (
|
|
45
|
+
!value ||
|
|
46
|
+
typeof value !== "object" ||
|
|
47
|
+
Array.isArray(value) ||
|
|
48
|
+
types.isProxy(value) ||
|
|
49
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(value)) ||
|
|
50
|
+
Reflect.ownKeys(value).length !== keys.length ||
|
|
51
|
+
keys.some((key) => {
|
|
52
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
53
|
+
return !descriptor?.enumerable || !("value" in descriptor);
|
|
54
|
+
})
|
|
55
|
+
)
|
|
56
|
+
throw new TypeError(`${label} has an invalid shape`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function snapshotJson(value, label) {
|
|
60
|
+
let nodes = 0;
|
|
61
|
+
const visit = (entry, depth) => {
|
|
62
|
+
if (++nodes > 2048 || depth > 12)
|
|
63
|
+
throw new TypeError(`${label} exceeds its structure budget`);
|
|
64
|
+
if (
|
|
65
|
+
entry === null ||
|
|
66
|
+
typeof entry === "boolean" ||
|
|
67
|
+
(typeof entry === "number" && Number.isFinite(entry)) ||
|
|
68
|
+
typeof entry === "string"
|
|
69
|
+
)
|
|
70
|
+
return entry;
|
|
71
|
+
if (
|
|
72
|
+
!entry ||
|
|
73
|
+
typeof entry !== "object" ||
|
|
74
|
+
types.isProxy(entry) ||
|
|
75
|
+
![Object.prototype, null, Array.prototype].includes(
|
|
76
|
+
Object.getPrototypeOf(entry),
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
throw new TypeError(`${label} must be finite plain JSON data`);
|
|
80
|
+
if (Array.isArray(entry))
|
|
81
|
+
return Object.freeze(entry.map((item) => visit(item, depth + 1)));
|
|
82
|
+
const output = {};
|
|
83
|
+
for (const key of Object.keys(entry)) {
|
|
84
|
+
const descriptor = Object.getOwnPropertyDescriptor(entry, key);
|
|
85
|
+
if (!descriptor?.enumerable || !("value" in descriptor))
|
|
86
|
+
throw new TypeError(`${label} must contain plain data`);
|
|
87
|
+
output[key] = visit(descriptor.value, depth + 1);
|
|
88
|
+
}
|
|
89
|
+
return Object.freeze(output);
|
|
90
|
+
};
|
|
91
|
+
const result = visit(value, 0);
|
|
92
|
+
if (Buffer.byteLength(canonical(result), "utf8") > 16 * 1024)
|
|
93
|
+
throw new TypeError(`${label} exceeds its byte budget`);
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function normalizeUrl(value, label = "browser download destination") {
|
|
98
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 16 * 1024)
|
|
99
|
+
throw new TypeError(`${label} is invalid`);
|
|
100
|
+
let parsed;
|
|
101
|
+
try {
|
|
102
|
+
parsed = new URL(value);
|
|
103
|
+
} catch {
|
|
104
|
+
throw new TypeError(`${label} is invalid`);
|
|
105
|
+
}
|
|
106
|
+
if (
|
|
107
|
+
!["http:", "https:"].includes(parsed.protocol) ||
|
|
108
|
+
parsed.username !== "" ||
|
|
109
|
+
parsed.password !== "" ||
|
|
110
|
+
parsed.href.length > 16 * 1024
|
|
111
|
+
)
|
|
112
|
+
throw new TypeError(`${label} is invalid`);
|
|
113
|
+
return parsed.href;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function normalizeOrigins(value, destinationUrl) {
|
|
117
|
+
if (
|
|
118
|
+
!Array.isArray(value) ||
|
|
119
|
+
types.isProxy(value) ||
|
|
120
|
+
value.length < 1 ||
|
|
121
|
+
value.length > 16
|
|
122
|
+
)
|
|
123
|
+
throw new TypeError("browser download redirect origins are invalid");
|
|
124
|
+
const origins = value.map((entry) => {
|
|
125
|
+
if (typeof entry !== "string" || entry.length > 2048)
|
|
126
|
+
throw new TypeError("browser download redirect origins are invalid");
|
|
127
|
+
let parsed;
|
|
128
|
+
try {
|
|
129
|
+
parsed = new URL(entry);
|
|
130
|
+
} catch {
|
|
131
|
+
throw new TypeError("browser download redirect origins are invalid");
|
|
132
|
+
}
|
|
133
|
+
if (
|
|
134
|
+
!["http:", "https:"].includes(parsed.protocol) ||
|
|
135
|
+
parsed.username !== "" ||
|
|
136
|
+
parsed.password !== "" ||
|
|
137
|
+
entry !== parsed.origin
|
|
138
|
+
)
|
|
139
|
+
throw new TypeError("browser download redirect origins are invalid");
|
|
140
|
+
return parsed.origin;
|
|
141
|
+
});
|
|
142
|
+
const normalized = [...new Set(origins)].sort();
|
|
143
|
+
if (
|
|
144
|
+
normalized.length !== origins.length ||
|
|
145
|
+
!normalized.includes(new URL(destinationUrl).origin)
|
|
146
|
+
)
|
|
147
|
+
throw new TypeError("browser download redirect origins are invalid");
|
|
148
|
+
return Object.freeze(normalized);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function normalizeContentTypes(value) {
|
|
152
|
+
if (
|
|
153
|
+
!Array.isArray(value) ||
|
|
154
|
+
types.isProxy(value) ||
|
|
155
|
+
value.length < 1 ||
|
|
156
|
+
value.length > 16 ||
|
|
157
|
+
value.some(
|
|
158
|
+
(entry) => typeof entry !== "string" || !CONTENT_TYPE.test(entry),
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
throw new TypeError("browser download content types are invalid");
|
|
162
|
+
const normalized = [...new Set(value)].sort();
|
|
163
|
+
if (normalized.length !== value.length)
|
|
164
|
+
throw new TypeError("browser download content types are invalid");
|
|
165
|
+
return Object.freeze(normalized);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function normalizeDescriptor(value) {
|
|
169
|
+
exact(
|
|
170
|
+
value,
|
|
171
|
+
[
|
|
172
|
+
"schema",
|
|
173
|
+
"authorityId",
|
|
174
|
+
"tenantId",
|
|
175
|
+
"handlerArtifactDigest",
|
|
176
|
+
"policyRevision",
|
|
177
|
+
"maxGrantTtlMs",
|
|
178
|
+
"approvalMode",
|
|
179
|
+
"auditMode",
|
|
180
|
+
"artifactMode",
|
|
181
|
+
],
|
|
182
|
+
"browser download authority descriptor",
|
|
183
|
+
);
|
|
184
|
+
if (
|
|
185
|
+
value.schema !== BROWSER_DOWNLOAD_ACTION_AUTHORITY_DESCRIPTOR_SCHEMA ||
|
|
186
|
+
!ID.test(value.authorityId) ||
|
|
187
|
+
!ID.test(value.tenantId) ||
|
|
188
|
+
!DIGEST.test(value.handlerArtifactDigest) ||
|
|
189
|
+
!ID.test(value.policyRevision) ||
|
|
190
|
+
!Number.isSafeInteger(value.maxGrantTtlMs) ||
|
|
191
|
+
value.maxGrantTtlMs < 1 ||
|
|
192
|
+
value.maxGrantTtlMs > 30_000 ||
|
|
193
|
+
value.approvalMode !== "interactive" ||
|
|
194
|
+
value.auditMode !== "authenticated-durable-readback" ||
|
|
195
|
+
value.artifactMode !== "opaque-quarantine-clean-scan"
|
|
196
|
+
)
|
|
197
|
+
throw new TypeError("browser download authority descriptor is invalid");
|
|
198
|
+
return Object.freeze({ ...value });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function normalizeRequest(value) {
|
|
202
|
+
exact(
|
|
203
|
+
value,
|
|
204
|
+
[
|
|
205
|
+
"schema",
|
|
206
|
+
"requestId",
|
|
207
|
+
"targetId",
|
|
208
|
+
"operation",
|
|
209
|
+
"senderId",
|
|
210
|
+
"frameUrlDigest",
|
|
211
|
+
"destinationUrl",
|
|
212
|
+
"allowedRedirectOrigins",
|
|
213
|
+
"allowedContentTypes",
|
|
214
|
+
"maxBytes",
|
|
215
|
+
"timeout",
|
|
216
|
+
"inputDigest",
|
|
217
|
+
"authorization",
|
|
218
|
+
"requestedAt",
|
|
219
|
+
],
|
|
220
|
+
"browser download request",
|
|
221
|
+
);
|
|
222
|
+
const destinationUrl = normalizeUrl(value.destinationUrl);
|
|
223
|
+
const allowedRedirectOrigins = normalizeOrigins(
|
|
224
|
+
value.allowedRedirectOrigins,
|
|
225
|
+
destinationUrl,
|
|
226
|
+
);
|
|
227
|
+
const allowedContentTypes = normalizeContentTypes(value.allowedContentTypes);
|
|
228
|
+
const inputCore = Object.freeze({
|
|
229
|
+
targetId: value.targetId,
|
|
230
|
+
operation: value.operation,
|
|
231
|
+
destinationUrl,
|
|
232
|
+
allowedRedirectOrigins,
|
|
233
|
+
allowedContentTypes,
|
|
234
|
+
maxBytes: value.maxBytes,
|
|
235
|
+
timeout: value.timeout,
|
|
236
|
+
});
|
|
237
|
+
if (
|
|
238
|
+
value.schema !== BROWSER_DOWNLOAD_ACTION_REQUEST_SCHEMA ||
|
|
239
|
+
!ID.test(value.requestId) ||
|
|
240
|
+
typeof value.targetId !== "string" ||
|
|
241
|
+
value.targetId.length < 1 ||
|
|
242
|
+
value.targetId.length > 512 ||
|
|
243
|
+
value.operation !== "download-url" ||
|
|
244
|
+
!Number.isSafeInteger(value.senderId) ||
|
|
245
|
+
value.senderId < 1 ||
|
|
246
|
+
!DIGEST.test(value.frameUrlDigest) ||
|
|
247
|
+
!Number.isSafeInteger(value.maxBytes) ||
|
|
248
|
+
value.maxBytes < 1 ||
|
|
249
|
+
value.maxBytes > MAX_BYTES ||
|
|
250
|
+
!Number.isSafeInteger(value.timeout) ||
|
|
251
|
+
value.timeout < 1 ||
|
|
252
|
+
value.timeout > 120_000 ||
|
|
253
|
+
value.inputDigest !==
|
|
254
|
+
digest("chainlesschain.browser-download-action-input/v1", inputCore) ||
|
|
255
|
+
!Number.isFinite(Date.parse(value.requestedAt))
|
|
256
|
+
)
|
|
257
|
+
throw new TypeError("browser download request is invalid");
|
|
258
|
+
return Object.freeze({
|
|
259
|
+
...value,
|
|
260
|
+
destinationUrl,
|
|
261
|
+
allowedRedirectOrigins,
|
|
262
|
+
allowedContentTypes,
|
|
263
|
+
authorization: snapshotJson(
|
|
264
|
+
value.authorization,
|
|
265
|
+
"browser download authorization",
|
|
266
|
+
),
|
|
267
|
+
inputCore,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function requestEvidence(request) {
|
|
272
|
+
const core = {
|
|
273
|
+
schema: request.schema,
|
|
274
|
+
requestId: request.requestId,
|
|
275
|
+
targetId: request.targetId,
|
|
276
|
+
operation: request.operation,
|
|
277
|
+
senderId: request.senderId,
|
|
278
|
+
frameUrlDigest: request.frameUrlDigest,
|
|
279
|
+
destinationDigest: digest(
|
|
280
|
+
"chainlesschain.browser-download-action-destination/v1",
|
|
281
|
+
request.destinationUrl,
|
|
282
|
+
),
|
|
283
|
+
redirectOriginsDigest: digest(
|
|
284
|
+
"chainlesschain.browser-download-action-redirect-origins/v1",
|
|
285
|
+
request.allowedRedirectOrigins,
|
|
286
|
+
),
|
|
287
|
+
contentTypesDigest: digest(
|
|
288
|
+
"chainlesschain.browser-download-action-content-types/v1",
|
|
289
|
+
request.allowedContentTypes,
|
|
290
|
+
),
|
|
291
|
+
maxBytes: request.maxBytes,
|
|
292
|
+
timeout: request.timeout,
|
|
293
|
+
inputDigest: request.inputDigest,
|
|
294
|
+
authorizationDigest: digest(
|
|
295
|
+
"chainlesschain.browser-download-action-authorization/v1",
|
|
296
|
+
request.authorization,
|
|
297
|
+
),
|
|
298
|
+
requestedAt: request.requestedAt,
|
|
299
|
+
};
|
|
300
|
+
return Object.freeze({
|
|
301
|
+
...core,
|
|
302
|
+
requestDigest: digest(BROWSER_DOWNLOAD_ACTION_REQUEST_SCHEMA, core),
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function normalizeDecision(value) {
|
|
307
|
+
if (!value || typeof value !== "object" || types.isProxy(value))
|
|
308
|
+
throw new TypeError("browser download decision is invalid");
|
|
309
|
+
if (value.decision === "deny") {
|
|
310
|
+
exact(
|
|
311
|
+
value,
|
|
312
|
+
Object.hasOwn(value, "reason") ? ["decision", "reason"] : ["decision"],
|
|
313
|
+
"browser download decision",
|
|
314
|
+
);
|
|
315
|
+
if (
|
|
316
|
+
value.reason !== undefined &&
|
|
317
|
+
(typeof value.reason !== "string" || value.reason.length > 512)
|
|
318
|
+
)
|
|
319
|
+
throw new TypeError("browser download denial reason is invalid");
|
|
320
|
+
return Object.freeze({ decision: "deny", reason: value.reason ?? null });
|
|
321
|
+
}
|
|
322
|
+
exact(
|
|
323
|
+
value,
|
|
324
|
+
["decision", "approvalEvidenceRef", "validUntil"],
|
|
325
|
+
"browser download decision",
|
|
326
|
+
);
|
|
327
|
+
if (value.decision !== "allow")
|
|
328
|
+
throw new TypeError("browser download decision is invalid");
|
|
329
|
+
return Object.freeze({ ...value });
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function normalizeArtifact(value, request, deadlineAtMs, finishedAtMs) {
|
|
333
|
+
exact(
|
|
334
|
+
value,
|
|
335
|
+
[
|
|
336
|
+
"schema",
|
|
337
|
+
"artifactRef",
|
|
338
|
+
"artifactDigest",
|
|
339
|
+
"sizeBytes",
|
|
340
|
+
"contentType",
|
|
341
|
+
"finalUrl",
|
|
342
|
+
"redirectOrigins",
|
|
343
|
+
"quarantined",
|
|
344
|
+
"scanVerdict",
|
|
345
|
+
"scanEvidenceDigest",
|
|
346
|
+
"quarantineReceiptDigest",
|
|
347
|
+
"completionReceiptDigest",
|
|
348
|
+
"completedAt",
|
|
349
|
+
],
|
|
350
|
+
"browser download artifact",
|
|
351
|
+
);
|
|
352
|
+
const finalUrl = normalizeUrl(value.finalUrl, "browser download final URL");
|
|
353
|
+
const redirectOrigins = normalizeOrigins(
|
|
354
|
+
value.redirectOrigins,
|
|
355
|
+
request.destinationUrl,
|
|
356
|
+
);
|
|
357
|
+
const finalOrigin = new URL(finalUrl).origin;
|
|
358
|
+
if (
|
|
359
|
+
value.schema !== BROWSER_DOWNLOAD_ARTIFACT_SCHEMA ||
|
|
360
|
+
!ARTIFACT_REF.test(value.artifactRef) ||
|
|
361
|
+
!DIGEST.test(value.artifactDigest) ||
|
|
362
|
+
!Number.isSafeInteger(value.sizeBytes) ||
|
|
363
|
+
value.sizeBytes < 1 ||
|
|
364
|
+
value.sizeBytes > request.maxBytes ||
|
|
365
|
+
typeof value.contentType !== "string" ||
|
|
366
|
+
!request.allowedContentTypes.includes(value.contentType) ||
|
|
367
|
+
redirectOrigins.some(
|
|
368
|
+
(origin) => !request.allowedRedirectOrigins.includes(origin),
|
|
369
|
+
) ||
|
|
370
|
+
!redirectOrigins.includes(finalOrigin) ||
|
|
371
|
+
value.quarantined !== true ||
|
|
372
|
+
value.scanVerdict !== "clean" ||
|
|
373
|
+
!DIGEST.test(value.scanEvidenceDigest) ||
|
|
374
|
+
!DIGEST.test(value.quarantineReceiptDigest) ||
|
|
375
|
+
!DIGEST.test(value.completionReceiptDigest) ||
|
|
376
|
+
!Number.isFinite(Date.parse(value.completedAt)) ||
|
|
377
|
+
Date.parse(value.completedAt) > deadlineAtMs ||
|
|
378
|
+
finishedAtMs > deadlineAtMs
|
|
379
|
+
)
|
|
380
|
+
throw new TypeError("browser download artifact evidence is invalid");
|
|
381
|
+
return Object.freeze({ ...value, finalUrl, redirectOrigins });
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function executionEvidence(artifact) {
|
|
385
|
+
const core = Object.freeze({
|
|
386
|
+
status: "succeeded",
|
|
387
|
+
failureClass: null,
|
|
388
|
+
artifactRef: artifact.artifactRef,
|
|
389
|
+
artifactDigest: artifact.artifactDigest,
|
|
390
|
+
sizeBytes: artifact.sizeBytes,
|
|
391
|
+
contentType: artifact.contentType,
|
|
392
|
+
finalUrlDigest: digest(
|
|
393
|
+
"chainlesschain.browser-download-action-final-url/v1",
|
|
394
|
+
artifact.finalUrl,
|
|
395
|
+
),
|
|
396
|
+
redirectOriginsDigest: digest(
|
|
397
|
+
"chainlesschain.browser-download-action-observed-origins/v1",
|
|
398
|
+
artifact.redirectOrigins,
|
|
399
|
+
),
|
|
400
|
+
scanEvidenceDigest: artifact.scanEvidenceDigest,
|
|
401
|
+
quarantineReceiptDigest: artifact.quarantineReceiptDigest,
|
|
402
|
+
completionReceiptDigest: artifact.completionReceiptDigest,
|
|
403
|
+
completedAt: artifact.completedAt,
|
|
404
|
+
});
|
|
405
|
+
return Object.freeze({
|
|
406
|
+
...core,
|
|
407
|
+
resultDigest: digest(
|
|
408
|
+
"chainlesschain.browser-download-action-result/v1",
|
|
409
|
+
core,
|
|
410
|
+
),
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function failedExecutionEvidence(failureClass) {
|
|
415
|
+
const core = Object.freeze({
|
|
416
|
+
status: "failed",
|
|
417
|
+
failureClass,
|
|
418
|
+
artifactRef: null,
|
|
419
|
+
artifactDigest: null,
|
|
420
|
+
sizeBytes: null,
|
|
421
|
+
contentType: null,
|
|
422
|
+
finalUrlDigest: null,
|
|
423
|
+
redirectOriginsDigest: null,
|
|
424
|
+
scanEvidenceDigest: null,
|
|
425
|
+
quarantineReceiptDigest: null,
|
|
426
|
+
completionReceiptDigest: null,
|
|
427
|
+
completedAt: null,
|
|
428
|
+
});
|
|
429
|
+
return Object.freeze({
|
|
430
|
+
...core,
|
|
431
|
+
resultDigest: digest(
|
|
432
|
+
"chainlesschain.browser-download-action-result/v1",
|
|
433
|
+
core,
|
|
434
|
+
),
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function normalizeOutcome(value) {
|
|
439
|
+
exact(
|
|
440
|
+
value,
|
|
441
|
+
[
|
|
442
|
+
"schema",
|
|
443
|
+
"actionReceiptDigest",
|
|
444
|
+
"requestDigest",
|
|
445
|
+
"targetId",
|
|
446
|
+
"operation",
|
|
447
|
+
"inputDigest",
|
|
448
|
+
"status",
|
|
449
|
+
"resultDigest",
|
|
450
|
+
"recordedAt",
|
|
451
|
+
],
|
|
452
|
+
"browser download outcome request",
|
|
453
|
+
);
|
|
454
|
+
if (
|
|
455
|
+
value.schema !== BROWSER_DOWNLOAD_ACTION_OUTCOME_REQUEST_SCHEMA ||
|
|
456
|
+
!DIGEST.test(value.actionReceiptDigest) ||
|
|
457
|
+
!DIGEST.test(value.requestDigest) ||
|
|
458
|
+
typeof value.targetId !== "string" ||
|
|
459
|
+
value.targetId.length < 1 ||
|
|
460
|
+
value.targetId.length > 512 ||
|
|
461
|
+
value.operation !== "download-url" ||
|
|
462
|
+
!DIGEST.test(value.inputDigest) ||
|
|
463
|
+
!["succeeded", "failed"].includes(value.status) ||
|
|
464
|
+
!DIGEST.test(value.resultDigest) ||
|
|
465
|
+
!Number.isFinite(Date.parse(value.recordedAt))
|
|
466
|
+
)
|
|
467
|
+
throw new TypeError("browser download outcome request is invalid");
|
|
468
|
+
const core = Object.freeze({ ...value });
|
|
469
|
+
return Object.freeze({
|
|
470
|
+
...core,
|
|
471
|
+
outcomeRequestDigest: digest(
|
|
472
|
+
BROWSER_DOWNLOAD_ACTION_OUTCOME_REQUEST_SCHEMA,
|
|
473
|
+
core,
|
|
474
|
+
),
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function normalizeAck(value, descriptor, request) {
|
|
479
|
+
exact(
|
|
480
|
+
value,
|
|
481
|
+
[
|
|
482
|
+
"schema",
|
|
483
|
+
"authorityId",
|
|
484
|
+
"tenantId",
|
|
485
|
+
"handlerArtifactDigest",
|
|
486
|
+
"actionReceiptDigest",
|
|
487
|
+
"outcomeRequestDigest",
|
|
488
|
+
"auditEventDigest",
|
|
489
|
+
"durabilityReceiptDigest",
|
|
490
|
+
"authenticated",
|
|
491
|
+
"durable",
|
|
492
|
+
"readbackVerified",
|
|
493
|
+
"qualifiesForPromotion",
|
|
494
|
+
],
|
|
495
|
+
"browser download outcome acknowledgement",
|
|
496
|
+
);
|
|
497
|
+
if (
|
|
498
|
+
value.schema !== BROWSER_DOWNLOAD_ACTION_OUTCOME_ACK_SCHEMA ||
|
|
499
|
+
value.authorityId !== descriptor.authorityId ||
|
|
500
|
+
value.tenantId !== descriptor.tenantId ||
|
|
501
|
+
value.handlerArtifactDigest !== descriptor.handlerArtifactDigest ||
|
|
502
|
+
value.actionReceiptDigest !== request.actionReceiptDigest ||
|
|
503
|
+
value.outcomeRequestDigest !== request.outcomeRequestDigest ||
|
|
504
|
+
!DIGEST.test(value.auditEventDigest) ||
|
|
505
|
+
!DIGEST.test(value.durabilityReceiptDigest) ||
|
|
506
|
+
value.authenticated !== true ||
|
|
507
|
+
value.durable !== true ||
|
|
508
|
+
value.readbackVerified !== true ||
|
|
509
|
+
value.qualifiesForPromotion !== false
|
|
510
|
+
)
|
|
511
|
+
throw new Error("browser download acknowledgement is invalid");
|
|
512
|
+
return Object.freeze({ ...value });
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export function createBrowserDownloadActionAuthority({
|
|
516
|
+
descriptor,
|
|
517
|
+
authorize,
|
|
518
|
+
executeDownload,
|
|
519
|
+
recordOutcome,
|
|
520
|
+
now = Date.now,
|
|
521
|
+
} = {}) {
|
|
522
|
+
const normalizedDescriptor = normalizeDescriptor(descriptor);
|
|
523
|
+
for (const [name, port] of Object.entries({
|
|
524
|
+
authorize,
|
|
525
|
+
executeDownload,
|
|
526
|
+
recordOutcome,
|
|
527
|
+
now,
|
|
528
|
+
})) {
|
|
529
|
+
if (typeof port !== "function" || types.isProxy(port))
|
|
530
|
+
throw new TypeError(`browser download ${name} port is invalid`);
|
|
531
|
+
}
|
|
532
|
+
const authority = Object.freeze({});
|
|
533
|
+
authorities.set(authority, {
|
|
534
|
+
descriptor: normalizedDescriptor,
|
|
535
|
+
authorize,
|
|
536
|
+
executeDownload,
|
|
537
|
+
recordOutcome,
|
|
538
|
+
now,
|
|
539
|
+
issued: new Map(),
|
|
540
|
+
});
|
|
541
|
+
return authority;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
export function captureBrowserDownloadActionAuthority(value) {
|
|
545
|
+
const captured = authorities.get(value);
|
|
546
|
+
if (!captured)
|
|
547
|
+
throw new TypeError("A branded browser download authority is required");
|
|
548
|
+
return Object.freeze({
|
|
549
|
+
descriptor: captured.descriptor,
|
|
550
|
+
authorizeAction: async (input) => {
|
|
551
|
+
const request = normalizeRequest(input);
|
|
552
|
+
const evidence = requestEvidence(request);
|
|
553
|
+
const decision = normalizeDecision(
|
|
554
|
+
await captured.authorize(
|
|
555
|
+
Object.freeze({ ...request, requestDigest: evidence.requestDigest }),
|
|
556
|
+
),
|
|
557
|
+
);
|
|
558
|
+
if (decision.decision !== "allow") {
|
|
559
|
+
const error = new Error(
|
|
560
|
+
decision.reason ?? "Browser download was denied",
|
|
561
|
+
);
|
|
562
|
+
error.code = "BROWSER_DOWNLOAD_ACTION_DENIED";
|
|
563
|
+
throw error;
|
|
564
|
+
}
|
|
565
|
+
const nowMs = captured.now();
|
|
566
|
+
const validUntilMs = Date.parse(decision.validUntil);
|
|
567
|
+
if (
|
|
568
|
+
typeof decision.approvalEvidenceRef !== "string" ||
|
|
569
|
+
decision.approvalEvidenceRef.length < 1 ||
|
|
570
|
+
decision.approvalEvidenceRef.length > 512 ||
|
|
571
|
+
!Number.isFinite(nowMs) ||
|
|
572
|
+
!Number.isFinite(validUntilMs) ||
|
|
573
|
+
validUntilMs <= nowMs ||
|
|
574
|
+
validUntilMs > nowMs + captured.descriptor.maxGrantTtlMs
|
|
575
|
+
)
|
|
576
|
+
throw new Error("Browser download decision is invalid");
|
|
577
|
+
const core = {
|
|
578
|
+
schema: BROWSER_DOWNLOAD_ACTION_RECEIPT_SCHEMA,
|
|
579
|
+
authorityId: captured.descriptor.authorityId,
|
|
580
|
+
tenantId: captured.descriptor.tenantId,
|
|
581
|
+
policyRevision: captured.descriptor.policyRevision,
|
|
582
|
+
handlerArtifactDigest: captured.descriptor.handlerArtifactDigest,
|
|
583
|
+
approvalMode: captured.descriptor.approvalMode,
|
|
584
|
+
artifactMode: captured.descriptor.artifactMode,
|
|
585
|
+
requestId: request.requestId,
|
|
586
|
+
targetId: request.targetId,
|
|
587
|
+
operation: request.operation,
|
|
588
|
+
senderId: request.senderId,
|
|
589
|
+
frameUrlDigest: request.frameUrlDigest,
|
|
590
|
+
destinationDigest: evidence.destinationDigest,
|
|
591
|
+
redirectOriginsDigest: evidence.redirectOriginsDigest,
|
|
592
|
+
contentTypesDigest: evidence.contentTypesDigest,
|
|
593
|
+
maxBytes: request.maxBytes,
|
|
594
|
+
timeout: request.timeout,
|
|
595
|
+
inputDigest: request.inputDigest,
|
|
596
|
+
authorizationDigest: evidence.authorizationDigest,
|
|
597
|
+
requestDigest: evidence.requestDigest,
|
|
598
|
+
approvalEvidenceRef: decision.approvalEvidenceRef,
|
|
599
|
+
authorizedAt: new Date(nowMs).toISOString(),
|
|
600
|
+
validUntil: new Date(validUntilMs).toISOString(),
|
|
601
|
+
};
|
|
602
|
+
const receipt = Object.freeze({
|
|
603
|
+
...core,
|
|
604
|
+
receiptDigest: digest(BROWSER_DOWNLOAD_ACTION_RECEIPT_SCHEMA, core),
|
|
605
|
+
});
|
|
606
|
+
captured.issued.set(receipt.receiptDigest, {
|
|
607
|
+
request,
|
|
608
|
+
receipt,
|
|
609
|
+
execution: null,
|
|
610
|
+
abortController: null,
|
|
611
|
+
cancelRequested: false,
|
|
612
|
+
outcomeRequestDigest: null,
|
|
613
|
+
});
|
|
614
|
+
return receipt;
|
|
615
|
+
},
|
|
616
|
+
cancelAuthorizedDownload: async (input) => {
|
|
617
|
+
exact(
|
|
618
|
+
input,
|
|
619
|
+
["receiptDigest", "requestDigest", "reason"],
|
|
620
|
+
"browser download cancellation request",
|
|
621
|
+
);
|
|
622
|
+
const issued = captured.issued.get(input.receiptDigest);
|
|
623
|
+
if (
|
|
624
|
+
!DIGEST.test(input.receiptDigest) ||
|
|
625
|
+
!DIGEST.test(input.requestDigest) ||
|
|
626
|
+
!["user-request", "renderer-destroyed", "operator-request"].includes(
|
|
627
|
+
input.reason,
|
|
628
|
+
) ||
|
|
629
|
+
!issued ||
|
|
630
|
+
issued.receipt.requestDigest !== input.requestDigest ||
|
|
631
|
+
issued.execution?.status !== "executing" ||
|
|
632
|
+
!(issued.abortController instanceof AbortController) ||
|
|
633
|
+
issued.cancelRequested
|
|
634
|
+
)
|
|
635
|
+
throw new Error("browser download cancellation target is not active");
|
|
636
|
+
issued.cancelRequested = true;
|
|
637
|
+
issued.abortController.abort(
|
|
638
|
+
new Error("browser download execution cancellation requested"),
|
|
639
|
+
);
|
|
640
|
+
return Object.freeze({ accepted: true });
|
|
641
|
+
},
|
|
642
|
+
executeAuthorizedDownload: async (input) => {
|
|
643
|
+
exact(
|
|
644
|
+
input,
|
|
645
|
+
["receiptDigest", "requestDigest"],
|
|
646
|
+
"browser download execution request",
|
|
647
|
+
);
|
|
648
|
+
const issued = captured.issued.get(input.receiptDigest);
|
|
649
|
+
const startMs = captured.now();
|
|
650
|
+
if (
|
|
651
|
+
!DIGEST.test(input.receiptDigest) ||
|
|
652
|
+
!DIGEST.test(input.requestDigest) ||
|
|
653
|
+
!issued ||
|
|
654
|
+
issued.receipt.requestDigest !== input.requestDigest ||
|
|
655
|
+
issued.execution !== null ||
|
|
656
|
+
!Number.isFinite(startMs) ||
|
|
657
|
+
startMs >= Date.parse(issued.receipt.validUntil)
|
|
658
|
+
)
|
|
659
|
+
throw new Error("browser download execution grant is invalid or spent");
|
|
660
|
+
issued.execution = Object.freeze({ status: "executing" });
|
|
661
|
+
const deadlineAtMs = startMs + issued.request.timeout;
|
|
662
|
+
const controller = new AbortController();
|
|
663
|
+
issued.abortController = controller;
|
|
664
|
+
let timer = null;
|
|
665
|
+
let timedOut = false;
|
|
666
|
+
let result;
|
|
667
|
+
try {
|
|
668
|
+
const execution = Object.freeze({
|
|
669
|
+
schema: BROWSER_DOWNLOAD_ACTION_EXECUTION_SCHEMA,
|
|
670
|
+
actionReceiptDigest: issued.receipt.receiptDigest,
|
|
671
|
+
requestDigest: issued.receipt.requestDigest,
|
|
672
|
+
targetId: issued.request.targetId,
|
|
673
|
+
operation: issued.request.operation,
|
|
674
|
+
destinationUrl: issued.request.destinationUrl,
|
|
675
|
+
allowedRedirectOrigins: issued.request.allowedRedirectOrigins,
|
|
676
|
+
allowedContentTypes: issued.request.allowedContentTypes,
|
|
677
|
+
maxBytes: issued.request.maxBytes,
|
|
678
|
+
deadlineAt: new Date(deadlineAtMs).toISOString(),
|
|
679
|
+
});
|
|
680
|
+
const artifactValue = await Promise.race([
|
|
681
|
+
Reflect.apply(captured.executeDownload, undefined, [
|
|
682
|
+
execution,
|
|
683
|
+
Object.freeze({ signal: controller.signal }),
|
|
684
|
+
]),
|
|
685
|
+
new Promise((_, reject) => {
|
|
686
|
+
timer = setTimeout(() => {
|
|
687
|
+
timedOut = true;
|
|
688
|
+
controller.abort();
|
|
689
|
+
reject(new Error("browser download execution timed out"));
|
|
690
|
+
}, issued.request.timeout);
|
|
691
|
+
}),
|
|
692
|
+
]);
|
|
693
|
+
if (timedOut)
|
|
694
|
+
throw new Error("browser download execution completed after timeout");
|
|
695
|
+
if (issued.cancelRequested || controller.signal.aborted)
|
|
696
|
+
throw new Error("browser download execution was cancelled");
|
|
697
|
+
const finishedAtMs = captured.now();
|
|
698
|
+
const artifact = normalizeArtifact(
|
|
699
|
+
artifactValue,
|
|
700
|
+
issued.request,
|
|
701
|
+
deadlineAtMs,
|
|
702
|
+
finishedAtMs,
|
|
703
|
+
);
|
|
704
|
+
result = executionEvidence(artifact);
|
|
705
|
+
} catch {
|
|
706
|
+
result = failedExecutionEvidence(
|
|
707
|
+
timedOut
|
|
708
|
+
? "download-timeout"
|
|
709
|
+
: issued.cancelRequested
|
|
710
|
+
? "download-cancelled"
|
|
711
|
+
: "download-provider-failed",
|
|
712
|
+
);
|
|
713
|
+
} finally {
|
|
714
|
+
if (timer !== null) clearTimeout(timer);
|
|
715
|
+
issued.abortController = null;
|
|
716
|
+
}
|
|
717
|
+
issued.execution = result;
|
|
718
|
+
return result;
|
|
719
|
+
},
|
|
720
|
+
recordActionOutcome: async (input) => {
|
|
721
|
+
const request = normalizeOutcome(input);
|
|
722
|
+
const issued = captured.issued.get(request.actionReceiptDigest);
|
|
723
|
+
if (
|
|
724
|
+
!issued ||
|
|
725
|
+
issued.receipt.requestDigest !== request.requestDigest ||
|
|
726
|
+
issued.request.targetId !== request.targetId ||
|
|
727
|
+
issued.request.operation !== request.operation ||
|
|
728
|
+
issued.request.inputDigest !== request.inputDigest ||
|
|
729
|
+
!issued.execution ||
|
|
730
|
+
issued.execution.status === "executing" ||
|
|
731
|
+
issued.execution.status !== request.status ||
|
|
732
|
+
issued.execution.resultDigest !== request.resultDigest ||
|
|
733
|
+
(issued.outcomeRequestDigest !== null &&
|
|
734
|
+
issued.outcomeRequestDigest !== request.outcomeRequestDigest)
|
|
735
|
+
)
|
|
736
|
+
throw new Error("browser download outcome differs from authorization");
|
|
737
|
+
issued.outcomeRequestDigest = request.outcomeRequestDigest;
|
|
738
|
+
const acknowledgement = normalizeAck(
|
|
739
|
+
await captured.recordOutcome(request),
|
|
740
|
+
captured.descriptor,
|
|
741
|
+
request,
|
|
742
|
+
);
|
|
743
|
+
captured.issued.delete(request.actionReceiptDigest);
|
|
744
|
+
return acknowledgement;
|
|
745
|
+
},
|
|
746
|
+
});
|
|
747
|
+
}
|