chainlesschain 0.165.6 → 0.165.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/assets/web-panel/assets/{AIOps-vfoDb6HZ.js → AIOps-B3gWmXjo.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Cm5f7p93.js → ActionButton-G9pdT-0e.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-yRuvgWxa.js → Analytics-Ca30J7m2.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BROsC4h1.js → AppLayout-BtVTm8PU.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CimHApb1.js → Artifacts-BhViNa6u.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DI8kr8zI.js → Audit-DhKq-FnY.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DSLzKy-n.js → BackgroundAgents-BL0SVbnE.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DkH_n2r1.js → Backup-BWkblnFi.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CKSrUoRA.js → BaseInput-ClaRNrF8.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-ClwfMXUS.js → Chat-sZE2Z77o.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CjMaqfL7.js → ChatBubbleRenderer-BAfKOnRe.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BYTehcQG.js → Checkbox-Cvhay5WM.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B8AMcp6J.js → Codegen-BpejL-4N.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BNSogPo3.js → Col-g2Ki6g_1.js} +1 -1
- package/src/assets/web-panel/assets/{Community-JxPvGb8O.js → Community-D2xKbNo9.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DF7bFAgE.js → Compact-DbL1Pds3.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Bvhqnwrq.js → Compliance-DGtZCV4c.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BDS0CJvM.js → Cowork-4kQpoNsI.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DqvRfxoQ.js → Cron-BxTYXXAN.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CDcIvnqS.js → Crosschain-k0J0LT2N.js} +1 -1
- package/src/assets/web-panel/assets/{DID-D7jaUxU3.js → DID-BaqhoPgN.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-B5A18wEV.js → Dashboard-T4RVPIM1.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-_6IVkGag.js → Dropdown-DszOToWq.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-C9PeiKCG.js → EmailListRenderer-C9KJU7HF.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DXjuEHrX.js → FamilyGuardDashboard-Cmr0JnsA.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-B4IAR9zv.js → Federation-BI-_2xZX.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CxQeSNx1.js → FormItemContext-DsnDEPsl.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-B8GS-Y3N.js → GenericCardRenderer-BLncAKKK.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BS8SouUX.js → Git-BPQhGXDn.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-Dq9aYb5Q.js → Governance-CyElbUQW.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BrCEe9AJ.js → Inference-CcSntBQD.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DybOpiY2.js → KnowledgeGraph-p6e7HZ2r.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DBpzbRfx.js → Logs-CNEtr-wz.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-Bj9xI0YH.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Cc7CCSCu.js → Marketplace-Dd2notAJ.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DFaIhOfu.js → McpTools-BBsN2CSW.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-KCqlp9ol.js → Memory-Dg2uTvwx.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Bys84ySN.js → MobileBridge-r1YMRiJV.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-B-6qU-je.js → MobileProjects-C-89kSm4.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BgY-fvCq.js → Mtc-CrPZcVgf.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-B8pCmqQJ.js → MtcAudit-sxi7hqJb.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-BbOmYutA.js → Multisig-CAgOMfF1.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BocLQz_M.js → NLProgramming-DafaPts3.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CSwbxEop.js → Notes-CJM-xUW_.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-CqFNSYG3.js → NotificationSettings-L0ZjfAh_.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C6gMfAk7.js → OrderTableRenderer-BB1CvCK0.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-QrDXVdMJ.js → Organization-DeLtgAD8.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Cxntotjt.js → Overflow-CKC2b10z.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CIdBOwNq.js → P2P-eyf2rzY1.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CT5aP8rD.js → PdhVaultBrowser-5upNIOW8.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DH7OM8XU.js → Permissions-B8p3W6JK.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CnJqbWce.js → PersonalDataHub-BnRMxTq3.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-5C3HG1kH.js → Pipeline-DjCNI1li.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B4yIp7xK.js → Privacy-u6gXLbWT.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BMvt-2M0.js → ProjectInit-BZAw4tkM.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CvSTEMRG.js → ProjectSettings-B1Tr72q7.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BKBJxEek.js → Projects-DO7HFuBQ.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BdwUqFWN.js → Providers-BjCztyhj.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DtnCKlTm.js → QrScannerModal-ZWyCUbR1.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-sTbgwyNr.js → QuickAsk-DhDjeW4G.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Bzr14gSb.js → Recommend-R_CN2MuU.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C_TF5IaJ.js → RemoteSession-uxFcujeu.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-GCAM4H2K.js → Reputation-DX8D7rks.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Dir0r6OQ.js → Row-BYahQILD.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CpGnlecX.js → RssFeed-CUSQ2tm-.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DMMamC6u.js → Search-CnN_S8Cs.js} +1 -1
- package/src/assets/web-panel/assets/{Security-yqay5yG-.js → Security-DcZlh-O4.js} +4 -4
- package/src/assets/web-panel/assets/{Services-C5i2wh9G.js → Services-ZhBOjzIX.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-vUqk35yj.js → Skeleton-D5Qnq_lI.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DzcSkhot.js → Skills-BC9cZ3Be.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CB6qbYNr.js → Sla-DauJ3E8_.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-DbNeZ_fh.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-D-RrsxTJ.js → SyncSettings-DUvJbSct.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DFxdOJiq.js → Tasks-BXa-5noQ.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-SKAob_Aq.js → Templates-JxaiUt_3.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BPfsfHQ-.js → Tenant-BW4ERQN8.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-8iw7FpUf.js → Terminal-DltUbdtv.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-C4Gjj5Su.js → TimelineRenderer-CMoZz0Tc.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-COYhN18e.js → Tokens-D_VEV60K.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-B-JzXcf6.js → Trigger-CCXTCs0R.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BJ5bg0nI.js → Trust-lkGRIPcG.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BG_ByRMf.js → UkeySign-BfGeQ9HX.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B3kOnSiU.js → VideoEditing-BEC7pYvc.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CcoGFnK7.js → Wallet-BZdG24o0.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DdT8vRC_.js → WebAuthn-yoEddedp.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-CZu3M9N8.js → WorkflowEditor-BXZEprOW.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DxJt3Qba.js → chat-gg0bzZal.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DdYfwJaB.js → colors-DjRVRuin.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BG3SUzwj.js → compact-item-su-dieAc.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-By51z2-1.js → createContext-Ci3S8oKF.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CxbFX9uH.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-B0apZ3KJ.js → hasIn-D8F10heH.js} +1 -1
- package/src/assets/web-panel/assets/{index-DE76D-kA.js → index-11wtYQiF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkvcEo4p.js → index-1IgxgALJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bos5_aMV.js → index-1agckWSq.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfWYsB9t.js → index-5JqhujnR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAhHirVH.js → index-6Tniqk1s.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYINoRLK.js → index-8Z-8BIlN.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5rLiEo0.js → index-BCI5v0yB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZ_0qm4g.js → index-BDJntYmJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWEuZWCK.js → index-BGzixOGZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLIbGOLX.js → index-BMMOVoLD.js} +1 -1
- package/src/assets/web-panel/assets/{index-bP_VlDb4.js → index-BTFpdBtt.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3rWuPMW.js → index-Ba_HyPnE.js} +1 -1
- package/src/assets/web-panel/assets/{index-Da_WBdLX.js → index-BbF1IHof.js} +1 -1
- package/src/assets/web-panel/assets/{index-RD6O-0C6.js → index-BgT9su_e.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpjZhgNd.js → index-BzPB6kGZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Q2SCX-MI.js → index-C6ygzplr.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIrJfqO4.js → index-CC3ZDjzY.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBPf3TpM.js → index-CEmXRhAE.js} +1 -1
- package/src/assets/web-panel/assets/index-CJGOlduY.js +1 -0
- package/src/assets/web-panel/assets/{index-Cp6e-hGw.js → index-CMPqd5Cv.js} +1 -1
- package/src/assets/web-panel/assets/{index-kf57LJ93.js → index-COWd8uGx.js} +1 -1
- package/src/assets/web-panel/assets/{index-CW93tZDv.js → index-CPIYSp4H.js} +1 -1
- package/src/assets/web-panel/assets/{index-BewKYrX9.js → index-CR0h6seT.js} +1 -1
- package/src/assets/web-panel/assets/{index-p1_8SbI1.js → index-CUmDhC4j.js} +1 -1
- package/src/assets/web-panel/assets/index-CkBCN5yH.js +1 -0
- package/src/assets/web-panel/assets/{index-ZxtP8jG7.js → index-CusgZPtc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BWF8_mkx.js → index-DBAETXft.js} +1 -1
- package/src/assets/web-panel/assets/{index-BP94CrBV.js → index-DIJ2qFOC.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNIxXMmN.js → index-Dbq4NMET.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzmQElZy.js → index-DkMEF5VT.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZFbps9po.js → index-DkfQR3hM.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dz49VOyQ.js → index-DnLucOn9.js} +1 -1
- package/src/assets/web-panel/assets/{index-oxRrmFO9.js → index-DugB_aKa.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bissefox.js → index-DyLUarhZ.js} +3 -3
- package/src/assets/web-panel/assets/{index-DaIz0NAW.js → index-T6_6ygJZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-SQqOlR3d.js → index-Tu8eWqh-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DCn8IcxV.js → index-W7XUgdVL.js} +1 -1
- package/src/assets/web-panel/assets/{index-3jWkIu5j.js → index-dHZinfko.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1C9i79A.js → index-jyNgp5FI.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BWJktcg8.js → initDefaultProps-Cv4p8e4w.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BEXBzUFV.js → motion-B8EwmMOw.js} +1 -1
- package/src/assets/web-panel/assets/{move-BUKwn2sx.js → move-B8R9F8TK.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-FTI7btqQ.js → mtc-parser-DoFYz5Bp.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CsLtSPH3.js → omit-CHWYLBjI.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DWZxsT_S.js → pickAttrs-CXuPDO77.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-Bcfs43CX.js → placementArrow-BaAtUUon.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-a9bcC1p-.js → responsiveObserve-DCc4zcDD.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BDTzgOXN.js → slide-B_lx89oZ.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Iq1j_HUg.js → statusUtils-DE8DPmR0.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DAZEYxeh.js → styleChecker-1ktXOsL_.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DVkjyDrh.js → useFlexGapSupport-xH89-wjh.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-wXTpNTHt.js → useFs-DscvfW5p.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CtQM_aZr.js → usePersonalDataHub-RdMKQta7.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-hjEoG85v.js → vnode-Z3wTni2T.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BHVpyKg_.js → zoom-B0iChCsm.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/commands/agent.js +91 -60
- package/src/commands/git.js +39 -0
- package/src/commands/plugin.js +17 -2
- package/src/commands/session-group.js +131 -0
- package/src/commands/session-location.js +48 -0
- package/src/commands/session.js +11 -0
- package/src/commands/sync-providers.js +4 -1
- package/src/commands/update.js +1 -0
- package/src/data/changelog.json +27 -1
- package/src/gateways/ws/ws-agent-handler.js +20 -12
- package/src/harness/jsonl-session-store.js +279 -37
- package/src/harness/mcp-client.js +701 -91
- package/src/harness/session-index.js +15 -4
- package/src/lib/agent-worktree.js +8 -1
- package/src/lib/auto-mode-safety-classifier.js +3 -17
- package/src/lib/background-agent-keeper-protocol.js +26 -6
- package/src/lib/background-agent-supervisor.js +17 -1
- package/src/lib/browser-evidence.js +1405 -0
- package/src/lib/chrome-connector.js +465 -79
- package/src/lib/claude-project-auto-memory.js +481 -0
- package/src/lib/claude-project-storage-layout.js +104 -0
- package/src/lib/cloud/cloud-client.js +40 -0
- package/src/lib/execution-location-local-supervisor.mjs +226 -0
- package/src/lib/execution-location-runner-lifecycle.js +991 -0
- package/src/lib/execution-location-target-preflight.js +333 -0
- package/src/lib/execution-location-target.js +783 -13
- package/src/lib/file-lines.js +6 -1
- package/src/lib/gateway-client-resilience.js +571 -0
- package/src/lib/gitlab-mr-worktree.js +176 -0
- package/src/lib/hooks-v2-producers.js +119 -15
- package/src/lib/hooks-v2-workspace-context.js +44 -1
- package/src/lib/host-resource-budget.js +295 -0
- package/src/lib/ide-context.js +15 -2
- package/src/lib/llm-config-defaults.js +43 -0
- package/src/lib/mcp-lifecycle-authority.js +887 -0
- package/src/lib/mcp-oauth.js +101 -20
- package/src/lib/mcp-tls.js +103 -3
- package/src/lib/paths.js +260 -32
- package/src/lib/permission-request.js +48 -0
- package/src/lib/plugin-runtime/bin.js +3 -1
- package/src/lib/plugin-runtime/hooks.js +3 -1
- package/src/lib/plugin-runtime/install.js +117 -1
- package/src/lib/plugin-runtime/lsp.js +4 -4
- package/src/lib/plugin-runtime/marketplace-archive-source.js +40 -5
- package/src/lib/plugin-runtime/marketplace-catalog.js +13 -2
- package/src/lib/plugin-runtime/marketplace-command-descriptor.js +343 -0
- package/src/lib/plugin-runtime/marketplace-command-source.js +580 -0
- package/src/lib/plugin-runtime/marketplace-source-adapter.js +33 -8
- package/src/lib/plugin-runtime/mcp.js +3 -1
- package/src/lib/plugin-runtime/monitors.js +3 -1
- package/src/lib/plugin-runtime/remote-source.js +151 -6
- package/src/lib/plugin-runtime/settings.js +3 -1
- package/src/lib/plugin-runtime/trust.js +274 -13
- package/src/lib/process-execution-broker/credential-agent.js +210 -41
- package/src/lib/process-execution-broker/index.js +15 -1
- package/src/lib/process-execution-broker/linux-cgroup-v2.js +391 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +50 -28
- package/src/lib/project-mcp-trust.js +165 -10
- package/src/lib/resume-session.js +9 -6
- package/src/lib/session-core-singletons.js +26 -11
- package/src/lib/session-host-lease.js +52 -16
- package/src/lib/session-lifecycle-scope.js +153 -0
- package/src/lib/session-message-fabric.js +168 -21
- package/src/lib/session-projection.js +244 -4
- package/src/lib/session-store-guard.js +3 -1
- package/src/lib/session-workbench-store.js +466 -0
- package/src/lib/spellcheck.js +211 -0
- package/src/lib/sub-agent-context.js +9 -9
- package/src/lib/sync-cli-db.js +485 -129
- package/src/lib/sync-credentials.js +31 -20
- package/src/lib/web-fetch.js +112 -40
- package/src/lib/web-search.js +127 -61
- package/src/lib/workspace-trust.js +793 -0
- package/src/repl/agent-repl.js +124 -5
- package/src/repl/permission-prompt.js +10 -12
- package/src/runtime/agent-core.js +311 -18
- package/src/runtime/bootstrap.js +37 -13
- package/src/runtime/coding-agent-contract-shared.cjs +11 -4
- package/src/runtime/headless-runner.js +234 -36
- package/src/runtime/headless-stream.js +181 -6
- package/src/runtime/mcp-config.js +298 -8
- package/src/runtime/policies/agent-policy.js +8 -0
- package/src/runtime/status-diagnostics-lite.js +9 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-CFyt8iUd.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-BHUI7cox.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DorRZcI9.js +0 -1
- package/src/assets/web-panel/assets/index-BETiL0a3.js +0 -1
- package/src/assets/web-panel/assets/index-BhAVHuW8.js +0 -1
package/src/lib/mcp-oauth.js
CHANGED
|
@@ -484,7 +484,23 @@ export function loadTokenStore() {
|
|
|
484
484
|
}
|
|
485
485
|
|
|
486
486
|
export function getStoredToken(serverUrl) {
|
|
487
|
-
|
|
487
|
+
const record = loadTokenStore()[serverKey(serverUrl)] || null;
|
|
488
|
+
return record?.revoked === true ? null : record;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function tokenRecordRevision(record) {
|
|
492
|
+
return Number.isSafeInteger(record?.credential_revision) &&
|
|
493
|
+
record.credential_revision >= 0
|
|
494
|
+
? record.credential_revision
|
|
495
|
+
: 0;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function tokenRevisionConflict(serverUrl) {
|
|
499
|
+
const error = new Error(
|
|
500
|
+
`MCP OAuth credential changed while it was being refreshed: ${serverKey(serverUrl)}`,
|
|
501
|
+
);
|
|
502
|
+
error.code = "MCP_TOKEN_STORE_REVISION_CONFLICT";
|
|
503
|
+
return error;
|
|
488
504
|
}
|
|
489
505
|
|
|
490
506
|
// The store holds OAuth access + refresh tokens, so it must not be world-
|
|
@@ -642,7 +658,7 @@ export function withStoreLock(file, fn) {
|
|
|
642
658
|
}
|
|
643
659
|
}
|
|
644
660
|
|
|
645
|
-
export function saveStoredToken(serverUrl, record) {
|
|
661
|
+
export function saveStoredToken(serverUrl, record, options = {}) {
|
|
646
662
|
const file = tokenStorePath();
|
|
647
663
|
return _deps.withStoreLock(file, () => {
|
|
648
664
|
const r = _readStore(file); // re-read inside the lock (latest state)
|
|
@@ -654,24 +670,56 @@ export function saveStoredToken(serverUrl, record) {
|
|
|
654
670
|
_warnCorruptStoreOnce(file, r.err, archived);
|
|
655
671
|
}
|
|
656
672
|
const store = r.store;
|
|
657
|
-
|
|
673
|
+
const key = serverKey(serverUrl);
|
|
674
|
+
const current = store[key] || null;
|
|
675
|
+
if (
|
|
676
|
+
options.expectedRevision != null &&
|
|
677
|
+
(tokenRecordRevision(current) !== options.expectedRevision ||
|
|
678
|
+
current?.revoked === true)
|
|
679
|
+
) {
|
|
680
|
+
throw tokenRevisionConflict(serverUrl);
|
|
681
|
+
}
|
|
682
|
+
store[key] = {
|
|
683
|
+
...record,
|
|
684
|
+
server: key,
|
|
685
|
+
credential_revision: tokenRecordRevision(current) + 1,
|
|
686
|
+
revoked: false,
|
|
687
|
+
};
|
|
658
688
|
_writeStoreSecure(file, store);
|
|
659
|
-
return store[
|
|
689
|
+
return store[key];
|
|
660
690
|
});
|
|
661
691
|
}
|
|
662
692
|
|
|
663
|
-
|
|
693
|
+
function revokeStoredToken(serverUrl, { expectedRevision = null } = {}) {
|
|
664
694
|
const file = tokenStorePath();
|
|
665
695
|
return _deps.withStoreLock(file, () => {
|
|
666
696
|
const store = loadTokenStore(); // re-read inside the lock (latest state)
|
|
667
697
|
const key = serverKey(serverUrl);
|
|
668
|
-
|
|
669
|
-
|
|
698
|
+
const current = store[key];
|
|
699
|
+
if (!current || current.revoked === true) return false;
|
|
700
|
+
if (
|
|
701
|
+
expectedRevision != null &&
|
|
702
|
+
tokenRecordRevision(current) !== expectedRevision
|
|
703
|
+
) {
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
// Keep a revision-only tombstone. An in-flight refresh that read the old
|
|
707
|
+
// credential must not resurrect it after explicit logout/revocation.
|
|
708
|
+
store[key] = {
|
|
709
|
+
server: key,
|
|
710
|
+
credential_revision: tokenRecordRevision(current) + 1,
|
|
711
|
+
revoked: true,
|
|
712
|
+
revoked_at: _deps.now(),
|
|
713
|
+
};
|
|
670
714
|
_writeStoreSecure(file, store);
|
|
671
715
|
return true;
|
|
672
716
|
});
|
|
673
717
|
}
|
|
674
718
|
|
|
719
|
+
export function deleteStoredToken(serverUrl) {
|
|
720
|
+
return revokeStoredToken(serverUrl);
|
|
721
|
+
}
|
|
722
|
+
|
|
675
723
|
/** True when a record's access token is missing or within `skewMs` of expiry. */
|
|
676
724
|
export function isTokenExpired(record, { skewMs = 60_000 } = {}) {
|
|
677
725
|
if (!record || !record.access_token) return true;
|
|
@@ -692,6 +740,8 @@ export function isTokenExpired(record, { skewMs = 60_000 } = {}) {
|
|
|
692
740
|
* @param {string} serverUrl
|
|
693
741
|
* @param {{ forceRefresh?: boolean }} [opts]
|
|
694
742
|
*/
|
|
743
|
+
const tokenRefreshFlights = new Map();
|
|
744
|
+
|
|
695
745
|
export async function ensureValidToken(
|
|
696
746
|
serverUrl,
|
|
697
747
|
{ forceRefresh = false } = {},
|
|
@@ -700,21 +750,52 @@ export async function ensureValidToken(
|
|
|
700
750
|
if (!record) return null;
|
|
701
751
|
if (!forceRefresh && !isTokenExpired(record)) return record.access_token;
|
|
702
752
|
if (!record.refresh_token || !record.endpoints?.token_endpoint) {
|
|
703
|
-
//
|
|
704
|
-
//
|
|
705
|
-
//
|
|
706
|
-
return
|
|
753
|
+
// Reaching this branch means the cached access token is expired (including
|
|
754
|
+
// the safety skew) or the server already rejected it. Reusing it would
|
|
755
|
+
// create a deterministic 401/403 loop, so every caller fails closed.
|
|
756
|
+
return null;
|
|
707
757
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
758
|
+
const key = serverKey(serverUrl);
|
|
759
|
+
let refresh = tokenRefreshFlights.get(key);
|
|
760
|
+
if (!refresh) {
|
|
761
|
+
refresh = (async () => {
|
|
762
|
+
try {
|
|
763
|
+
const tok = await refreshAccessToken(
|
|
764
|
+
{ token_endpoint: record.endpoints.token_endpoint },
|
|
765
|
+
{ refreshToken: record.refresh_token, clientId: record.client_id },
|
|
766
|
+
);
|
|
767
|
+
const updated = saveStoredToken(
|
|
768
|
+
serverUrl,
|
|
769
|
+
{ ...record, ...tok },
|
|
770
|
+
{ expectedRevision: tokenRecordRevision(record) },
|
|
771
|
+
);
|
|
772
|
+
return { status: "refreshed", accessToken: updated.access_token };
|
|
773
|
+
} catch (error) {
|
|
774
|
+
if ([400, 401, 403].includes(error?.status)) {
|
|
775
|
+
const revoked = revokeStoredToken(serverUrl, {
|
|
776
|
+
expectedRevision: tokenRecordRevision(record),
|
|
777
|
+
});
|
|
778
|
+
return { status: revoked ? "revoked" : "conflict" };
|
|
779
|
+
}
|
|
780
|
+
return {
|
|
781
|
+
status:
|
|
782
|
+
error?.code === "MCP_TOKEN_STORE_REVISION_CONFLICT"
|
|
783
|
+
? "conflict"
|
|
784
|
+
: "failed",
|
|
785
|
+
};
|
|
786
|
+
} finally {
|
|
787
|
+
tokenRefreshFlights.delete(key);
|
|
788
|
+
}
|
|
789
|
+
})();
|
|
790
|
+
tokenRefreshFlights.set(key, refresh);
|
|
791
|
+
}
|
|
792
|
+
const outcome = await refresh;
|
|
793
|
+
if (outcome.status === "refreshed") return outcome.accessToken;
|
|
794
|
+
if (outcome.status === "revoked") return null;
|
|
795
|
+
if (outcome.status === "conflict") {
|
|
796
|
+
return getStoredToken(serverUrl)?.access_token || null;
|
|
717
797
|
}
|
|
798
|
+
return null;
|
|
718
799
|
}
|
|
719
800
|
|
|
720
801
|
// ── interactive orchestrator ──────────────────────────────────────────────
|
package/src/lib/mcp-tls.js
CHANGED
|
@@ -2,11 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { createHash } from "node:crypto";
|
|
5
|
+
import { createHash, X509Certificate } from "node:crypto";
|
|
6
|
+
import { createSecureContext } from "node:tls";
|
|
6
7
|
import { isProxy } from "node:util/types";
|
|
7
8
|
import { Agent } from "undici";
|
|
8
9
|
|
|
9
10
|
export const MCP_TLS_MAX_FILE_BYTES = 1024 * 1024;
|
|
11
|
+
export const MCP_TLS_MANAGED_SOURCE_REQUIRED_CODE =
|
|
12
|
+
"CC_MCP_TLS_MANAGED_SOURCE_REQUIRED";
|
|
13
|
+
export const MCP_TLS_MANAGED_SOURCE_REQUIRED_MESSAGE =
|
|
14
|
+
"MCP TLS configuration is restricted to managed provisioned sources";
|
|
15
|
+
|
|
16
|
+
// TLS file paths are a privileged configuration capability: merely marking a
|
|
17
|
+
// JSON object as `configScope: "managed"` must never let a project, user, or
|
|
18
|
+
// headless config read certificate material. Keep the provisioned snapshot in
|
|
19
|
+
// a module-private WeakMap so it cannot be recreated by JSON, object spread,
|
|
20
|
+
// or a lookalike field supplied by an MCP config source.
|
|
21
|
+
const MANAGED_MCP_TLS_PROVENANCE = new WeakMap();
|
|
22
|
+
const MANAGED_MCP_TLS_SCOPE = "managed";
|
|
10
23
|
|
|
11
24
|
const TLS_KEYS = new Set([
|
|
12
25
|
"certFile",
|
|
@@ -22,6 +35,16 @@ function tlsError(code, message, cause = undefined) {
|
|
|
22
35
|
return error;
|
|
23
36
|
}
|
|
24
37
|
|
|
38
|
+
function managedTlsSourceRequiredError() {
|
|
39
|
+
// Do not include the claimed scope, source file, certificate path, or any
|
|
40
|
+
// TLS configuration field here. This error is surfaced by headless and MCP
|
|
41
|
+
// connection diagnostics, where config values are not safe to disclose.
|
|
42
|
+
return tlsError(
|
|
43
|
+
MCP_TLS_MANAGED_SOURCE_REQUIRED_CODE,
|
|
44
|
+
MCP_TLS_MANAGED_SOURCE_REQUIRED_MESSAGE,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
25
48
|
function plainTlsObject(value) {
|
|
26
49
|
if (
|
|
27
50
|
!value ||
|
|
@@ -129,6 +152,47 @@ export function normalizeMcpTlsConfig(value) {
|
|
|
129
152
|
};
|
|
130
153
|
}
|
|
131
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Convert TLS settings from the organization-managed MCP loader into an
|
|
157
|
+
* immutable, non-forgeable configuration capability.
|
|
158
|
+
*
|
|
159
|
+
* This is deliberately separate from `normalizeMcpTlsConfig`: normalization
|
|
160
|
+
* validates data, while provisioning records the trusted source provenance
|
|
161
|
+
* that is required before a certificate/key/CA file can be opened.
|
|
162
|
+
*/
|
|
163
|
+
export function provisionManagedMcpTlsConfig(value) {
|
|
164
|
+
const config = normalizeMcpTlsConfig(value);
|
|
165
|
+
if (!config) return null;
|
|
166
|
+
const provisioned = Object.freeze({ ...config });
|
|
167
|
+
MANAGED_MCP_TLS_PROVENANCE.set(
|
|
168
|
+
provisioned,
|
|
169
|
+
Object.freeze({ configScope: MANAGED_MCP_TLS_SCOPE }),
|
|
170
|
+
);
|
|
171
|
+
return provisioned;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Enforce the managed source boundary before any TLS path is normalized or
|
|
176
|
+
* opened. The private provenance association survives normal config-object
|
|
177
|
+
* spreads because the provisioned `tls` value itself is preserved, but a
|
|
178
|
+
* caller cannot manufacture it from JSON or by setting `configScope`.
|
|
179
|
+
*/
|
|
180
|
+
export function assertManagedMcpTlsConfig(value, { configScope } = {}) {
|
|
181
|
+
if (value == null) return null;
|
|
182
|
+
const provenance =
|
|
183
|
+
value && typeof value === "object"
|
|
184
|
+
? MANAGED_MCP_TLS_PROVENANCE.get(value)
|
|
185
|
+
: null;
|
|
186
|
+
if (
|
|
187
|
+
!provenance ||
|
|
188
|
+
configScope !== MANAGED_MCP_TLS_SCOPE ||
|
|
189
|
+
provenance.configScope !== MANAGED_MCP_TLS_SCOPE
|
|
190
|
+
) {
|
|
191
|
+
throw managedTlsSourceRequiredError();
|
|
192
|
+
}
|
|
193
|
+
return value;
|
|
194
|
+
}
|
|
195
|
+
|
|
132
196
|
function readTlsFile(filePath, label, fsImpl) {
|
|
133
197
|
if (!filePath) return null;
|
|
134
198
|
let descriptor = null;
|
|
@@ -194,8 +258,29 @@ function updateDigestPart(hash, value) {
|
|
|
194
258
|
hash.update(length).update(bytes);
|
|
195
259
|
}
|
|
196
260
|
|
|
197
|
-
|
|
198
|
-
|
|
261
|
+
function validateCertificateBytes(bytes) {
|
|
262
|
+
if (!bytes) return;
|
|
263
|
+
const text = bytes.toString("utf8");
|
|
264
|
+
const pemCertificates = text.match(
|
|
265
|
+
/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/gu,
|
|
266
|
+
);
|
|
267
|
+
if (pemCertificates?.length) {
|
|
268
|
+
for (const certificate of pemCertificates) new X509Certificate(certificate);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
new X509Certificate(bytes);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function loadMcpTlsMaterial(
|
|
275
|
+
value,
|
|
276
|
+
{
|
|
277
|
+
configScope,
|
|
278
|
+
fsImpl = fs,
|
|
279
|
+
createSecureContextImpl = createSecureContext,
|
|
280
|
+
validateCertificateImpl = validateCertificateBytes,
|
|
281
|
+
} = {},
|
|
282
|
+
) {
|
|
283
|
+
const config = assertManagedMcpTlsConfig(value, { configScope });
|
|
199
284
|
if (!config) return null;
|
|
200
285
|
const cert = readTlsFile(config.certFile, "certFile", fsImpl);
|
|
201
286
|
const key = readTlsFile(config.keyFile, "keyFile", fsImpl);
|
|
@@ -207,6 +292,21 @@ export function loadMcpTlsMaterial(value, { fsImpl = fs } = {}) {
|
|
|
207
292
|
...(config.serverName ? { servername: config.serverName } : {}),
|
|
208
293
|
rejectUnauthorized: config.rejectUnauthorized,
|
|
209
294
|
};
|
|
295
|
+
try {
|
|
296
|
+
// Parse certificates and verify the client cert/key pair before creating a
|
|
297
|
+
// dispatcher or issuing any outbound request. Node/Undici would otherwise
|
|
298
|
+
// discover malformed or mismatched material only during a network dial.
|
|
299
|
+
validateCertificateImpl(cert);
|
|
300
|
+
validateCertificateImpl(ca);
|
|
301
|
+
createSecureContextImpl(connectOptions);
|
|
302
|
+
} catch {
|
|
303
|
+
// TLS parser diagnostics may quote attacker-controlled PEM text or paths.
|
|
304
|
+
// Keep the product error fixed and do not retain the parser error as cause.
|
|
305
|
+
throw tlsError(
|
|
306
|
+
"CC_MCP_TLS_MATERIAL_INVALID",
|
|
307
|
+
"MCP TLS certificate material is invalid or the client key does not match",
|
|
308
|
+
);
|
|
309
|
+
}
|
|
210
310
|
const identityHash = createHash("sha256");
|
|
211
311
|
updateDigestPart(identityHash, cert);
|
|
212
312
|
updateDigestPart(identityHash, key);
|
package/src/lib/paths.js
CHANGED
|
@@ -1,49 +1,277 @@
|
|
|
1
|
+
import { existsSync, lstatSync } from "node:fs";
|
|
1
2
|
import { homedir } from "node:os";
|
|
2
|
-
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
4
|
import { CONFIG_DIR_NAME } from "../constants.js";
|
|
4
5
|
import { getPlatform } from "./platform.js";
|
|
6
|
+
import {
|
|
7
|
+
CLAUDE_CONFIG_DIR_ENV,
|
|
8
|
+
resolveClaudeProjectStorageDir,
|
|
9
|
+
usesClaudeConfigDirectory,
|
|
10
|
+
} from "./claude-project-storage-layout.js";
|
|
5
11
|
import {
|
|
6
12
|
assertSafeOwnerOnlyPath,
|
|
7
13
|
ensurePrivateDirectory,
|
|
8
14
|
repairPrivatePaths,
|
|
9
15
|
} from "./secure-fs.js";
|
|
10
16
|
|
|
17
|
+
const securedClaudeProjectStorageEntries = new Map();
|
|
18
|
+
|
|
19
|
+
function pathContains(parent, candidate) {
|
|
20
|
+
const relation = relative(parent, candidate);
|
|
21
|
+
return (
|
|
22
|
+
relation === "" ||
|
|
23
|
+
(!relation.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) &&
|
|
24
|
+
relation !== ".." &&
|
|
25
|
+
!isAbsolute(relation))
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function nearestGitWorktreeRoot(cwd) {
|
|
30
|
+
let current = resolve(cwd);
|
|
31
|
+
for (;;) {
|
|
32
|
+
if (existsSync(join(current, ".git"))) return current;
|
|
33
|
+
const parent = dirname(current);
|
|
34
|
+
if (parent === current) return null;
|
|
35
|
+
current = parent;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function configHomeError(message) {
|
|
40
|
+
const error = new Error(message);
|
|
41
|
+
error.code = "CONFIG_HOME_UNSAFE";
|
|
42
|
+
return error;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isBroadSharedConfigRoot(resolvedHome, env) {
|
|
46
|
+
const platform = getPlatform();
|
|
47
|
+
const candidates =
|
|
48
|
+
platform === "win32"
|
|
49
|
+
? [
|
|
50
|
+
"C:\\Users",
|
|
51
|
+
"C:\\Users\\Public",
|
|
52
|
+
"C:\\Windows",
|
|
53
|
+
"C:\\ProgramData",
|
|
54
|
+
"C:\\Program Files",
|
|
55
|
+
"C:\\Program Files (x86)",
|
|
56
|
+
env?.PUBLIC,
|
|
57
|
+
env?.ProgramData,
|
|
58
|
+
env?.PROGRAMDATA,
|
|
59
|
+
env?.SystemRoot,
|
|
60
|
+
env?.WINDIR,
|
|
61
|
+
]
|
|
62
|
+
: [
|
|
63
|
+
"/tmp",
|
|
64
|
+
"/var/tmp",
|
|
65
|
+
"/usr",
|
|
66
|
+
"/etc",
|
|
67
|
+
"/var",
|
|
68
|
+
"/home",
|
|
69
|
+
"/root",
|
|
70
|
+
"/run",
|
|
71
|
+
"/opt",
|
|
72
|
+
"/srv",
|
|
73
|
+
"/mnt",
|
|
74
|
+
"/media",
|
|
75
|
+
];
|
|
76
|
+
return candidates.some((candidate) => {
|
|
77
|
+
if (typeof candidate !== "string" || !candidate) return false;
|
|
78
|
+
try {
|
|
79
|
+
const normalized = resolve(candidate);
|
|
80
|
+
return platform === "win32"
|
|
81
|
+
? normalized.toLowerCase() === resolvedHome.toLowerCase()
|
|
82
|
+
: normalized === resolvedHome;
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Reject broad shared directories before any owner-only chmod/DACL repair.
|
|
91
|
+
* Unlike the generic secure-fs guard, config/data roots are high-value and
|
|
92
|
+
* must never target a shared system parent such as C:\\Users or /home.
|
|
93
|
+
*/
|
|
94
|
+
export function assertSafeConfigDataRoot(dirPath, options = {}) {
|
|
95
|
+
const resolvedPath = resolve(dirPath);
|
|
96
|
+
if (isBroadSharedConfigRoot(resolvedPath, options.env || process.env)) {
|
|
97
|
+
throw configHomeError(
|
|
98
|
+
"Config/data root must not be a broad shared system directory",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
assertSafeOwnerOnlyPath(resolvedPath);
|
|
102
|
+
return resolvedPath;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function resolveConfiguredHome(configured, variableName, cwd, env) {
|
|
106
|
+
const windowsLiteral = configured.replaceAll("/", "\\");
|
|
107
|
+
if (
|
|
108
|
+
windowsLiteral.startsWith("\\\\?\\") ||
|
|
109
|
+
windowsLiteral.startsWith("\\\\.\\")
|
|
110
|
+
) {
|
|
111
|
+
throw configHomeError(
|
|
112
|
+
`${variableName} must not use a Windows device namespace`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (!isAbsolute(configured)) {
|
|
116
|
+
throw configHomeError(
|
|
117
|
+
`${variableName} must be an absolute path for owner-only storage`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const resolvedHome = resolve(configured);
|
|
121
|
+
try {
|
|
122
|
+
assertSafeConfigDataRoot(resolvedHome, { env });
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (error?.code === "CONFIG_HOME_UNSAFE") throw error;
|
|
125
|
+
throw configHomeError(
|
|
126
|
+
`${variableName} must not be a broad shared system directory`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
const resolvedCwd = resolve(cwd);
|
|
130
|
+
const worktreeRoot = nearestGitWorktreeRoot(resolvedCwd);
|
|
131
|
+
if (
|
|
132
|
+
pathContains(resolvedHome, resolvedCwd) ||
|
|
133
|
+
pathContains(resolvedCwd, resolvedHome) ||
|
|
134
|
+
(worktreeRoot &&
|
|
135
|
+
(pathContains(worktreeRoot, resolvedHome) ||
|
|
136
|
+
pathContains(resolvedHome, worktreeRoot)))
|
|
137
|
+
) {
|
|
138
|
+
throw configHomeError(
|
|
139
|
+
`${variableName} must not be the current working directory, its worktree, or any nested/ancestor path`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
// This is deliberately a no-IO validation. Directory creation/ACL repair
|
|
143
|
+
// remains in ensureHomeDir(), but broad roots and device aliases are refused
|
|
144
|
+
// before any caller can ask the secure-fs layer to mutate them.
|
|
145
|
+
return resolvedHome;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Resolve the CLI's authoritative config/data root. CHAINLESSCHAIN_HOME is a
|
|
150
|
+
* native explicit override and takes precedence over CLAUDE_CONFIG_DIR; the
|
|
151
|
+
* latter is only a compatible fallback for an otherwise unconfigured CLI.
|
|
152
|
+
*/
|
|
153
|
+
export function resolveConfigDataRoot(options = {}) {
|
|
154
|
+
const env = options.env || process.env;
|
|
155
|
+
const cwd = options.cwd || process.cwd();
|
|
156
|
+
const nativeHome = env.CHAINLESSCHAIN_HOME;
|
|
157
|
+
if (nativeHome) {
|
|
158
|
+
return Object.freeze({
|
|
159
|
+
path: resolveConfiguredHome(nativeHome, "CHAINLESSCHAIN_HOME", cwd, env),
|
|
160
|
+
source: "chainlesschain",
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (usesClaudeConfigDirectory(env)) {
|
|
164
|
+
const configured = env[CLAUDE_CONFIG_DIR_ENV].trim();
|
|
165
|
+
return Object.freeze({
|
|
166
|
+
path: resolveConfiguredHome(configured, CLAUDE_CONFIG_DIR_ENV, cwd, env),
|
|
167
|
+
source: "claude",
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return Object.freeze({
|
|
171
|
+
path: join(homedir(), CONFIG_DIR_NAME),
|
|
172
|
+
source: "default",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
11
176
|
export function getHomeDir() {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
177
|
+
return resolveConfigDataRoot().path;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Claude-compatible session storage is opt-in and only activates when the
|
|
182
|
+
* launcher supplied both CLAUDE_CONFIG_DIR and a safe project directory name.
|
|
183
|
+
* Legacy callers continue to use `<home>/sessions`.
|
|
184
|
+
*/
|
|
185
|
+
export function getClaudeProjectStorageDir(options = {}) {
|
|
186
|
+
const env = options.env || process.env;
|
|
187
|
+
const root = resolveConfigDataRoot({ ...options, env });
|
|
188
|
+
if (root.source !== "claude") return null;
|
|
189
|
+
return resolveClaudeProjectStorageDir(root.path, { env });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function getSessionStoreDir(options = {}) {
|
|
193
|
+
return (
|
|
194
|
+
getClaudeProjectStorageDir(options) ||
|
|
195
|
+
join(resolveConfigDataRoot(options).path, "sessions")
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Secure a Claude-compatible project storage tree without trusting inherited
|
|
201
|
+
* Windows ACLs on pre-existing descendants. The config root is repaired
|
|
202
|
+
* first, then every child is created below that verified boundary and repaired
|
|
203
|
+
* together in one Windows ACL batch. This keeps the security invariant while
|
|
204
|
+
* avoiding a PowerShell process for every storage subdirectory.
|
|
205
|
+
*/
|
|
206
|
+
export function ensureClaudeProjectStorageTree(
|
|
207
|
+
homeDir,
|
|
208
|
+
projectDir,
|
|
209
|
+
options = {},
|
|
210
|
+
) {
|
|
211
|
+
const home = resolve(homeDir);
|
|
212
|
+
const projectsDir = join(home, "projects");
|
|
213
|
+
const project = resolve(projectDir);
|
|
214
|
+
const extraDirectories = Array.isArray(options.extraDirectories)
|
|
215
|
+
? options.extraDirectories.map((entry) => resolve(entry))
|
|
216
|
+
: [];
|
|
217
|
+
if (!pathContains(projectsDir, project) || project === projectsDir) {
|
|
218
|
+
throw configHomeError(
|
|
219
|
+
"Claude project storage path must remain below projects",
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (extraDirectories.some((entry) => !pathContains(project, entry))) {
|
|
223
|
+
throw configHomeError(
|
|
224
|
+
"Claude project storage descendants must remain below the project root",
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
ensurePrivateDirectory(home, {
|
|
229
|
+
applyWindowsAcl: true,
|
|
230
|
+
failIfUnavailable: true,
|
|
231
|
+
});
|
|
232
|
+
const descendants = [projectsDir, project, ...extraDirectories];
|
|
233
|
+
if (getPlatform() === "win32") {
|
|
234
|
+
const repairTargets = [];
|
|
235
|
+
for (const directory of descendants) {
|
|
236
|
+
ensurePrivateDirectory(directory, {
|
|
237
|
+
applyWindowsAcl: false,
|
|
238
|
+
failIfUnavailable: true,
|
|
239
|
+
});
|
|
240
|
+
let current = null;
|
|
241
|
+
try {
|
|
242
|
+
const entry = lstatSync(directory);
|
|
243
|
+
current = `${entry.dev}:${entry.ino}`;
|
|
244
|
+
} catch {
|
|
245
|
+
// ensurePrivateDirectory above either created the directory or threw;
|
|
246
|
+
// retain a fail-closed repair target if a filesystem race removed it.
|
|
247
|
+
}
|
|
248
|
+
// The secure-fs layer caches verified Windows ACLs per path. Keep a
|
|
249
|
+
// matching tree cache so repeated session writes do not re-run a batch,
|
|
250
|
+
// but force a repair whenever this helper first observes a descendant.
|
|
251
|
+
if (securedClaudeProjectStorageEntries.get(directory) !== current) {
|
|
252
|
+
repairTargets.push(directory);
|
|
253
|
+
}
|
|
24
254
|
}
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
|
|
255
|
+
if (repairTargets.length > 0) {
|
|
256
|
+
// A private parent is not proof that an older child has no broad explicit
|
|
257
|
+
// ACE. Repair all newly observed descendants in one fail-closed batch.
|
|
258
|
+
repairPrivatePaths(repairTargets, { platform: "win32" });
|
|
259
|
+
for (const directory of repairTargets) {
|
|
260
|
+
securedClaudeProjectStorageEntries.set(
|
|
261
|
+
directory,
|
|
262
|
+
(() => {
|
|
263
|
+
const entry = lstatSync(directory);
|
|
264
|
+
return `${entry.dev}:${entry.ino}`;
|
|
265
|
+
})(),
|
|
266
|
+
);
|
|
267
|
+
}
|
|
31
268
|
}
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
relativeCwd === "" ||
|
|
36
|
-
(!relativeCwd.startsWith("..") && !isAbsolute(relativeCwd))
|
|
37
|
-
) {
|
|
38
|
-
const error = new Error(
|
|
39
|
-
"CHAINLESSCHAIN_HOME must not be the current working directory or one of its ancestors",
|
|
40
|
-
);
|
|
41
|
-
error.code = "CONFIG_HOME_UNSAFE";
|
|
42
|
-
throw error;
|
|
269
|
+
} else {
|
|
270
|
+
for (const directory of descendants) {
|
|
271
|
+
ensurePrivateDirectory(directory, { failIfUnavailable: true });
|
|
43
272
|
}
|
|
44
|
-
return configured;
|
|
45
273
|
}
|
|
46
|
-
return
|
|
274
|
+
return project;
|
|
47
275
|
}
|
|
48
276
|
|
|
49
277
|
export function getBinDir() {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonicalize the part of a tool request that is classified and shown to the
|
|
3
|
+
* user for approval. Keeping this in one place prevents the safety classifier
|
|
4
|
+
* and the permission prompt from silently reasoning about different inputs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function quoteArgvToken(value) {
|
|
8
|
+
const token = String(value);
|
|
9
|
+
if (token && !/[\s|;&"'\\]/u.test(token)) return token;
|
|
10
|
+
return `"${token.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"')}"`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function normalizePermissionRequest({ tool, args } = {}) {
|
|
14
|
+
const normalizedArgs = args && typeof args === "object" ? args : {};
|
|
15
|
+
const command =
|
|
16
|
+
typeof normalizedArgs.command === "string"
|
|
17
|
+
? normalizedArgs.command
|
|
18
|
+
: Array.isArray(normalizedArgs.argv)
|
|
19
|
+
? normalizedArgs.argv.map(quoteArgvToken).join(" ")
|
|
20
|
+
: "";
|
|
21
|
+
const source = String(
|
|
22
|
+
normalizedArgs.path ??
|
|
23
|
+
normalizedArgs.file_path ??
|
|
24
|
+
normalizedArgs.source ??
|
|
25
|
+
normalizedArgs.from ??
|
|
26
|
+
"",
|
|
27
|
+
);
|
|
28
|
+
const destination = String(
|
|
29
|
+
normalizedArgs.destination ??
|
|
30
|
+
normalizedArgs.new_path ??
|
|
31
|
+
normalizedArgs.to ??
|
|
32
|
+
"",
|
|
33
|
+
);
|
|
34
|
+
const detail =
|
|
35
|
+
command ||
|
|
36
|
+
(source && destination
|
|
37
|
+
? `${source} -> ${destination}`
|
|
38
|
+
: source || destination);
|
|
39
|
+
|
|
40
|
+
return Object.freeze({
|
|
41
|
+
tool: String(tool || "unknown").trim() || "unknown",
|
|
42
|
+
args: normalizedArgs,
|
|
43
|
+
command,
|
|
44
|
+
source,
|
|
45
|
+
destination,
|
|
46
|
+
detail,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -81,7 +81,9 @@ export function collectPluginBinCommands(opts = {}) {
|
|
|
81
81
|
}
|
|
82
82
|
return [];
|
|
83
83
|
}
|
|
84
|
-
const { trusted, skipped } = partitionByTrust(plugins
|
|
84
|
+
const { trusted, skipped } = partitionByTrust(plugins, {
|
|
85
|
+
workspaceRoot: opts.cwd,
|
|
86
|
+
});
|
|
85
87
|
warnUntrustedOnce(
|
|
86
88
|
skipped
|
|
87
89
|
.filter((p) => p.manifest?.components?.bin?.length)
|