chainlesschain 0.162.149 → 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.
Files changed (273) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-BJif14yR.js → AIOps-CBLvrjeG.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-CXek6gJY.js → ActionButton-DYv2GtE0.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-BgQhdAJi.js → Analytics-CL6PZ-Ww.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-DTYl5NtR.js → AppLayout-xuyHt4uA.js} +3 -3
  6. package/src/assets/web-panel/assets/{Audit-BzATQAeR.js → Audit-DOvSVycq.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-BzdPEQhL.js → Backup-DMhfTswr.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-BM0KVh8E.js → BaseInput-CQxDdm63.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-D7fHXHkz.js → Chat-BEeNhK2a.js} +5 -5
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-Wm34RR-b.js → ChatBubbleRenderer-8nul8eTq.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-CAiSQ9vO.js → Checkbox-CL04O-ER.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-Df40CrEe.js → Codegen-C50o-n7x.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-DIT2fNFU.js → Col-CH-bDkzs.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-CSTIbW2k.js → Community-DqAIMvSe.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-DtqXZZUi.js → Compact-C43fo_my.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-CUtzw6o7.js → Compliance-BERnMrdP.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-CezmlnmU.js → Cowork-Baw3w1gp.js} +4 -4
  18. package/src/assets/web-panel/assets/{Cron-P4BITarg.js → Cron-BEqAHF1-.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-BRRiYwvq.js → Crosschain-CvnmKwK4.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-L5ijB9i4.js → DID-iMPxgVdt.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-CFxro4ob.js → Dashboard--4SJX3YR.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-BmAFCQ71.js → Dropdown-BgLV6xgz.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-DJBCEuon.js → EmailListRenderer-Bvb8oUD8.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-z4oLq6eT.js → FamilyGuardDashboard-DpXPjJB5.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-DsVCpHMF.js → Federation-BbFfJ1Xi.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-DP3bP5th.js → FormItemContext-DeGq1B-q.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-BAXP2Gc6.js → GenericCardRenderer-CNW7s9zM.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-I3g_bAw9.js → Git-L1PjW-lT.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-jHS5-ioS.js → Governance-PK-X58to.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-CPL7sfx9.js → Inference-BePWEH-d.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-Jf236h4C.js → KnowledgeGraph-LF_sBvdL.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-Cm2tcSKg.js → Logs-BKkfk9hy.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-CZK82rhi.js → Marketplace-BNMqUDla.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-CZCeji7a.js → McpTools-w4WPiyz2.js} +5 -5
  35. package/src/assets/web-panel/assets/{Memory-3l2KVS9k.js → Memory-DUKMVGNi.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-VYdpoLh6.js → MobileBridge-BWhiEyTJ.js} +1 -1
  37. package/src/assets/web-panel/assets/{MobileProjects-CiB9cmm1.js → MobileProjects-Ocf5JEkX.js} +1 -1
  38. package/src/assets/web-panel/assets/{Mtc-wCFZbBuL.js → Mtc-D2B6MMhU.js} +4 -4
  39. package/src/assets/web-panel/assets/{MtcAudit-1a3THIyG.js → MtcAudit-DdYjCq1O.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-DMzLB2hG.js → Multisig-Cb1hfuUZ.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-D9wZbf6l.js → NLProgramming-CTeImGp7.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-hrFe2ZM-.js → Notes-BjYNc7eQ.js} +4 -4
  43. package/src/assets/web-panel/assets/{NotificationSettings-CHGX5Jwm.js → NotificationSettings-Dn_NtRXQ.js} +1 -1
  44. package/src/assets/web-panel/assets/OrderTableRenderer-DVYYIizh.js +1 -0
  45. package/src/assets/web-panel/assets/{Organization-By7FkhtY.js → Organization-Be2Kmr5j.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow--khGSzJn.js → Overflow-Cu_-qRlR.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-CAG9dH35.js → P2P-DD2HoGzE.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-B3SQZmK9.js → PdhVaultBrowser-BRI7DVth.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-D35ec6JA.js → Permissions-C9Srcs2M.js} +3 -3
  50. package/src/assets/web-panel/assets/{PersonalDataHub-CxBF7hW_.js → PersonalDataHub-CUqcMgo1.js} +3 -3
  51. package/src/assets/web-panel/assets/{Pipeline-s6EtOO1s.js → Pipeline-HBrQGLXt.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-BbJYmWmO.js → Privacy-D-B0vjwh.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-VGQq1jMT.js → ProjectInit-eTeLIu9e.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-B1ew3Teh.js → ProjectSettings-DrRqCsC6.js} +2 -2
  55. package/src/assets/web-panel/assets/Projects-1G9DNOhI.js +1 -0
  56. package/src/assets/web-panel/assets/{Providers-mrO47FIK.js → Providers-RkTZzpxP.js} +1 -1
  57. package/src/assets/web-panel/assets/{QrScannerModal-IJF2mHyw.js → QrScannerModal-BSCUgsgC.js} +1 -1
  58. package/src/assets/web-panel/assets/{QuickAsk-CU0wN_Z0.js → QuickAsk-CHgyM3Bz.js} +1 -1
  59. package/src/assets/web-panel/assets/{Recommend-DhN37R6G.js → Recommend-0lry4OZq.js} +1 -1
  60. package/src/assets/web-panel/assets/{RemoteSession-D_xOX_hu.js → RemoteSession-Cn45UroZ.js} +2 -2
  61. package/src/assets/web-panel/assets/{Reputation-D_Ssv7uH.js → Reputation-Q-Zjb707.js} +1 -1
  62. package/src/assets/web-panel/assets/{Row-Kx5dKxX7.js → Row-BdM70oda.js} +1 -1
  63. package/src/assets/web-panel/assets/{RssFeed-BlGwqZkY.js → RssFeed-DmOAKKap.js} +2 -2
  64. package/src/assets/web-panel/assets/{Search-hLFQzxpb.js → Search-Dwavbm07.js} +1 -1
  65. package/src/assets/web-panel/assets/{Security-DMlvsVt4.js → Security-DukXFCnk.js} +3 -3
  66. package/src/assets/web-panel/assets/{Services-Bd0Qsj2m.js → Services-BIlRnITu.js} +2 -2
  67. package/src/assets/web-panel/assets/{Skeleton-DFclq9X3.js → Skeleton-YYVQdhhQ.js} +1 -1
  68. package/src/assets/web-panel/assets/{Skills-BNtln2qY.js → Skills-B8_iYHz2.js} +1 -1
  69. package/src/assets/web-panel/assets/{Sla-Dch5H19G.js → Sla-BdVrhT31.js} +1 -1
  70. package/src/assets/web-panel/assets/{SpeechSettings-2UfQcU1g.js → SpeechSettings-CD3ggRx7.js} +1 -1
  71. package/src/assets/web-panel/assets/{SyncSettings-CD9YQr4i.js → SyncSettings-Bp02VZFH.js} +2 -2
  72. package/src/assets/web-panel/assets/{Tasks-BNu-7MzI.js → Tasks-DEVmCEsr.js} +1 -1
  73. package/src/assets/web-panel/assets/{Templates-D1n9CaIR.js → Templates-B6czWc4N.js} +1 -1
  74. package/src/assets/web-panel/assets/{Tenant-DenL3iBW.js → Tenant-BCJLv17r.js} +1 -1
  75. package/src/assets/web-panel/assets/{Terminal-ZzU0Io1Y.js → Terminal-CD132d2Y.js} +2 -2
  76. package/src/assets/web-panel/assets/{TimelineRenderer-D87IfukO.js → TimelineRenderer-ZB-CvkZl.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tokens-BkiZc8E3.js → Tokens-BnSY0S-s.js} +1 -1
  78. package/src/assets/web-panel/assets/{Trigger-BNRSytGN.js → Trigger-VZw9Oy2w.js} +1 -1
  79. package/src/assets/web-panel/assets/{Trust-Bwbd4X2m.js → Trust-pZq7Hjd2.js} +1 -1
  80. package/src/assets/web-panel/assets/{UkeySign-B8O2bs0o.js → UkeySign-DnQaqk4q.js} +1 -1
  81. package/src/assets/web-panel/assets/{VideoEditing-C2fL8zmH.js → VideoEditing-BEUfmvJV.js} +1 -1
  82. package/src/assets/web-panel/assets/{Wallet-BORYDdE1.js → Wallet-CoZKMXJ2.js} +4 -4
  83. package/src/assets/web-panel/assets/{WebAuthn-C0V5S2FM.js → WebAuthn-CBOGVx1I.js} +5 -5
  84. package/src/assets/web-panel/assets/{WorkflowEditor-2dR0fcHL.js → WorkflowEditor-huSKn7TT.js} +1 -1
  85. package/src/assets/web-panel/assets/{chat-BmuAFAn8.js → chat-A_3w6FBO.js} +1 -1
  86. package/src/assets/web-panel/assets/{colors-CN3smMcK.js → colors-D1Bem9Oy.js} +1 -1
  87. package/src/assets/web-panel/assets/{compact-item-DCnxb4kW.js → compact-item-CJYJj0oO.js} +1 -1
  88. package/src/assets/web-panel/assets/{createContext-vlR43pHn.js → createContext-BkbFiKT4.js} +1 -1
  89. package/src/assets/web-panel/assets/devWarning-WDr3_M_N.js +1 -0
  90. package/src/assets/web-panel/assets/{hasIn-D04tHYFd.js → hasIn-CKvUafVY.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-0DnaZGkC.js → index-AjrSPnvv.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-BudvKQfG.js → index-B8i0lViZ.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-BeGDEXFs.js → index-B9Svn0zc.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-D5LZVZ0L.js → index-BHdHSX06.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BTPEZTk1.js → index-BHdailyo.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DIpY0qM5.js → index-BJwlEnYo.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CsAsCPJ6.js → index-BOoAQjJz.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-ljuBiQW9.js → index-BSgWxAOG.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-hpvvtAZ3.js → index-BdutMsne.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-_FIkN3jd.js → index-BiDWxzbn.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-C-6NO5il.js → index-By5a0T_W.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-B78xL3s2.js → index-C-WIY-FQ.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-ClhAHDK3.js → index-C37f6iey.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-ToUh2b1-.js → index-C9bWmTcO.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-D28DeAAB.js → index-CBaosWRO.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-D8vwp4qp.js → index-CBlBVJ_m.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BqhASEL4.js → index-CD62EEGo.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DDwLgQY9.js → index-CUj-l7GM.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DbVsQBOH.js → index-CVS8Ymuq.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Cr-A0FYJ.js → index-ChkXUj3f.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-D65_XseV.js → index-Cj0OZ-37.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BXRg8GFQ.js → index-Co3OFL0t.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-430S68MN.js → index-Crp8CbRg.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-CngCC8hC.js → index-D22zNXiS.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-HhNjnCfe.js → index-DTs_D1OL.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CP9m9Ggr.js → index-DfgCdFrN.js} +3 -3
  117. package/src/assets/web-panel/assets/{index-jzR7Ujuw.js → index-DjYrecNb.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CTOCnIQ7.js → index-DpW9cf3e.js} +1 -1
  119. package/src/assets/web-panel/assets/index-Dwix3p4g.js +1 -0
  120. package/src/assets/web-panel/assets/{index-D_n8_vfI.js → index-F--HuPG1.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BasvsWDM.js → index-Fuk6t_3K.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CnXebZLL.js → index-JEAGMii9.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DYAtmqiv.js → index-PF-mpv8K.js} +1 -1
  124. package/src/assets/web-panel/assets/index-SHaoZ0CA.js +1 -0
  125. package/src/assets/web-panel/assets/{index-DTWg-18U.js → index-b-sbVdjQ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-C5Sxb1sE.js → index-pyvMVX8r.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-TslMTwNy.js → index-qNTtOVi_.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DTtRscUa.js → index-tgO8iza6.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DbADHwhr.js → index-ttDQVhZy.js} +1 -1
  130. package/src/assets/web-panel/assets/{initDefaultProps-C3wUb4je.js → initDefaultProps-DtrnfsZH.js} +1 -1
  131. package/src/assets/web-panel/assets/{motion-D-jYFUNA.js → motion-D4zqSaX3.js} +1 -1
  132. package/src/assets/web-panel/assets/{move-tqb8nwJ2.js → move-CGFGOUQj.js} +1 -1
  133. package/src/assets/web-panel/assets/{mtc-parser-DSOjMJMz.js → mtc-parser-DH2HvkJl.js} +1 -1
  134. package/src/assets/web-panel/assets/{omit-CJ7VimIW.js → omit-jJ2at5HX.js} +1 -1
  135. package/src/assets/web-panel/assets/{pickAttrs-qXiG1iT3.js → pickAttrs-CRyMT1Fv.js} +1 -1
  136. package/src/assets/web-panel/assets/{placementArrow-CkGBcy1Z.js → placementArrow-DZ_CX0Pt.js} +1 -1
  137. package/src/assets/web-panel/assets/{responsiveObserve-D3WvTlLO.js → responsiveObserve-wruHk-h5.js} +1 -1
  138. package/src/assets/web-panel/assets/{slide-BEE2ftge.js → slide-BGgx8q67.js} +1 -1
  139. package/src/assets/web-panel/assets/{statusUtils-CsfBpxiG.js → statusUtils-Do5M7gKm.js} +1 -1
  140. package/src/assets/web-panel/assets/{styleChecker-xPrIiJPI.js → styleChecker-CRa-GZpX.js} +1 -1
  141. package/src/assets/web-panel/assets/{useFlexGapSupport-C5Yd-SCR.js → useFlexGapSupport-BxPcq6pj.js} +1 -1
  142. package/src/assets/web-panel/assets/{useFs-D_MDS8HI.js → useFs-v5O2ZcJm.js} +1 -1
  143. package/src/assets/web-panel/assets/{usePersonalDataHub-BqJh-usA.js → usePersonalDataHub--z2FU0Px.js} +1 -1
  144. package/src/assets/web-panel/assets/{vnode-chm_p-gz.js → vnode-1emidVKd.js} +1 -1
  145. package/src/assets/web-panel/assets/{zoom-3-YDNz0Z.js → zoom-CcJaRyHs.js} +1 -1
  146. package/src/assets/web-panel/index.html +1 -1
  147. package/src/commands/a2a.js +19 -4
  148. package/src/commands/activitypub.js +20 -3
  149. package/src/commands/agent.js +27 -5
  150. package/src/commands/audit.js +40 -24
  151. package/src/commands/codeintel.js +68 -0
  152. package/src/commands/collab.js +15 -2
  153. package/src/commands/compliance.js +5 -0
  154. package/src/commands/config.js +4 -1
  155. package/src/commands/dao.js +86 -14
  156. package/src/commands/dev.js +2 -0
  157. package/src/commands/did.js +7 -1
  158. package/src/commands/dlp.js +23 -1
  159. package/src/commands/economy.js +29 -3
  160. package/src/commands/eval.js +7 -0
  161. package/src/commands/evomap.js +26 -0
  162. package/src/commands/governance.js +17 -3
  163. package/src/commands/hardening.js +18 -0
  164. package/src/commands/incentive.js +5 -0
  165. package/src/commands/init.js +46 -2
  166. package/src/commands/kg.js +4 -0
  167. package/src/commands/loop.js +6 -1
  168. package/src/commands/lowcode.js +15 -2
  169. package/src/commands/marketplace.js +40 -6
  170. package/src/commands/matrix.js +20 -1
  171. package/src/commands/memory.js +4 -1
  172. package/src/commands/mtc.js +7 -1
  173. package/src/commands/nostr.js +31 -4
  174. package/src/commands/note.js +7 -4
  175. package/src/commands/plugin.js +125 -5
  176. package/src/commands/pqc.js +5 -0
  177. package/src/commands/reputation.js +10 -1
  178. package/src/commands/scim.js +6 -0
  179. package/src/commands/session.js +32 -10
  180. package/src/commands/siem.js +11 -0
  181. package/src/commands/sla.js +18 -3
  182. package/src/commands/social.js +38 -5
  183. package/src/commands/sso.js +10 -2
  184. package/src/commands/stress.js +23 -4
  185. package/src/commands/team.js +9 -2
  186. package/src/commands/tech.js +2 -0
  187. package/src/commands/tenant.js +10 -1
  188. package/src/commands/terraform.js +6 -0
  189. package/src/commands/update.js +1 -1
  190. package/src/commands/zkp.js +20 -0
  191. package/src/gateways/ws/message-dispatcher.js +5 -2
  192. package/src/gateways/ws/remote-session-protocol.js +103 -42
  193. package/src/harness/jsonl-session-store.js +12 -5
  194. package/src/harness/plugin-manager.js +16 -4
  195. package/src/harness/remote-command-ledger.js +46 -15
  196. package/src/harness/worktree-isolator.js +16 -6
  197. package/src/lib/__tests__/logger.test.js +5 -2
  198. package/src/lib/a2a-protocol.js +25 -1
  199. package/src/lib/activitypub-bridge.js +61 -0
  200. package/src/lib/agent-core.js +2 -0
  201. package/src/lib/agent-economy.js +262 -29
  202. package/src/lib/agent-network.js +7 -1
  203. package/src/lib/agent-team/team-runner.js +25 -9
  204. package/src/lib/agent-team/team-worktree.js +36 -4
  205. package/src/lib/async-hook-supervisor.cjs +102 -16
  206. package/src/lib/audit-logger.js +7 -6
  207. package/src/lib/autonomous-developer.js +60 -0
  208. package/src/lib/chat-core.js +17 -4
  209. package/src/lib/collaboration-governance.js +56 -0
  210. package/src/lib/community-governance.js +68 -0
  211. package/src/lib/compliance-manager.js +63 -0
  212. package/src/lib/cross-chain.js +5 -0
  213. package/src/lib/dao-governance.js +151 -2
  214. package/src/lib/did-manager.js +23 -10
  215. package/src/lib/dlp-engine.js +70 -0
  216. package/src/lib/eval/runner.js +89 -1
  217. package/src/lib/eval/tasks.js +170 -0
  218. package/src/lib/eval/trend.js +16 -1
  219. package/src/lib/evolution-system.js +92 -0
  220. package/src/lib/evomap-federation.js +55 -0
  221. package/src/lib/evomap-governance.js +94 -0
  222. package/src/lib/fatal-handler.js +17 -1
  223. package/src/lib/hardening-manager.js +73 -0
  224. package/src/lib/hierarchical-memory.js +14 -8
  225. package/src/lib/knowledge-exporter.js +8 -2
  226. package/src/lib/knowledge-graph.js +79 -0
  227. package/src/lib/llm-providers.js +23 -14
  228. package/src/lib/logger.js +4 -1
  229. package/src/lib/lsp/__tests__/benchmark.test.js +88 -0
  230. package/src/lib/lsp/__tests__/lsp-client.test.js +11 -0
  231. package/src/lib/lsp/__tests__/lsp-manager.test.js +69 -0
  232. package/src/lib/lsp/benchmark.js +257 -0
  233. package/src/lib/lsp/lsp-client.js +30 -9
  234. package/src/lib/lsp/lsp-manager.js +54 -2
  235. package/src/lib/matrix-bridge.js +84 -0
  236. package/src/lib/memory-manager.js +5 -3
  237. package/src/lib/nostr-bridge.js +53 -0
  238. package/src/lib/permission-engine.js +22 -4
  239. package/src/lib/plugin-runtime/__tests__/remote-source.test.js +256 -0
  240. package/src/lib/plugin-runtime/install.js +9 -1
  241. package/src/lib/plugin-runtime/policy.js +9 -1
  242. package/src/lib/plugin-runtime/remote-source.js +240 -0
  243. package/src/lib/pqc-manager.js +64 -0
  244. package/src/lib/reputation-optimizer.js +101 -0
  245. package/src/lib/sandbox-egress-proxy.js +159 -59
  246. package/src/lib/sandbox-fs-policy.js +112 -0
  247. package/src/lib/sandbox-network-policy.js +18 -1
  248. package/src/lib/sandbox-v2.js +14 -14
  249. package/src/lib/scim-manager.js +36 -0
  250. package/src/lib/session-manager.js +5 -2
  251. package/src/lib/settings-hooks.cjs +1 -0
  252. package/src/lib/siem-exporter.js +45 -0
  253. package/src/lib/skill-loader.js +20 -2
  254. package/src/lib/skill-marketplace.js +83 -0
  255. package/src/lib/sla-manager.js +88 -0
  256. package/src/lib/social-manager.js +74 -0
  257. package/src/lib/stress-tester.js +65 -0
  258. package/src/lib/tech-learning-engine.js +75 -0
  259. package/src/lib/tenant-saas.js +107 -0
  260. package/src/lib/terraform-manager.js +67 -0
  261. package/src/lib/token-incentive.js +180 -29
  262. package/src/lib/wallet-manager.js +81 -35
  263. package/src/lib/zkp-engine.js +87 -0
  264. package/src/repl/agent-repl.js +17 -2
  265. package/src/runtime/__tests__/auto-pin.test.js +104 -0
  266. package/src/runtime/agent-core.js +241 -68
  267. package/src/runtime/auto-pin.js +117 -0
  268. package/src/runtime/headless-runner.js +35 -0
  269. package/src/assets/web-panel/assets/OrderTableRenderer-CRIFE2Tj.js +0 -1
  270. package/src/assets/web-panel/assets/Projects-CGcNIs_g.js +0 -1
  271. package/src/assets/web-panel/assets/devWarning-DxwmsL4j.js +0 -1
  272. package/src/assets/web-panel/assets/index-Cg2ldRfU.js +0 -1
  273. package/src/assets/web-panel/assets/index-Dwzo-dtJ.js +0 -1
@@ -178,6 +178,70 @@ export function ensurePQCTables(db) {
178
178
  `);
179
179
  }
180
180
 
181
+ /**
182
+ * Rehydrate _keys/_migrations from the persisted pqc_keys/pqc_migration_status
183
+ * tables. The `cc` CLI runs every command in a fresh process, so without this
184
+ * the Maps start empty and a key generated / migration run in a prior
185
+ * invocation is invisible (`cc pqc keys` reports "No PQC keys" and `cc pqc
186
+ * migration-status` is empty even when the rows exist; `cc pqc migrate` counts
187
+ * sourceKeys off the empty Map and reports "Migrated 0/0" while real keys sit
188
+ * unmigrated). Call after ensurePQCTables(db). NOTE: this module holds only
189
+ * public-key inventory metadata — there is no private key or sign/verify path
190
+ * here — so the impact is inventory visibility + migration accounting, not
191
+ * crypto verification.
192
+ */
193
+ export function loadFromDb(db) {
194
+ if (!db) return 0;
195
+ _keys.clear();
196
+ _migrations.clear();
197
+
198
+ const keyRows = db.prepare(`SELECT * FROM pqc_keys`).all();
199
+ for (const row of keyRows) {
200
+ let metadata = {};
201
+ try {
202
+ metadata = row.metadata ? JSON.parse(row.metadata) : {};
203
+ } catch {
204
+ metadata = {};
205
+ }
206
+ // Derived fields (family/publicKeyBytes/signatureBytes) come from the
207
+ // algorithm spec, not the DB — reconstruct them exactly as generateKey does.
208
+ const spec = ALGORITHM_SPECS[row.algorithm] || {};
209
+ _keys.set(row.id, {
210
+ id: row.id,
211
+ algorithm: row.algorithm,
212
+ family: spec.family ?? null,
213
+ purpose: row.purpose,
214
+ publicKey: row.public_key,
215
+ keySize: row.key_size ?? spec.keySize ?? 0,
216
+ publicKeyBytes: spec.publicKeyBytes ?? null,
217
+ signatureBytes: spec.signatureBytes ?? null,
218
+ hybridMode: !!row.hybrid_mode,
219
+ classicalAlgorithm: row.classical_algorithm ?? null,
220
+ status: row.status,
221
+ metadata,
222
+ createdAt: row.created_at ?? null,
223
+ });
224
+ }
225
+
226
+ const migRows = db.prepare(`SELECT * FROM pqc_migration_status`).all();
227
+ for (const row of migRows) {
228
+ _migrations.set(row.id, {
229
+ id: row.id,
230
+ planName: row.plan_name,
231
+ sourceAlgorithm: row.source_algorithm,
232
+ targetAlgorithm: row.target_algorithm,
233
+ totalKeys: row.total_keys ?? 0,
234
+ migratedKeys: row.migrated_keys ?? 0,
235
+ status: row.status,
236
+ startedAt: row.started_at ?? null,
237
+ completedAt: row.completed_at ?? null,
238
+ errorMessage: row.error_message ?? null,
239
+ });
240
+ }
241
+
242
+ return _keys.size + _migrations.size;
243
+ }
244
+
181
245
  /* ── Key Management ───────────────────────────────────────── */
182
246
 
183
247
  export function listKeys(filter = {}) {
@@ -509,6 +509,107 @@ export function _resetState() {
509
509
  _maxConcurrentOptimizations = DEFAULT_MAX_CONCURRENT_OPTIMIZATIONS;
510
510
  }
511
511
 
512
+ /**
513
+ * Rehydrate the in-memory Maps from the persisted reputation_* tables. The CLI
514
+ * runs each `cc reputation` subcommand in a fresh node process, so
515
+ * _observations/_runs/_analytics start empty every invocation. Every write
516
+ * dual-writes (Map + INSERT into reputation_observations/
517
+ * reputation_optimization_runs/reputation_analytics) but every read
518
+ * (computeScore/listScores/detectAnomalies/getOptimizationStatus/getAnalytics/
519
+ * listOptimizationRuns/applyOptimizedParams + the V2 drivers) is Map-only and
520
+ * nothing SELECTed the tables back — so observed scores read back as 0, `list`/
521
+ * `runs`/`anomalies` were always empty, and `status`/`complete`/`apply <runId>`
522
+ * threw "Optimization run not found" for a run that genuinely exists on disk.
523
+ * Call after ensureReputationTables(db).
524
+ *
525
+ * Non-persisted fields default safely: run.history=[] (V1), errorMessage=null.
526
+ */
527
+ export function loadFromDb(db) {
528
+ if (!db) return 0;
529
+ ensureReputationTables(db);
530
+ _observations.clear();
531
+ _runs.clear();
532
+ _analytics.clear();
533
+
534
+ for (const r of db
535
+ .prepare(
536
+ `SELECT observation_id, did, score, kind, weight, recorded_at
537
+ FROM reputation_observations ORDER BY recorded_at ASC`,
538
+ )
539
+ .all()) {
540
+ if (!_observations.has(r.did)) _observations.set(r.did, []);
541
+ _observations.get(r.did).push({
542
+ observationId: r.observation_id,
543
+ did: r.did,
544
+ score: Number(r.score),
545
+ kind: r.kind,
546
+ weight: Number(r.weight),
547
+ recordedAt: Number(r.recorded_at),
548
+ });
549
+ }
550
+
551
+ for (const r of db
552
+ .prepare(
553
+ `SELECT run_id, objective, iterations, param_space, best_params, best_score,
554
+ status, created_at, completed_at
555
+ FROM reputation_optimization_runs ORDER BY created_at ASC`,
556
+ )
557
+ .all()) {
558
+ // One corrupt param/params cell must not sink the whole run list.
559
+ let paramSpace = {};
560
+ let bestParams = null;
561
+ try {
562
+ paramSpace = r.param_space ? JSON.parse(r.param_space) : {};
563
+ } catch {
564
+ paramSpace = {};
565
+ }
566
+ try {
567
+ bestParams = r.best_params ? JSON.parse(r.best_params) : null;
568
+ } catch {
569
+ bestParams = null;
570
+ }
571
+ _runs.set(r.run_id, {
572
+ runId: r.run_id,
573
+ objective: r.objective,
574
+ iterations: Number(r.iterations),
575
+ paramSpace,
576
+ bestParams,
577
+ bestScore: r.best_score == null ? null : Number(r.best_score),
578
+ errorMessage: null,
579
+ status: r.status,
580
+ createdAt: Number(r.created_at),
581
+ completedAt: r.completed_at == null ? null : Number(r.completed_at),
582
+ history: [],
583
+ _seq: ++_seq,
584
+ });
585
+ }
586
+
587
+ for (const r of db
588
+ .prepare(
589
+ `SELECT analytics_id, run_id, reputation_distribution, anomalies, recommendations, created_at
590
+ FROM reputation_analytics ORDER BY created_at ASC`,
591
+ )
592
+ .all()) {
593
+ const parse = (v, fallback) => {
594
+ try {
595
+ return v ? JSON.parse(v) : fallback;
596
+ } catch {
597
+ return fallback;
598
+ }
599
+ };
600
+ _analytics.set(r.run_id, {
601
+ analyticsId: r.analytics_id,
602
+ runId: r.run_id,
603
+ reputationDistribution: parse(r.reputation_distribution, {}),
604
+ anomalies: parse(r.anomalies, []),
605
+ recommendations: parse(r.recommendations, []),
606
+ createdAt: Number(r.created_at),
607
+ });
608
+ }
609
+
610
+ return _observations.size + _runs.size + _analytics.size;
611
+ }
612
+
512
613
  /* ═══════════════════════════════════════════════════════════════
513
614
  * V2 (Phase 60) — Frozen enums + async optimization lifecycle +
514
615
  * concurrency limiter + patch-merged setRunStatus + stats-v2.
@@ -17,7 +17,60 @@
17
17
 
18
18
  import http from "node:http";
19
19
  import net from "node:net";
20
- import { evaluateNetworkAccess } from "./sandbox-network-policy.js";
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
+ }
21
74
 
22
75
  /**
23
76
  * Build the proxy env vars a child process needs to route through the proxy.
@@ -64,77 +117,124 @@ export function createEgressProxy(policy = {}, opts = {}) {
64
117
  return verdict;
65
118
  };
66
119
 
67
- const server = http.createServer((req, res) => {
68
- // A forward-proxy request carries the absolute URL in the request line.
69
- const verdict = decide(req.url, "http");
70
- if (!verdict.allowed) {
71
- res.writeHead(403, { "content-type": "text/plain" });
72
- res.end(`egress blocked: ${verdict.host} ${verdict.reason}\n`);
73
- return;
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 };
74
132
  }
75
- let u;
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) => {
76
149
  try {
77
- u = new URL(req.url);
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);
78
184
  } catch {
79
- res.writeHead(400, { "content-type": "text/plain" });
80
- res.end("bad request target\n");
81
- return;
82
- }
83
- const upstream = http.request(
84
- {
85
- hostname: u.hostname,
86
- port: u.port || 80,
87
- path: `${u.pathname}${u.search}`,
88
- method: req.method,
89
- headers: req.headers,
90
- },
91
- (upRes) => {
92
- res.writeHead(upRes.statusCode || 502, upRes.headers);
93
- upRes.pipe(res);
94
- },
95
- );
96
- upstream.on("error", () => {
97
185
  if (!res.headersSent)
98
186
  res.writeHead(502, { "content-type": "text/plain" });
99
- res.end("upstream error\n");
100
- });
101
- req.pipe(upstream);
187
+ res.end("proxy error\n");
188
+ }
102
189
  });
103
190
 
104
191
  // HTTPS (and any TLS) goes through CONNECT — tunnel only allowed hosts.
105
- server.on("connect", (req, clientSocket, head) => {
106
- const verdict = decide(req.url, "connect"); // req.url = "host:port"
107
- if (!verdict.allowed) {
108
- clientSocket.write(
109
- "HTTP/1.1 403 Forbidden\r\n" +
110
- "Content-Type: text/plain\r\n\r\n" +
111
- `egress blocked: ${verdict.host} — ${verdict.reason}\n`,
112
- );
113
- clientSocket.end();
114
- return;
115
- }
116
- const [host, portStr] = req.url.split(":");
117
- const port = parseInt(portStr, 10) || 443;
118
- const upstream = net.connect(port, host, () => {
119
- clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
120
- if (head && head.length) upstream.write(head);
121
- upstream.pipe(clientSocket);
122
- clientSocket.pipe(upstream);
123
- });
124
- upstream.on("error", () => {
125
- try {
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
+ );
126
204
  clientSocket.end();
127
- } catch {
128
- /* already closed */
205
+ return;
129
206
  }
130
- });
131
- clientSocket.on("error", () => {
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 {
132
232
  try {
133
- upstream.destroy();
233
+ clientSocket.end();
134
234
  } catch {
135
- /* already gone */
235
+ /* already closed */
136
236
  }
137
- });
237
+ }
138
238
  });
139
239
 
140
240
  return {
@@ -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
+ }
@@ -33,6 +33,13 @@ export function extractHost(target) {
33
33
  }
34
34
  // Strip IPv6 brackets ("[::1]" → "::1") — the documented SSRF gotcha.
35
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(/\.+$/, "");
36
43
  return s.toLowerCase() || null;
37
44
  }
38
45
 
@@ -107,7 +114,17 @@ export function evaluateNetworkAccess(target, policy = {}) {
107
114
  allowed.filter((p) => String(p).trim() !== "*"),
108
115
  );
109
116
  if (explicitNonStar) {
110
- return { allowed: true, host, reason: "matched allowedDomains" };
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
+ };
111
128
  }
112
129
  // 3) Private / loopback / metadata targets are blocked unless allowed above.
113
130
  if (isPrivateHost(host) && !policy.allowPrivate) {
@@ -6,6 +6,7 @@
6
6
  import crypto from "crypto";
7
7
  import { safeJsonParse } from "./safe-json.js";
8
8
  import { evaluateNetworkAccess } from "./sandbox-network-policy.js";
9
+ import { evaluatePathAccess } from "./sandbox-fs-policy.js";
9
10
  import { createRequire } from "module";
10
11
 
11
12
  const _require = createRequire(import.meta.url);
@@ -142,20 +143,19 @@ export function ensureSandboxTables(db) {
142
143
  // ─── Permission checking ──────────────────────────────────────
143
144
 
144
145
  function checkFilePermission(permissions, filePath, mode) {
145
- const fs = permissions.fileSystem || {};
146
- const denied = fs.denied || [];
147
- for (const d of denied) {
148
- if (filePath.startsWith(d)) return false;
149
- }
150
- if (mode === "read") {
151
- const allowed = fs.read || [];
152
- return allowed.some((p) => filePath.startsWith(p));
153
- }
154
- if (mode === "write") {
155
- const allowed = fs.write || [];
156
- return allowed.some((p) => filePath.startsWith(p));
157
- }
158
- 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;
159
159
  }
160
160
 
161
161
  function checkNetworkPermission(permissions, host) {
@@ -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
- messages: JSON.parse(session.messages || "[]"),
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
- ? JSON.parse(session.metadata || "{}")
138
+ ? safeJsonParse(session.metadata, {})
136
139
  : session.metadata || {},
137
140
  };
138
141
  }
@@ -48,6 +48,7 @@ const HOOK_EVENTS = Object.freeze([
48
48
  "ConfigChange",
49
49
  "PreCompact",
50
50
  "Notification",
51
+ "PermissionRequest",
51
52
  ]);
52
53
 
53
54
  /**