chainlesschain 0.162.148 → 0.162.150
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 +1 -1
- package/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-DwpCCp64.js → AIOps-CBLvrjeG.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-C9pBYocA.js → ActionButton-DYv2GtE0.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BWAkeXxK.js → Analytics-CL6PZ-Ww.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CivFGH6B.js → AppLayout-xuyHt4uA.js} +3 -3
- package/src/assets/web-panel/assets/{Audit-Cm8hRpZm.js → Audit-DOvSVycq.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DXdFMsE8.js → Backup-DMhfTswr.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-uAwSTeql.js → BaseInput-CQxDdm63.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-KU8eeJJE.js → Chat-BEeNhK2a.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-8nul8eTq.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-C6AMDkjd.js → Checkbox-CL04O-ER.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-t2moDxcO.js → Codegen-C50o-n7x.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DCcsRRhN.js → Col-CH-bDkzs.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DtB-8SAC.js → Community-DqAIMvSe.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CZm8THYA.js → Compact-C43fo_my.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-LiQgC7g1.js → Compliance-BERnMrdP.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CA8SAxht.js → Cowork-Baw3w1gp.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-CtRaF1wD.js → Cron-BEqAHF1-.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-6-br6Hub.js → Crosschain-CvnmKwK4.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Do2EccrL.js → DID-iMPxgVdt.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-wrXcbzGe.js → Dashboard--4SJX3YR.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-p3FsT245.js → Dropdown-BgLV6xgz.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D95A2L8q.js → EmailListRenderer-Bvb8oUD8.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-cLuJW2zo.js → FamilyGuardDashboard-DpXPjJB5.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Brgq_cbN.js → Federation-BbFfJ1Xi.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DWLCkNgh.js → FormItemContext-DeGq1B-q.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BBqUfQd6.js → GenericCardRenderer-CNW7s9zM.js} +1 -1
- package/src/assets/web-panel/assets/{Git-8F090w4I.js → Git-L1PjW-lT.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CCogEbxb.js → Governance-PK-X58to.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CfLD7v7C.js → Inference-BePWEH-d.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DDuHJewd.js → KnowledgeGraph-LF_sBvdL.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CE8KSEVC.js → Logs-BKkfk9hy.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-DmwpZmhT.js → Marketplace-BNMqUDla.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Bf7AazU8.js → McpTools-w4WPiyz2.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-D0QU_00S.js → Memory-DUKMVGNi.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-yXS9xdhx.js → MobileBridge-BWhiEyTJ.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BuDUoGUP.js → MobileProjects-Ocf5JEkX.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BYyHy28p.js → Mtc-D2B6MMhU.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-CK0aqpiZ.js → MtcAudit-DdYjCq1O.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-mNimKYeb.js → Multisig-Cb1hfuUZ.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-AioAJ0YA.js → NLProgramming-CTeImGp7.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CVVNCLHE.js → Notes-BjYNc7eQ.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-C4ABj-TK.js → NotificationSettings-Dn_NtRXQ.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DIp8Xcbd.js → OrderTableRenderer-DVYYIizh.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-uozl_gWK.js → Organization-Be2Kmr5j.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BCZOM2hK.js → Overflow-Cu_-qRlR.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CArcaiaj.js → P2P-DD2HoGzE.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Sgq2Srr8.js → PdhVaultBrowser-BRI7DVth.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-BYrQrXnH.js → Permissions-C9Srcs2M.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BzHStAbz.js → PersonalDataHub-CUqcMgo1.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-k7KqxX1M.js → Pipeline-HBrQGLXt.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-IumTUWqx.js → Privacy-D-B0vjwh.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BRyImYTf.js → ProjectInit-eTeLIu9e.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BFIqTBFk.js → ProjectSettings-DrRqCsC6.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Dpid9CDg.js → Projects-1G9DNOhI.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CyyFnUPs.js → Providers-RkTZzpxP.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-C82cRx-X.js → QrScannerModal-BSCUgsgC.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DGxDF521.js → QuickAsk-CHgyM3Bz.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cns-Am1y.js → Recommend-0lry4OZq.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-R7OqAIlg.js → RemoteSession-Cn45UroZ.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-D7mgPIYV.js → Reputation-Q-Zjb707.js} +1 -1
- package/src/assets/web-panel/assets/{Row-B3lEURyd.js → Row-BdM70oda.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-9_UVrpBt.js → RssFeed-DmOAKKap.js} +2 -2
- package/src/assets/web-panel/assets/{Search-ClZeO80q.js → Search-Dwavbm07.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BNNJczEp.js → Security-DukXFCnk.js} +3 -3
- package/src/assets/web-panel/assets/{Services-C5SjrWUj.js → Services-BIlRnITu.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Ci_obQ-g.js → Skeleton-YYVQdhhQ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DdebN15P.js → Skills-B8_iYHz2.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-OinZP2vi.js → Sla-BdVrhT31.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CxzbNF51.js → SpeechSettings-CD3ggRx7.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-D06N_66b.js → SyncSettings-Bp02VZFH.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BdEdW0AZ.js → Tasks-DEVmCEsr.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-F1ctKmPa.js → Templates-B6czWc4N.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CVz1Urot.js → Tenant-BCJLv17r.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-CD132d2Y.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-Doitzjmf.js → TimelineRenderer-ZB-CvkZl.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BpyVRpVA.js → Tokens-BnSY0S-s.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-nWhWYTbU.js → Trigger-VZw9Oy2w.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DK_G-wLx.js → Trust-pZq7Hjd2.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B5yBUojO.js → UkeySign-DnQaqk4q.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C5D51qAe.js → VideoEditing-BEUfmvJV.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CLbL9K7v.js → Wallet-CoZKMXJ2.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DZUelI3V.js → WebAuthn-CBOGVx1I.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-8RPxt4KW.js → WorkflowEditor-huSKn7TT.js} +1 -1
- package/src/assets/web-panel/assets/{chat-4N4tOA3d.js → chat-A_3w6FBO.js} +1 -1
- package/src/assets/web-panel/assets/{colors-zbbGVrua.js → colors-D1Bem9Oy.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-D7iMkbnE.js → compact-item-CJYJj0oO.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CBzPJv-w.js → createContext-BkbFiKT4.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-WDr3_M_N.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-bHdrQSqZ.js → hasIn-CKvUafVY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbE5D0WL.js → index-AjrSPnvv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCr0geV9.js → index-B8i0lViZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiG1IZao.js → index-B9Svn0zc.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1s0Bz9O.js → index-BHdHSX06.js} +1 -1
- package/src/assets/web-panel/assets/{index-QFObYeo1.js → index-BHdailyo.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnBOpqv3.js → index-BJwlEnYo.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdmMoYN1.js → index-BOoAQjJz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUyjUkY7.js → index-BSgWxAOG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqdPyWm5.js → index-BdutMsne.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmqlJed-.js → index-BiDWxzbn.js} +1 -1
- package/src/assets/web-panel/assets/{index-S74FpAIn.js → index-By5a0T_W.js} +1 -1
- package/src/assets/web-panel/assets/{index-kmh1TxRa.js → index-C-WIY-FQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTIjjXry.js → index-C37f6iey.js} +1 -1
- package/src/assets/web-panel/assets/{index-BS4_Mwk6.js → index-C9bWmTcO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBNr77E1.js → index-CBaosWRO.js} +1 -1
- package/src/assets/web-panel/assets/{index-QNAG4JtR.js → index-CBlBVJ_m.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bqmx24kh.js → index-CD62EEGo.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQfu1U78.js → index-CUj-l7GM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJ4ZGyW0.js → index-CVS8Ymuq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ci009ijp.js → index-ChkXUj3f.js} +1 -1
- package/src/assets/web-panel/assets/{index-oDKNgCsP.js → index-Cj0OZ-37.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZb8F8YG.js → index-Co3OFL0t.js} +1 -1
- package/src/assets/web-panel/assets/{index-308gCGh7.js → index-Crp8CbRg.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPxkoKgA.js → index-D22zNXiS.js} +1 -1
- package/src/assets/web-panel/assets/{index-xYCm6W2h.js → index-DTs_D1OL.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtbbdLnf.js → index-DfgCdFrN.js} +3 -3
- package/src/assets/web-panel/assets/{index-BsKehmmS.js → index-DjYrecNb.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkxK86vf.js → index-DpW9cf3e.js} +1 -1
- package/src/assets/web-panel/assets/index-Dwix3p4g.js +1 -0
- package/src/assets/web-panel/assets/{index-CvRMA9uT.js → index-F--HuPG1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DE9j5YgT.js → index-Fuk6t_3K.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7b1hGry.js → index-JEAGMii9.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1YDh7Wr.js → index-PF-mpv8K.js} +1 -1
- package/src/assets/web-panel/assets/index-SHaoZ0CA.js +1 -0
- package/src/assets/web-panel/assets/{index-CQ6NcfWz.js → index-b-sbVdjQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyoNgbXl.js → index-pyvMVX8r.js} +1 -1
- package/src/assets/web-panel/assets/{index-DISWAYce.js → index-qNTtOVi_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4XKpj6c.js → index-tgO8iza6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CahryTX0.js → index-ttDQVhZy.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BD55yNBt.js → initDefaultProps-DtrnfsZH.js} +1 -1
- package/src/assets/web-panel/assets/{motion-B5Bbe77U.js → motion-D4zqSaX3.js} +1 -1
- package/src/assets/web-panel/assets/{move-CTvIqHEH.js → move-CGFGOUQj.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BkNyPQKB.js → mtc-parser-DH2HvkJl.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BaXJXgHA.js → omit-jJ2at5HX.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Bw6-YTxH.js → pickAttrs-CRyMT1Fv.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BLdbkLqJ.js → placementArrow-DZ_CX0Pt.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-IVYkzntU.js → responsiveObserve-wruHk-h5.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DFMFwwtY.js → slide-BGgx8q67.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CURYRtZ1.js → statusUtils-Do5M7gKm.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Cfz63s1r.js → styleChecker-CRa-GZpX.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-COJL0KE3.js → useFlexGapSupport-BxPcq6pj.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-YLLojQQf.js → useFs-v5O2ZcJm.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BF_21qUC.js → usePersonalDataHub--z2FU0Px.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-9ZpkA7bb.js → vnode-1emidVKd.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DhtAfhl8.js → zoom-CcJaRyHs.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/assets/web-panel/remote-session-sw.js +43 -0
- package/src/command-manifest.json +22 -1
- package/src/commands/a2a.js +19 -4
- package/src/commands/activitypub.js +20 -3
- package/src/commands/agent.js +71 -10
- package/src/commands/audit.js +40 -24
- package/src/commands/codeintel.js +320 -0
- package/src/commands/collab.js +15 -2
- package/src/commands/compliance.js +5 -0
- package/src/commands/config.js +4 -1
- package/src/commands/dao.js +86 -14
- package/src/commands/dev.js +2 -0
- package/src/commands/did.js +7 -1
- package/src/commands/dlp.js +23 -1
- package/src/commands/economy.js +29 -3
- package/src/commands/eval.js +255 -0
- package/src/commands/evomap.js +26 -0
- package/src/commands/governance.js +17 -3
- package/src/commands/hardening.js +18 -0
- package/src/commands/incentive.js +5 -0
- package/src/commands/init.js +46 -2
- package/src/commands/kg.js +4 -0
- package/src/commands/loop.js +6 -1
- package/src/commands/lowcode.js +15 -2
- package/src/commands/marketplace.js +40 -6
- package/src/commands/matrix.js +20 -1
- package/src/commands/memory.js +4 -1
- package/src/commands/mtc.js +7 -1
- package/src/commands/nostr.js +31 -4
- package/src/commands/note.js +7 -4
- package/src/commands/plugin.js +474 -0
- package/src/commands/pqc.js +5 -0
- package/src/commands/reputation.js +10 -1
- package/src/commands/scim.js +6 -0
- package/src/commands/session.js +32 -10
- package/src/commands/siem.js +11 -0
- package/src/commands/sla.js +18 -3
- package/src/commands/social.js +38 -5
- package/src/commands/sso.js +10 -2
- package/src/commands/stress.js +23 -4
- package/src/commands/team.js +463 -0
- package/src/commands/tech.js +2 -0
- package/src/commands/tenant.js +10 -1
- package/src/commands/terraform.js +6 -0
- package/src/commands/update.js +1 -1
- package/src/commands/zkp.js +20 -0
- package/src/gateways/ws/message-dispatcher.js +48 -2
- package/src/gateways/ws/remote-session-protocol.js +140 -42
- package/src/gateways/ws/ws-server.js +1 -1
- package/src/harness/jsonl-session-store.js +12 -5
- package/src/harness/plugin-manager.js +16 -4
- package/src/harness/prompt-compressor.js +27 -1
- package/src/harness/remote-command-ledger.js +189 -0
- package/src/harness/remote-session-push-apns.js +246 -0
- package/src/harness/remote-session-push-errors.js +15 -0
- package/src/harness/remote-session-push-fcm.js +9 -25
- package/src/harness/remote-session-push-huawei.js +174 -0
- package/src/harness/remote-session-push-oppo.js +171 -0
- package/src/harness/remote-session-push-senders.js +42 -0
- package/src/harness/remote-session-push-vivo.js +179 -0
- package/src/harness/remote-session-push-web.js +299 -0
- package/src/harness/remote-session-push-xiaomi.js +99 -0
- package/src/harness/worktree-isolator.js +16 -6
- package/src/index.js +6 -0
- package/src/lib/__tests__/logger.test.js +5 -2
- package/src/lib/a2a-protocol.js +25 -1
- package/src/lib/activitypub-bridge.js +61 -0
- package/src/lib/agent-core.js +4 -0
- package/src/lib/agent-economy.js +262 -29
- package/src/lib/agent-network.js +7 -1
- package/src/lib/agent-sandbox.js +161 -5
- package/src/lib/agent-team/task-lease.js +434 -0
- package/src/lib/agent-team/team-budget.js +165 -0
- package/src/lib/agent-team/team-mailbox.js +106 -0
- package/src/lib/agent-team/team-runner.js +282 -0
- package/src/lib/agent-team/team-worktree.js +236 -0
- package/src/lib/agents.js +18 -0
- package/src/lib/async-hook-supervisor.cjs +391 -0
- package/src/lib/audit-logger.js +7 -6
- package/src/lib/autonomous-developer.js +60 -0
- package/src/lib/chat-core.js +17 -4
- package/src/lib/collaboration-governance.js +56 -0
- package/src/lib/community-governance.js +68 -0
- package/src/lib/compliance-manager.js +63 -0
- package/src/lib/cross-chain.js +5 -0
- package/src/lib/dao-governance.js +151 -2
- package/src/lib/did-manager.js +23 -10
- package/src/lib/dlp-engine.js +70 -0
- package/src/lib/eval/runner.js +251 -0
- package/src/lib/eval/tasks.js +626 -0
- package/src/lib/eval/trend.js +200 -0
- package/src/lib/evolution-system.js +92 -0
- package/src/lib/evomap-federation.js +55 -0
- package/src/lib/evomap-governance.js +94 -0
- package/src/lib/fatal-handler.js +17 -1
- package/src/lib/hardening-manager.js +73 -0
- package/src/lib/hierarchical-memory.js +14 -8
- package/src/lib/knowledge-exporter.js +8 -2
- package/src/lib/knowledge-graph.js +79 -0
- package/src/lib/llm-providers.js +23 -14
- package/src/lib/logger.js +4 -1
- package/src/lib/lsp/__tests__/benchmark.test.js +88 -0
- package/src/lib/lsp/__tests__/code-intelligence.integration.test.js +129 -0
- package/src/lib/lsp/__tests__/code-intelligence.test.js +228 -0
- package/src/lib/lsp/__tests__/jsonrpc-stream.test.js +104 -0
- package/src/lib/lsp/__tests__/lsp-client.test.js +269 -0
- package/src/lib/lsp/__tests__/lsp-manager.test.js +333 -0
- package/src/lib/lsp/__tests__/lsp-server-registry.test.js +128 -0
- package/src/lib/lsp/benchmark.js +257 -0
- package/src/lib/lsp/code-intelligence.js +356 -0
- package/src/lib/lsp/jsonrpc-stream.js +139 -0
- package/src/lib/lsp/lsp-client.js +339 -0
- package/src/lib/lsp/lsp-manager.js +375 -0
- package/src/lib/lsp/lsp-server-registry.js +196 -0
- package/src/lib/matrix-bridge.js +84 -0
- package/src/lib/memory-manager.js +5 -3
- package/src/lib/nostr-bridge.js +53 -0
- package/src/lib/permission-engine.js +22 -4
- package/src/lib/plugin-monitor-supervisor.js +238 -0
- package/src/lib/plugin-runtime/__tests__/remote-source.test.js +256 -0
- package/src/lib/plugin-runtime/agents.js +51 -0
- package/src/lib/plugin-runtime/bin.js +100 -0
- package/src/lib/plugin-runtime/hooks.js +100 -0
- package/src/lib/plugin-runtime/install.js +396 -0
- package/src/lib/plugin-runtime/lsp.js +75 -0
- package/src/lib/plugin-runtime/manifest.js +458 -0
- package/src/lib/plugin-runtime/mcp.js +89 -0
- package/src/lib/plugin-runtime/monitors.js +100 -0
- package/src/lib/plugin-runtime/policy.js +152 -0
- package/src/lib/plugin-runtime/reload.js +79 -0
- package/src/lib/plugin-runtime/remote-source.js +240 -0
- package/src/lib/plugin-runtime/scopes.js +197 -0
- package/src/lib/plugin-runtime/settings.js +119 -0
- package/src/lib/plugin-runtime/signature.js +107 -0
- package/src/lib/plugin-runtime/skills.js +43 -0
- package/src/lib/plugin-runtime/trust.js +140 -0
- package/src/lib/pqc-manager.js +64 -0
- package/src/lib/reputation-optimizer.js +101 -0
- package/src/lib/sandbox-egress-proxy.js +262 -0
- package/src/lib/sandbox-fs-policy.js +112 -0
- package/src/lib/sandbox-network-policy.js +151 -0
- package/src/lib/sandbox-v2.js +24 -18
- package/src/lib/scim-manager.js +36 -0
- package/src/lib/session-manager.js +5 -2
- package/src/lib/settings-hook-events.cjs +78 -6
- package/src/lib/settings-hooks.cjs +30 -3
- package/src/lib/settings-loader.cjs +35 -1
- package/src/lib/siem-exporter.js +45 -0
- package/src/lib/skill-loader.js +38 -2
- package/src/lib/skill-marketplace.js +83 -0
- package/src/lib/sla-manager.js +88 -0
- package/src/lib/social-manager.js +74 -0
- package/src/lib/stress-tester.js +65 -0
- package/src/lib/tech-learning-engine.js +75 -0
- package/src/lib/telemetry/span-recorder.js +237 -0
- package/src/lib/tenant-saas.js +107 -0
- package/src/lib/terraform-manager.js +67 -0
- package/src/lib/token-incentive.js +180 -29
- package/src/lib/wallet-manager.js +81 -35
- package/src/lib/zkp-engine.js +87 -0
- package/src/repl/agent-repl.js +417 -6
- package/src/runtime/__tests__/auto-pin.test.js +104 -0
- package/src/runtime/agent-core.js +771 -104
- package/src/runtime/auto-pin.js +117 -0
- package/src/runtime/coding-agent-contract-shared.cjs +82 -8
- package/src/runtime/coding-agent-policy.cjs +32 -7
- package/src/runtime/fallback-model.js +134 -7
- package/src/runtime/headless-runner.js +388 -108
- package/src/runtime/mcp-config.js +46 -0
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DCNfbdlx.js +0 -1
- package/src/assets/web-panel/assets/Terminal-BvAK_Zel.js +0 -3
- package/src/assets/web-panel/assets/devWarning-C2Z5LRgq.js +0 -1
- package/src/assets/web-panel/assets/index-BGp6Yr-Y.js +0 -1
- package/src/assets/web-panel/assets/index-BXiw9dQf.js +0 -1
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox egress-filtering proxy (Phase 1 enforcement).
|
|
3
|
+
*
|
|
4
|
+
* The domain policy in `sandbox-network-policy.js` DECIDES whether a host is
|
|
5
|
+
* reachable; this turns that decision into ENFORCEMENT against real
|
|
6
|
+
* subprocesses. A local forward proxy consults `evaluateNetworkAccess` for
|
|
7
|
+
* every request (plain HTTP) and CONNECT tunnel (HTTPS) and refuses the blocked
|
|
8
|
+
* ones. Point a sandboxed process's `HTTP_PROXY`/`HTTPS_PROXY` at it and its
|
|
9
|
+
* `curl`/`npm`/`pip`/`git`/`wget`/`fetch` traffic is filtered.
|
|
10
|
+
*
|
|
11
|
+
* This is a proxy-level control, not a kernel one: a tool that deliberately
|
|
12
|
+
* ignores proxy env can still open a raw socket, so a hard guarantee also needs
|
|
13
|
+
* OS network isolation (Docker `--network`, bwrap netns). But it is fully
|
|
14
|
+
* cross-platform (works on Windows, no root) and closes the common egress path
|
|
15
|
+
* that the domain allow/deny list is actually about.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import http from "node:http";
|
|
19
|
+
import net from "node:net";
|
|
20
|
+
import dns from "node:dns";
|
|
21
|
+
import {
|
|
22
|
+
evaluateNetworkAccess,
|
|
23
|
+
isPrivateHost,
|
|
24
|
+
} from "./sandbox-network-policy.js";
|
|
25
|
+
|
|
26
|
+
/** Promisified `dns.lookup` returning ALL resolved addresses. */
|
|
27
|
+
function defaultLookup(host) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
dns.lookup(host, { all: true }, (err, addrs) =>
|
|
30
|
+
err ? reject(err) : resolve(addrs),
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* DNS-rebinding / SSRF-by-resolution guard. The domain policy is decided by
|
|
37
|
+
* NAME, but a public-looking name can RESOLVE to a private / metadata IP
|
|
38
|
+
* (`rebind.evil.com → 127.0.0.1` / `169.254.169.254`) and the name check would
|
|
39
|
+
* never see it — a classic rebinding bypass (Phase 1 acceptance "DNS 重绑定有
|
|
40
|
+
* 安全测试"). After a wildcard/permissive allow, resolve the host and reject if
|
|
41
|
+
* ANY address is private (unless `allowPrivate`). Fail CLOSED on an unresolvable
|
|
42
|
+
* host. On success, pin the connection to the exact validated address so a
|
|
43
|
+
* second resolution at connect time can't rebind to a different (private) IP.
|
|
44
|
+
* A `specific` allow (exact/subdomain name the user vetted) is exempt — the
|
|
45
|
+
* caller skips this — so intentional internal-name allowlisting still works.
|
|
46
|
+
* @returns {Promise<{ok:boolean, ip?:string, reason?:string}>}
|
|
47
|
+
*/
|
|
48
|
+
async function guardResolvedTarget(host, policy, lookup) {
|
|
49
|
+
let addrs;
|
|
50
|
+
try {
|
|
51
|
+
addrs = await lookup(host);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
reason: `unresolvable host (fail-closed): ${e.code || e.message}`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const list = Array.isArray(addrs) ? addrs : addrs ? [addrs] : [];
|
|
59
|
+
if (list.length === 0) {
|
|
60
|
+
return { ok: false, reason: "host resolved to no addresses (fail-closed)" };
|
|
61
|
+
}
|
|
62
|
+
for (const a of list) {
|
|
63
|
+
const ip = typeof a === "string" ? a : a && a.address;
|
|
64
|
+
if (ip && isPrivateHost(ip) && !policy.allowPrivate) {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
reason: `dns-rebinding: ${host} resolves to private ${ip}`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const first = list[0];
|
|
72
|
+
return { ok: true, ip: typeof first === "string" ? first : first.address };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build the proxy env vars a child process needs to route through the proxy.
|
|
77
|
+
* `NO_PROXY` is intentionally empty so nothing bypasses the filter.
|
|
78
|
+
* @param {number} port
|
|
79
|
+
* @param {string} host address the CHILD uses to reach the proxy (for a Docker
|
|
80
|
+
* container this is `host.docker.internal`, not localhost)
|
|
81
|
+
*/
|
|
82
|
+
export function proxyEnv(port, host = "127.0.0.1") {
|
|
83
|
+
const url = `http://${host}:${port}`;
|
|
84
|
+
return {
|
|
85
|
+
HTTP_PROXY: url,
|
|
86
|
+
HTTPS_PROXY: url,
|
|
87
|
+
ALL_PROXY: url,
|
|
88
|
+
http_proxy: url,
|
|
89
|
+
https_proxy: url,
|
|
90
|
+
all_proxy: url,
|
|
91
|
+
NO_PROXY: "",
|
|
92
|
+
no_proxy: "",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Create (but do not yet listen on) an egress-filtering proxy for a policy.
|
|
98
|
+
* @param {object} policy { allowedDomains?, deniedDomains?, allowPrivate? }
|
|
99
|
+
* @param {object} [opts] { onDecision?(info), bindHost? }
|
|
100
|
+
* @returns {{ listen():Promise<{port,env,server}>, close():Promise<void>,
|
|
101
|
+
* server:import('http').Server, blocked:number, allowed:number }}
|
|
102
|
+
*/
|
|
103
|
+
export function createEgressProxy(policy = {}, opts = {}) {
|
|
104
|
+
const bindHost = opts.bindHost || "127.0.0.1";
|
|
105
|
+
const state = { blocked: 0, allowed: 0 };
|
|
106
|
+
const decide = (target, kind) => {
|
|
107
|
+
const verdict = evaluateNetworkAccess(target, policy);
|
|
108
|
+
if (verdict.allowed) state.allowed += 1;
|
|
109
|
+
else state.blocked += 1;
|
|
110
|
+
if (typeof opts.onDecision === "function") {
|
|
111
|
+
try {
|
|
112
|
+
opts.onDecision({ kind, target, ...verdict });
|
|
113
|
+
} catch {
|
|
114
|
+
/* observation is best-effort */
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return verdict;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const lookup = opts.lookup || defaultLookup;
|
|
121
|
+
const rebindGuard = opts.checkDnsRebinding !== false; // on by default
|
|
122
|
+
|
|
123
|
+
// Second gate: after a NAME-based allow, re-check the RESOLVED address for
|
|
124
|
+
// DNS rebinding (unless the allow was `specific` — a name the user vetted —
|
|
125
|
+
// or the guard is disabled). Returns the verdict to ACT on plus `ip`, the
|
|
126
|
+
// exact address to pin the connection to (null → connect by the original
|
|
127
|
+
// name). When the resolved IP is private the name-based allow is overturned:
|
|
128
|
+
// fix the counters and re-notify observers with the block reason.
|
|
129
|
+
const guard = async (verdict, kind) => {
|
|
130
|
+
if (!verdict.allowed || verdict.specific || !rebindGuard) {
|
|
131
|
+
return { verdict, ip: null };
|
|
132
|
+
}
|
|
133
|
+
const res = await guardResolvedTarget(verdict.host, policy, lookup);
|
|
134
|
+
if (res.ok) return { verdict, ip: res.ip };
|
|
135
|
+
state.allowed -= 1;
|
|
136
|
+
state.blocked += 1;
|
|
137
|
+
const blocked = { allowed: false, host: verdict.host, reason: res.reason };
|
|
138
|
+
if (typeof opts.onDecision === "function") {
|
|
139
|
+
try {
|
|
140
|
+
opts.onDecision({ kind, target: verdict.host, ...blocked });
|
|
141
|
+
} catch {
|
|
142
|
+
/* observation is best-effort */
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { verdict: blocked, ip: null };
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const server = http.createServer(async (req, res) => {
|
|
149
|
+
try {
|
|
150
|
+
// A forward-proxy request carries the absolute URL in the request line.
|
|
151
|
+
const { verdict, ip } = await guard(decide(req.url, "http"), "http");
|
|
152
|
+
if (!verdict.allowed) {
|
|
153
|
+
res.writeHead(403, { "content-type": "text/plain" });
|
|
154
|
+
res.end(`egress blocked: ${verdict.host} — ${verdict.reason}\n`);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
let u;
|
|
158
|
+
try {
|
|
159
|
+
u = new URL(req.url);
|
|
160
|
+
} catch {
|
|
161
|
+
res.writeHead(400, { "content-type": "text/plain" });
|
|
162
|
+
res.end("bad request target\n");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const upstream = http.request(
|
|
166
|
+
{
|
|
167
|
+
hostname: ip || u.hostname, // pin to the validated IP when re-checked
|
|
168
|
+
port: u.port || 80,
|
|
169
|
+
path: `${u.pathname}${u.search}`,
|
|
170
|
+
method: req.method,
|
|
171
|
+
headers: req.headers, // preserves the original Host header
|
|
172
|
+
},
|
|
173
|
+
(upRes) => {
|
|
174
|
+
res.writeHead(upRes.statusCode || 502, upRes.headers);
|
|
175
|
+
upRes.pipe(res);
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
upstream.on("error", () => {
|
|
179
|
+
if (!res.headersSent)
|
|
180
|
+
res.writeHead(502, { "content-type": "text/plain" });
|
|
181
|
+
res.end("upstream error\n");
|
|
182
|
+
});
|
|
183
|
+
req.pipe(upstream);
|
|
184
|
+
} catch {
|
|
185
|
+
if (!res.headersSent)
|
|
186
|
+
res.writeHead(502, { "content-type": "text/plain" });
|
|
187
|
+
res.end("proxy error\n");
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// HTTPS (and any TLS) goes through CONNECT — tunnel only allowed hosts.
|
|
192
|
+
server.on("connect", async (req, clientSocket, head) => {
|
|
193
|
+
try {
|
|
194
|
+
const { verdict, ip } = await guard(
|
|
195
|
+
decide(req.url, "connect"),
|
|
196
|
+
"connect",
|
|
197
|
+
); // req.url = "host:port"
|
|
198
|
+
if (!verdict.allowed) {
|
|
199
|
+
clientSocket.write(
|
|
200
|
+
"HTTP/1.1 403 Forbidden\r\n" +
|
|
201
|
+
"Content-Type: text/plain\r\n\r\n" +
|
|
202
|
+
`egress blocked: ${verdict.host} — ${verdict.reason}\n`,
|
|
203
|
+
);
|
|
204
|
+
clientSocket.end();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const [host, portStr] = req.url.split(":");
|
|
208
|
+
const port = parseInt(portStr, 10) || 443;
|
|
209
|
+
// Connect to the pinned validated IP so a rebind at connect time can't
|
|
210
|
+
// swap in a private address between our check and the socket.
|
|
211
|
+
const upstream = net.connect(port, ip || host, () => {
|
|
212
|
+
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
|
213
|
+
if (head && head.length) upstream.write(head);
|
|
214
|
+
upstream.pipe(clientSocket);
|
|
215
|
+
clientSocket.pipe(upstream);
|
|
216
|
+
});
|
|
217
|
+
upstream.on("error", () => {
|
|
218
|
+
try {
|
|
219
|
+
clientSocket.end();
|
|
220
|
+
} catch {
|
|
221
|
+
/* already closed */
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
clientSocket.on("error", () => {
|
|
225
|
+
try {
|
|
226
|
+
upstream.destroy();
|
|
227
|
+
} catch {
|
|
228
|
+
/* already gone */
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
} catch {
|
|
232
|
+
try {
|
|
233
|
+
clientSocket.end();
|
|
234
|
+
} catch {
|
|
235
|
+
/* already closed */
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
server,
|
|
242
|
+
get blocked() {
|
|
243
|
+
return state.blocked;
|
|
244
|
+
},
|
|
245
|
+
get allowed() {
|
|
246
|
+
return state.allowed;
|
|
247
|
+
},
|
|
248
|
+
listen() {
|
|
249
|
+
return new Promise((resolve, reject) => {
|
|
250
|
+
server.once("error", reject);
|
|
251
|
+
server.listen(0, bindHost, () => {
|
|
252
|
+
server.removeListener("error", reject);
|
|
253
|
+
const port = server.address().port;
|
|
254
|
+
resolve({ port, env: proxyEnv(port), server });
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
close() {
|
|
259
|
+
return new Promise((resolve) => server.close(() => resolve()));
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox filesystem path policy (Phase 1) — decide whether a Shell subprocess
|
|
3
|
+
* (or the agent's own file tools) may read/write a given path under
|
|
4
|
+
* `sandbox.filesystem.{allowRead,denyRead,allowWrite,denyWrite}` (here the
|
|
5
|
+
* legacy `{read, write, denied}` shape). OS-level ENFORCEMENT is platform
|
|
6
|
+
* specific (bubblewrap `--bind`/`--ro-bind`/`--tmpfs`, seatbelt), but the POLICY
|
|
7
|
+
* DECISION is pure, shared, and must get the tricky cases right — the twin of
|
|
8
|
+
* `sandbox-network-policy.js`. The prior check used raw `filePath.startsWith()`,
|
|
9
|
+
* which let three attacks through:
|
|
10
|
+
*
|
|
11
|
+
* - PATH TRAVERSAL: `/tmp/../etc/passwd` starts with an allowed `/tmp` but
|
|
12
|
+
* resolves OUTSIDE it. We `path.resolve` first so `..` is collapsed.
|
|
13
|
+
* - BOUNDARY CONFUSION: `/tmpfoo` starts with `/tmp` yet is a different tree.
|
|
14
|
+
* We compare with `path.relative` (a real path boundary), not a substring.
|
|
15
|
+
* - SYMLINK ESCAPE: an allowed dir containing a symlink to `/etc` would read
|
|
16
|
+
* `/etc`. We resolve symlinks (of the longest existing ancestor) before the
|
|
17
|
+
* boundary check, and resolve the policy roots the same way so they stay
|
|
18
|
+
* comparable.
|
|
19
|
+
*
|
|
20
|
+
* Default-DENY: a path must match an allow root for the requested mode and must
|
|
21
|
+
* not fall under any deny root. This mirrors the previous behaviour (an empty
|
|
22
|
+
* allow list denied everything) while fixing the bypasses.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import fs from "node:fs";
|
|
26
|
+
import path from "node:path";
|
|
27
|
+
|
|
28
|
+
/** Real path of `abs`, resolving symlinks of the LONGEST EXISTING ancestor and
|
|
29
|
+
* re-appending the non-existent tail — so a symlinked existing parent is caught
|
|
30
|
+
* even when the leaf (e.g. a not-yet-created write target) does not exist. */
|
|
31
|
+
function resolveReal(abs, realpath) {
|
|
32
|
+
let cur = abs;
|
|
33
|
+
const tail = [];
|
|
34
|
+
// Bounded by the path depth; each iteration strips one segment.
|
|
35
|
+
for (;;) {
|
|
36
|
+
try {
|
|
37
|
+
const real = realpath(cur);
|
|
38
|
+
return tail.length ? path.join(real, ...tail.reverse()) : real;
|
|
39
|
+
} catch {
|
|
40
|
+
const parent = path.dirname(cur);
|
|
41
|
+
if (parent === cur) return abs; // hit the root, nothing on this path exists
|
|
42
|
+
tail.push(path.basename(cur));
|
|
43
|
+
cur = parent;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Is `target` the same as, or nested inside, `root`? Uses a real path boundary
|
|
49
|
+
* (via `path.relative`) so `/tmpfoo` is NOT inside `/tmp` and `..` can't sneak
|
|
50
|
+
* out. Cross-platform (handles Windows drive letters + separators). */
|
|
51
|
+
export function isWithinRoot(root, target) {
|
|
52
|
+
if (!root) return false;
|
|
53
|
+
const rel = path.relative(root, target);
|
|
54
|
+
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeRoot(root, cwd, realpath) {
|
|
58
|
+
return resolveReal(path.resolve(cwd, String(root)), realpath);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Evaluate filesystem access for a target path under a policy.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} target a path (relative resolves against `cwd`)
|
|
65
|
+
* @param {object} opts
|
|
66
|
+
* @param {"read"|"write"} [opts.mode="read"]
|
|
67
|
+
* @param {{read?:string[], write?:string[], denied?:string[]}} [opts.policy]
|
|
68
|
+
* @param {string} [opts.cwd] base for relative targets/roots
|
|
69
|
+
* @param {(p:string)=>string} [opts.realpath] injectable for tests
|
|
70
|
+
* @returns {{allowed:boolean, path:string|null, reason:string}}
|
|
71
|
+
*/
|
|
72
|
+
export function evaluatePathAccess(target, opts = {}) {
|
|
73
|
+
const {
|
|
74
|
+
mode = "read",
|
|
75
|
+
policy = {},
|
|
76
|
+
cwd = process.cwd(),
|
|
77
|
+
realpath = fs.realpathSync,
|
|
78
|
+
} = opts;
|
|
79
|
+
|
|
80
|
+
if (target == null || String(target).trim() === "") {
|
|
81
|
+
return { allowed: false, path: null, reason: "empty path" };
|
|
82
|
+
}
|
|
83
|
+
if (mode !== "read" && mode !== "write") {
|
|
84
|
+
return { allowed: false, path: null, reason: `unknown mode "${mode}"` };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const abs = path.resolve(cwd, String(target)); // collapses `..`
|
|
88
|
+
const real = resolveReal(abs, realpath); // resolves symlink escapes
|
|
89
|
+
|
|
90
|
+
const denied = policy.denied || [];
|
|
91
|
+
for (const d of denied) {
|
|
92
|
+
if (isWithinRoot(normalizeRoot(d, cwd, realpath), real)) {
|
|
93
|
+
return { allowed: false, path: real, reason: `denied by ${d}` };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const allowList = (mode === "read" ? policy.read : policy.write) || [];
|
|
98
|
+
for (const a of allowList) {
|
|
99
|
+
if (isWithinRoot(normalizeRoot(a, cwd, realpath), real)) {
|
|
100
|
+
return { allowed: true, path: real, reason: `within ${a}` };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Default-deny: not under any allowed root for this mode.
|
|
105
|
+
return {
|
|
106
|
+
allowed: false,
|
|
107
|
+
path: real,
|
|
108
|
+
reason: allowList.length
|
|
109
|
+
? `outside all allowed ${mode} roots`
|
|
110
|
+
: `no allowed ${mode} roots`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox network domain policy (Phase 1) — decide whether a Shell subprocess is
|
|
3
|
+
* allowed to reach a given URL/host under `sandbox.network.allowedDomains` /
|
|
4
|
+
* `deniedDomains`. OS-level ENFORCEMENT is platform-specific (a proxy / seatbelt
|
|
5
|
+
* / bwrap egress filter), but the POLICY decision is pure, shared, and must get
|
|
6
|
+
* the tricky cases right — so it lives here, fully unit-tested:
|
|
7
|
+
*
|
|
8
|
+
* - wildcard subdomains (`*.example.com` matches `api.example.com` AND the
|
|
9
|
+
* apex `example.com`), exact hosts, and `*` (all);
|
|
10
|
+
* - deny takes precedence over allow;
|
|
11
|
+
* - default-DENY when an allowlist is set and the host isn't on it
|
|
12
|
+
* (Phase 1 acceptance "未授权域名无法通过 curl/npm/pip 访问");
|
|
13
|
+
* - private / loopback / link-local / metadata targets are blocked unless
|
|
14
|
+
* explicitly allowed — an SSRF / DNS-rebinding guard, and it fixes the
|
|
15
|
+
* `new URL()` IPv6-bracket gotcha where `[::1]` leaks brackets into the
|
|
16
|
+
* hostname and defeats a naive string check.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Extract a normalized lowercase host from a URL or bare host:port. */
|
|
20
|
+
export function extractHost(target) {
|
|
21
|
+
if (target == null) return null;
|
|
22
|
+
let s = String(target).trim();
|
|
23
|
+
if (!s) return null;
|
|
24
|
+
// Try URL parse first (covers scheme://host:port/path, userinfo, etc.).
|
|
25
|
+
try {
|
|
26
|
+
// Add a scheme if it looks scheme-less so URL() can parse host:port.
|
|
27
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(s) ? s : `http://${s}`;
|
|
28
|
+
const u = new URL(withScheme);
|
|
29
|
+
s = u.hostname; // may be "[::1]" for IPv6 — normalize below
|
|
30
|
+
} catch {
|
|
31
|
+
// Not a URL — treat as a bare host, strip any :port.
|
|
32
|
+
s = s.replace(/:\d+$/, "");
|
|
33
|
+
}
|
|
34
|
+
// Strip IPv6 brackets ("[::1]" → "::1") — the documented SSRF gotcha.
|
|
35
|
+
if (s.startsWith("[") && s.endsWith("]")) s = s.slice(1, -1);
|
|
36
|
+
// Strip the trailing DNS root dot. "localhost." / "example.com." resolve to
|
|
37
|
+
// the exact same host as the dot-less form, but the extra dot defeats BOTH
|
|
38
|
+
// the deny/allow matcher (host !== "example.com") AND the loopback/private
|
|
39
|
+
// guard ("localhost." !== "localhost") — a one-character SSRF / deny-list
|
|
40
|
+
// bypass. (Node's URL already normalizes a trailing dot away for IPv4 literals
|
|
41
|
+
// but NOT for named hosts, so we must do it here.)
|
|
42
|
+
s = s.replace(/\.+$/, "");
|
|
43
|
+
return s.toLowerCase() || null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Does `host` match a domain `pattern` (`*`, `*.example.com`, or exact)? */
|
|
47
|
+
export function matchesDomain(host, pattern) {
|
|
48
|
+
if (!host || !pattern) return false;
|
|
49
|
+
const p = String(pattern).trim().toLowerCase();
|
|
50
|
+
if (p === "*") return true;
|
|
51
|
+
if (p.startsWith("*.")) {
|
|
52
|
+
const base = p.slice(2);
|
|
53
|
+
// `*.example.com` matches subdomains AND the apex `example.com`.
|
|
54
|
+
return host === base || host.endsWith(`.${base}`);
|
|
55
|
+
}
|
|
56
|
+
return host === p;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function anyMatch(host, patterns) {
|
|
60
|
+
return (patterns || []).some((p) => matchesDomain(host, p));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Private / loopback / link-local / cloud-metadata target? (SSRF guard) */
|
|
64
|
+
export function isPrivateHost(host) {
|
|
65
|
+
if (!host) return false;
|
|
66
|
+
const h = host.toLowerCase();
|
|
67
|
+
if (h === "localhost" || h.endsWith(".localhost")) return true;
|
|
68
|
+
// Cloud metadata endpoint.
|
|
69
|
+
if (h === "169.254.169.254" || h === "metadata.google.internal") return true;
|
|
70
|
+
// IPv6 loopback / unique-local / link-local.
|
|
71
|
+
if (h === "::1" || h === "::") return true;
|
|
72
|
+
if (/^f[cd][0-9a-f]{2}:/i.test(h)) return true; // fc00::/7 unique-local
|
|
73
|
+
if (/^fe80:/i.test(h)) return true; // link-local
|
|
74
|
+
// IPv4-mapped IPv6 (::ffff:127.0.0.1) — check the tail.
|
|
75
|
+
const mapped = h.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);
|
|
76
|
+
const ipv4 = mapped ? mapped[1] : h;
|
|
77
|
+
const m = ipv4.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
78
|
+
if (m) {
|
|
79
|
+
const [a, b] = [Number(m[1]), Number(m[2])];
|
|
80
|
+
if (a === 127) return true; // 127.0.0.0/8 loopback
|
|
81
|
+
if (a === 10) return true; // 10.0.0.0/8
|
|
82
|
+
if (a === 192 && b === 168) return true; // 192.168.0.0/16
|
|
83
|
+
if (a === 172 && b >= 16 && b <= 31) return true; // 172.16.0.0/12
|
|
84
|
+
if (a === 169 && b === 254) return true; // 169.254.0.0/16 link-local
|
|
85
|
+
if (a === 0) return true; // 0.0.0.0/8
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Evaluate network access for a target under a policy.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} target URL or host (optionally with :port)
|
|
94
|
+
* @param {object} policy { allowedDomains?:string[], deniedDomains?:string[],
|
|
95
|
+
* allowPrivate?:boolean }
|
|
96
|
+
* @returns {{ allowed:boolean, host:string|null, reason:string }}
|
|
97
|
+
*/
|
|
98
|
+
export function evaluateNetworkAccess(target, policy = {}) {
|
|
99
|
+
const allowed = policy.allowedDomains || [];
|
|
100
|
+
const denied = policy.deniedDomains || [];
|
|
101
|
+
const host = extractHost(target);
|
|
102
|
+
if (!host) {
|
|
103
|
+
return { allowed: false, host: null, reason: "unparseable target" };
|
|
104
|
+
}
|
|
105
|
+
// 1) Deny wins over everything.
|
|
106
|
+
if (anyMatch(host, denied)) {
|
|
107
|
+
return { allowed: false, host, reason: "denied by deniedDomains" };
|
|
108
|
+
}
|
|
109
|
+
// 2) A SPECIFIC (non-`*`) allow-list match overrides the private-host guard,
|
|
110
|
+
// so an intentionally-allowed localhost/dev host still works. A bare `*`
|
|
111
|
+
// does NOT — it means "all public domains", never internal/metadata.
|
|
112
|
+
const explicitNonStar = anyMatch(
|
|
113
|
+
host,
|
|
114
|
+
allowed.filter((p) => String(p).trim() !== "*"),
|
|
115
|
+
);
|
|
116
|
+
if (explicitNonStar) {
|
|
117
|
+
// `specific: true` marks a host the user vetted by exact/subdomain name (not
|
|
118
|
+
// a bare `*`). The egress proxy trusts these even when they resolve to a
|
|
119
|
+
// private IP (intentional internal/dev allowlisting) and so SKIPS the
|
|
120
|
+
// DNS-rebinding re-check for them — whereas `*`/permissive allows still get
|
|
121
|
+
// their resolved IP checked.
|
|
122
|
+
return {
|
|
123
|
+
allowed: true,
|
|
124
|
+
host,
|
|
125
|
+
reason: "matched allowedDomains",
|
|
126
|
+
specific: true,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// 3) Private / loopback / metadata targets are blocked unless allowed above.
|
|
130
|
+
if (isPrivateHost(host) && !policy.allowPrivate) {
|
|
131
|
+
return {
|
|
132
|
+
allowed: false,
|
|
133
|
+
host,
|
|
134
|
+
reason: "private/loopback target blocked (SSRF guard)",
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
// 4) A `*` (or any remaining) allow-list match → allow.
|
|
138
|
+
if (anyMatch(host, allowed)) {
|
|
139
|
+
return { allowed: true, host, reason: "matched allowedDomains" };
|
|
140
|
+
}
|
|
141
|
+
// 5) With an allow-list present, anything not on it is denied (default-deny).
|
|
142
|
+
if (allowed.length > 0) {
|
|
143
|
+
return {
|
|
144
|
+
allowed: false,
|
|
145
|
+
host,
|
|
146
|
+
reason: "not in allowedDomains (default-deny)",
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// 6) No allow-list → permissive (deny-list + private guard still applied).
|
|
150
|
+
return { allowed: true, host, reason: "no allowlist (permissive)" };
|
|
151
|
+
}
|
package/src/lib/sandbox-v2.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import crypto from "crypto";
|
|
7
7
|
import { safeJsonParse } from "./safe-json.js";
|
|
8
|
+
import { evaluateNetworkAccess } from "./sandbox-network-policy.js";
|
|
9
|
+
import { evaluatePathAccess } from "./sandbox-fs-policy.js";
|
|
8
10
|
import { createRequire } from "module";
|
|
9
11
|
|
|
10
12
|
const _require = createRequire(import.meta.url);
|
|
@@ -141,28 +143,32 @@ export function ensureSandboxTables(db) {
|
|
|
141
143
|
// ─── Permission checking ──────────────────────────────────────
|
|
142
144
|
|
|
143
145
|
function checkFilePermission(permissions, filePath, mode) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
return false;
|
|
146
|
+
// Delegate to the shared path policy: `path.resolve` collapses `..` (traversal),
|
|
147
|
+
// a `path.relative` boundary rejects `/tmpfoo` matching `/tmp`, and symlinks are
|
|
148
|
+
// resolved so a link inside an allowed dir can't escape. The old check was
|
|
149
|
+
// `filePath.startsWith(root)` and let all three through.
|
|
150
|
+
const fsPol = permissions.fileSystem || {};
|
|
151
|
+
return evaluatePathAccess(filePath, {
|
|
152
|
+
mode,
|
|
153
|
+
policy: {
|
|
154
|
+
read: fsPol.read || [],
|
|
155
|
+
write: fsPol.write || [],
|
|
156
|
+
denied: fsPol.denied || [],
|
|
157
|
+
},
|
|
158
|
+
}).allowed;
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
function checkNetworkPermission(permissions, host) {
|
|
162
|
+
// Delegate to the shared domain policy: wildcard subdomains, deny-precedence,
|
|
163
|
+
// default-deny under an allowlist, URL/host normalization, and the SSRF /
|
|
164
|
+
// loopback / metadata guard (incl. the IPv6-bracket gotcha). The old check was
|
|
165
|
+
// exact-string only and let e.g. a URL or `[::1]` slip past.
|
|
161
166
|
const net = permissions.network || {};
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
return evaluateNetworkAccess(host, {
|
|
168
|
+
allowedDomains: net.allowed || [],
|
|
169
|
+
deniedDomains: net.denied || [],
|
|
170
|
+
allowPrivate: net.allowPrivate === true,
|
|
171
|
+
}).allowed;
|
|
166
172
|
}
|
|
167
173
|
|
|
168
174
|
function checkSystemCallPermission(permissions, call) {
|
package/src/lib/scim-manager.js
CHANGED
|
@@ -211,6 +211,42 @@ export function _resetState() {
|
|
|
211
211
|
_syncLog.length = 0;
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Rehydrate the in-memory _users Map from the persisted scim_resources table.
|
|
216
|
+
* The CLI runs each `cc scim` subcommand in a fresh node process, so _users
|
|
217
|
+
* starts empty every invocation. createUser dual-writes (Map + INSERT INTO
|
|
218
|
+
* scim_resources) but listUsers/getUser/deleteUser/getStatus are Map-only and
|
|
219
|
+
* nothing SELECTed the table back — so a provisioned user was invisible next
|
|
220
|
+
* invocation (`No SCIM users.` / `User not found`), and deleteUser threw before
|
|
221
|
+
* its DELETE ran, orphaning rows with no CLI path to remove them. The duplicate
|
|
222
|
+
* check in createUser was also blind, allowing duplicate userNames on disk.
|
|
223
|
+
* Call after ensureSCIMTables(db).
|
|
224
|
+
*
|
|
225
|
+
* Mirrors the write-side shape exactly (active INTEGER→boolean).
|
|
226
|
+
*/
|
|
227
|
+
export function loadFromDb(db) {
|
|
228
|
+
if (!db) return 0;
|
|
229
|
+
ensureSCIMTables(db);
|
|
230
|
+
_users.clear();
|
|
231
|
+
for (const r of db
|
|
232
|
+
.prepare(
|
|
233
|
+
`SELECT id, display_name, user_name, email, active, created_at, updated_at
|
|
234
|
+
FROM scim_resources WHERE resource_type = 'User' ORDER BY created_at ASC`,
|
|
235
|
+
)
|
|
236
|
+
.all()) {
|
|
237
|
+
_users.set(r.id, {
|
|
238
|
+
id: r.id,
|
|
239
|
+
userName: r.user_name,
|
|
240
|
+
displayName: r.display_name || r.user_name,
|
|
241
|
+
email: r.email || null,
|
|
242
|
+
active: !!r.active,
|
|
243
|
+
createdAt: r.created_at,
|
|
244
|
+
updatedAt: r.updated_at,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return _users.size;
|
|
248
|
+
}
|
|
249
|
+
|
|
214
250
|
/* ═══════════════════════════════════════════════════════════════
|
|
215
251
|
* V2 Surface — SCIM provisioning lifecycle layer.
|
|
216
252
|
* Tracks identities and sync-job lifecycle independent of legacy
|
|
@@ -129,10 +129,13 @@ export function getSession(db, sessionId) {
|
|
|
129
129
|
|
|
130
130
|
return {
|
|
131
131
|
...session,
|
|
132
|
-
|
|
132
|
+
// A single corrupt (non-JSON, non-empty) column must not make the whole
|
|
133
|
+
// session unloadable — that would block resume of the user's own data.
|
|
134
|
+
// Matches the safeJsonParse fallback listSessions already uses.
|
|
135
|
+
messages: safeJsonParse(session.messages, []),
|
|
133
136
|
metadata:
|
|
134
137
|
typeof session.metadata === "string"
|
|
135
|
-
?
|
|
138
|
+
? safeJsonParse(session.metadata, {})
|
|
136
139
|
: session.metadata || {},
|
|
137
140
|
};
|
|
138
141
|
}
|