@noxsoft/anima 2.0.2 → 2.0.4
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 +1260 -28
- package/dist/accounts-Bth3PpPD.js +260 -0
- package/dist/accounts-D8CPKNkN.js +259 -0
- package/dist/acp-cli-ByK6lS6c.js +1081 -0
- package/dist/acp-cli-CaQCjIw4.js +1084 -0
- package/dist/agent-BgIkqd3F.js +725 -0
- package/dist/agent-N5BDcge4.js +725 -0
- package/dist/agent-events-COH7NDW2.js +182 -0
- package/dist/agent-scope-CPphqq-U.js +452 -0
- package/dist/agent-scope-DZgptr9J.js +452 -0
- package/dist/agent-scope-cj2QCT6R.js +112 -0
- package/dist/agents-NEudYMdg.js +774 -0
- package/dist/agents.config-Bujs-NIy.js +182 -0
- package/dist/agents.config-jp7OLssr.js +182 -0
- package/dist/argv-BMZMiW7v.js +73 -0
- package/dist/audit-C-UJhfdv.js +2401 -0
- package/dist/audit-CeCO7SK5.js +2401 -0
- package/dist/auth-BNZsOHGF.js +648 -0
- package/dist/auth-DMPZWzEa.js +639 -0
- package/dist/auth-choice-5VnaGMD-.js +2681 -0
- package/dist/auth-choice-DA2k4vs8.js +2681 -0
- package/dist/auth-health-B7FqA26_.js +149 -0
- package/dist/auth-health-VO_MPqVX.js +149 -0
- package/dist/auth-profiles-BDrNYX_n.js +1564 -0
- package/dist/auth-profiles-CxSHydjn.js +2689 -0
- package/dist/banner-BtDZPRzi.js +294 -0
- package/dist/browser-cli-8yQMpxb8.js +1679 -0
- package/dist/browser-cli-Czg3JtDH.js +1676 -0
- package/dist/build-info.json +3 -3
- package/dist/bundled/boot-md/handler.js +16 -16
- package/dist/bundled/bootstrap-extra-files/handler.js +4 -4
- package/dist/bundled/command-logger/handler.js +1 -1
- package/dist/bundled/session-memory/handler.js +5 -5
- package/dist/call-BIzCaKZb.js +282 -0
- package/dist/call-BYDpTVCZ.js +282 -0
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/catalog-CqKiUgu6.js +185 -0
- package/dist/catalog-DMfEg-oK.js +185 -0
- package/dist/channel-options-BrtCtyrT.js +32 -0
- package/dist/channel-options-CO21Gl8p.js +33 -0
- package/dist/channel-selection-Bbm1lq3P.js +51 -0
- package/dist/channel-selection-CqcX7Ocw.js +51 -0
- package/dist/channel-web-DrsT6OAE.js +2162 -0
- package/dist/channels-cli-Juyh1S6n.js +1304 -0
- package/dist/channels-cli-zNvi1m5c.js +1306 -0
- package/dist/channels-status-issues-CqzqshW4.js +18 -0
- package/dist/channels-status-issues-DdJdO866.js +18 -0
- package/dist/chrome-C4dOMO8z.js +1601 -0
- package/dist/chrome-DdcDzAtH.js +1629 -0
- package/dist/chrome-U3DRzjJD.js +1601 -0
- package/dist/chunk-D2nLsrEW.js +348 -0
- package/dist/clack-prompter-BI3RDW5w.js +92 -0
- package/dist/clack-prompter-Dwr1m_IZ.js +92 -0
- package/dist/cli/daemon-cli.js +1 -1
- package/dist/cli-C3cpDaz8.js +99 -0
- package/dist/cli-CjWUGdGC.js +101 -0
- package/dist/cli-session-BVjY_XrW.js +5463 -0
- package/dist/cli-session-gtuYN2Iq.js +5408 -0
- package/dist/client-Dswwze5_.js +1692 -0
- package/dist/client-LRKFjo4A.js +1692 -0
- package/dist/clipboard-BZKS9O1u.js +31 -0
- package/dist/clipboard-DES8b1AM.js +31 -0
- package/dist/command-format-CP1YTNCl.js +52 -0
- package/dist/command-format-CVL4K5cj.js +52 -0
- package/dist/command-format-G6N2zghg.js +38 -0
- package/dist/command-registry-BBvNvysr.js +248 -0
- package/dist/commands-AZ3n8Y2c.js +726 -0
- package/dist/commands-BMnD_QRY.js +726 -0
- package/dist/commands-registry-cFqZ6Ib4.js +766 -0
- package/dist/commands-registry-q13H7ng5.js +766 -0
- package/dist/common-CX5458fH.js +287 -0
- package/dist/common-DJbnT8ws.js +287 -0
- package/dist/completion-cli-BADRBcIl.js +432 -0
- package/dist/completion-cli-DMQgiObF.js +431 -0
- package/dist/config-CU-Axg8P.js +5704 -0
- package/dist/config-DaqbUdkI.js +5705 -0
- package/dist/config-cli-BPlbwiuA.js +244 -0
- package/dist/config-cli-DXgZJkPU.js +247 -0
- package/dist/config-guard-Ba49JNds.js +76 -0
- package/dist/config-guard-Cu0qMKZJ.js +93 -0
- package/dist/config-kVVm5EYV.js +6523 -0
- package/dist/config-sync-CzLnLTXt.js +91 -0
- package/dist/config-sync-DuydxPWx.js +91 -0
- package/dist/configure-CHgacLyi.js +960 -0
- package/dist/configure-DfHXDa1L.js +959 -0
- package/dist/context-DzgXOckU.js +60 -0
- package/dist/control-service-8_wKHwBa.js +72 -0
- package/dist/control-service-BtL1Jto_.js +72 -0
- package/dist/cron-cli-BCzSR2c0.js +448 -0
- package/dist/cron-cli-CCWNkykU.js +451 -0
- package/dist/daemon-cli-Bjkbu9Vy.js +565 -0
- package/dist/daemon-cli-CmlHcC1J.js +566 -0
- package/dist/daemon-cli.js +16 -16
- package/dist/daemon-runtime-C0tz7VAC.js +460 -0
- package/dist/daemon-runtime-rUTqCVwJ.js +460 -0
- package/dist/deliver-BBggsviM.js +1097 -0
- package/dist/deliver-CePITOl8.js +1162 -0
- package/dist/deliver-DFnVaetP.js +1097 -0
- package/dist/delivery-queue-BJQK3oh5.js +220 -0
- package/dist/deps-CeEKhrp7.js +42 -0
- package/dist/devices-cli-DQrDMrZH.js +198 -0
- package/dist/devices-cli-Oe-A1Dv0.js +195 -0
- package/dist/diagnostics-DxMFrBLO.js +35 -0
- package/dist/diagnostics-m79ZlMmZ.js +35 -0
- package/dist/directory-cli-BL6h8cGF.js +246 -0
- package/dist/directory-cli-Cjgmi_sj.js +243 -0
- package/dist/dispatcher-DAFbQM-c.js +100 -0
- package/dist/dispatcher-DNd40gUn.js +100 -0
- package/dist/dist-CqDI82ei.js +929 -0
- package/dist/dist-DnHRxR5U.js +929 -0
- package/dist/dns-cli-CFtV3BXK.js +200 -0
- package/dist/dns-cli-NyIHvQ5S.js +197 -0
- package/dist/dock-BdXLb5oY.js +753 -0
- package/dist/dock-jYICmNcI.js +753 -0
- package/dist/docs-cli-CrOaIK_H.js +161 -0
- package/dist/docs-cli-D_cmJDSr.js +159 -0
- package/dist/doctor-BpGxKrBl.js +1815 -0
- package/dist/doctor-D12wNQPU.js +1813 -0
- package/dist/doctor-completion-DeOfofek.js +92 -0
- package/dist/doctor-completion-DwjqdEcK.js +92 -0
- package/dist/doctor-config-flow-BI3mpkbd.js +1232 -0
- package/dist/doctor-config-flow-wMHheFkC.js +1232 -0
- package/dist/engine-BCtL-AMw.js +563 -0
- package/dist/engine-Bk_UT413.js +563 -0
- package/dist/entry.js +5 -5
- package/dist/env-v6411I8h.js +32 -0
- package/dist/exec-B7sUS164.js +1167 -0
- package/dist/exec-approvals-CroGJRUg.js +1221 -0
- package/dist/exec-approvals-cli-BTxF_RsH.js +371 -0
- package/dist/exec-approvals-cli-n1gyGwH2.js +368 -0
- package/dist/exec-mhSykkaa.js +255 -0
- package/dist/extensionAPI.js +3 -3
- package/dist/frontmatter-BmHq0vRD.js +204 -0
- package/dist/gateway-cli-DDBadlrS.js +19971 -0
- package/dist/gateway-cli-IZNkOMBe.js +19972 -0
- package/dist/gateway-rpc-Dtx8HN-n.js +28 -0
- package/dist/gateway-rpc-L2PVSqGj.js +28 -0
- package/dist/github-copilot-auth-DKyqDaGU.js +1418 -0
- package/dist/github-copilot-auth-DXpOMSd3.js +1418 -0
- package/dist/gmail-setup-utils-BKNczIJ9.js +428 -0
- package/dist/gmail-setup-utils-co0ppccC.js +428 -0
- package/dist/health-Bm8ZTvC3.js +1253 -0
- package/dist/health-DUf1gt4E.js +1258 -0
- package/dist/health-format-BksT6F68.js +208 -0
- package/dist/health-format-uzh1xYLD.js +208 -0
- package/dist/heartbeat-visibility-1TJb1Zao.js +98 -0
- package/dist/heartbeat-visibility-CwodtdcX.js +98 -0
- package/dist/help-format-C6cv_aZp.js +17 -0
- package/dist/helpers-N-uSFKOn.js +10 -0
- package/dist/hooks-cli-1POsXqOl.js +993 -0
- package/dist/hooks-cli-BGjILbze.js +991 -0
- package/dist/hooks-status-DE07n5RC.js +356 -0
- package/dist/hooks-status-Du-d1jde.js +356 -0
- package/dist/image-ops-B_AYV3tp.js +541 -0
- package/dist/image-ops-Bp0C6Mvr.js +541 -0
- package/dist/index.js +82 -82
- package/dist/init-9A0s7bWG.js +122 -0
- package/dist/init-DoyCHJDC.js +122 -0
- package/dist/installs-D1C9wHAq.js +383 -0
- package/dist/installs-Dh4dHayM.js +383 -0
- package/dist/ipv4-DCItfaJo.js +1964 -0
- package/dist/ipv4-DSOUVx0i.js +1964 -0
- package/dist/lanes-BvSnHq2h.js +232 -0
- package/dist/lifecycle-core-BY4WIf9g.js +388 -0
- package/dist/lifecycle-core-TQKyXO-6.js +387 -0
- package/dist/links-CNu_8RZl.js +15 -0
- package/dist/links-D2tt2ouh.js +15 -0
- package/dist/llm-slug-generator.js +4 -4
- package/dist/logging-BIeRw0WR.js +15 -0
- package/dist/logging-C7lb3Vjc.js +15 -0
- package/dist/login-DXWKewA2.js +59 -0
- package/dist/login-Fhh4uWmf.js +61 -0
- package/dist/login-pPs3UO38.js +61 -0
- package/dist/login-qr-CevLD8cV.js +326 -0
- package/dist/login-qr-GF2JMIy-.js +323 -0
- package/dist/login-qr-ZYYKD6Yt.js +321 -0
- package/dist/logs-cli-CzXbX8HZ.js +242 -0
- package/dist/logs-cli-D9ngH9PF.js +245 -0
- package/dist/manager-BD5rA3w0.js +3244 -0
- package/dist/manager-BDPgBQSH.js +3246 -0
- package/dist/manager-DRWMWM--.js +3244 -0
- package/dist/manifest-registry-DbvPaBXY.js +748 -0
- package/dist/manifest-registry-kHX_MFa1.js +748 -0
- package/dist/markdown-tables-CqwihY2m.js +347 -0
- package/dist/markdown-tables-DJV7eAJZ.js +348 -0
- package/dist/media-lUqN-0O9.js +1342 -0
- package/dist/memory-cli-BLXSpgnN.js +868 -0
- package/dist/memory-cli-BcGVkkRJ.js +869 -0
- package/dist/message-channel-D_jIO87f.js +110 -0
- package/dist/migrate-BpVOar4L.js +157 -0
- package/dist/migrate-CkgGDkWy.js +157 -0
- package/dist/model-selection-Cqt6aJ0G.js +2691 -0
- package/dist/models-CExsNQPH.js +2510 -0
- package/dist/models-cli-Ba3Jmwev.js +2739 -0
- package/dist/models-cli-iDAlsbL2.js +258 -0
- package/dist/net-0A_zcaQD.js +218 -0
- package/dist/node-cli-ATmwCXIk.js +1319 -0
- package/dist/node-cli-DYFR_V25.js +1322 -0
- package/dist/node-service-CN4LqR1A.js +67 -0
- package/dist/node-service-CWt3MdSC.js +67 -0
- package/dist/nodes-cli-BeVmhTz3.js +1197 -0
- package/dist/nodes-cli-QeJIfa18.js +1200 -0
- package/dist/nodes-screen-DHyWAlla.js +234 -0
- package/dist/nodes-screen-qs3jRBPk.js +234 -0
- package/dist/note-CSlg2BnB.js +73 -0
- package/dist/note-Ctvglhp1.js +73 -0
- package/dist/npm-registry-spec-DQd4M22q.js +351 -0
- package/dist/npm-registry-spec-PxisIMts.js +351 -0
- package/dist/onboard-DeruD10m.js +1166 -0
- package/dist/onboard-SAcu5N6N.js +1165 -0
- package/dist/onboard-channels-C4iSfFXR.js +672 -0
- package/dist/onboard-channels-oVTVgoyg.js +672 -0
- package/dist/onboard-helpers-B8roRwLP.js +365 -0
- package/dist/onboard-helpers-Dgh26hgP.js +365 -0
- package/dist/onboarding-Bi-ac8we.js +911 -0
- package/dist/onboarding-C2gjB2u8.js +910 -0
- package/dist/orchestrator-DlbAYMQP.js +357 -0
- package/dist/orchestrator-DlwVRVDA.js +357 -0
- package/dist/outbound-CkKgc6iR.js +2062 -0
- package/dist/outbound-Vfm5yDh3.js +214 -0
- package/dist/outbound-bs_VK51X.js +214 -0
- package/dist/outbound-send-deps-DDjiMfEL.js +55 -0
- package/dist/pairing-cli-CJYeuEik.js +118 -0
- package/dist/pairing-cli-mqopHI8s.js +121 -0
- package/dist/pairing-store-BsXzUDPv.js +388 -0
- package/dist/pairing-store-DoNj00-X.js +388 -0
- package/dist/path-env-C_xpiG8l.js +89 -0
- package/dist/path-env-DSSMHu5A.js +89 -0
- package/dist/paths-B1vRVCad.js +126 -0
- package/dist/paths-BMuHNFxg.js +238 -0
- package/dist/paths-BXQQzXGQ.js +129 -0
- package/dist/paths-Buw_geoe.js +54 -0
- package/dist/paths-DA9WYabg.js +222 -0
- package/dist/paths-DfQGx0_k.js +129 -0
- package/dist/pi-auth-json-DOPW3e4X.js +78 -0
- package/dist/pi-auth-json-MruLmI_X.js +82 -0
- package/dist/pi-auth-json-lae_wwwo.js +80 -0
- package/dist/pi-model-discovery-7q0GxMrp.js +3 -0
- package/dist/pi-tools.policy-Csmla32P.js +200 -0
- package/dist/pi-tools.policy-xYdDLEv9.js +200 -0
- package/dist/plugin-auto-enable-CViVVWgg.js +282 -0
- package/dist/plugin-auto-enable-CjZ238UI.js +282 -0
- package/dist/plugin-registry-B4Aw2hzq.js +32 -0
- package/dist/plugin-registry-DW81arxW.js +32 -0
- package/dist/plugin-sdk/cli/cli-name.d.ts +1 -1
- package/dist/plugin-sdk/config/paths.d.ts +2 -2
- package/dist/plugin-sdk/index.js +7 -7
- package/dist/plugins-DhcGAPDB.js +38 -0
- package/dist/plugins-DtghNRtM.js +168 -0
- package/dist/plugins-cli-4vWTmOAb.js +736 -0
- package/dist/plugins-cli-CdTMbP0X.js +734 -0
- package/dist/polls-D6eCdatA.js +1343 -0
- package/dist/ports-BtZx-JKD.js +96 -0
- package/dist/ports-C8bKN8s0.js +96 -0
- package/dist/ports-DHiKnPRX.js +344 -0
- package/dist/ports-vd93M_Pt.js +317 -0
- package/dist/program-CX3aUVeb.js +176 -0
- package/dist/program-context-BPos0ivo.js +496 -0
- package/dist/progress-oiAjiiNi.js +133 -0
- package/dist/prompt-style-Cm4wOtKm.js +9 -0
- package/dist/pw-ai-4QbK5YFe.js +1865 -0
- package/dist/pw-ai-BWz3Cxt7.js +1868 -0
- package/dist/pw-ai-C83HBue2.js +1867 -0
- package/dist/qmd-manager-BcMeZiGD.js +938 -0
- package/dist/qmd-manager-CPypGJ0P.js +935 -0
- package/dist/qmd-manager-CRrSkfia.js +937 -0
- package/dist/register.agent-DDY8KJhn.js +265 -0
- package/dist/register.agent-DKawm-9d.js +1003 -0
- package/dist/register.anima-CEWUo29k.js +193 -0
- package/dist/register.anima-DBWz2rk_.js +193 -0
- package/dist/register.configure-BX67qV8k.js +103 -0
- package/dist/register.configure-CWsySuiq.js +101 -0
- package/dist/register.maintenance-0k-ZNhDg.js +543 -0
- package/dist/register.maintenance-BIwx1fzX.js +543 -0
- package/dist/register.message-CXPsoakA.js +657 -0
- package/dist/register.message-DA3jvfgI.js +660 -0
- package/dist/register.onboard-C4HG7Hqv.js +170 -0
- package/dist/register.onboard-GOpdif-j.js +170 -0
- package/dist/register.setup-B17vZT7C.js +175 -0
- package/dist/register.setup-GJyUDCqh.js +175 -0
- package/dist/register.status-health-sessions-D5876dGx.js +313 -0
- package/dist/register.status-health-sessions-lOewVIZR.js +142 -0
- package/dist/register.subclis-Dwnujj5C.js +255 -0
- package/dist/reply-CR5T_oQJ.js +32212 -0
- package/dist/reply-prefix-BcrS4Umd.js +100 -0
- package/dist/reply-prefix-Btb5o2NH.js +100 -0
- package/dist/reply-r089HuRA.js +32212 -0
- package/dist/routes-B4czFzIb.js +1820 -0
- package/dist/routes-ucJWAk5O.js +1820 -0
- package/dist/rpc-BnKxnQ0v.js +70 -0
- package/dist/rpc-DgE-xnyx.js +70 -0
- package/dist/run-main-B74kv84C.js +371 -0
- package/dist/runtime-guard-CKFdts2L.js +60 -0
- package/dist/sandbox-CJTS3er6.js +858 -0
- package/dist/sandbox-DBSiVHt_.js +859 -0
- package/dist/sandbox-cli-CrkjyU5M.js +461 -0
- package/dist/sandbox-cli-D1r5y6Sz.js +458 -0
- package/dist/security-cli-BZUdnkhn.js +462 -0
- package/dist/security-cli-DS09ebvA.js +465 -0
- package/dist/server-context-C0xZbYhg.js +824 -0
- package/dist/server-context-DVh2z7om.js +824 -0
- package/dist/server-node-events-bu9lpkMH.js +233 -0
- package/dist/server-node-events-i1Rrww31.js +231 -0
- package/dist/service-CJJwLEor.js +642 -0
- package/dist/service-DxLxBhaU.js +642 -0
- package/dist/service-audit-DB4Y3Ekp.js +488 -0
- package/dist/service-audit-M8y4TXVb.js +488 -0
- package/dist/session-CGxOLFs2.js +179 -0
- package/dist/session-DTTbdKb0.js +181 -0
- package/dist/session-cost-usage-FcdJl9c3.js +600 -0
- package/dist/session-cost-usage-qdfsGU2a.js +600 -0
- package/dist/session-yOhWcsD2.js +181 -0
- package/dist/sessions-B-Cu7JZq.js +1296 -0
- package/dist/sessions-BgLN4KFr.js +180 -0
- package/dist/sessions-CnRjwdVr.js +1296 -0
- package/dist/sessions-wRKla1Qh.js +2038 -0
- package/dist/shared-DS3UaJSP.js +66 -0
- package/dist/shared-DxNHzky3.js +77 -0
- package/dist/shared-Qpt4hUDi.js +66 -0
- package/dist/shared-kzrojZ1B.js +77 -0
- package/dist/skill-scanner-DLJji5Ye.js +263 -0
- package/dist/skills-BWFIEp4j.js +807 -0
- package/dist/skills-DV4zKdCx.js +808 -0
- package/dist/skills-cli-BY53ILm2.js +289 -0
- package/dist/skills-cli-CO3gxl8A.js +286 -0
- package/dist/skills-status-DX5pcqY3.js +166 -0
- package/dist/skills-status-zhcKzGkp.js +166 -0
- package/dist/sqlite-B6MojU1I.js +321 -0
- package/dist/sqlite-CuprTGR7.js +453 -0
- package/dist/sqlite-dzD-jMjs.js +368 -0
- package/dist/start-Cu3aLoSf.js +297 -0
- package/dist/start-Dz7tMAl8.js +296 -0
- package/dist/status-CaSxhxfV.js +2132 -0
- package/dist/status-D2C0JCX3.js +2137 -0
- package/dist/status-DlFMsQzh.js +27 -0
- package/dist/status-G0CITnKR.js +27 -0
- package/dist/status.update-CHjhVxJY.js +79 -0
- package/dist/status.update-DVFelehi.js +79 -0
- package/dist/subagent-registry-3Xb4el-8.js +14 -0
- package/dist/subagent-registry-CdSjz14I.js +2760 -0
- package/dist/subagent-registry-DNDhbHWi.js +2759 -0
- package/dist/subsystem-DfKstnEK.js +860 -0
- package/dist/system-cli-B5mt0FWa.js +82 -0
- package/dist/system-cli-Dg3UQ3Zz.js +79 -0
- package/dist/systemd-B43AvOGx.js +452 -0
- package/dist/systemd-RpPE0XGg.js +452 -0
- package/dist/systemd-hints-DMJT-Bbc.js +36 -0
- package/dist/systemd-hints-vRInKcz9.js +36 -0
- package/dist/systemd-linger-Dzyxqsod.js +75 -0
- package/dist/systemd-linger-EujbmI5A.js +75 -0
- package/dist/table-DhXHfRX2.js +279 -0
- package/dist/table-bWCLW-3P.js +279 -0
- package/dist/timeout-Ddn-5kAO.js +232 -0
- package/dist/tokens-3psI_Qk2.js +14 -0
- package/dist/tokens-BaM53PEx.js +14 -0
- package/dist/trash-Bmxs1Rnm.js +23 -0
- package/dist/trash-C39a6hKA.js +23 -0
- package/dist/tui-BHgBWhHE.js +3894 -0
- package/dist/tui-cli-B9Sq5-cC.js +50 -0
- package/dist/tui-cli-Dw7v4JoJ.js +47 -0
- package/dist/tui-mUwDwqvd.js +3894 -0
- package/dist/update-DF0GHG0j.js +317 -0
- package/dist/update-DoZLVjva.js +317 -0
- package/dist/update-check-Bt1dVPVN.js +400 -0
- package/dist/update-check-D5qAKes7.js +400 -0
- package/dist/update-cli-BNu2Oi7H.js +1105 -0
- package/dist/update-cli-D36AmALA.js +1105 -0
- package/dist/update-runner-CNQQaTwA.js +894 -0
- package/dist/update-runner-CvxZmbu-.js +894 -0
- package/dist/usage-BGCwNnjk.js +4516 -0
- package/dist/utils-DZ8pnOD5.js +243 -0
- package/dist/web-B5QG839O.js +46842 -0
- package/dist/web-Cmnvk9v0.js +2203 -0
- package/dist/web-Cv2KnTnL.js +63 -0
- package/dist/webhooks-cli-B6y89Pj_.js +319 -0
- package/dist/webhooks-cli-BDzHON4w.js +316 -0
- package/dist/whatsapp-actions-C_5MwVxM.js +45 -0
- package/dist/whatsapp-actions-hgYA12To.js +53 -0
- package/dist/whatsapp-actions-zTiVOoOV.js +49 -0
- package/dist/widearea-dns-BeIdnISJ.js +127 -0
- package/dist/widearea-dns-CF1gxpJ-.js +127 -0
- package/dist/workspace-DLna1IxR.js +649 -0
- package/dist/ws-log-Q4wO1Ztb.js +267 -0
- package/dist/ws-log-xF0kxDzp.js +267 -0
- package/package.json +1 -2
- package/dist/accounts-Cc5E4IDO.js +0 -260
- package/dist/accounts-CcVrwKqv.js +0 -259
- package/dist/acp-cli-DvphOKuh.js +0 -1081
- package/dist/acp-cli-p28pQ65a.js +0 -1084
- package/dist/agent-Cj7uDJaZ.js +0 -725
- package/dist/agent-Cuj9-2sT.js +0 -725
- package/dist/agent-events-BEBQsyE5.js +0 -182
- package/dist/agent-scope-BVf4aSwY.js +0 -112
- package/dist/agent-scope-OZi7lb8S.js +0 -452
- package/dist/agent-scope-V1bi9OYL.js +0 -452
- package/dist/agents-BUWqn_Ui.js +0 -774
- package/dist/agents.config-Dvo2ULxs.js +0 -182
- package/dist/agents.config-d6H0_3oj.js +0 -182
- package/dist/argv-DqUHKf0o.js +0 -73
- package/dist/audit-C6okOOSh.js +0 -2401
- package/dist/audit-VWjIdwC7.js +0 -2401
- package/dist/auth-91o2YM96.js +0 -648
- package/dist/auth-choice-CAmACV13.js +0 -2681
- package/dist/auth-choice-p3SeHPj2.js +0 -2681
- package/dist/auth-health-B_jXrWe6.js +0 -149
- package/dist/auth-health-DCicUKYR.js +0 -149
- package/dist/auth-lZ26wsbN.js +0 -639
- package/dist/auth-profiles-CCDD56dU.js +0 -1564
- package/dist/auth-profiles-DxI8L7bs.js +0 -2689
- package/dist/banner-Cohn04J6.js +0 -294
- package/dist/browser-cli-DANzjztE.js +0 -1676
- package/dist/browser-cli-WjsVH741.js +0 -1679
- package/dist/call-BAHvlu2G.js +0 -282
- package/dist/call-Ct7EGP_L.js +0 -282
- package/dist/catalog-BAayBt1L.js +0 -185
- package/dist/catalog-BNsf97BM.js +0 -185
- package/dist/channel-options-Dx9nPlX8.js +0 -33
- package/dist/channel-options-ZdvXrTGs.js +0 -32
- package/dist/channel-selection-CujyiWGM.js +0 -51
- package/dist/channel-selection-DfGpCyh2.js +0 -51
- package/dist/channel-web-CC0hkgkR.js +0 -2162
- package/dist/channels-cli-D7lNBpIb.js +0 -1304
- package/dist/channels-cli-DUPG8WDv.js +0 -1306
- package/dist/channels-status-issues-DBc1pU_R.js +0 -18
- package/dist/channels-status-issues-DjO9MHIG.js +0 -18
- package/dist/chrome-Bi6iZ5sG.js +0 -1601
- package/dist/chrome-DNSv7Cpy.js +0 -1629
- package/dist/chrome-DScZx4Lk.js +0 -1601
- package/dist/chunk-mxPVo000.js +0 -348
- package/dist/clack-prompter-B0kl7shw.js +0 -92
- package/dist/clack-prompter-B1YxZdRy.js +0 -92
- package/dist/cli-CfHUkOD0.js +0 -101
- package/dist/cli-ClMrIh6l.js +0 -99
- package/dist/cli-session-BkPTd9Pk.js +0 -5463
- package/dist/cli-session-Dd8DKb5a.js +0 -5408
- package/dist/client-C1avc0vD.js +0 -1692
- package/dist/client-CC94YZrT.js +0 -1692
- package/dist/clipboard-B2fBy8tG.js +0 -31
- package/dist/clipboard-BbGnZskJ.js +0 -31
- package/dist/command-format-Clp46jkj.js +0 -38
- package/dist/command-format-DELazozB.js +0 -52
- package/dist/command-format-SkzzRqR1.js +0 -52
- package/dist/command-registry-DZ4hkmA0.js +0 -248
- package/dist/commands-DtYZJSPn.js +0 -568
- package/dist/commands-Dujk1JmY.js +0 -568
- package/dist/commands-registry-Bd0xbvwG.js +0 -766
- package/dist/commands-registry-DYfRSVF3.js +0 -766
- package/dist/common-D6bu0zHC.js +0 -287
- package/dist/common-zW9Y2P1B.js +0 -287
- package/dist/completion-cli-tSe7Pmqm.js +0 -431
- package/dist/completion-cli-vn4IScs5.js +0 -432
- package/dist/config-C8rUDJXY.js +0 -5704
- package/dist/config-CLZ_XGVw.js +0 -6523
- package/dist/config-SY8M0kM_.js +0 -5705
- package/dist/config-cli-1V7D2Wsw.js +0 -247
- package/dist/config-cli-CjWEC81L.js +0 -244
- package/dist/config-guard-BW2gpKj_.js +0 -93
- package/dist/config-guard-BvxuzHpo.js +0 -76
- package/dist/config-sync-CoIIbEOe.js +0 -91
- package/dist/config-sync-DvAttep0.js +0 -91
- package/dist/configure-Bf0oupCE.js +0 -959
- package/dist/configure-DRM-7zFf.js +0 -960
- package/dist/context-D5iEFzv9.js +0 -60
- package/dist/control-service-C8m8F9pr.js +0 -72
- package/dist/control-service-DKotCWCg.js +0 -72
- package/dist/cron-cli-DB_FLYHD.js +0 -448
- package/dist/cron-cli-bxm5lrrO.js +0 -451
- package/dist/daemon-cli-1LsOnICv.js +0 -566
- package/dist/daemon-cli-CC2NrJ7a.js +0 -565
- package/dist/daemon-runtime-BXZhtBL9.js +0 -460
- package/dist/daemon-runtime-DW4USC7r.js +0 -460
- package/dist/deliver-B4HuPwJA.js +0 -1162
- package/dist/deliver-LiY5oL52.js +0 -1097
- package/dist/deliver-xrmk7xjh.js +0 -1097
- package/dist/delivery-queue-TnQykYsg.js +0 -220
- package/dist/deps-CMMOiOsF.js +0 -42
- package/dist/devices-cli-Be5he2SA.js +0 -195
- package/dist/devices-cli-z6ecoFe9.js +0 -198
- package/dist/diagnostics-Dj75aEHN.js +0 -35
- package/dist/diagnostics-DlIw6fqD.js +0 -35
- package/dist/directory-cli-CEy-0nxj.js +0 -243
- package/dist/directory-cli-DpzKcigr.js +0 -246
- package/dist/dispatcher-10Shiuz3.js +0 -100
- package/dist/dispatcher-3Jae6AiW.js +0 -100
- package/dist/dns-cli-Bat1pkc-.js +0 -200
- package/dist/dns-cli-NohNyEo0.js +0 -197
- package/dist/dock-DbxBBv30.js +0 -753
- package/dist/dock-cPBY4qGl.js +0 -753
- package/dist/docs-cli-BWp6p-Tq.js +0 -161
- package/dist/docs-cli-x22FnZfL.js +0 -159
- package/dist/doctor-BrT5m_on.js +0 -1815
- package/dist/doctor-Pp2HVnjM.js +0 -1813
- package/dist/doctor-completion-DNTimX9o.js +0 -92
- package/dist/doctor-completion-ylN9QAJ6.js +0 -92
- package/dist/doctor-config-flow-D1w3700T.js +0 -1232
- package/dist/doctor-config-flow-Dq50iE1R.js +0 -1232
- package/dist/engine-B9avUJL5.js +0 -563
- package/dist/engine-BiUQ25D4.js +0 -563
- package/dist/env-0lJfCPsw.js +0 -32
- package/dist/exec-BenD3A5l.js +0 -1167
- package/dist/exec-Bv3pyjeM.js +0 -255
- package/dist/exec-approvals-CdLmKX2R.js +0 -1221
- package/dist/exec-approvals-cli-DXfV6G8H.js +0 -368
- package/dist/exec-approvals-cli-J2cZs10o.js +0 -371
- package/dist/frontmatter-YijVi0FQ.js +0 -204
- package/dist/gateway-cli-DOAbA0pc.js +0 -19972
- package/dist/gateway-cli-QpWtBhQy.js +0 -19971
- package/dist/gateway-rpc-DJKBil9s.js +0 -28
- package/dist/gateway-rpc-DVterpLP.js +0 -28
- package/dist/github-copilot-auth-4IUFp669.js +0 -1418
- package/dist/github-copilot-auth-C9E0IROs.js +0 -1418
- package/dist/gmail-setup-utils-BPo_LkKI.js +0 -428
- package/dist/gmail-setup-utils-D3Yqgor7.js +0 -428
- package/dist/health-BeZnqp6m.js +0 -1258
- package/dist/health-Cn2OoVWZ.js +0 -1253
- package/dist/health-format-CdP99j3Y.js +0 -208
- package/dist/health-format-JEChH08S.js +0 -208
- package/dist/heartbeat-visibility-BL3WAchI.js +0 -98
- package/dist/heartbeat-visibility-CQ9QimI7.js +0 -98
- package/dist/help-format-Dl4bsrLI.js +0 -17
- package/dist/helpers-ZKNRexvX.js +0 -10
- package/dist/hooks-cli-D99hXt7K.js +0 -991
- package/dist/hooks-cli-DMB8RiEO.js +0 -993
- package/dist/hooks-status-B-e96dZj.js +0 -356
- package/dist/hooks-status-C_9sE0ox.js +0 -356
- package/dist/image-ops-Dlt3T7th.js +0 -541
- package/dist/image-ops-omlvdfah.js +0 -541
- package/dist/init-Bm04RagW.js +0 -122
- package/dist/init-CaJBf4p1.js +0 -122
- package/dist/installs-C2iMRBVz.js +0 -383
- package/dist/installs-D-cPGdCw.js +0 -383
- package/dist/ipv4-Bf7NS3QU.js +0 -1964
- package/dist/ipv4-wWNs8IH_.js +0 -1964
- package/dist/lanes-CNxj3tit.js +0 -232
- package/dist/lifecycle-core-B_7XRcvF.js +0 -388
- package/dist/lifecycle-core-By83PVAK.js +0 -387
- package/dist/links-BfjHVTB_.js +0 -15
- package/dist/links-DPGe0OHw.js +0 -15
- package/dist/logging-DB6BQmhi.js +0 -15
- package/dist/logging-mcb66J0p.js +0 -15
- package/dist/login-BDCg6D0N.js +0 -61
- package/dist/login-BDfnbjnZ.js +0 -59
- package/dist/login-BqH1itcg.js +0 -61
- package/dist/login-qr-CyOw3R4r.js +0 -321
- package/dist/login-qr-D8ECtb72.js +0 -323
- package/dist/login-qr-RnR7e4Bw.js +0 -326
- package/dist/logs-cli--j89L74J.js +0 -245
- package/dist/logs-cli-DpEMg_Gq.js +0 -242
- package/dist/manager-B4OyvcxT.js +0 -3244
- package/dist/manager-Cqc1CeH7.js +0 -3246
- package/dist/manager-DUyQPFvj.js +0 -3244
- package/dist/manifest-registry-CW1zCyRF.js +0 -748
- package/dist/manifest-registry-D4lM2RdV.js +0 -748
- package/dist/markdown-tables-BT1X6jqH.js +0 -347
- package/dist/markdown-tables-DHgOK2vI.js +0 -348
- package/dist/media-THyainiE.js +0 -1342
- package/dist/memory-cli-BKocCWXM.js +0 -868
- package/dist/memory-cli-Jmma-xI_.js +0 -869
- package/dist/message-channel-dSTVVCyX.js +0 -110
- package/dist/migrate-BR6iAIjO.js +0 -157
- package/dist/migrate-D0EcMs0f.js +0 -157
- package/dist/model-selection-YcSr9CgC.js +0 -2691
- package/dist/models-1vUQBVfw.js +0 -2510
- package/dist/models-cli-BK3BwUhL.js +0 -2739
- package/dist/models-cli-DECrM8oA.js +0 -258
- package/dist/net-B5lXhYLV.js +0 -218
- package/dist/node-cli-cLHUNpPD.js +0 -1319
- package/dist/node-cli-fO7Y132S.js +0 -1322
- package/dist/node-service-BFxHJsno.js +0 -67
- package/dist/node-service-DUnan4uK.js +0 -67
- package/dist/nodes-cli-BCq35E6N.js +0 -1200
- package/dist/nodes-cli-vD7MwAKP.js +0 -1197
- package/dist/nodes-screen-1YiLkqr5.js +0 -234
- package/dist/nodes-screen-DZeD8hE5.js +0 -234
- package/dist/note-Bi8Wb8DV.js +0 -73
- package/dist/note-uiuPxhyX.js +0 -73
- package/dist/npm-registry-spec-B-XIShkB.js +0 -351
- package/dist/npm-registry-spec-za3itb5Y.js +0 -351
- package/dist/onboard-Ds6w_sWo.js +0 -1165
- package/dist/onboard-SAVx3bp4.js +0 -1166
- package/dist/onboard-channels-Cg_EkBa4.js +0 -672
- package/dist/onboard-channels-D7NbA55V.js +0 -672
- package/dist/onboard-helpers-DO_hgZb9.js +0 -365
- package/dist/onboard-helpers-_XgJgeqh.js +0 -365
- package/dist/onboarding-3hLmDd0r.js +0 -911
- package/dist/onboarding-B4LKLsbU.js +0 -910
- package/dist/orchestrator-BKzmyBWy.js +0 -357
- package/dist/orchestrator-BN3QCz2s.js +0 -357
- package/dist/outbound-BgA9hNlP.js +0 -2062
- package/dist/outbound-CjdvVhUI.js +0 -214
- package/dist/outbound-DOGe6qb2.js +0 -214
- package/dist/outbound-send-deps-Du5aBpd7.js +0 -55
- package/dist/pairing-cli-2vnyg_Nd.js +0 -118
- package/dist/pairing-cli-BH1KQtNV.js +0 -121
- package/dist/pairing-store-DJz_9Gv0.js +0 -388
- package/dist/pairing-store-DmOzxcuk.js +0 -388
- package/dist/path-env-Bu6k0jDQ.js +0 -89
- package/dist/path-env-C0zQSjw8.js +0 -89
- package/dist/paths-BTc4nk-6.js +0 -126
- package/dist/paths-BgUi2Z2G.js +0 -54
- package/dist/paths-C6VCWKo3.js +0 -238
- package/dist/paths-CCxa0o9c.js +0 -222
- package/dist/paths-CxRf2rBG.js +0 -129
- package/dist/paths-hcX1Gqg5.js +0 -129
- package/dist/pi-auth-json-B68R7q7_.js +0 -82
- package/dist/pi-auth-json-CR0jXAgq.js +0 -78
- package/dist/pi-auth-json-ZYzi3nxs.js +0 -80
- package/dist/pi-model-discovery-Cxs4pvC2.js +0 -3
- package/dist/pi-tools.policy-D81U5xy0.js +0 -200
- package/dist/pi-tools.policy-DSHkkb5b.js +0 -200
- package/dist/plugin-auto-enable-CxF4bpDN.js +0 -282
- package/dist/plugin-auto-enable-jNaAeyEh.js +0 -282
- package/dist/plugin-registry-C7XWotZG.js +0 -32
- package/dist/plugin-registry-DcUCbGax.js +0 -32
- package/dist/plugins-B362e77G.js +0 -168
- package/dist/plugins-CmSUIUNi.js +0 -38
- package/dist/plugins-cli-BsCEnoQ7.js +0 -734
- package/dist/plugins-cli-QSIsMUG7.js +0 -736
- package/dist/polls-CItfB1H8.js +0 -1343
- package/dist/ports-BVLMN1Sr.js +0 -96
- package/dist/ports-CqLSlU6Z.js +0 -317
- package/dist/ports-D94CwCrv.js +0 -344
- package/dist/ports-D_NHthOz.js +0 -96
- package/dist/program-DkJHjI0R.js +0 -176
- package/dist/program-context-DnyGM2SC.js +0 -496
- package/dist/progress-Bek_GyWS.js +0 -133
- package/dist/prompt-style-lu0clOOE.js +0 -9
- package/dist/pw-ai-BLVMuSLv.js +0 -1867
- package/dist/pw-ai-DZJWEF_f.js +0 -1865
- package/dist/pw-ai-dzf-ptcn.js +0 -1868
- package/dist/qmd-manager-Cur_Ekn0.js +0 -937
- package/dist/qmd-manager-DNAUuwjK.js +0 -938
- package/dist/qmd-manager-DepEoASu.js +0 -935
- package/dist/register.agent-CSWvzOkR.js +0 -265
- package/dist/register.agent-UeH2NXmH.js +0 -1003
- package/dist/register.anima-DOdee0dh.js +0 -193
- package/dist/register.anima-HHDWsz6r.js +0 -193
- package/dist/register.configure-CSJFxdz9.js +0 -103
- package/dist/register.configure-D84Fvcz4.js +0 -101
- package/dist/register.maintenance-B3pvNbZb.js +0 -543
- package/dist/register.maintenance-BKVOwkw6.js +0 -543
- package/dist/register.message-BAO6CPl2.js +0 -657
- package/dist/register.message-OXoOKE_6.js +0 -660
- package/dist/register.onboard-BK_ixVmD.js +0 -170
- package/dist/register.onboard-cfCaPx6j.js +0 -170
- package/dist/register.setup-BGfDnzph.js +0 -175
- package/dist/register.setup-Y-Q74M-0.js +0 -175
- package/dist/register.status-health-sessions-CT14eitH.js +0 -142
- package/dist/register.status-health-sessions-TfZMzAUn.js +0 -313
- package/dist/register.subclis-BZwdlNHC.js +0 -255
- package/dist/reply-mlsExaZm.js +0 -32212
- package/dist/reply-prefix-B0CfR4bM.js +0 -100
- package/dist/reply-prefix-w4a39ybC.js +0 -100
- package/dist/reply-qalRISe_.js +0 -32212
- package/dist/routes-CENsHJyg.js +0 -1820
- package/dist/routes-DO0HqW2e.js +0 -1820
- package/dist/rpc-C0pjNhBi.js +0 -70
- package/dist/rpc-DZ44PIXE.js +0 -70
- package/dist/run-main-BMpKw8Mp.js +0 -371
- package/dist/runtime-guard-BSUFiAQV.js +0 -60
- package/dist/sandbox-BIGfMYEI.js +0 -858
- package/dist/sandbox-DxP3IpUP.js +0 -859
- package/dist/sandbox-cli-DtLGH8sL.js +0 -461
- package/dist/sandbox-cli-_Tg7lfJ_.js +0 -458
- package/dist/security-cli-BRwgbedo.js +0 -462
- package/dist/security-cli-D3bSuyZt.js +0 -465
- package/dist/server-context-49XFFxFg.js +0 -824
- package/dist/server-context-LrlgrZzS.js +0 -824
- package/dist/server-node-events-Dm52i7NW.js +0 -231
- package/dist/server-node-events-QX523UyF.js +0 -233
- package/dist/service-BNVpYcQe.js +0 -642
- package/dist/service-D56aMXUB.js +0 -642
- package/dist/service-audit-D0X_XAB2.js +0 -488
- package/dist/service-audit-qmf6XMmP.js +0 -488
- package/dist/session-CrQQLLhx.js +0 -179
- package/dist/session-LocsOOWJ.js +0 -181
- package/dist/session-Vlce2BAT.js +0 -181
- package/dist/session-cost-usage-BwiTZuKl.js +0 -600
- package/dist/session-cost-usage-DT9YNXTJ.js +0 -600
- package/dist/sessions-BfV53TbG.js +0 -1296
- package/dist/sessions-BimpX_km.js +0 -180
- package/dist/sessions-DcXpzig0.js +0 -1296
- package/dist/sessions-Wd18dukK.js +0 -2038
- package/dist/shared-Bsr69u_7.js +0 -77
- package/dist/shared-Cgly1vPb.js +0 -66
- package/dist/shared-JOo05hST.js +0 -66
- package/dist/shared-f7dvQsi7.js +0 -77
- package/dist/skill-scanner-CkaVLABv.js +0 -263
- package/dist/skills-B-G7UHOa.js +0 -808
- package/dist/skills-B5LQx4lT.js +0 -807
- package/dist/skills-cli-DUGe2ZWW.js +0 -286
- package/dist/skills-cli-DtOk0bvK.js +0 -289
- package/dist/skills-status-Clq9ZnYu.js +0 -166
- package/dist/skills-status-JQluhU-P.js +0 -166
- package/dist/sqlite-BukcjdJa.js +0 -321
- package/dist/sqlite-CGcOZZ0C.js +0 -368
- package/dist/sqlite-Ck6f9KWc.js +0 -453
- package/dist/start--xmSFepB.js +0 -372
- package/dist/start-BdlZbqrr.js +0 -371
- package/dist/status-BgoeFm6g.js +0 -2137
- package/dist/status-BjjDrUq7.js +0 -27
- package/dist/status-Ct0DgOZ-.js +0 -2132
- package/dist/status-RA_uNmK0.js +0 -27
- package/dist/status.update-BjOH3GlS.js +0 -79
- package/dist/status.update-DLU1qBf0.js +0 -79
- package/dist/subagent-registry-9RLdKxES.js +0 -2760
- package/dist/subagent-registry-Byuex3zp.js +0 -2759
- package/dist/subagent-registry-DOBunBYS.js +0 -14
- package/dist/subsystem-Dowf8fSU.js +0 -860
- package/dist/system-cli-C5oBpzni.js +0 -79
- package/dist/system-cli-DXNKD_Id.js +0 -82
- package/dist/systemd-BSrHDyeU.js +0 -452
- package/dist/systemd-By5xdSB4.js +0 -452
- package/dist/systemd-hints-BtjL_5Rh.js +0 -36
- package/dist/systemd-hints-sJmr6cjb.js +0 -36
- package/dist/systemd-linger-CTmV2Gci.js +0 -75
- package/dist/systemd-linger-CmyqQkeC.js +0 -75
- package/dist/table-BL0lJzsm.js +0 -279
- package/dist/table-DoiRPsn0.js +0 -279
- package/dist/timeout-CswI_K-U.js +0 -232
- package/dist/tokens-C-X7wDKj.js +0 -14
- package/dist/tokens-DkvqA72p.js +0 -14
- package/dist/trash-BJLK1vMn.js +0 -23
- package/dist/trash-_x5UZ94k.js +0 -23
- package/dist/tui-BHjxDFZC.js +0 -3894
- package/dist/tui-CgOocwN8.js +0 -3894
- package/dist/tui-cli-5ANH8dE5.js +0 -47
- package/dist/tui-cli-BQ4P-JW_.js +0 -50
- package/dist/update-LFgxHHPd.js +0 -317
- package/dist/update-TxptCqk7.js +0 -317
- package/dist/update-check-CWc7YXmc.js +0 -400
- package/dist/update-check-IhlWaui6.js +0 -400
- package/dist/update-cli-PtXU62w7.js +0 -1105
- package/dist/update-cli-Va0EtETG.js +0 -1105
- package/dist/update-runner-BLeKFkiB.js +0 -894
- package/dist/update-runner-Iuzpc-_y.js +0 -894
- package/dist/usage-ApGvBLVg.js +0 -4516
- package/dist/utils-Bsw__U-F.js +0 -243
- package/dist/web-B6_Ky60G.js +0 -63
- package/dist/web-EZLQEWXY.js +0 -46842
- package/dist/web-pec8YJUX.js +0 -2203
- package/dist/webhooks-cli-BYQKTHTp.js +0 -319
- package/dist/webhooks-cli-C2_xtsUQ.js +0 -316
- package/dist/whatsapp-actions-C72VCq8f.js +0 -49
- package/dist/whatsapp-actions-Ck9Uv0Nw.js +0 -45
- package/dist/whatsapp-actions-D0reTj2k.js +0 -53
- package/dist/widearea-dns-B6ocX23x.js +0 -127
- package/dist/widearea-dns-NsEUNYwz.js +0 -127
- package/dist/workspace-Dcfoy5JJ.js +0 -649
- package/dist/ws-log-N8R5MvGE.js +0 -267
- package/dist/ws-log-gwFxPxj5.js +0 -267
- /package/dist/{auto-update-CUeF99gI.js → auto-update-CpF0fycd.js} +0 -0
- /package/dist/{auto-update-cgkp9ZTJ.js → auto-update-DNWdO7uF.js} +0 -0
- /package/dist/{brew-CVZkr0GU.js → brew-nqf_MiE4.js} +0 -0
- /package/dist/{budget-DxYQSekw.js → budget-CPedI-qW.js} +0 -0
- /package/dist/{budget-BWBp8Res.js → budget-CRpvqDRX.js} +0 -0
- /package/dist/{cli-utils-DtAxdCte.js → cli-utils-C1YHVD4o.js} +0 -0
- /package/dist/{command-options-CSbuuqHr.js → command-options-BbponVnw.js} +0 -0
- /package/dist/{command-options-Cp1tf96a.js → command-options-s0gnvXnS.js} +0 -0
- /package/dist/{constants-O8yBqCBv.js → constants-Dhb6zSIV.js} +0 -0
- /package/dist/{dangerous-tools-5ObDWy1N.js → dangerous-tools-DGTtJ_JR.js} +0 -0
- /package/dist/{dangerous-tools-Jwr7jqNw.js → dangerous-tools-DxrfTOfT.js} +0 -0
- /package/dist/{delivery-queue-B6IHz4Ry.js → delivery-queue-Bxm0nzw7.js} +0 -0
- /package/dist/{display-BDOsXu8F.js → display-Jy3UdGzA.js} +0 -0
- /package/dist/{errors-CHow2wtt.js → errors-CKaCqKga.js} +0 -0
- /package/dist/{exec-BizYYQgP.js → exec-DDmuVVNq.js} +0 -0
- /package/dist/{format-Mq6iU0_5.js → format-ByEjgyTF.js} +0 -0
- /package/dist/{format-duration-DhWzz_5b.js → format-duration-Aaj5tjJd.js} +0 -0
- /package/dist/{format-relative-C6kUHuOj.js → format-relative-79_Y1n2Y.js} +0 -0
- /package/dist/{help-format-DUBI91Ti.js → help-format-BMKzarov.js} +0 -0
- /package/dist/{helpers-eJFa4K6r.js → helpers-DpEB9Mh0.js} +0 -0
- /package/dist/{helpers-DLgbkcEn.js → helpers-FMld9sBT.js} +0 -0
- /package/dist/{input-provenance-DJBdpeKk.js → input-provenance-Cy_KnBlP.js} +0 -0
- /package/dist/{is-main-Dt9DTcH1.js → is-main-yjaVwMtJ.js} +0 -0
- /package/dist/{loader-l2OBdJ8x.js → loader-Br7Vr0zn.js} +0 -0
- /package/dist/{loader-BoYxRfcW.js → loader-CkmOrXcC.js} +0 -0
- /package/dist/{logging-BdnOSVPD.js → logging-CY-Q5cwf.js} +0 -0
- /package/dist/{message-channel-w4F2b2F6.js → message-channel-dua8OOGJ.js} +0 -0
- /package/dist/{mime-B1ZoR53M.js → mime-CBg4KybI.js} +0 -0
- /package/dist/{model-param-b-DPwyNGn8.js → model-param-b-DW9f0NN8.js} +0 -0
- /package/dist/{node-match-8XZnaid6.js → node-match-BV8bTBd4.js} +0 -0
- /package/dist/{normalize-GDK8JTNW.js → normalize-_lmlBOW9.js} +0 -0
- /package/dist/{openclaw-root-C85WMnVV.js → openclaw-root-JPvmPTf7.js} +0 -0
- /package/dist/{outbound-send-deps-ANnAhImn.js → outbound-send-deps-BfUvuWGa.js} +0 -0
- /package/dist/{parse-6-2MDhdT.js → parse-CZRwKocn.js} +0 -0
- /package/dist/{parse-log-line-Bqh1SSzC.js → parse-log-line-CvrZEK6A.js} +0 -0
- /package/dist/{parse-log-line-DUZCjXbl.js → parse-log-line-mLdat0AH.js} +0 -0
- /package/dist/{parse-port-BKB9Exlg.js → parse-port-BSOOdo7I.js} +0 -0
- /package/dist/{parse-port-DrfvwwiL.js → parse-port-Y0NK62x1.js} +0 -0
- /package/dist/{parse-timeout-Di_tcEmi.js → parse-timeout-DVPQ3n9j.js} +0 -0
- /package/dist/{paths-DcVEkYX5.js → paths-DHjlJ6cn.js} +0 -0
- /package/dist/{pi-model-discovery-DsRqYJLy.js → pi-model-discovery-DzEIEgHL.js} +0 -0
- /package/dist/{plugins-CDJw924T.js → plugins-D6PBOdkn.js} +0 -0
- /package/dist/{program-context-Bvn8046-.js → program-context-Q1hkT73c.js} +0 -0
- /package/dist/{progress-CbZ2D53A.js → progress-C9Ha1NJh.js} +0 -0
- /package/dist/{prompt-style-DKy6qQxR.js → prompt-style-DQi8j03a.js} +0 -0
- /package/dist/{prompts-BI__va99.js → prompts-BEHxUC3w.js} +0 -0
- /package/dist/{prompts-_dDWkCAz.js → prompts-CSOhuiqe.js} +0 -0
- /package/dist/{queue-D_u34pbL.js → queue-BJGo7kAB.js} +0 -0
- /package/dist/{queue-PG591iID.js → queue-DYgUbdoq.js} +0 -0
- /package/dist/{redact-ClVwO7Nn.js → redact-CyKvdFrg.js} +0 -0
- /package/dist/{registry-Bs_DJK9E.js → registry-C5MAYD4V.js} +0 -0
- /package/dist/{registry-D_zlP1U-.js → registry-CRrXXVs0.js} +0 -0
- /package/dist/{requirements-BzZxj2Wu.js → requirements-CGkxTCu4.js} +0 -0
- /package/dist/{requirements-DIW1svgA.js → requirements-CIDaOcbO.js} +0 -0
- /package/dist/{runtime-guard-DeOXA_86.js → runtime-guard-nL3Lp8T-.js} +0 -0
- /package/dist/{secret-equal-Dghy3xsA.js → secret-equal-DJpmLXlG.js} +0 -0
- /package/dist/{send-BhAfdGII.js → send-CTcxgDDU.js} +0 -0
- /package/dist/{send-ga9udK1_.js → send-DPezUR3-.js} +0 -0
- /package/dist/{send-C2t9xpXI.js → send-DZQTaG7-.js} +0 -0
- /package/dist/{send-DigO-i9j.js → send-VDff2gra.js} +0 -0
- /package/dist/{send-Dz2BDHll.js → send-bgQNV8d1.js} +0 -0
- /package/dist/{session-key-BGiG_JcT.js → session-key-CQT-NR6w.js} +0 -0
- /package/dist/{shell-argv-CAq1mLa2.js → shell-argv-n9IueeJQ.js} +0 -0
- /package/dist/{skill-scanner-Coo4QoCd.js → skill-scanner-o6NgVMD9.js} +0 -0
- /package/dist/{status-CMnlcBVc.js → status-C53kTIXF.js} +0 -0
- /package/dist/{status-tDZPwewW.js → status-CZDDA_Sy.js} +0 -0
- /package/dist/{system-run-command-X9lDJIy0.js → system-run-command-BCjUffN9.js} +0 -0
- /package/dist/{system-run-command-DGk7dwQP.js → system-run-command-CqAqKL9K.js} +0 -0
- /package/dist/{tailnet-CuiNECdL.js → tailnet-Ciwjv243.js} +0 -0
- /package/dist/{templates-CeYJjVzw.js → templates-37RKpACb.js} +0 -0
- /package/dist/{templates-I3Z0xplD.js → templates-DPalk30o.js} +0 -0
- /package/dist/{thinking-BXEswx1X.js → thinking-2hxwmvTl.js} +0 -0
- /package/dist/{transcript-events-C1hdue6u.js → transcript-events-Bp7fGnwv.js} +0 -0
- /package/dist/{transcript-tools-DuyYOkUq.js → transcript-tools-D4Lbxlka.js} +0 -0
- /package/dist/{usage-format-BAirWUSO.js → usage-format-6Uar63S0.js} +0 -0
- /package/dist/{utils-C9sj30YY.js → utils-DT8uXjFS.js} +0 -0
- /package/dist/{wsl-CqyuRvtM.js → wsl-CrPvx2kZ.js} +0 -0
- /package/dist/{wsl-ymJYvc9Q.js → wsl-UvJ5dHah.js} +0 -0
|
@@ -0,0 +1,2401 @@
|
|
|
1
|
+
import { g as resolveStateDir, m as resolveOAuthDir, o as resolveConfigPath } from "./paths-BMuHNFxg.js";
|
|
2
|
+
import { l as normalizeAgentId } from "./session-key-DxcgHezu.js";
|
|
3
|
+
import { n as runExec } from "./exec-mhSykkaa.js";
|
|
4
|
+
import { c as resolveDefaultAgentId, s as resolveAgentWorkspaceDir } from "./agent-scope-CPphqq-U.js";
|
|
5
|
+
import { t as formatCliCommand } from "./command-format-CVL4K5cj.js";
|
|
6
|
+
import { D as INCLUDE_KEY, O as MAX_INCLUDE_DEPTH, r as createConfigIO } from "./config-DaqbUdkI.js";
|
|
7
|
+
import { a as MANIFEST_KEY, l as normalizePluginsConfig } from "./manifest-registry-kHX_MFa1.js";
|
|
8
|
+
import { n as formatErrorMessage } from "./errors-Cojm0Kl7.js";
|
|
9
|
+
import { _ as resolveToolProfilePolicy, c as resolveSandboxConfigForAgent, u as resolveSandboxToolPolicyForAgent } from "./sandbox-DBSiVHt_.js";
|
|
10
|
+
import { i as resolveGatewayAuth } from "./auth-BNZsOHGF.js";
|
|
11
|
+
import { a as resolveProfile, i as resolveBrowserConfig, m as resolveBrowserControlAuth } from "./server-context-C0xZbYhg.js";
|
|
12
|
+
import { i as loadWorkspaceSkillEntries } from "./skills-DV4zKdCx.js";
|
|
13
|
+
import { h as GATEWAY_CLIENT_NAMES, m as GATEWAY_CLIENT_MODES } from "./message-channel-D_jIO87f.js";
|
|
14
|
+
import { n as listChannelPlugins, r as normalizeChannelId } from "./plugins-DhcGAPDB.js";
|
|
15
|
+
import { i as readChannelAllowFromStore } from "./pairing-store-BsXzUDPv.js";
|
|
16
|
+
import { t as GatewayClient } from "./client-LRKFjo4A.js";
|
|
17
|
+
import { t as buildGatewayConnectionDetails } from "./call-BYDpTVCZ.js";
|
|
18
|
+
import { n as isToolAllowedByPolicies } from "./pi-tools.policy-Csmla32P.js";
|
|
19
|
+
import { n as DEFAULT_GATEWAY_HTTP_TOOL_DENY } from "./dangerous-tools-DGTtJ_JR.js";
|
|
20
|
+
import { t as resolveChannelDefaultAccountId } from "./helpers-DpEB9Mh0.js";
|
|
21
|
+
import { n as extensionUsesSkippedScannerPath, r as isPathInside, t as scanDirectoryWithSummary } from "./skill-scanner-o6NgVMD9.js";
|
|
22
|
+
import { t as inferParamBFromIdOrName } from "./model-param-b-DW9f0NN8.js";
|
|
23
|
+
import os from "node:os";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
import JSON5 from "json5";
|
|
26
|
+
import * as fs$1 from "node:fs/promises";
|
|
27
|
+
import fs from "node:fs/promises";
|
|
28
|
+
import { randomUUID } from "node:crypto";
|
|
29
|
+
|
|
30
|
+
//#region src/gateway/probe.ts
|
|
31
|
+
async function probeGateway(opts) {
|
|
32
|
+
const startedAt = Date.now();
|
|
33
|
+
const instanceId = randomUUID();
|
|
34
|
+
let connectLatencyMs = null;
|
|
35
|
+
let connectError = null;
|
|
36
|
+
let close = null;
|
|
37
|
+
return await new Promise((resolve) => {
|
|
38
|
+
let settled = false;
|
|
39
|
+
const settle = (result) => {
|
|
40
|
+
if (settled) return;
|
|
41
|
+
settled = true;
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
client.stop();
|
|
44
|
+
resolve({
|
|
45
|
+
url: opts.url,
|
|
46
|
+
...result
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
const client = new GatewayClient({
|
|
50
|
+
url: opts.url,
|
|
51
|
+
token: opts.auth?.token,
|
|
52
|
+
password: opts.auth?.password,
|
|
53
|
+
clientName: GATEWAY_CLIENT_NAMES.CLI,
|
|
54
|
+
clientVersion: "dev",
|
|
55
|
+
mode: GATEWAY_CLIENT_MODES.PROBE,
|
|
56
|
+
instanceId,
|
|
57
|
+
onConnectError: (err) => {
|
|
58
|
+
connectError = formatErrorMessage(err);
|
|
59
|
+
},
|
|
60
|
+
onClose: (code, reason) => {
|
|
61
|
+
close = {
|
|
62
|
+
code,
|
|
63
|
+
reason
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
onHelloOk: async () => {
|
|
67
|
+
connectLatencyMs = Date.now() - startedAt;
|
|
68
|
+
try {
|
|
69
|
+
const [health, status, presence, configSnapshot] = await Promise.all([
|
|
70
|
+
client.request("health"),
|
|
71
|
+
client.request("status"),
|
|
72
|
+
client.request("system-presence"),
|
|
73
|
+
client.request("config.get", {})
|
|
74
|
+
]);
|
|
75
|
+
settle({
|
|
76
|
+
ok: true,
|
|
77
|
+
connectLatencyMs,
|
|
78
|
+
error: null,
|
|
79
|
+
close,
|
|
80
|
+
health,
|
|
81
|
+
status,
|
|
82
|
+
presence: Array.isArray(presence) ? presence : null,
|
|
83
|
+
configSnapshot
|
|
84
|
+
});
|
|
85
|
+
} catch (err) {
|
|
86
|
+
settle({
|
|
87
|
+
ok: false,
|
|
88
|
+
connectLatencyMs,
|
|
89
|
+
error: formatErrorMessage(err),
|
|
90
|
+
close,
|
|
91
|
+
health: null,
|
|
92
|
+
status: null,
|
|
93
|
+
presence: null,
|
|
94
|
+
configSnapshot: null
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
const timer = setTimeout(() => {
|
|
100
|
+
settle({
|
|
101
|
+
ok: false,
|
|
102
|
+
connectLatencyMs,
|
|
103
|
+
error: connectError ? `connect failed: ${connectError}` : "timeout",
|
|
104
|
+
close,
|
|
105
|
+
health: null,
|
|
106
|
+
status: null,
|
|
107
|
+
presence: null,
|
|
108
|
+
configSnapshot: null
|
|
109
|
+
});
|
|
110
|
+
}, Math.max(250, opts.timeoutMs));
|
|
111
|
+
client.start();
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/gateway/node-command-policy.ts
|
|
117
|
+
const CANVAS_COMMANDS = [
|
|
118
|
+
"canvas.present",
|
|
119
|
+
"canvas.hide",
|
|
120
|
+
"canvas.navigate",
|
|
121
|
+
"canvas.eval",
|
|
122
|
+
"canvas.snapshot",
|
|
123
|
+
"canvas.a2ui.push",
|
|
124
|
+
"canvas.a2ui.pushJSONL",
|
|
125
|
+
"canvas.a2ui.reset"
|
|
126
|
+
];
|
|
127
|
+
const CAMERA_COMMANDS = ["camera.list"];
|
|
128
|
+
const CAMERA_DANGEROUS_COMMANDS = ["camera.snap", "camera.clip"];
|
|
129
|
+
const SCREEN_DANGEROUS_COMMANDS = ["screen.record"];
|
|
130
|
+
const LOCATION_COMMANDS = ["location.get"];
|
|
131
|
+
const DEVICE_COMMANDS = ["device.info", "device.status"];
|
|
132
|
+
const CONTACTS_COMMANDS = ["contacts.search"];
|
|
133
|
+
const CONTACTS_DANGEROUS_COMMANDS = ["contacts.add"];
|
|
134
|
+
const CALENDAR_COMMANDS = ["calendar.events"];
|
|
135
|
+
const CALENDAR_DANGEROUS_COMMANDS = ["calendar.add"];
|
|
136
|
+
const REMINDERS_COMMANDS = ["reminders.list"];
|
|
137
|
+
const REMINDERS_DANGEROUS_COMMANDS = ["reminders.add"];
|
|
138
|
+
const PHOTOS_COMMANDS = ["photos.latest"];
|
|
139
|
+
const MOTION_COMMANDS = ["motion.activity", "motion.pedometer"];
|
|
140
|
+
const SMS_DANGEROUS_COMMANDS = ["sms.send"];
|
|
141
|
+
const IOS_SYSTEM_COMMANDS = ["system.notify"];
|
|
142
|
+
const SYSTEM_COMMANDS = [
|
|
143
|
+
"system.run",
|
|
144
|
+
"system.which",
|
|
145
|
+
"system.notify",
|
|
146
|
+
"browser.proxy"
|
|
147
|
+
];
|
|
148
|
+
const DEFAULT_DANGEROUS_NODE_COMMANDS = [
|
|
149
|
+
...CAMERA_DANGEROUS_COMMANDS,
|
|
150
|
+
...SCREEN_DANGEROUS_COMMANDS,
|
|
151
|
+
...CONTACTS_DANGEROUS_COMMANDS,
|
|
152
|
+
...CALENDAR_DANGEROUS_COMMANDS,
|
|
153
|
+
...REMINDERS_DANGEROUS_COMMANDS,
|
|
154
|
+
...SMS_DANGEROUS_COMMANDS
|
|
155
|
+
];
|
|
156
|
+
const PLATFORM_DEFAULTS = {
|
|
157
|
+
ios: [
|
|
158
|
+
...CANVAS_COMMANDS,
|
|
159
|
+
...CAMERA_COMMANDS,
|
|
160
|
+
...LOCATION_COMMANDS,
|
|
161
|
+
...DEVICE_COMMANDS,
|
|
162
|
+
...CONTACTS_COMMANDS,
|
|
163
|
+
...CALENDAR_COMMANDS,
|
|
164
|
+
...REMINDERS_COMMANDS,
|
|
165
|
+
...PHOTOS_COMMANDS,
|
|
166
|
+
...MOTION_COMMANDS,
|
|
167
|
+
...IOS_SYSTEM_COMMANDS
|
|
168
|
+
],
|
|
169
|
+
android: [
|
|
170
|
+
...CANVAS_COMMANDS,
|
|
171
|
+
...CAMERA_COMMANDS,
|
|
172
|
+
...LOCATION_COMMANDS,
|
|
173
|
+
...DEVICE_COMMANDS,
|
|
174
|
+
...CONTACTS_COMMANDS,
|
|
175
|
+
...CALENDAR_COMMANDS,
|
|
176
|
+
...REMINDERS_COMMANDS,
|
|
177
|
+
...PHOTOS_COMMANDS,
|
|
178
|
+
...MOTION_COMMANDS
|
|
179
|
+
],
|
|
180
|
+
macos: [
|
|
181
|
+
...CANVAS_COMMANDS,
|
|
182
|
+
...CAMERA_COMMANDS,
|
|
183
|
+
...LOCATION_COMMANDS,
|
|
184
|
+
...DEVICE_COMMANDS,
|
|
185
|
+
...CONTACTS_COMMANDS,
|
|
186
|
+
...CALENDAR_COMMANDS,
|
|
187
|
+
...REMINDERS_COMMANDS,
|
|
188
|
+
...PHOTOS_COMMANDS,
|
|
189
|
+
...MOTION_COMMANDS,
|
|
190
|
+
...SYSTEM_COMMANDS
|
|
191
|
+
],
|
|
192
|
+
linux: [...SYSTEM_COMMANDS],
|
|
193
|
+
windows: [...SYSTEM_COMMANDS],
|
|
194
|
+
unknown: [
|
|
195
|
+
...CANVAS_COMMANDS,
|
|
196
|
+
...CAMERA_COMMANDS,
|
|
197
|
+
...LOCATION_COMMANDS,
|
|
198
|
+
...SYSTEM_COMMANDS
|
|
199
|
+
]
|
|
200
|
+
};
|
|
201
|
+
function normalizePlatformId(platform, deviceFamily) {
|
|
202
|
+
const raw = (platform ?? "").trim().toLowerCase();
|
|
203
|
+
if (raw.startsWith("ios")) return "ios";
|
|
204
|
+
if (raw.startsWith("android")) return "android";
|
|
205
|
+
if (raw.startsWith("mac")) return "macos";
|
|
206
|
+
if (raw.startsWith("darwin")) return "macos";
|
|
207
|
+
if (raw.startsWith("win")) return "windows";
|
|
208
|
+
if (raw.startsWith("linux")) return "linux";
|
|
209
|
+
const family = (deviceFamily ?? "").trim().toLowerCase();
|
|
210
|
+
if (family.includes("iphone") || family.includes("ipad") || family.includes("ios")) return "ios";
|
|
211
|
+
if (family.includes("android")) return "android";
|
|
212
|
+
if (family.includes("mac")) return "macos";
|
|
213
|
+
if (family.includes("windows")) return "windows";
|
|
214
|
+
if (family.includes("linux")) return "linux";
|
|
215
|
+
return "unknown";
|
|
216
|
+
}
|
|
217
|
+
function resolveNodeCommandAllowlist(cfg, node) {
|
|
218
|
+
const base = PLATFORM_DEFAULTS[normalizePlatformId(node?.platform, node?.deviceFamily)] ?? PLATFORM_DEFAULTS.unknown;
|
|
219
|
+
const extra = cfg.gateway?.nodes?.allowCommands ?? [];
|
|
220
|
+
const deny = new Set(cfg.gateway?.nodes?.denyCommands ?? []);
|
|
221
|
+
const allow = new Set([...base, ...extra].map((cmd) => cmd.trim()).filter(Boolean));
|
|
222
|
+
for (const blocked of deny) {
|
|
223
|
+
const trimmed = blocked.trim();
|
|
224
|
+
if (trimmed) allow.delete(trimmed);
|
|
225
|
+
}
|
|
226
|
+
return allow;
|
|
227
|
+
}
|
|
228
|
+
function isNodeCommandAllowed(params) {
|
|
229
|
+
const command = params.command.trim();
|
|
230
|
+
if (!command) return {
|
|
231
|
+
ok: false,
|
|
232
|
+
reason: "command required"
|
|
233
|
+
};
|
|
234
|
+
if (!params.allowlist.has(command)) return {
|
|
235
|
+
ok: false,
|
|
236
|
+
reason: "command not allowlisted"
|
|
237
|
+
};
|
|
238
|
+
if (Array.isArray(params.declaredCommands) && params.declaredCommands.length > 0) {
|
|
239
|
+
if (!params.declaredCommands.includes(command)) return {
|
|
240
|
+
ok: false,
|
|
241
|
+
reason: "command not declared by node"
|
|
242
|
+
};
|
|
243
|
+
} else return {
|
|
244
|
+
ok: false,
|
|
245
|
+
reason: "node did not declare commands"
|
|
246
|
+
};
|
|
247
|
+
return { ok: true };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region src/gateway/probe-auth.ts
|
|
252
|
+
function resolveGatewayProbeAuth(params) {
|
|
253
|
+
const env = params.env ?? process.env;
|
|
254
|
+
const authToken = params.cfg.gateway?.auth?.token;
|
|
255
|
+
const authPassword = params.cfg.gateway?.auth?.password;
|
|
256
|
+
const remote = params.cfg.gateway?.remote;
|
|
257
|
+
return {
|
|
258
|
+
token: params.mode === "remote" ? typeof remote?.token === "string" && remote.token.trim() ? remote.token.trim() : void 0 : env.ANIMA_GATEWAY_TOKEN?.trim() || (typeof authToken === "string" && authToken.trim() ? authToken.trim() : void 0),
|
|
259
|
+
password: env.ANIMA_GATEWAY_PASSWORD?.trim() || (params.mode === "remote" ? typeof remote?.password === "string" && remote.password.trim() ? remote.password.trim() : void 0 : typeof authPassword === "string" && authPassword.trim() ? authPassword.trim() : void 0)
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
//#endregion
|
|
264
|
+
//#region src/config/commands.ts
|
|
265
|
+
function resolveAutoDefault(providerId) {
|
|
266
|
+
const id = normalizeChannelId(providerId);
|
|
267
|
+
if (!id) return false;
|
|
268
|
+
if (id === "discord" || id === "telegram") return true;
|
|
269
|
+
if (id === "slack") return false;
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
function resolveNativeSkillsEnabled(params) {
|
|
273
|
+
const { providerId, providerSetting, globalSetting } = params;
|
|
274
|
+
const setting = providerSetting === void 0 ? globalSetting : providerSetting;
|
|
275
|
+
if (setting === true) return true;
|
|
276
|
+
if (setting === false) return false;
|
|
277
|
+
return resolveAutoDefault(providerId);
|
|
278
|
+
}
|
|
279
|
+
function resolveNativeCommandsEnabled(params) {
|
|
280
|
+
const { providerId, providerSetting, globalSetting } = params;
|
|
281
|
+
const setting = providerSetting === void 0 ? globalSetting : providerSetting;
|
|
282
|
+
if (setting === true) return true;
|
|
283
|
+
if (setting === false) return false;
|
|
284
|
+
return resolveAutoDefault(providerId);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region src/security/audit-channel.ts
|
|
289
|
+
function normalizeAllowFromList$1(list) {
|
|
290
|
+
if (!Array.isArray(list)) return [];
|
|
291
|
+
return list.map((v) => String(v).trim()).filter(Boolean);
|
|
292
|
+
}
|
|
293
|
+
function normalizeTelegramAllowFromEntry(raw) {
|
|
294
|
+
return (typeof raw === "string" ? raw : typeof raw === "number" ? String(raw) : "").trim().replace(/^(telegram|tg):/i, "").trim();
|
|
295
|
+
}
|
|
296
|
+
function isNumericTelegramUserId(raw) {
|
|
297
|
+
return /^\d+$/.test(raw);
|
|
298
|
+
}
|
|
299
|
+
function classifyChannelWarningSeverity(message) {
|
|
300
|
+
const s = message.toLowerCase();
|
|
301
|
+
if (s.includes("dms: open") || s.includes("grouppolicy=\"open\"") || s.includes("dmpolicy=\"open\"")) return "critical";
|
|
302
|
+
if (s.includes("allows any") || s.includes("anyone can dm") || s.includes("public")) return "critical";
|
|
303
|
+
if (s.includes("locked") || s.includes("disabled")) return "info";
|
|
304
|
+
return "warn";
|
|
305
|
+
}
|
|
306
|
+
async function collectChannelSecurityFindings(params) {
|
|
307
|
+
const findings = [];
|
|
308
|
+
const coerceNativeSetting = (value) => {
|
|
309
|
+
if (value === true) return true;
|
|
310
|
+
if (value === false) return false;
|
|
311
|
+
if (value === "auto") return "auto";
|
|
312
|
+
};
|
|
313
|
+
const warnDmPolicy = async (input) => {
|
|
314
|
+
const policyPath = input.policyPath ?? `${input.allowFromPath}policy`;
|
|
315
|
+
const configAllowFrom = normalizeAllowFromList$1(input.allowFrom);
|
|
316
|
+
const hasWildcard = configAllowFrom.includes("*");
|
|
317
|
+
const dmScope = params.cfg.session?.dmScope ?? "main";
|
|
318
|
+
const storeAllowFrom = await readChannelAllowFromStore(input.provider).catch(() => []);
|
|
319
|
+
const normalizeEntry = input.normalizeEntry ?? ((value) => value);
|
|
320
|
+
const normalizedCfg = configAllowFrom.filter((value) => value !== "*").map((value) => normalizeEntry(value)).map((value) => value.trim()).filter(Boolean);
|
|
321
|
+
const normalizedStore = storeAllowFrom.map((value) => normalizeEntry(value)).map((value) => value.trim()).filter(Boolean);
|
|
322
|
+
const allowCount = Array.from(new Set([...normalizedCfg, ...normalizedStore])).length;
|
|
323
|
+
const isMultiUserDm = hasWildcard || allowCount > 1;
|
|
324
|
+
if (input.dmPolicy === "open") {
|
|
325
|
+
const allowFromKey = `${input.allowFromPath}allowFrom`;
|
|
326
|
+
findings.push({
|
|
327
|
+
checkId: `channels.${input.provider}.dm.open`,
|
|
328
|
+
severity: "critical",
|
|
329
|
+
title: `${input.label} DMs are open`,
|
|
330
|
+
detail: `${policyPath}="open" allows anyone to DM the bot.`,
|
|
331
|
+
remediation: `Use pairing/allowlist; if you really need open DMs, ensure ${allowFromKey} includes "*".`
|
|
332
|
+
});
|
|
333
|
+
if (!hasWildcard) findings.push({
|
|
334
|
+
checkId: `channels.${input.provider}.dm.open_invalid`,
|
|
335
|
+
severity: "warn",
|
|
336
|
+
title: `${input.label} DM config looks inconsistent`,
|
|
337
|
+
detail: `"open" requires ${allowFromKey} to include "*".`
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
if (input.dmPolicy === "disabled") {
|
|
341
|
+
findings.push({
|
|
342
|
+
checkId: `channels.${input.provider}.dm.disabled`,
|
|
343
|
+
severity: "info",
|
|
344
|
+
title: `${input.label} DMs are disabled`,
|
|
345
|
+
detail: `${policyPath}="disabled" ignores inbound DMs.`
|
|
346
|
+
});
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (dmScope === "main" && isMultiUserDm) findings.push({
|
|
350
|
+
checkId: `channels.${input.provider}.dm.scope_main_multiuser`,
|
|
351
|
+
severity: "warn",
|
|
352
|
+
title: `${input.label} DMs share the main session`,
|
|
353
|
+
detail: "Multiple DM senders currently share the main session, which can leak context across users.",
|
|
354
|
+
remediation: "Run: " + formatCliCommand("anima config set session.dmScope \"per-channel-peer\"") + " (or \"per-account-channel-peer\" for multi-account channels) to isolate DM sessions per sender."
|
|
355
|
+
});
|
|
356
|
+
};
|
|
357
|
+
for (const plugin of params.plugins) {
|
|
358
|
+
if (!plugin.security) continue;
|
|
359
|
+
const accountIds = plugin.config.listAccountIds(params.cfg);
|
|
360
|
+
const defaultAccountId = resolveChannelDefaultAccountId({
|
|
361
|
+
plugin,
|
|
362
|
+
cfg: params.cfg,
|
|
363
|
+
accountIds
|
|
364
|
+
});
|
|
365
|
+
const account = plugin.config.resolveAccount(params.cfg, defaultAccountId);
|
|
366
|
+
if (!(plugin.config.isEnabled ? plugin.config.isEnabled(account, params.cfg) : true)) continue;
|
|
367
|
+
if (!(plugin.config.isConfigured ? await plugin.config.isConfigured(account, params.cfg) : true)) continue;
|
|
368
|
+
if (plugin.id === "discord") {
|
|
369
|
+
const discordCfg = account?.config ?? {};
|
|
370
|
+
const nativeEnabled = resolveNativeCommandsEnabled({
|
|
371
|
+
providerId: "discord",
|
|
372
|
+
providerSetting: coerceNativeSetting(discordCfg.commands?.native),
|
|
373
|
+
globalSetting: params.cfg.commands?.native
|
|
374
|
+
});
|
|
375
|
+
const nativeSkillsEnabled = resolveNativeSkillsEnabled({
|
|
376
|
+
providerId: "discord",
|
|
377
|
+
providerSetting: coerceNativeSetting(discordCfg.commands?.nativeSkills),
|
|
378
|
+
globalSetting: params.cfg.commands?.nativeSkills
|
|
379
|
+
});
|
|
380
|
+
if (nativeEnabled || nativeSkillsEnabled) {
|
|
381
|
+
const defaultGroupPolicy = params.cfg.channels?.defaults?.groupPolicy;
|
|
382
|
+
const groupPolicy = discordCfg.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
|
383
|
+
const guildEntries = discordCfg.guilds ?? {};
|
|
384
|
+
const guildsConfigured = Object.keys(guildEntries).length > 0;
|
|
385
|
+
const hasAnyUserAllowlist = Object.values(guildEntries).some((guild) => {
|
|
386
|
+
if (!guild || typeof guild !== "object") return false;
|
|
387
|
+
const g = guild;
|
|
388
|
+
if (Array.isArray(g.users) && g.users.length > 0) return true;
|
|
389
|
+
const channels = g.channels;
|
|
390
|
+
if (!channels || typeof channels !== "object") return false;
|
|
391
|
+
return Object.values(channels).some((channel) => {
|
|
392
|
+
if (!channel || typeof channel !== "object") return false;
|
|
393
|
+
const c = channel;
|
|
394
|
+
return Array.isArray(c.users) && c.users.length > 0;
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
const dmAllowFromRaw = discordCfg.dm?.allowFrom;
|
|
398
|
+
const dmAllowFrom = Array.isArray(dmAllowFromRaw) ? dmAllowFromRaw : [];
|
|
399
|
+
const storeAllowFrom = await readChannelAllowFromStore("discord").catch(() => []);
|
|
400
|
+
const ownerAllowFromConfigured = normalizeAllowFromList$1([...dmAllowFrom, ...storeAllowFrom]).length > 0;
|
|
401
|
+
const useAccessGroups = params.cfg.commands?.useAccessGroups !== false;
|
|
402
|
+
if (!useAccessGroups && groupPolicy !== "disabled" && guildsConfigured && !hasAnyUserAllowlist) findings.push({
|
|
403
|
+
checkId: "channels.discord.commands.native.unrestricted",
|
|
404
|
+
severity: "critical",
|
|
405
|
+
title: "Discord slash commands are unrestricted",
|
|
406
|
+
detail: "commands.useAccessGroups=false disables sender allowlists for Discord slash commands unless a per-guild/channel users allowlist is configured; with no users allowlist, any user in allowed guild channels can invoke /… commands.",
|
|
407
|
+
remediation: "Set commands.useAccessGroups=true (recommended), or configure channels.discord.guilds.<id>.users (or channels.discord.guilds.<id>.channels.<channel>.users)."
|
|
408
|
+
});
|
|
409
|
+
else if (useAccessGroups && groupPolicy !== "disabled" && guildsConfigured && !ownerAllowFromConfigured && !hasAnyUserAllowlist) findings.push({
|
|
410
|
+
checkId: "channels.discord.commands.native.no_allowlists",
|
|
411
|
+
severity: "warn",
|
|
412
|
+
title: "Discord slash commands have no allowlists",
|
|
413
|
+
detail: "Discord slash commands are enabled, but neither an owner allowFrom list nor any per-guild/channel users allowlist is configured; /… commands will be rejected for everyone.",
|
|
414
|
+
remediation: "Add your user id to channels.discord.allowFrom (or approve yourself via pairing), or configure channels.discord.guilds.<id>.users."
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (plugin.id === "slack") {
|
|
419
|
+
const slackCfg = account?.config ?? {};
|
|
420
|
+
const nativeEnabled = resolveNativeCommandsEnabled({
|
|
421
|
+
providerId: "slack",
|
|
422
|
+
providerSetting: coerceNativeSetting(slackCfg.commands?.native),
|
|
423
|
+
globalSetting: params.cfg.commands?.native
|
|
424
|
+
});
|
|
425
|
+
const nativeSkillsEnabled = resolveNativeSkillsEnabled({
|
|
426
|
+
providerId: "slack",
|
|
427
|
+
providerSetting: coerceNativeSetting(slackCfg.commands?.nativeSkills),
|
|
428
|
+
globalSetting: params.cfg.commands?.nativeSkills
|
|
429
|
+
});
|
|
430
|
+
if (nativeEnabled || nativeSkillsEnabled || slackCfg.slashCommand?.enabled === true) if (!(params.cfg.commands?.useAccessGroups !== false)) findings.push({
|
|
431
|
+
checkId: "channels.slack.commands.slash.useAccessGroups_off",
|
|
432
|
+
severity: "critical",
|
|
433
|
+
title: "Slack slash commands bypass access groups",
|
|
434
|
+
detail: "Slack slash/native commands are enabled while commands.useAccessGroups=false; this can allow unrestricted /… command execution from channels/users you didn't explicitly authorize.",
|
|
435
|
+
remediation: "Set commands.useAccessGroups=true (recommended)."
|
|
436
|
+
});
|
|
437
|
+
else {
|
|
438
|
+
const allowFromRaw = account?.config?.allowFrom;
|
|
439
|
+
const legacyAllowFromRaw = account?.dm?.allowFrom;
|
|
440
|
+
const allowFrom = Array.isArray(allowFromRaw) ? allowFromRaw : Array.isArray(legacyAllowFromRaw) ? legacyAllowFromRaw : [];
|
|
441
|
+
const storeAllowFrom = await readChannelAllowFromStore("slack").catch(() => []);
|
|
442
|
+
const ownerAllowFromConfigured = normalizeAllowFromList$1([...allowFrom, ...storeAllowFrom]).length > 0;
|
|
443
|
+
const channels = slackCfg.channels ?? {};
|
|
444
|
+
const hasAnyChannelUsersAllowlist = Object.values(channels).some((value) => {
|
|
445
|
+
if (!value || typeof value !== "object") return false;
|
|
446
|
+
const channel = value;
|
|
447
|
+
return Array.isArray(channel.users) && channel.users.length > 0;
|
|
448
|
+
});
|
|
449
|
+
if (!ownerAllowFromConfigured && !hasAnyChannelUsersAllowlist) findings.push({
|
|
450
|
+
checkId: "channels.slack.commands.slash.no_allowlists",
|
|
451
|
+
severity: "warn",
|
|
452
|
+
title: "Slack slash commands have no allowlists",
|
|
453
|
+
detail: "Slack slash/native commands are enabled, but neither an owner allowFrom list nor any channels.<id>.users allowlist is configured; /… commands will be rejected for everyone.",
|
|
454
|
+
remediation: "Approve yourself via pairing (recommended), or set channels.slack.allowFrom and/or channels.slack.channels.<id>.users."
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
const dmPolicy = plugin.security.resolveDmPolicy?.({
|
|
459
|
+
cfg: params.cfg,
|
|
460
|
+
accountId: defaultAccountId,
|
|
461
|
+
account
|
|
462
|
+
});
|
|
463
|
+
if (dmPolicy) await warnDmPolicy({
|
|
464
|
+
label: plugin.meta.label ?? plugin.id,
|
|
465
|
+
provider: plugin.id,
|
|
466
|
+
dmPolicy: dmPolicy.policy,
|
|
467
|
+
allowFrom: dmPolicy.allowFrom,
|
|
468
|
+
policyPath: dmPolicy.policyPath,
|
|
469
|
+
allowFromPath: dmPolicy.allowFromPath,
|
|
470
|
+
normalizeEntry: dmPolicy.normalizeEntry
|
|
471
|
+
});
|
|
472
|
+
if (plugin.security.collectWarnings) {
|
|
473
|
+
const warnings = await plugin.security.collectWarnings({
|
|
474
|
+
cfg: params.cfg,
|
|
475
|
+
accountId: defaultAccountId,
|
|
476
|
+
account
|
|
477
|
+
});
|
|
478
|
+
for (const message of warnings ?? []) {
|
|
479
|
+
const trimmed = String(message).trim();
|
|
480
|
+
if (!trimmed) continue;
|
|
481
|
+
findings.push({
|
|
482
|
+
checkId: `channels.${plugin.id}.warning.${findings.length + 1}`,
|
|
483
|
+
severity: classifyChannelWarningSeverity(trimmed),
|
|
484
|
+
title: `${plugin.meta.label ?? plugin.id} security warning`,
|
|
485
|
+
detail: trimmed.replace(/^-\s*/, "")
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (plugin.id === "telegram") {
|
|
490
|
+
if (!(params.cfg.commands?.text !== false)) continue;
|
|
491
|
+
const telegramCfg = account?.config ?? {};
|
|
492
|
+
const defaultGroupPolicy = params.cfg.channels?.defaults?.groupPolicy;
|
|
493
|
+
const groupPolicy = telegramCfg.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
|
494
|
+
const groups = telegramCfg.groups;
|
|
495
|
+
const groupsConfigured = Boolean(groups) && Object.keys(groups ?? {}).length > 0;
|
|
496
|
+
if (!(groupPolicy === "open" || groupPolicy === "allowlist" && groupsConfigured)) continue;
|
|
497
|
+
const storeAllowFrom = await readChannelAllowFromStore("telegram").catch(() => []);
|
|
498
|
+
const storeHasWildcard = storeAllowFrom.some((v) => String(v).trim() === "*");
|
|
499
|
+
const invalidTelegramAllowFromEntries = /* @__PURE__ */ new Set();
|
|
500
|
+
for (const entry of storeAllowFrom) {
|
|
501
|
+
const normalized = normalizeTelegramAllowFromEntry(entry);
|
|
502
|
+
if (!normalized || normalized === "*") continue;
|
|
503
|
+
if (!isNumericTelegramUserId(normalized)) invalidTelegramAllowFromEntries.add(normalized);
|
|
504
|
+
}
|
|
505
|
+
const groupAllowFrom = Array.isArray(telegramCfg.groupAllowFrom) ? telegramCfg.groupAllowFrom : [];
|
|
506
|
+
const groupAllowFromHasWildcard = groupAllowFrom.some((v) => String(v).trim() === "*");
|
|
507
|
+
for (const entry of groupAllowFrom) {
|
|
508
|
+
const normalized = normalizeTelegramAllowFromEntry(entry);
|
|
509
|
+
if (!normalized || normalized === "*") continue;
|
|
510
|
+
if (!isNumericTelegramUserId(normalized)) invalidTelegramAllowFromEntries.add(normalized);
|
|
511
|
+
}
|
|
512
|
+
const dmAllowFrom = Array.isArray(telegramCfg.allowFrom) ? telegramCfg.allowFrom : [];
|
|
513
|
+
for (const entry of dmAllowFrom) {
|
|
514
|
+
const normalized = normalizeTelegramAllowFromEntry(entry);
|
|
515
|
+
if (!normalized || normalized === "*") continue;
|
|
516
|
+
if (!isNumericTelegramUserId(normalized)) invalidTelegramAllowFromEntries.add(normalized);
|
|
517
|
+
}
|
|
518
|
+
const anyGroupOverride = Boolean(groups && Object.values(groups).some((value) => {
|
|
519
|
+
if (!value || typeof value !== "object") return false;
|
|
520
|
+
const group = value;
|
|
521
|
+
const allowFrom = Array.isArray(group.allowFrom) ? group.allowFrom : [];
|
|
522
|
+
if (allowFrom.length > 0) {
|
|
523
|
+
for (const entry of allowFrom) {
|
|
524
|
+
const normalized = normalizeTelegramAllowFromEntry(entry);
|
|
525
|
+
if (!normalized || normalized === "*") continue;
|
|
526
|
+
if (!isNumericTelegramUserId(normalized)) invalidTelegramAllowFromEntries.add(normalized);
|
|
527
|
+
}
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
const topics = group.topics;
|
|
531
|
+
if (!topics || typeof topics !== "object") return false;
|
|
532
|
+
return Object.values(topics).some((topicValue) => {
|
|
533
|
+
if (!topicValue || typeof topicValue !== "object") return false;
|
|
534
|
+
const topic = topicValue;
|
|
535
|
+
const topicAllow = Array.isArray(topic.allowFrom) ? topic.allowFrom : [];
|
|
536
|
+
for (const entry of topicAllow) {
|
|
537
|
+
const normalized = normalizeTelegramAllowFromEntry(entry);
|
|
538
|
+
if (!normalized || normalized === "*") continue;
|
|
539
|
+
if (!isNumericTelegramUserId(normalized)) invalidTelegramAllowFromEntries.add(normalized);
|
|
540
|
+
}
|
|
541
|
+
return topicAllow.length > 0;
|
|
542
|
+
});
|
|
543
|
+
}));
|
|
544
|
+
const hasAnySenderAllowlist = storeAllowFrom.length > 0 || groupAllowFrom.length > 0 || anyGroupOverride;
|
|
545
|
+
if (invalidTelegramAllowFromEntries.size > 0) {
|
|
546
|
+
const examples = Array.from(invalidTelegramAllowFromEntries).slice(0, 5);
|
|
547
|
+
const more = invalidTelegramAllowFromEntries.size > examples.length ? ` (+${invalidTelegramAllowFromEntries.size - examples.length} more)` : "";
|
|
548
|
+
findings.push({
|
|
549
|
+
checkId: "channels.telegram.allowFrom.invalid_entries",
|
|
550
|
+
severity: "warn",
|
|
551
|
+
title: "Telegram allowlist contains non-numeric entries",
|
|
552
|
+
detail: `Telegram sender authorization requires numeric Telegram user IDs. Found non-numeric allowFrom entries: ${examples.join(", ")}${more}.`,
|
|
553
|
+
remediation: "Replace @username entries with numeric Telegram user IDs (use onboarding to resolve), then re-run the audit."
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
if (storeHasWildcard || groupAllowFromHasWildcard) {
|
|
557
|
+
findings.push({
|
|
558
|
+
checkId: "channels.telegram.groups.allowFrom.wildcard",
|
|
559
|
+
severity: "critical",
|
|
560
|
+
title: "Telegram group allowlist contains wildcard",
|
|
561
|
+
detail: "Telegram group sender allowlist contains \"*\", which allows any group member to run /… commands and control directives.",
|
|
562
|
+
remediation: "Remove \"*\" from channels.telegram.groupAllowFrom and pairing store; prefer explicit numeric Telegram user IDs."
|
|
563
|
+
});
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
if (!hasAnySenderAllowlist) {
|
|
567
|
+
const providerSetting = telegramCfg.commands?.nativeSkills;
|
|
568
|
+
const skillsEnabled = resolveNativeSkillsEnabled({
|
|
569
|
+
providerId: "telegram",
|
|
570
|
+
providerSetting,
|
|
571
|
+
globalSetting: params.cfg.commands?.nativeSkills
|
|
572
|
+
});
|
|
573
|
+
findings.push({
|
|
574
|
+
checkId: "channels.telegram.groups.allowFrom.missing",
|
|
575
|
+
severity: "critical",
|
|
576
|
+
title: "Telegram group commands have no sender allowlist",
|
|
577
|
+
detail: `Telegram group access is enabled but no sender allowlist is configured; this allows any group member to invoke /… commands` + (skillsEnabled ? " (including skill commands)." : "."),
|
|
578
|
+
remediation: "Approve yourself via pairing (recommended), or set channels.telegram.groupAllowFrom (or per-group groups.<id>.allowFrom)."
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return findings;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
//#endregion
|
|
587
|
+
//#region src/security/audit-extra.sync.ts
|
|
588
|
+
const SMALL_MODEL_PARAM_B_MAX = 300;
|
|
589
|
+
function summarizeGroupPolicy(cfg) {
|
|
590
|
+
const channels = cfg.channels;
|
|
591
|
+
if (!channels || typeof channels !== "object") return {
|
|
592
|
+
open: 0,
|
|
593
|
+
allowlist: 0,
|
|
594
|
+
other: 0
|
|
595
|
+
};
|
|
596
|
+
let open = 0;
|
|
597
|
+
let allowlist = 0;
|
|
598
|
+
let other = 0;
|
|
599
|
+
for (const value of Object.values(channels)) {
|
|
600
|
+
if (!value || typeof value !== "object") continue;
|
|
601
|
+
const policy = value.groupPolicy;
|
|
602
|
+
if (policy === "open") open += 1;
|
|
603
|
+
else if (policy === "allowlist") allowlist += 1;
|
|
604
|
+
else other += 1;
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
open,
|
|
608
|
+
allowlist,
|
|
609
|
+
other
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function isProbablySyncedPath(p) {
|
|
613
|
+
const s = p.toLowerCase();
|
|
614
|
+
return s.includes("icloud") || s.includes("dropbox") || s.includes("google drive") || s.includes("googledrive") || s.includes("onedrive");
|
|
615
|
+
}
|
|
616
|
+
function looksLikeEnvRef(value) {
|
|
617
|
+
const v = value.trim();
|
|
618
|
+
return v.startsWith("${") && v.endsWith("}");
|
|
619
|
+
}
|
|
620
|
+
function isGatewayRemotelyExposed(cfg) {
|
|
621
|
+
if ((typeof cfg.gateway?.bind === "string" ? cfg.gateway.bind : "loopback") !== "loopback") return true;
|
|
622
|
+
const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off";
|
|
623
|
+
return tailscaleMode === "serve" || tailscaleMode === "funnel";
|
|
624
|
+
}
|
|
625
|
+
function addModel(models, raw, source) {
|
|
626
|
+
if (typeof raw !== "string") return;
|
|
627
|
+
const id = raw.trim();
|
|
628
|
+
if (!id) return;
|
|
629
|
+
models.push({
|
|
630
|
+
id,
|
|
631
|
+
source
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
function collectModels(cfg) {
|
|
635
|
+
const out = [];
|
|
636
|
+
addModel(out, cfg.agents?.defaults?.model?.primary, "agents.defaults.model.primary");
|
|
637
|
+
for (const f of cfg.agents?.defaults?.model?.fallbacks ?? []) addModel(out, f, "agents.defaults.model.fallbacks");
|
|
638
|
+
addModel(out, cfg.agents?.defaults?.imageModel?.primary, "agents.defaults.imageModel.primary");
|
|
639
|
+
for (const f of cfg.agents?.defaults?.imageModel?.fallbacks ?? []) addModel(out, f, "agents.defaults.imageModel.fallbacks");
|
|
640
|
+
const list = Array.isArray(cfg.agents?.list) ? cfg.agents?.list : [];
|
|
641
|
+
for (const agent of list ?? []) {
|
|
642
|
+
if (!agent || typeof agent !== "object") continue;
|
|
643
|
+
const id = typeof agent.id === "string" ? agent.id : "";
|
|
644
|
+
const model = agent.model;
|
|
645
|
+
if (typeof model === "string") addModel(out, model, `agents.list.${id}.model`);
|
|
646
|
+
else if (model && typeof model === "object") {
|
|
647
|
+
addModel(out, model.primary, `agents.list.${id}.model.primary`);
|
|
648
|
+
const fallbacks = model.fallbacks;
|
|
649
|
+
if (Array.isArray(fallbacks)) for (const f of fallbacks) addModel(out, f, `agents.list.${id}.model.fallbacks`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return out;
|
|
653
|
+
}
|
|
654
|
+
const LEGACY_MODEL_PATTERNS = [
|
|
655
|
+
{
|
|
656
|
+
id: "openai.gpt35",
|
|
657
|
+
re: /\bgpt-3\.5\b/i,
|
|
658
|
+
label: "GPT-3.5 family"
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
id: "anthropic.claude2",
|
|
662
|
+
re: /\bclaude-(instant|2)\b/i,
|
|
663
|
+
label: "Claude 2/Instant family"
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
id: "openai.gpt4_legacy",
|
|
667
|
+
re: /\bgpt-4-(0314|0613)\b/i,
|
|
668
|
+
label: "Legacy GPT-4 snapshots"
|
|
669
|
+
}
|
|
670
|
+
];
|
|
671
|
+
const WEAK_TIER_MODEL_PATTERNS = [{
|
|
672
|
+
id: "anthropic.haiku",
|
|
673
|
+
re: /\bhaiku\b/i,
|
|
674
|
+
label: "Haiku tier (smaller model)"
|
|
675
|
+
}];
|
|
676
|
+
function isGptModel(id) {
|
|
677
|
+
return /\bgpt-/i.test(id);
|
|
678
|
+
}
|
|
679
|
+
function isGpt5OrHigher(id) {
|
|
680
|
+
return /\bgpt-5(?:\b|[.-])/i.test(id);
|
|
681
|
+
}
|
|
682
|
+
function isClaudeModel(id) {
|
|
683
|
+
return /\bclaude-/i.test(id);
|
|
684
|
+
}
|
|
685
|
+
function isClaude45OrHigher(id) {
|
|
686
|
+
return /\bclaude-[^\s/]*?(?:-4-?(?:[5-9]|[1-9]\d)\b|4\.(?:[5-9]|[1-9]\d)\b|-[5-9](?:\b|[.-]))/i.test(id);
|
|
687
|
+
}
|
|
688
|
+
function extractAgentIdFromSource(source) {
|
|
689
|
+
return source.match(/^agents\.list\.([^.]*)\./)?.[1] ?? null;
|
|
690
|
+
}
|
|
691
|
+
function unionAllow$1(base, extra) {
|
|
692
|
+
if (!Array.isArray(extra) || extra.length === 0) return base;
|
|
693
|
+
if (!Array.isArray(base) || base.length === 0) return Array.from(new Set(["*", ...extra]));
|
|
694
|
+
return Array.from(new Set([...base, ...extra]));
|
|
695
|
+
}
|
|
696
|
+
function pickToolPolicy$1(config) {
|
|
697
|
+
if (!config) return null;
|
|
698
|
+
const allow = Array.isArray(config.allow) ? unionAllow$1(config.allow, config.alsoAllow) : Array.isArray(config.alsoAllow) && config.alsoAllow.length > 0 ? unionAllow$1(void 0, config.alsoAllow) : void 0;
|
|
699
|
+
const deny = Array.isArray(config.deny) ? config.deny : void 0;
|
|
700
|
+
if (!allow && !deny) return null;
|
|
701
|
+
return {
|
|
702
|
+
allow,
|
|
703
|
+
deny
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
function hasConfiguredDockerConfig(docker) {
|
|
707
|
+
if (!docker || typeof docker !== "object") return false;
|
|
708
|
+
return Object.values(docker).some((value) => value !== void 0);
|
|
709
|
+
}
|
|
710
|
+
function normalizeNodeCommand(value) {
|
|
711
|
+
return typeof value === "string" ? value.trim() : "";
|
|
712
|
+
}
|
|
713
|
+
function listKnownNodeCommands(cfg) {
|
|
714
|
+
const baseCfg = {
|
|
715
|
+
...cfg,
|
|
716
|
+
gateway: {
|
|
717
|
+
...cfg.gateway,
|
|
718
|
+
nodes: {
|
|
719
|
+
...cfg.gateway?.nodes,
|
|
720
|
+
denyCommands: []
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
const out = /* @__PURE__ */ new Set();
|
|
725
|
+
for (const platform of [
|
|
726
|
+
"ios",
|
|
727
|
+
"android",
|
|
728
|
+
"macos",
|
|
729
|
+
"linux",
|
|
730
|
+
"windows",
|
|
731
|
+
"unknown"
|
|
732
|
+
]) {
|
|
733
|
+
const allow = resolveNodeCommandAllowlist(baseCfg, { platform });
|
|
734
|
+
for (const cmd of allow) {
|
|
735
|
+
const normalized = normalizeNodeCommand(cmd);
|
|
736
|
+
if (normalized) out.add(normalized);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return out;
|
|
740
|
+
}
|
|
741
|
+
function looksLikeNodeCommandPattern(value) {
|
|
742
|
+
if (!value) return false;
|
|
743
|
+
if (/[?*[\]{}(),|]/.test(value)) return true;
|
|
744
|
+
if (value.startsWith("/") || value.endsWith("/") || value.startsWith("^") || value.endsWith("$")) return true;
|
|
745
|
+
return /\s/.test(value) || value.includes("group:");
|
|
746
|
+
}
|
|
747
|
+
function resolveToolPolicies$1(params) {
|
|
748
|
+
const policies = [];
|
|
749
|
+
const profilePolicy = resolveToolProfilePolicy(params.agentTools?.profile ?? params.cfg.tools?.profile);
|
|
750
|
+
if (profilePolicy) policies.push(profilePolicy);
|
|
751
|
+
const globalPolicy = pickToolPolicy$1(params.cfg.tools ?? void 0);
|
|
752
|
+
if (globalPolicy) policies.push(globalPolicy);
|
|
753
|
+
const agentPolicy = pickToolPolicy$1(params.agentTools);
|
|
754
|
+
if (agentPolicy) policies.push(agentPolicy);
|
|
755
|
+
if (params.sandboxMode === "all") {
|
|
756
|
+
const sandboxPolicy = resolveSandboxToolPolicyForAgent(params.cfg, params.agentId ?? void 0);
|
|
757
|
+
policies.push(sandboxPolicy);
|
|
758
|
+
}
|
|
759
|
+
return policies;
|
|
760
|
+
}
|
|
761
|
+
function hasWebSearchKey(cfg, env) {
|
|
762
|
+
const search = cfg.tools?.web?.search;
|
|
763
|
+
return Boolean(search?.apiKey || search?.perplexity?.apiKey || env.BRAVE_API_KEY || env.PERPLEXITY_API_KEY || env.OPENROUTER_API_KEY);
|
|
764
|
+
}
|
|
765
|
+
function isWebSearchEnabled(cfg, env) {
|
|
766
|
+
const enabled = cfg.tools?.web?.search?.enabled;
|
|
767
|
+
if (enabled === false) return false;
|
|
768
|
+
if (enabled === true) return true;
|
|
769
|
+
return hasWebSearchKey(cfg, env);
|
|
770
|
+
}
|
|
771
|
+
function isWebFetchEnabled(cfg) {
|
|
772
|
+
if (cfg.tools?.web?.fetch?.enabled === false) return false;
|
|
773
|
+
return true;
|
|
774
|
+
}
|
|
775
|
+
function isBrowserEnabled(cfg) {
|
|
776
|
+
try {
|
|
777
|
+
return resolveBrowserConfig(cfg.browser, cfg).enabled;
|
|
778
|
+
} catch {
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function listGroupPolicyOpen(cfg) {
|
|
783
|
+
const out = [];
|
|
784
|
+
const channels = cfg.channels;
|
|
785
|
+
if (!channels || typeof channels !== "object") return out;
|
|
786
|
+
for (const [channelId, value] of Object.entries(channels)) {
|
|
787
|
+
if (!value || typeof value !== "object") continue;
|
|
788
|
+
const section = value;
|
|
789
|
+
if (section.groupPolicy === "open") out.push(`channels.${channelId}.groupPolicy`);
|
|
790
|
+
const accounts = section.accounts;
|
|
791
|
+
if (accounts && typeof accounts === "object") for (const [accountId, accountVal] of Object.entries(accounts)) {
|
|
792
|
+
if (!accountVal || typeof accountVal !== "object") continue;
|
|
793
|
+
if (accountVal.groupPolicy === "open") out.push(`channels.${channelId}.accounts.${accountId}.groupPolicy`);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return out;
|
|
797
|
+
}
|
|
798
|
+
function collectAttackSurfaceSummaryFindings(cfg) {
|
|
799
|
+
const group = summarizeGroupPolicy(cfg);
|
|
800
|
+
const elevated = cfg.tools?.elevated?.enabled !== false;
|
|
801
|
+
const webhooksEnabled = cfg.hooks?.enabled === true;
|
|
802
|
+
const internalHooksEnabled = cfg.hooks?.internal?.enabled === true;
|
|
803
|
+
const browserEnabled = cfg.browser?.enabled ?? true;
|
|
804
|
+
return [{
|
|
805
|
+
checkId: "summary.attack_surface",
|
|
806
|
+
severity: "info",
|
|
807
|
+
title: "Attack surface summary",
|
|
808
|
+
detail: `groups: open=${group.open}, allowlist=${group.allowlist}\ntools.elevated: ${elevated ? "enabled" : "disabled"}\nhooks.webhooks: ${webhooksEnabled ? "enabled" : "disabled"}\nhooks.internal: ${internalHooksEnabled ? "enabled" : "disabled"}\nbrowser control: ${browserEnabled ? "enabled" : "disabled"}`
|
|
809
|
+
}];
|
|
810
|
+
}
|
|
811
|
+
function collectSyncedFolderFindings(params) {
|
|
812
|
+
const findings = [];
|
|
813
|
+
if (isProbablySyncedPath(params.stateDir) || isProbablySyncedPath(params.configPath)) findings.push({
|
|
814
|
+
checkId: "fs.synced_dir",
|
|
815
|
+
severity: "warn",
|
|
816
|
+
title: "State/config path looks like a synced folder",
|
|
817
|
+
detail: `stateDir=${params.stateDir}, configPath=${params.configPath}. Synced folders (iCloud/Dropbox/OneDrive/Google Drive) can leak tokens and transcripts onto other devices.`,
|
|
818
|
+
remediation: `Keep ANIMA_STATE_DIR on a local-only volume and re-run "${formatCliCommand("anima security audit --fix")}".`
|
|
819
|
+
});
|
|
820
|
+
return findings;
|
|
821
|
+
}
|
|
822
|
+
function collectSecretsInConfigFindings(cfg) {
|
|
823
|
+
const findings = [];
|
|
824
|
+
const password = typeof cfg.gateway?.auth?.password === "string" ? cfg.gateway.auth.password.trim() : "";
|
|
825
|
+
if (password && !looksLikeEnvRef(password)) findings.push({
|
|
826
|
+
checkId: "config.secrets.gateway_password_in_config",
|
|
827
|
+
severity: "warn",
|
|
828
|
+
title: "Gateway password is stored in config",
|
|
829
|
+
detail: "gateway.auth.password is set in the config file; prefer environment variables for secrets when possible.",
|
|
830
|
+
remediation: "Prefer ANIMA_GATEWAY_PASSWORD (env) and remove gateway.auth.password from disk."
|
|
831
|
+
});
|
|
832
|
+
const hooksToken = typeof cfg.hooks?.token === "string" ? cfg.hooks.token.trim() : "";
|
|
833
|
+
if (cfg.hooks?.enabled === true && hooksToken && !looksLikeEnvRef(hooksToken)) findings.push({
|
|
834
|
+
checkId: "config.secrets.hooks_token_in_config",
|
|
835
|
+
severity: "info",
|
|
836
|
+
title: "Hooks token is stored in config",
|
|
837
|
+
detail: "hooks.token is set in the config file; keep config perms tight and treat it like an API secret."
|
|
838
|
+
});
|
|
839
|
+
return findings;
|
|
840
|
+
}
|
|
841
|
+
function collectHooksHardeningFindings(cfg, env = process.env) {
|
|
842
|
+
const findings = [];
|
|
843
|
+
if (cfg.hooks?.enabled !== true) return findings;
|
|
844
|
+
const token = typeof cfg.hooks?.token === "string" ? cfg.hooks.token.trim() : "";
|
|
845
|
+
if (token && token.length < 24) findings.push({
|
|
846
|
+
checkId: "hooks.token_too_short",
|
|
847
|
+
severity: "warn",
|
|
848
|
+
title: "Hooks token looks short",
|
|
849
|
+
detail: `hooks.token is ${token.length} chars; prefer a long random token.`
|
|
850
|
+
});
|
|
851
|
+
const gatewayAuth = resolveGatewayAuth({
|
|
852
|
+
authConfig: cfg.gateway?.auth,
|
|
853
|
+
tailscaleMode: cfg.gateway?.tailscale?.mode ?? "off",
|
|
854
|
+
env
|
|
855
|
+
});
|
|
856
|
+
const animaGatewayToken = typeof env.ANIMA_GATEWAY_TOKEN === "string" && env.ANIMA_GATEWAY_TOKEN.trim() ? env.ANIMA_GATEWAY_TOKEN.trim() : null;
|
|
857
|
+
const gatewayToken = gatewayAuth.mode === "token" && typeof gatewayAuth.token === "string" && gatewayAuth.token.trim() ? gatewayAuth.token.trim() : animaGatewayToken ? animaGatewayToken : null;
|
|
858
|
+
if (token && gatewayToken && token === gatewayToken) findings.push({
|
|
859
|
+
checkId: "hooks.token_reuse_gateway_token",
|
|
860
|
+
severity: "warn",
|
|
861
|
+
title: "Hooks token reuses the Gateway token",
|
|
862
|
+
detail: "hooks.token matches gateway.auth token; compromise of hooks expands blast radius to the Gateway API.",
|
|
863
|
+
remediation: "Use a separate hooks.token dedicated to hook ingress."
|
|
864
|
+
});
|
|
865
|
+
if ((typeof cfg.hooks?.path === "string" ? cfg.hooks.path.trim() : "") === "/") findings.push({
|
|
866
|
+
checkId: "hooks.path_root",
|
|
867
|
+
severity: "critical",
|
|
868
|
+
title: "Hooks base path is '/'",
|
|
869
|
+
detail: "hooks.path='/' would shadow other HTTP endpoints and is unsafe.",
|
|
870
|
+
remediation: "Use a dedicated path like '/hooks'."
|
|
871
|
+
});
|
|
872
|
+
const allowRequestSessionKey = cfg.hooks?.allowRequestSessionKey === true;
|
|
873
|
+
const defaultSessionKey = typeof cfg.hooks?.defaultSessionKey === "string" ? cfg.hooks.defaultSessionKey.trim() : "";
|
|
874
|
+
const allowedPrefixes = Array.isArray(cfg.hooks?.allowedSessionKeyPrefixes) ? cfg.hooks.allowedSessionKeyPrefixes.map((prefix) => prefix.trim()).filter((prefix) => prefix.length > 0) : [];
|
|
875
|
+
const remoteExposure = isGatewayRemotelyExposed(cfg);
|
|
876
|
+
if (!defaultSessionKey) findings.push({
|
|
877
|
+
checkId: "hooks.default_session_key_unset",
|
|
878
|
+
severity: "warn",
|
|
879
|
+
title: "hooks.defaultSessionKey is not configured",
|
|
880
|
+
detail: "Hook agent runs without explicit sessionKey use generated per-request keys. Set hooks.defaultSessionKey to keep hook ingress scoped to a known session.",
|
|
881
|
+
remediation: "Set hooks.defaultSessionKey (for example, \"hook:ingress\")."
|
|
882
|
+
});
|
|
883
|
+
if (allowRequestSessionKey) findings.push({
|
|
884
|
+
checkId: "hooks.request_session_key_enabled",
|
|
885
|
+
severity: remoteExposure ? "critical" : "warn",
|
|
886
|
+
title: "External hook payloads may override sessionKey",
|
|
887
|
+
detail: "hooks.allowRequestSessionKey=true allows `/hooks/agent` callers to choose the session key. Treat hook token holders as full-trust unless you also restrict prefixes.",
|
|
888
|
+
remediation: "Set hooks.allowRequestSessionKey=false (recommended) or constrain hooks.allowedSessionKeyPrefixes."
|
|
889
|
+
});
|
|
890
|
+
if (allowRequestSessionKey && allowedPrefixes.length === 0) findings.push({
|
|
891
|
+
checkId: "hooks.request_session_key_prefixes_missing",
|
|
892
|
+
severity: remoteExposure ? "critical" : "warn",
|
|
893
|
+
title: "Request sessionKey override is enabled without prefix restrictions",
|
|
894
|
+
detail: "hooks.allowRequestSessionKey=true and hooks.allowedSessionKeyPrefixes is unset/empty, so request payloads can target arbitrary session key shapes.",
|
|
895
|
+
remediation: "Set hooks.allowedSessionKeyPrefixes (for example, [\"hook:\"]) or disable request overrides."
|
|
896
|
+
});
|
|
897
|
+
return findings;
|
|
898
|
+
}
|
|
899
|
+
function collectGatewayHttpSessionKeyOverrideFindings(cfg) {
|
|
900
|
+
const findings = [];
|
|
901
|
+
const chatCompletionsEnabled = cfg.gateway?.http?.endpoints?.chatCompletions?.enabled === true;
|
|
902
|
+
const responsesEnabled = cfg.gateway?.http?.endpoints?.responses?.enabled === true;
|
|
903
|
+
if (!chatCompletionsEnabled && !responsesEnabled) return findings;
|
|
904
|
+
const enabledEndpoints = [chatCompletionsEnabled ? "/v1/chat/completions" : null, responsesEnabled ? "/v1/responses" : null].filter((entry) => Boolean(entry));
|
|
905
|
+
findings.push({
|
|
906
|
+
checkId: "gateway.http.session_key_override_enabled",
|
|
907
|
+
severity: "info",
|
|
908
|
+
title: "HTTP API session-key override is enabled",
|
|
909
|
+
detail: `${enabledEndpoints.join(", ")} accept x-anima-session-key for per-request session routing. Treat API credential holders as trusted principals.`
|
|
910
|
+
});
|
|
911
|
+
return findings;
|
|
912
|
+
}
|
|
913
|
+
function collectSandboxDockerNoopFindings(cfg) {
|
|
914
|
+
const findings = [];
|
|
915
|
+
const configuredPaths = [];
|
|
916
|
+
const agents = Array.isArray(cfg.agents?.list) ? cfg.agents.list : [];
|
|
917
|
+
const defaultsSandbox = cfg.agents?.defaults?.sandbox;
|
|
918
|
+
const hasDefaultDocker = hasConfiguredDockerConfig(defaultsSandbox?.docker);
|
|
919
|
+
const defaultMode = defaultsSandbox?.mode ?? "off";
|
|
920
|
+
const hasAnySandboxEnabledAgent = agents.some((entry) => {
|
|
921
|
+
if (!entry || typeof entry !== "object" || typeof entry.id !== "string") return false;
|
|
922
|
+
return resolveSandboxConfigForAgent(cfg, entry.id).mode !== "off";
|
|
923
|
+
});
|
|
924
|
+
if (hasDefaultDocker && defaultMode === "off" && !hasAnySandboxEnabledAgent) configuredPaths.push("agents.defaults.sandbox.docker");
|
|
925
|
+
for (const entry of agents) {
|
|
926
|
+
if (!entry || typeof entry !== "object" || typeof entry.id !== "string") continue;
|
|
927
|
+
if (!hasConfiguredDockerConfig(entry.sandbox?.docker)) continue;
|
|
928
|
+
if (resolveSandboxConfigForAgent(cfg, entry.id).mode === "off") configuredPaths.push(`agents.list.${entry.id}.sandbox.docker`);
|
|
929
|
+
}
|
|
930
|
+
if (configuredPaths.length === 0) return findings;
|
|
931
|
+
findings.push({
|
|
932
|
+
checkId: "sandbox.docker_config_mode_off",
|
|
933
|
+
severity: "warn",
|
|
934
|
+
title: "Sandbox docker settings configured while sandbox mode is off",
|
|
935
|
+
detail: "These docker settings will not take effect until sandbox mode is enabled:\n" + configuredPaths.map((entry) => `- ${entry}`).join("\n"),
|
|
936
|
+
remediation: "Enable sandbox mode (`agents.defaults.sandbox.mode=\"non-main\"` or `\"all\"`) where needed, or remove unused docker settings."
|
|
937
|
+
});
|
|
938
|
+
return findings;
|
|
939
|
+
}
|
|
940
|
+
function collectNodeDenyCommandPatternFindings(cfg) {
|
|
941
|
+
const findings = [];
|
|
942
|
+
const denyListRaw = cfg.gateway?.nodes?.denyCommands;
|
|
943
|
+
if (!Array.isArray(denyListRaw) || denyListRaw.length === 0) return findings;
|
|
944
|
+
const denyList = denyListRaw.map(normalizeNodeCommand).filter(Boolean);
|
|
945
|
+
if (denyList.length === 0) return findings;
|
|
946
|
+
const knownCommands = listKnownNodeCommands(cfg);
|
|
947
|
+
const patternLike = denyList.filter((entry) => looksLikeNodeCommandPattern(entry));
|
|
948
|
+
const unknownExact = denyList.filter((entry) => !looksLikeNodeCommandPattern(entry) && !knownCommands.has(entry));
|
|
949
|
+
if (patternLike.length === 0 && unknownExact.length === 0) return findings;
|
|
950
|
+
const detailParts = [];
|
|
951
|
+
if (patternLike.length > 0) detailParts.push(`Pattern-like entries (not supported by exact matching): ${patternLike.join(", ")}`);
|
|
952
|
+
if (unknownExact.length > 0) detailParts.push(`Unknown command names (not in defaults/allowCommands): ${unknownExact.join(", ")}`);
|
|
953
|
+
const examples = Array.from(knownCommands).slice(0, 8);
|
|
954
|
+
findings.push({
|
|
955
|
+
checkId: "gateway.nodes.deny_commands_ineffective",
|
|
956
|
+
severity: "warn",
|
|
957
|
+
title: "Some gateway.nodes.denyCommands entries are ineffective",
|
|
958
|
+
detail: "gateway.nodes.denyCommands uses exact command-name matching only.\n" + detailParts.map((entry) => `- ${entry}`).join("\n"),
|
|
959
|
+
remediation: `Use exact command names (for example: ${examples.join(", ")}). If you need broader restrictions, remove risky commands from allowCommands/default workflows.`
|
|
960
|
+
});
|
|
961
|
+
return findings;
|
|
962
|
+
}
|
|
963
|
+
function collectMinimalProfileOverrideFindings(cfg) {
|
|
964
|
+
const findings = [];
|
|
965
|
+
if (cfg.tools?.profile !== "minimal") return findings;
|
|
966
|
+
const overrides = (cfg.agents?.list ?? []).filter((entry) => {
|
|
967
|
+
return Boolean(entry && typeof entry === "object" && typeof entry.id === "string" && entry.tools?.profile && entry.tools.profile !== "minimal");
|
|
968
|
+
}).map((entry) => `${entry.id}=${entry.tools?.profile}`);
|
|
969
|
+
if (overrides.length === 0) return findings;
|
|
970
|
+
findings.push({
|
|
971
|
+
checkId: "tools.profile_minimal_overridden",
|
|
972
|
+
severity: "warn",
|
|
973
|
+
title: "Global tools.profile=minimal is overridden by agent profiles",
|
|
974
|
+
detail: "Global minimal profile is set, but these agent profiles take precedence:\n" + overrides.map((entry) => `- agents.list.${entry}`).join("\n"),
|
|
975
|
+
remediation: "Set those agents to `tools.profile=\"minimal\"` (or remove the agent override) if you want minimal tools enforced globally."
|
|
976
|
+
});
|
|
977
|
+
return findings;
|
|
978
|
+
}
|
|
979
|
+
function collectModelHygieneFindings(cfg) {
|
|
980
|
+
const findings = [];
|
|
981
|
+
const models = collectModels(cfg);
|
|
982
|
+
if (models.length === 0) return findings;
|
|
983
|
+
const weakMatches = /* @__PURE__ */ new Map();
|
|
984
|
+
const addWeakMatch = (model, source, reason) => {
|
|
985
|
+
const key = `${model}@@${source}`;
|
|
986
|
+
const existing = weakMatches.get(key);
|
|
987
|
+
if (!existing) {
|
|
988
|
+
weakMatches.set(key, {
|
|
989
|
+
model,
|
|
990
|
+
source,
|
|
991
|
+
reasons: [reason]
|
|
992
|
+
});
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
if (!existing.reasons.includes(reason)) existing.reasons.push(reason);
|
|
996
|
+
};
|
|
997
|
+
for (const entry of models) {
|
|
998
|
+
for (const pat of WEAK_TIER_MODEL_PATTERNS) if (pat.re.test(entry.id)) {
|
|
999
|
+
addWeakMatch(entry.id, entry.source, pat.label);
|
|
1000
|
+
break;
|
|
1001
|
+
}
|
|
1002
|
+
if (isGptModel(entry.id) && !isGpt5OrHigher(entry.id)) addWeakMatch(entry.id, entry.source, "Below GPT-5 family");
|
|
1003
|
+
if (isClaudeModel(entry.id) && !isClaude45OrHigher(entry.id)) addWeakMatch(entry.id, entry.source, "Below Claude 4.5");
|
|
1004
|
+
}
|
|
1005
|
+
const matches = [];
|
|
1006
|
+
for (const entry of models) for (const pat of LEGACY_MODEL_PATTERNS) if (pat.re.test(entry.id)) {
|
|
1007
|
+
matches.push({
|
|
1008
|
+
model: entry.id,
|
|
1009
|
+
source: entry.source,
|
|
1010
|
+
reason: pat.label
|
|
1011
|
+
});
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1014
|
+
if (matches.length > 0) {
|
|
1015
|
+
const lines = matches.slice(0, 12).map((m) => `- ${m.model} (${m.reason}) @ ${m.source}`).join("\n");
|
|
1016
|
+
const more = matches.length > 12 ? `\n…${matches.length - 12} more` : "";
|
|
1017
|
+
findings.push({
|
|
1018
|
+
checkId: "models.legacy",
|
|
1019
|
+
severity: "warn",
|
|
1020
|
+
title: "Some configured models look legacy",
|
|
1021
|
+
detail: "Older/legacy models can be less robust against prompt injection and tool misuse.\n" + lines + more,
|
|
1022
|
+
remediation: "Prefer modern, instruction-hardened models for any bot that can run tools."
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
if (weakMatches.size > 0) {
|
|
1026
|
+
const lines = Array.from(weakMatches.values()).slice(0, 12).map((m) => `- ${m.model} (${m.reasons.join("; ")}) @ ${m.source}`).join("\n");
|
|
1027
|
+
const more = weakMatches.size > 12 ? `\n…${weakMatches.size - 12} more` : "";
|
|
1028
|
+
findings.push({
|
|
1029
|
+
checkId: "models.weak_tier",
|
|
1030
|
+
severity: "warn",
|
|
1031
|
+
title: "Some configured models are below recommended tiers",
|
|
1032
|
+
detail: "Smaller/older models are generally more susceptible to prompt injection and tool misuse.\n" + lines + more,
|
|
1033
|
+
remediation: "Use the latest, top-tier model for any bot with tools or untrusted inboxes. Avoid Haiku tiers; prefer GPT-5+ and Claude 4.5+."
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
return findings;
|
|
1037
|
+
}
|
|
1038
|
+
function collectSmallModelRiskFindings(params) {
|
|
1039
|
+
const findings = [];
|
|
1040
|
+
const models = collectModels(params.cfg).filter((entry) => !entry.source.includes("imageModel"));
|
|
1041
|
+
if (models.length === 0) return findings;
|
|
1042
|
+
const smallModels = models.map((entry) => {
|
|
1043
|
+
const paramB = inferParamBFromIdOrName(entry.id);
|
|
1044
|
+
if (!paramB || paramB > SMALL_MODEL_PARAM_B_MAX) return null;
|
|
1045
|
+
return {
|
|
1046
|
+
...entry,
|
|
1047
|
+
paramB
|
|
1048
|
+
};
|
|
1049
|
+
}).filter((entry) => Boolean(entry));
|
|
1050
|
+
if (smallModels.length === 0) return findings;
|
|
1051
|
+
let hasUnsafe = false;
|
|
1052
|
+
const modelLines = [];
|
|
1053
|
+
const exposureSet = /* @__PURE__ */ new Set();
|
|
1054
|
+
for (const entry of smallModels) {
|
|
1055
|
+
const agentId = extractAgentIdFromSource(entry.source);
|
|
1056
|
+
const sandboxMode = resolveSandboxConfigForAgent(params.cfg, agentId ?? void 0).mode;
|
|
1057
|
+
const agentTools = agentId && params.cfg.agents?.list ? params.cfg.agents.list.find((agent) => agent?.id === agentId)?.tools : void 0;
|
|
1058
|
+
const policies = resolveToolPolicies$1({
|
|
1059
|
+
cfg: params.cfg,
|
|
1060
|
+
agentTools,
|
|
1061
|
+
sandboxMode,
|
|
1062
|
+
agentId
|
|
1063
|
+
});
|
|
1064
|
+
const exposed = [];
|
|
1065
|
+
if (isWebSearchEnabled(params.cfg, params.env)) {
|
|
1066
|
+
if (isToolAllowedByPolicies("web_search", policies)) exposed.push("web_search");
|
|
1067
|
+
}
|
|
1068
|
+
if (isWebFetchEnabled(params.cfg)) {
|
|
1069
|
+
if (isToolAllowedByPolicies("web_fetch", policies)) exposed.push("web_fetch");
|
|
1070
|
+
}
|
|
1071
|
+
if (isBrowserEnabled(params.cfg)) {
|
|
1072
|
+
if (isToolAllowedByPolicies("browser", policies)) exposed.push("browser");
|
|
1073
|
+
}
|
|
1074
|
+
for (const tool of exposed) exposureSet.add(tool);
|
|
1075
|
+
const sandboxLabel = sandboxMode === "all" ? "sandbox=all" : `sandbox=${sandboxMode}`;
|
|
1076
|
+
const exposureLabel = exposed.length > 0 ? ` web=[${exposed.join(", ")}]` : " web=[off]";
|
|
1077
|
+
const safe = sandboxMode === "all" && exposed.length === 0;
|
|
1078
|
+
if (!safe) hasUnsafe = true;
|
|
1079
|
+
const statusLabel = safe ? "ok" : "unsafe";
|
|
1080
|
+
modelLines.push(`- ${entry.id} (${entry.paramB}B) @ ${entry.source} (${statusLabel}; ${sandboxLabel};${exposureLabel})`);
|
|
1081
|
+
}
|
|
1082
|
+
const exposureList = Array.from(exposureSet);
|
|
1083
|
+
const exposureDetail = exposureList.length > 0 ? `Uncontrolled input tools allowed: ${exposureList.join(", ")}.` : "No web/browser tools detected for these models.";
|
|
1084
|
+
findings.push({
|
|
1085
|
+
checkId: "models.small_params",
|
|
1086
|
+
severity: hasUnsafe ? "critical" : "info",
|
|
1087
|
+
title: "Small models require sandboxing and web tools disabled",
|
|
1088
|
+
detail: `Small models (<=${SMALL_MODEL_PARAM_B_MAX}B params) detected:\n` + modelLines.join("\n") + `\n` + exposureDetail + "\nSmall models are not recommended for untrusted inputs.",
|
|
1089
|
+
remediation: "If you must use small models, enable sandboxing for all sessions (agents.defaults.sandbox.mode=\"all\") and disable web_search/web_fetch/browser (tools.deny=[\"group:web\",\"browser\"])."
|
|
1090
|
+
});
|
|
1091
|
+
return findings;
|
|
1092
|
+
}
|
|
1093
|
+
function collectExposureMatrixFindings(cfg) {
|
|
1094
|
+
const findings = [];
|
|
1095
|
+
const openGroups = listGroupPolicyOpen(cfg);
|
|
1096
|
+
if (openGroups.length === 0) return findings;
|
|
1097
|
+
if (cfg.tools?.elevated?.enabled !== false) findings.push({
|
|
1098
|
+
checkId: "security.exposure.open_groups_with_elevated",
|
|
1099
|
+
severity: "critical",
|
|
1100
|
+
title: "Open groupPolicy with elevated tools enabled",
|
|
1101
|
+
detail: `Found groupPolicy="open" at:\n${openGroups.map((p) => `- ${p}`).join("\n")}\nWith tools.elevated enabled, a prompt injection in those rooms can become a high-impact incident.`,
|
|
1102
|
+
remediation: `Set groupPolicy="allowlist" and keep elevated allowlists extremely tight.`
|
|
1103
|
+
});
|
|
1104
|
+
return findings;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
//#endregion
|
|
1108
|
+
//#region src/config/includes-scan.ts
|
|
1109
|
+
function listDirectIncludes(parsed) {
|
|
1110
|
+
const out = [];
|
|
1111
|
+
const visit = (value) => {
|
|
1112
|
+
if (!value) return;
|
|
1113
|
+
if (Array.isArray(value)) {
|
|
1114
|
+
for (const item of value) visit(item);
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
if (typeof value !== "object") return;
|
|
1118
|
+
const rec = value;
|
|
1119
|
+
const includeVal = rec[INCLUDE_KEY];
|
|
1120
|
+
if (typeof includeVal === "string") out.push(includeVal);
|
|
1121
|
+
else if (Array.isArray(includeVal)) {
|
|
1122
|
+
for (const item of includeVal) if (typeof item === "string") out.push(item);
|
|
1123
|
+
}
|
|
1124
|
+
for (const v of Object.values(rec)) visit(v);
|
|
1125
|
+
};
|
|
1126
|
+
visit(parsed);
|
|
1127
|
+
return out;
|
|
1128
|
+
}
|
|
1129
|
+
function resolveIncludePath(baseConfigPath, includePath) {
|
|
1130
|
+
return path.normalize(path.isAbsolute(includePath) ? includePath : path.resolve(path.dirname(baseConfigPath), includePath));
|
|
1131
|
+
}
|
|
1132
|
+
async function collectIncludePathsRecursive(params) {
|
|
1133
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1134
|
+
const result = [];
|
|
1135
|
+
const walk = async (basePath, parsed, depth) => {
|
|
1136
|
+
if (depth > MAX_INCLUDE_DEPTH) return;
|
|
1137
|
+
for (const raw of listDirectIncludes(parsed)) {
|
|
1138
|
+
const resolved = resolveIncludePath(basePath, raw);
|
|
1139
|
+
if (visited.has(resolved)) continue;
|
|
1140
|
+
visited.add(resolved);
|
|
1141
|
+
result.push(resolved);
|
|
1142
|
+
const rawText = await fs$1.readFile(resolved, "utf-8").catch(() => null);
|
|
1143
|
+
if (!rawText) continue;
|
|
1144
|
+
const nestedParsed = (() => {
|
|
1145
|
+
try {
|
|
1146
|
+
return JSON5.parse(rawText);
|
|
1147
|
+
} catch {
|
|
1148
|
+
return null;
|
|
1149
|
+
}
|
|
1150
|
+
})();
|
|
1151
|
+
if (nestedParsed) await walk(resolved, nestedParsed, depth + 1);
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
await walk(params.configPath, params.parsed, 0);
|
|
1155
|
+
return result;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
//#endregion
|
|
1159
|
+
//#region src/security/windows-acl.ts
|
|
1160
|
+
const INHERIT_FLAGS = new Set([
|
|
1161
|
+
"I",
|
|
1162
|
+
"OI",
|
|
1163
|
+
"CI",
|
|
1164
|
+
"IO",
|
|
1165
|
+
"NP"
|
|
1166
|
+
]);
|
|
1167
|
+
const WORLD_PRINCIPALS = new Set([
|
|
1168
|
+
"everyone",
|
|
1169
|
+
"users",
|
|
1170
|
+
"builtin\\users",
|
|
1171
|
+
"authenticated users",
|
|
1172
|
+
"nt authority\\authenticated users"
|
|
1173
|
+
]);
|
|
1174
|
+
const TRUSTED_BASE = new Set([
|
|
1175
|
+
"nt authority\\system",
|
|
1176
|
+
"system",
|
|
1177
|
+
"builtin\\administrators",
|
|
1178
|
+
"creator owner"
|
|
1179
|
+
]);
|
|
1180
|
+
const WORLD_SUFFIXES = ["\\users", "\\authenticated users"];
|
|
1181
|
+
const TRUSTED_SUFFIXES = ["\\administrators", "\\system"];
|
|
1182
|
+
const normalize = (value) => value.trim().toLowerCase();
|
|
1183
|
+
function resolveWindowsUserPrincipal(env) {
|
|
1184
|
+
const username = env?.USERNAME?.trim() || os.userInfo().username?.trim();
|
|
1185
|
+
if (!username) return null;
|
|
1186
|
+
const domain = env?.USERDOMAIN?.trim();
|
|
1187
|
+
return domain ? `${domain}\\${username}` : username;
|
|
1188
|
+
}
|
|
1189
|
+
function buildTrustedPrincipals(env) {
|
|
1190
|
+
const trusted = new Set(TRUSTED_BASE);
|
|
1191
|
+
const principal = resolveWindowsUserPrincipal(env);
|
|
1192
|
+
if (principal) {
|
|
1193
|
+
trusted.add(normalize(principal));
|
|
1194
|
+
const userOnly = principal.split("\\").at(-1);
|
|
1195
|
+
if (userOnly) trusted.add(normalize(userOnly));
|
|
1196
|
+
}
|
|
1197
|
+
return trusted;
|
|
1198
|
+
}
|
|
1199
|
+
function classifyPrincipal(principal, env) {
|
|
1200
|
+
const normalized = normalize(principal);
|
|
1201
|
+
if (buildTrustedPrincipals(env).has(normalized) || TRUSTED_SUFFIXES.some((s) => normalized.endsWith(s))) return "trusted";
|
|
1202
|
+
if (WORLD_PRINCIPALS.has(normalized) || WORLD_SUFFIXES.some((s) => normalized.endsWith(s))) return "world";
|
|
1203
|
+
return "group";
|
|
1204
|
+
}
|
|
1205
|
+
function rightsFromTokens(tokens) {
|
|
1206
|
+
const upper = tokens.join("").toUpperCase();
|
|
1207
|
+
const canWrite = upper.includes("F") || upper.includes("M") || upper.includes("W") || upper.includes("D");
|
|
1208
|
+
return {
|
|
1209
|
+
canRead: upper.includes("F") || upper.includes("M") || upper.includes("R"),
|
|
1210
|
+
canWrite
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
function parseIcaclsOutput(output, targetPath) {
|
|
1214
|
+
const entries = [];
|
|
1215
|
+
const normalizedTarget = targetPath.trim();
|
|
1216
|
+
const lowerTarget = normalizedTarget.toLowerCase();
|
|
1217
|
+
const quotedTarget = `"${normalizedTarget}"`;
|
|
1218
|
+
const quotedLower = quotedTarget.toLowerCase();
|
|
1219
|
+
for (const rawLine of output.split(/\r?\n/)) {
|
|
1220
|
+
const line = rawLine.trimEnd();
|
|
1221
|
+
if (!line.trim()) continue;
|
|
1222
|
+
const trimmed = line.trim();
|
|
1223
|
+
const lower = trimmed.toLowerCase();
|
|
1224
|
+
if (lower.startsWith("successfully processed") || lower.startsWith("processed") || lower.startsWith("failed processing") || lower.startsWith("no mapping between account names")) continue;
|
|
1225
|
+
let entry = trimmed;
|
|
1226
|
+
if (lower.startsWith(lowerTarget)) entry = trimmed.slice(normalizedTarget.length).trim();
|
|
1227
|
+
else if (lower.startsWith(quotedLower)) entry = trimmed.slice(quotedTarget.length).trim();
|
|
1228
|
+
if (!entry) continue;
|
|
1229
|
+
const idx = entry.indexOf(":");
|
|
1230
|
+
if (idx === -1) continue;
|
|
1231
|
+
const principal = entry.slice(0, idx).trim();
|
|
1232
|
+
const rawRights = entry.slice(idx + 1).trim();
|
|
1233
|
+
const tokens = rawRights.match(/\(([^)]+)\)/g)?.map((token) => token.slice(1, -1).trim()).filter(Boolean) ?? [];
|
|
1234
|
+
if (tokens.some((token) => token.toUpperCase() === "DENY")) continue;
|
|
1235
|
+
const rights = tokens.filter((token) => !INHERIT_FLAGS.has(token.toUpperCase()));
|
|
1236
|
+
if (rights.length === 0) continue;
|
|
1237
|
+
const { canRead, canWrite } = rightsFromTokens(rights);
|
|
1238
|
+
entries.push({
|
|
1239
|
+
principal,
|
|
1240
|
+
rights,
|
|
1241
|
+
rawRights,
|
|
1242
|
+
canRead,
|
|
1243
|
+
canWrite
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
return entries;
|
|
1247
|
+
}
|
|
1248
|
+
function summarizeWindowsAcl(entries, env) {
|
|
1249
|
+
const trusted = [];
|
|
1250
|
+
const untrustedWorld = [];
|
|
1251
|
+
const untrustedGroup = [];
|
|
1252
|
+
for (const entry of entries) {
|
|
1253
|
+
const classification = classifyPrincipal(entry.principal, env);
|
|
1254
|
+
if (classification === "trusted") trusted.push(entry);
|
|
1255
|
+
else if (classification === "world") untrustedWorld.push(entry);
|
|
1256
|
+
else untrustedGroup.push(entry);
|
|
1257
|
+
}
|
|
1258
|
+
return {
|
|
1259
|
+
trusted,
|
|
1260
|
+
untrustedWorld,
|
|
1261
|
+
untrustedGroup
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
async function inspectWindowsAcl(targetPath, opts) {
|
|
1265
|
+
const exec = opts?.exec ?? runExec;
|
|
1266
|
+
try {
|
|
1267
|
+
const { stdout, stderr } = await exec("icacls", [targetPath]);
|
|
1268
|
+
const entries = parseIcaclsOutput(`${stdout}\n${stderr}`.trim(), targetPath);
|
|
1269
|
+
const { trusted, untrustedWorld, untrustedGroup } = summarizeWindowsAcl(entries, opts?.env);
|
|
1270
|
+
return {
|
|
1271
|
+
ok: true,
|
|
1272
|
+
entries,
|
|
1273
|
+
trusted,
|
|
1274
|
+
untrustedWorld,
|
|
1275
|
+
untrustedGroup
|
|
1276
|
+
};
|
|
1277
|
+
} catch (err) {
|
|
1278
|
+
return {
|
|
1279
|
+
ok: false,
|
|
1280
|
+
entries: [],
|
|
1281
|
+
trusted: [],
|
|
1282
|
+
untrustedWorld: [],
|
|
1283
|
+
untrustedGroup: [],
|
|
1284
|
+
error: String(err)
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
function formatWindowsAclSummary(summary) {
|
|
1289
|
+
if (!summary.ok) return "unknown";
|
|
1290
|
+
const untrusted = [...summary.untrustedWorld, ...summary.untrustedGroup];
|
|
1291
|
+
if (untrusted.length === 0) return "trusted-only";
|
|
1292
|
+
return untrusted.map((entry) => `${entry.principal}:${entry.rawRights}`).join(", ");
|
|
1293
|
+
}
|
|
1294
|
+
function formatIcaclsResetCommand(targetPath, opts) {
|
|
1295
|
+
const user = resolveWindowsUserPrincipal(opts.env) ?? "%USERNAME%";
|
|
1296
|
+
const grant = opts.isDir ? "(OI)(CI)F" : "F";
|
|
1297
|
+
return `icacls "${targetPath}" /inheritance:r /grant:r "${user}:${grant}" /grant:r "SYSTEM:${grant}"`;
|
|
1298
|
+
}
|
|
1299
|
+
function createIcaclsResetCommand(targetPath, opts) {
|
|
1300
|
+
const user = resolveWindowsUserPrincipal(opts.env);
|
|
1301
|
+
if (!user) return null;
|
|
1302
|
+
const grant = opts.isDir ? "(OI)(CI)F" : "F";
|
|
1303
|
+
return {
|
|
1304
|
+
command: "icacls",
|
|
1305
|
+
args: [
|
|
1306
|
+
targetPath,
|
|
1307
|
+
"/inheritance:r",
|
|
1308
|
+
"/grant:r",
|
|
1309
|
+
`${user}:${grant}`,
|
|
1310
|
+
"/grant:r",
|
|
1311
|
+
`SYSTEM:${grant}`
|
|
1312
|
+
],
|
|
1313
|
+
display: formatIcaclsResetCommand(targetPath, opts)
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
//#endregion
|
|
1318
|
+
//#region src/security/audit-fs.ts
|
|
1319
|
+
async function safeStat(targetPath) {
|
|
1320
|
+
try {
|
|
1321
|
+
const lst = await fs.lstat(targetPath);
|
|
1322
|
+
return {
|
|
1323
|
+
ok: true,
|
|
1324
|
+
isSymlink: lst.isSymbolicLink(),
|
|
1325
|
+
isDir: lst.isDirectory(),
|
|
1326
|
+
mode: typeof lst.mode === "number" ? lst.mode : null,
|
|
1327
|
+
uid: typeof lst.uid === "number" ? lst.uid : null,
|
|
1328
|
+
gid: typeof lst.gid === "number" ? lst.gid : null
|
|
1329
|
+
};
|
|
1330
|
+
} catch (err) {
|
|
1331
|
+
return {
|
|
1332
|
+
ok: false,
|
|
1333
|
+
isSymlink: false,
|
|
1334
|
+
isDir: false,
|
|
1335
|
+
mode: null,
|
|
1336
|
+
uid: null,
|
|
1337
|
+
gid: null,
|
|
1338
|
+
error: String(err)
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
async function inspectPathPermissions(targetPath, opts) {
|
|
1343
|
+
const st = await safeStat(targetPath);
|
|
1344
|
+
if (!st.ok) return {
|
|
1345
|
+
ok: false,
|
|
1346
|
+
isSymlink: false,
|
|
1347
|
+
isDir: false,
|
|
1348
|
+
mode: null,
|
|
1349
|
+
bits: null,
|
|
1350
|
+
source: "unknown",
|
|
1351
|
+
worldWritable: false,
|
|
1352
|
+
groupWritable: false,
|
|
1353
|
+
worldReadable: false,
|
|
1354
|
+
groupReadable: false,
|
|
1355
|
+
error: st.error
|
|
1356
|
+
};
|
|
1357
|
+
const bits = modeBits(st.mode);
|
|
1358
|
+
if ((opts?.platform ?? process.platform) === "win32") {
|
|
1359
|
+
const acl = await inspectWindowsAcl(targetPath, {
|
|
1360
|
+
env: opts?.env,
|
|
1361
|
+
exec: opts?.exec
|
|
1362
|
+
});
|
|
1363
|
+
if (!acl.ok) return {
|
|
1364
|
+
ok: true,
|
|
1365
|
+
isSymlink: st.isSymlink,
|
|
1366
|
+
isDir: st.isDir,
|
|
1367
|
+
mode: st.mode,
|
|
1368
|
+
bits,
|
|
1369
|
+
source: "unknown",
|
|
1370
|
+
worldWritable: false,
|
|
1371
|
+
groupWritable: false,
|
|
1372
|
+
worldReadable: false,
|
|
1373
|
+
groupReadable: false,
|
|
1374
|
+
error: acl.error
|
|
1375
|
+
};
|
|
1376
|
+
return {
|
|
1377
|
+
ok: true,
|
|
1378
|
+
isSymlink: st.isSymlink,
|
|
1379
|
+
isDir: st.isDir,
|
|
1380
|
+
mode: st.mode,
|
|
1381
|
+
bits,
|
|
1382
|
+
source: "windows-acl",
|
|
1383
|
+
worldWritable: acl.untrustedWorld.some((entry) => entry.canWrite),
|
|
1384
|
+
groupWritable: acl.untrustedGroup.some((entry) => entry.canWrite),
|
|
1385
|
+
worldReadable: acl.untrustedWorld.some((entry) => entry.canRead),
|
|
1386
|
+
groupReadable: acl.untrustedGroup.some((entry) => entry.canRead),
|
|
1387
|
+
aclSummary: formatWindowsAclSummary(acl)
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
return {
|
|
1391
|
+
ok: true,
|
|
1392
|
+
isSymlink: st.isSymlink,
|
|
1393
|
+
isDir: st.isDir,
|
|
1394
|
+
mode: st.mode,
|
|
1395
|
+
bits,
|
|
1396
|
+
source: "posix",
|
|
1397
|
+
worldWritable: isWorldWritable(bits),
|
|
1398
|
+
groupWritable: isGroupWritable(bits),
|
|
1399
|
+
worldReadable: isWorldReadable(bits),
|
|
1400
|
+
groupReadable: isGroupReadable(bits)
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
function formatPermissionDetail(targetPath, perms) {
|
|
1404
|
+
if (perms.source === "windows-acl") return `${targetPath} acl=${perms.aclSummary ?? "unknown"}`;
|
|
1405
|
+
return `${targetPath} mode=${formatOctal(perms.bits)}`;
|
|
1406
|
+
}
|
|
1407
|
+
function formatPermissionRemediation(params) {
|
|
1408
|
+
if (params.perms.source === "windows-acl") return formatIcaclsResetCommand(params.targetPath, {
|
|
1409
|
+
isDir: params.isDir,
|
|
1410
|
+
env: params.env
|
|
1411
|
+
});
|
|
1412
|
+
return `chmod ${params.posixMode.toString(8).padStart(3, "0")} ${params.targetPath}`;
|
|
1413
|
+
}
|
|
1414
|
+
function modeBits(mode) {
|
|
1415
|
+
if (mode == null) return null;
|
|
1416
|
+
return mode & 511;
|
|
1417
|
+
}
|
|
1418
|
+
function formatOctal(bits) {
|
|
1419
|
+
if (bits == null) return "unknown";
|
|
1420
|
+
return bits.toString(8).padStart(3, "0");
|
|
1421
|
+
}
|
|
1422
|
+
function isWorldWritable(bits) {
|
|
1423
|
+
if (bits == null) return false;
|
|
1424
|
+
return (bits & 2) !== 0;
|
|
1425
|
+
}
|
|
1426
|
+
function isGroupWritable(bits) {
|
|
1427
|
+
if (bits == null) return false;
|
|
1428
|
+
return (bits & 16) !== 0;
|
|
1429
|
+
}
|
|
1430
|
+
function isWorldReadable(bits) {
|
|
1431
|
+
if (bits == null) return false;
|
|
1432
|
+
return (bits & 4) !== 0;
|
|
1433
|
+
}
|
|
1434
|
+
function isGroupReadable(bits) {
|
|
1435
|
+
if (bits == null) return false;
|
|
1436
|
+
return (bits & 32) !== 0;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
//#endregion
|
|
1440
|
+
//#region src/security/audit-extra.async.ts
|
|
1441
|
+
/**
|
|
1442
|
+
* Asynchronous security audit collector functions.
|
|
1443
|
+
*
|
|
1444
|
+
* These functions perform I/O (filesystem, config reads) to detect security issues.
|
|
1445
|
+
*/
|
|
1446
|
+
function expandTilde(p, env) {
|
|
1447
|
+
if (!p.startsWith("~")) return p;
|
|
1448
|
+
const home = typeof env.HOME === "string" && env.HOME.trim() ? env.HOME.trim() : null;
|
|
1449
|
+
if (!home) return null;
|
|
1450
|
+
if (p === "~") return home;
|
|
1451
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) return path.join(home, p.slice(2));
|
|
1452
|
+
return null;
|
|
1453
|
+
}
|
|
1454
|
+
async function readPluginManifestExtensions(pluginPath) {
|
|
1455
|
+
const manifestPath = path.join(pluginPath, "package.json");
|
|
1456
|
+
const raw = await fs.readFile(manifestPath, "utf-8").catch(() => "");
|
|
1457
|
+
if (!raw.trim()) return [];
|
|
1458
|
+
const extensions = JSON.parse(raw)?.[MANIFEST_KEY]?.extensions;
|
|
1459
|
+
if (!Array.isArray(extensions)) return [];
|
|
1460
|
+
return extensions.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
|
|
1461
|
+
}
|
|
1462
|
+
function listWorkspaceDirs(cfg) {
|
|
1463
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
1464
|
+
const list = cfg.agents?.list;
|
|
1465
|
+
if (Array.isArray(list)) {
|
|
1466
|
+
for (const entry of list) if (entry && typeof entry === "object" && typeof entry.id === "string") dirs.add(resolveAgentWorkspaceDir(cfg, entry.id));
|
|
1467
|
+
}
|
|
1468
|
+
dirs.add(resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)));
|
|
1469
|
+
return [...dirs];
|
|
1470
|
+
}
|
|
1471
|
+
function formatCodeSafetyDetails(findings, rootDir) {
|
|
1472
|
+
return findings.map((finding) => {
|
|
1473
|
+
const relPath = path.relative(rootDir, finding.file);
|
|
1474
|
+
const normalizedPath = (relPath && relPath !== "." && !relPath.startsWith("..") ? relPath : path.basename(finding.file)).replaceAll("\\", "/");
|
|
1475
|
+
return ` - [${finding.ruleId}] ${finding.message} (${normalizedPath}:${finding.line})`;
|
|
1476
|
+
}).join("\n");
|
|
1477
|
+
}
|
|
1478
|
+
function unionAllow(base, extra) {
|
|
1479
|
+
if (!Array.isArray(extra) || extra.length === 0) return base;
|
|
1480
|
+
if (!Array.isArray(base) || base.length === 0) return Array.from(new Set(["*", ...extra]));
|
|
1481
|
+
return Array.from(new Set([...base, ...extra]));
|
|
1482
|
+
}
|
|
1483
|
+
function pickToolPolicy(config) {
|
|
1484
|
+
if (!config) return;
|
|
1485
|
+
const allow = Array.isArray(config.allow) ? unionAllow(config.allow, config.alsoAllow) : Array.isArray(config.alsoAllow) && config.alsoAllow.length > 0 ? unionAllow(void 0, config.alsoAllow) : void 0;
|
|
1486
|
+
const deny = Array.isArray(config.deny) ? config.deny : void 0;
|
|
1487
|
+
if (!allow && !deny) return;
|
|
1488
|
+
return {
|
|
1489
|
+
allow,
|
|
1490
|
+
deny
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
function resolveToolPolicies(params) {
|
|
1494
|
+
const policies = [
|
|
1495
|
+
resolveToolProfilePolicy(params.agentTools?.profile ?? params.cfg.tools?.profile),
|
|
1496
|
+
pickToolPolicy(params.cfg.tools ?? void 0),
|
|
1497
|
+
pickToolPolicy(params.agentTools)
|
|
1498
|
+
];
|
|
1499
|
+
if (params.sandboxMode === "all") policies.push(resolveSandboxToolPolicyForAgent(params.cfg, params.agentId ?? void 0));
|
|
1500
|
+
return policies;
|
|
1501
|
+
}
|
|
1502
|
+
function normalizePluginIdSet(entries) {
|
|
1503
|
+
return new Set(entries.map((entry) => entry.trim().toLowerCase()).filter(Boolean));
|
|
1504
|
+
}
|
|
1505
|
+
function resolveEnabledExtensionPluginIds(params) {
|
|
1506
|
+
const normalized = normalizePluginsConfig(params.cfg.plugins);
|
|
1507
|
+
if (!normalized.enabled) return [];
|
|
1508
|
+
const allowSet = normalizePluginIdSet(normalized.allow);
|
|
1509
|
+
const denySet = normalizePluginIdSet(normalized.deny);
|
|
1510
|
+
const entryById = /* @__PURE__ */ new Map();
|
|
1511
|
+
for (const [id, entry] of Object.entries(normalized.entries)) entryById.set(id.trim().toLowerCase(), entry);
|
|
1512
|
+
const enabled = [];
|
|
1513
|
+
for (const id of params.pluginDirs) {
|
|
1514
|
+
const normalizedId = id.trim().toLowerCase();
|
|
1515
|
+
if (!normalizedId) continue;
|
|
1516
|
+
if (denySet.has(normalizedId)) continue;
|
|
1517
|
+
if (allowSet.size > 0 && !allowSet.has(normalizedId)) continue;
|
|
1518
|
+
if (entryById.get(normalizedId)?.enabled === false) continue;
|
|
1519
|
+
enabled.push(normalizedId);
|
|
1520
|
+
}
|
|
1521
|
+
return enabled;
|
|
1522
|
+
}
|
|
1523
|
+
function collectAllowEntries(config) {
|
|
1524
|
+
const out = [];
|
|
1525
|
+
if (Array.isArray(config?.allow)) out.push(...config.allow);
|
|
1526
|
+
if (Array.isArray(config?.alsoAllow)) out.push(...config.alsoAllow);
|
|
1527
|
+
return out.map((entry) => entry.trim().toLowerCase()).filter(Boolean);
|
|
1528
|
+
}
|
|
1529
|
+
function hasExplicitPluginAllow(params) {
|
|
1530
|
+
return params.allowEntries.some((entry) => entry === "group:plugins" || params.enabledPluginIds.has(entry));
|
|
1531
|
+
}
|
|
1532
|
+
function hasProviderPluginAllow(params) {
|
|
1533
|
+
if (!params.byProvider) return false;
|
|
1534
|
+
for (const policy of Object.values(params.byProvider)) if (hasExplicitPluginAllow({
|
|
1535
|
+
allowEntries: collectAllowEntries(policy),
|
|
1536
|
+
enabledPluginIds: params.enabledPluginIds
|
|
1537
|
+
})) return true;
|
|
1538
|
+
return false;
|
|
1539
|
+
}
|
|
1540
|
+
async function collectPluginsTrustFindings(params) {
|
|
1541
|
+
const findings = [];
|
|
1542
|
+
const extensionsDir = path.join(params.stateDir, "extensions");
|
|
1543
|
+
const st = await safeStat(extensionsDir);
|
|
1544
|
+
if (!st.ok || !st.isDir) return findings;
|
|
1545
|
+
const pluginDirs = (await fs.readdir(extensionsDir, { withFileTypes: true }).catch(() => [])).filter((e) => e.isDirectory()).map((e) => e.name).filter(Boolean);
|
|
1546
|
+
if (pluginDirs.length === 0) return findings;
|
|
1547
|
+
const allow = params.cfg.plugins?.allow;
|
|
1548
|
+
if (!(Array.isArray(allow) && allow.length > 0)) {
|
|
1549
|
+
const hasString = (value) => typeof value === "string" && value.trim().length > 0;
|
|
1550
|
+
const hasAccountStringKey = (account, key) => Boolean(account && typeof account === "object" && hasString(account[key]));
|
|
1551
|
+
const discordConfigured = hasString(params.cfg.channels?.discord?.token) || Boolean(params.cfg.channels?.discord?.accounts && Object.values(params.cfg.channels.discord.accounts).some((a) => hasAccountStringKey(a, "token"))) || hasString(process.env.DISCORD_BOT_TOKEN);
|
|
1552
|
+
const telegramConfigured = hasString(params.cfg.channels?.telegram?.botToken) || hasString(params.cfg.channels?.telegram?.tokenFile) || Boolean(params.cfg.channels?.telegram?.accounts && Object.values(params.cfg.channels.telegram.accounts).some((a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "tokenFile"))) || hasString(process.env.TELEGRAM_BOT_TOKEN);
|
|
1553
|
+
const slackConfigured = hasString(params.cfg.channels?.slack?.botToken) || hasString(params.cfg.channels?.slack?.appToken) || Boolean(params.cfg.channels?.slack?.accounts && Object.values(params.cfg.channels.slack.accounts).some((a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "appToken"))) || hasString(process.env.SLACK_BOT_TOKEN) || hasString(process.env.SLACK_APP_TOKEN);
|
|
1554
|
+
const skillCommandsLikelyExposed = discordConfigured && resolveNativeSkillsEnabled({
|
|
1555
|
+
providerId: "discord",
|
|
1556
|
+
providerSetting: params.cfg.channels?.discord?.commands?.nativeSkills,
|
|
1557
|
+
globalSetting: params.cfg.commands?.nativeSkills
|
|
1558
|
+
}) || telegramConfigured && resolveNativeSkillsEnabled({
|
|
1559
|
+
providerId: "telegram",
|
|
1560
|
+
providerSetting: params.cfg.channels?.telegram?.commands?.nativeSkills,
|
|
1561
|
+
globalSetting: params.cfg.commands?.nativeSkills
|
|
1562
|
+
}) || slackConfigured && resolveNativeSkillsEnabled({
|
|
1563
|
+
providerId: "slack",
|
|
1564
|
+
providerSetting: params.cfg.channels?.slack?.commands?.nativeSkills,
|
|
1565
|
+
globalSetting: params.cfg.commands?.nativeSkills
|
|
1566
|
+
});
|
|
1567
|
+
findings.push({
|
|
1568
|
+
checkId: "plugins.extensions_no_allowlist",
|
|
1569
|
+
severity: skillCommandsLikelyExposed ? "critical" : "warn",
|
|
1570
|
+
title: "Extensions exist but plugins.allow is not set",
|
|
1571
|
+
detail: `Found ${pluginDirs.length} extension(s) under ${extensionsDir}. Without plugins.allow, any discovered plugin id may load (depending on config and plugin behavior).` + (skillCommandsLikelyExposed ? "\nNative skill commands are enabled on at least one configured chat surface; treat unpinned/unallowlisted extensions as high risk." : ""),
|
|
1572
|
+
remediation: "Set plugins.allow to an explicit list of plugin ids you trust."
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
const enabledExtensionPluginIds = resolveEnabledExtensionPluginIds({
|
|
1576
|
+
cfg: params.cfg,
|
|
1577
|
+
pluginDirs
|
|
1578
|
+
});
|
|
1579
|
+
if (enabledExtensionPluginIds.length > 0) {
|
|
1580
|
+
const enabledPluginSet = new Set(enabledExtensionPluginIds);
|
|
1581
|
+
const contexts = [{ label: "default" }];
|
|
1582
|
+
for (const entry of params.cfg.agents?.list ?? []) {
|
|
1583
|
+
if (!entry || typeof entry !== "object" || typeof entry.id !== "string") continue;
|
|
1584
|
+
contexts.push({
|
|
1585
|
+
label: `agents.list.${entry.id}`,
|
|
1586
|
+
agentId: entry.id,
|
|
1587
|
+
tools: entry.tools
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
const permissiveContexts = [];
|
|
1591
|
+
for (const context of contexts) {
|
|
1592
|
+
const profile = context.tools?.profile ?? params.cfg.tools?.profile;
|
|
1593
|
+
const restrictiveProfile = Boolean(resolveToolProfilePolicy(profile));
|
|
1594
|
+
const sandboxMode = resolveSandboxConfigForAgent(params.cfg, context.agentId).mode;
|
|
1595
|
+
const broadPolicy = isToolAllowedByPolicies("__anima_plugin_probe__", resolveToolPolicies({
|
|
1596
|
+
cfg: params.cfg,
|
|
1597
|
+
agentTools: context.tools,
|
|
1598
|
+
sandboxMode,
|
|
1599
|
+
agentId: context.agentId
|
|
1600
|
+
}));
|
|
1601
|
+
const explicitPluginAllow = !restrictiveProfile && (hasExplicitPluginAllow({
|
|
1602
|
+
allowEntries: collectAllowEntries(params.cfg.tools),
|
|
1603
|
+
enabledPluginIds: enabledPluginSet
|
|
1604
|
+
}) || hasProviderPluginAllow({
|
|
1605
|
+
byProvider: params.cfg.tools?.byProvider,
|
|
1606
|
+
enabledPluginIds: enabledPluginSet
|
|
1607
|
+
}) || hasExplicitPluginAllow({
|
|
1608
|
+
allowEntries: collectAllowEntries(context.tools),
|
|
1609
|
+
enabledPluginIds: enabledPluginSet
|
|
1610
|
+
}) || hasProviderPluginAllow({
|
|
1611
|
+
byProvider: context.tools?.byProvider,
|
|
1612
|
+
enabledPluginIds: enabledPluginSet
|
|
1613
|
+
}));
|
|
1614
|
+
if (broadPolicy || explicitPluginAllow) permissiveContexts.push(context.label);
|
|
1615
|
+
}
|
|
1616
|
+
if (permissiveContexts.length > 0) findings.push({
|
|
1617
|
+
checkId: "plugins.tools_reachable_permissive_policy",
|
|
1618
|
+
severity: "warn",
|
|
1619
|
+
title: "Extension plugin tools may be reachable under permissive tool policy",
|
|
1620
|
+
detail: `Enabled extension plugins: ${enabledExtensionPluginIds.join(", ")}.\nPermissive tool policy contexts:\n${permissiveContexts.map((entry) => `- ${entry}`).join("\n")}`,
|
|
1621
|
+
remediation: "Use restrictive profiles (`minimal`/`coding`) or explicit tool allowlists that exclude plugin tools for agents handling untrusted input."
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
return findings;
|
|
1625
|
+
}
|
|
1626
|
+
async function collectIncludeFilePermFindings(params) {
|
|
1627
|
+
const findings = [];
|
|
1628
|
+
if (!params.configSnapshot.exists) return findings;
|
|
1629
|
+
const configPath = params.configSnapshot.path;
|
|
1630
|
+
const includePaths = await collectIncludePathsRecursive({
|
|
1631
|
+
configPath,
|
|
1632
|
+
parsed: params.configSnapshot.parsed
|
|
1633
|
+
});
|
|
1634
|
+
if (includePaths.length === 0) return findings;
|
|
1635
|
+
for (const p of includePaths) {
|
|
1636
|
+
const perms = await inspectPathPermissions(p, {
|
|
1637
|
+
env: params.env,
|
|
1638
|
+
platform: params.platform,
|
|
1639
|
+
exec: params.execIcacls
|
|
1640
|
+
});
|
|
1641
|
+
if (!perms.ok) continue;
|
|
1642
|
+
if (perms.worldWritable || perms.groupWritable) findings.push({
|
|
1643
|
+
checkId: "fs.config_include.perms_writable",
|
|
1644
|
+
severity: "critical",
|
|
1645
|
+
title: "Config include file is writable by others",
|
|
1646
|
+
detail: `${formatPermissionDetail(p, perms)}; another user could influence your effective config.`,
|
|
1647
|
+
remediation: formatPermissionRemediation({
|
|
1648
|
+
targetPath: p,
|
|
1649
|
+
perms,
|
|
1650
|
+
isDir: false,
|
|
1651
|
+
posixMode: 384,
|
|
1652
|
+
env: params.env
|
|
1653
|
+
})
|
|
1654
|
+
});
|
|
1655
|
+
else if (perms.worldReadable) findings.push({
|
|
1656
|
+
checkId: "fs.config_include.perms_world_readable",
|
|
1657
|
+
severity: "critical",
|
|
1658
|
+
title: "Config include file is world-readable",
|
|
1659
|
+
detail: `${formatPermissionDetail(p, perms)}; include files can contain tokens and private settings.`,
|
|
1660
|
+
remediation: formatPermissionRemediation({
|
|
1661
|
+
targetPath: p,
|
|
1662
|
+
perms,
|
|
1663
|
+
isDir: false,
|
|
1664
|
+
posixMode: 384,
|
|
1665
|
+
env: params.env
|
|
1666
|
+
})
|
|
1667
|
+
});
|
|
1668
|
+
else if (perms.groupReadable) findings.push({
|
|
1669
|
+
checkId: "fs.config_include.perms_group_readable",
|
|
1670
|
+
severity: "warn",
|
|
1671
|
+
title: "Config include file is group-readable",
|
|
1672
|
+
detail: `${formatPermissionDetail(p, perms)}; include files can contain tokens and private settings.`,
|
|
1673
|
+
remediation: formatPermissionRemediation({
|
|
1674
|
+
targetPath: p,
|
|
1675
|
+
perms,
|
|
1676
|
+
isDir: false,
|
|
1677
|
+
posixMode: 384,
|
|
1678
|
+
env: params.env
|
|
1679
|
+
})
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
return findings;
|
|
1683
|
+
}
|
|
1684
|
+
async function collectStateDeepFilesystemFindings(params) {
|
|
1685
|
+
const findings = [];
|
|
1686
|
+
const oauthDir = resolveOAuthDir(params.env, params.stateDir);
|
|
1687
|
+
const oauthPerms = await inspectPathPermissions(oauthDir, {
|
|
1688
|
+
env: params.env,
|
|
1689
|
+
platform: params.platform,
|
|
1690
|
+
exec: params.execIcacls
|
|
1691
|
+
});
|
|
1692
|
+
if (oauthPerms.ok && oauthPerms.isDir) {
|
|
1693
|
+
if (oauthPerms.worldWritable || oauthPerms.groupWritable) findings.push({
|
|
1694
|
+
checkId: "fs.credentials_dir.perms_writable",
|
|
1695
|
+
severity: "critical",
|
|
1696
|
+
title: "Credentials dir is writable by others",
|
|
1697
|
+
detail: `${formatPermissionDetail(oauthDir, oauthPerms)}; another user could drop/modify credential files.`,
|
|
1698
|
+
remediation: formatPermissionRemediation({
|
|
1699
|
+
targetPath: oauthDir,
|
|
1700
|
+
perms: oauthPerms,
|
|
1701
|
+
isDir: true,
|
|
1702
|
+
posixMode: 448,
|
|
1703
|
+
env: params.env
|
|
1704
|
+
})
|
|
1705
|
+
});
|
|
1706
|
+
else if (oauthPerms.groupReadable || oauthPerms.worldReadable) findings.push({
|
|
1707
|
+
checkId: "fs.credentials_dir.perms_readable",
|
|
1708
|
+
severity: "warn",
|
|
1709
|
+
title: "Credentials dir is readable by others",
|
|
1710
|
+
detail: `${formatPermissionDetail(oauthDir, oauthPerms)}; credentials and allowlists can be sensitive.`,
|
|
1711
|
+
remediation: formatPermissionRemediation({
|
|
1712
|
+
targetPath: oauthDir,
|
|
1713
|
+
perms: oauthPerms,
|
|
1714
|
+
isDir: true,
|
|
1715
|
+
posixMode: 448,
|
|
1716
|
+
env: params.env
|
|
1717
|
+
})
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
const agentIds = Array.isArray(params.cfg.agents?.list) ? params.cfg.agents?.list.map((a) => a && typeof a === "object" && typeof a.id === "string" ? a.id.trim() : "").filter(Boolean) : [];
|
|
1721
|
+
const defaultAgentId = resolveDefaultAgentId(params.cfg);
|
|
1722
|
+
const ids = Array.from(new Set([defaultAgentId, ...agentIds])).map((id) => normalizeAgentId(id));
|
|
1723
|
+
for (const agentId of ids) {
|
|
1724
|
+
const agentDir = path.join(params.stateDir, "agents", agentId, "agent");
|
|
1725
|
+
const authPath = path.join(agentDir, "auth-profiles.json");
|
|
1726
|
+
const authPerms = await inspectPathPermissions(authPath, {
|
|
1727
|
+
env: params.env,
|
|
1728
|
+
platform: params.platform,
|
|
1729
|
+
exec: params.execIcacls
|
|
1730
|
+
});
|
|
1731
|
+
if (authPerms.ok) {
|
|
1732
|
+
if (authPerms.worldWritable || authPerms.groupWritable) findings.push({
|
|
1733
|
+
checkId: "fs.auth_profiles.perms_writable",
|
|
1734
|
+
severity: "critical",
|
|
1735
|
+
title: "auth-profiles.json is writable by others",
|
|
1736
|
+
detail: `${formatPermissionDetail(authPath, authPerms)}; another user could inject credentials.`,
|
|
1737
|
+
remediation: formatPermissionRemediation({
|
|
1738
|
+
targetPath: authPath,
|
|
1739
|
+
perms: authPerms,
|
|
1740
|
+
isDir: false,
|
|
1741
|
+
posixMode: 384,
|
|
1742
|
+
env: params.env
|
|
1743
|
+
})
|
|
1744
|
+
});
|
|
1745
|
+
else if (authPerms.worldReadable || authPerms.groupReadable) findings.push({
|
|
1746
|
+
checkId: "fs.auth_profiles.perms_readable",
|
|
1747
|
+
severity: "warn",
|
|
1748
|
+
title: "auth-profiles.json is readable by others",
|
|
1749
|
+
detail: `${formatPermissionDetail(authPath, authPerms)}; auth-profiles.json contains API keys and OAuth tokens.`,
|
|
1750
|
+
remediation: formatPermissionRemediation({
|
|
1751
|
+
targetPath: authPath,
|
|
1752
|
+
perms: authPerms,
|
|
1753
|
+
isDir: false,
|
|
1754
|
+
posixMode: 384,
|
|
1755
|
+
env: params.env
|
|
1756
|
+
})
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1759
|
+
const storePath = path.join(params.stateDir, "agents", agentId, "sessions", "sessions.json");
|
|
1760
|
+
const storePerms = await inspectPathPermissions(storePath, {
|
|
1761
|
+
env: params.env,
|
|
1762
|
+
platform: params.platform,
|
|
1763
|
+
exec: params.execIcacls
|
|
1764
|
+
});
|
|
1765
|
+
if (storePerms.ok) {
|
|
1766
|
+
if (storePerms.worldReadable || storePerms.groupReadable) findings.push({
|
|
1767
|
+
checkId: "fs.sessions_store.perms_readable",
|
|
1768
|
+
severity: "warn",
|
|
1769
|
+
title: "sessions.json is readable by others",
|
|
1770
|
+
detail: `${formatPermissionDetail(storePath, storePerms)}; routing and transcript metadata can be sensitive.`,
|
|
1771
|
+
remediation: formatPermissionRemediation({
|
|
1772
|
+
targetPath: storePath,
|
|
1773
|
+
perms: storePerms,
|
|
1774
|
+
isDir: false,
|
|
1775
|
+
posixMode: 384,
|
|
1776
|
+
env: params.env
|
|
1777
|
+
})
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
const logFile = typeof params.cfg.logging?.file === "string" ? params.cfg.logging.file.trim() : "";
|
|
1782
|
+
if (logFile) {
|
|
1783
|
+
const expanded = logFile.startsWith("~") ? expandTilde(logFile, params.env) : logFile;
|
|
1784
|
+
if (expanded) {
|
|
1785
|
+
const logPath = path.resolve(expanded);
|
|
1786
|
+
const logPerms = await inspectPathPermissions(logPath, {
|
|
1787
|
+
env: params.env,
|
|
1788
|
+
platform: params.platform,
|
|
1789
|
+
exec: params.execIcacls
|
|
1790
|
+
});
|
|
1791
|
+
if (logPerms.ok) {
|
|
1792
|
+
if (logPerms.worldReadable || logPerms.groupReadable) findings.push({
|
|
1793
|
+
checkId: "fs.log_file.perms_readable",
|
|
1794
|
+
severity: "warn",
|
|
1795
|
+
title: "Log file is readable by others",
|
|
1796
|
+
detail: `${formatPermissionDetail(logPath, logPerms)}; logs can contain private messages and tool output.`,
|
|
1797
|
+
remediation: formatPermissionRemediation({
|
|
1798
|
+
targetPath: logPath,
|
|
1799
|
+
perms: logPerms,
|
|
1800
|
+
isDir: false,
|
|
1801
|
+
posixMode: 384,
|
|
1802
|
+
env: params.env
|
|
1803
|
+
})
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
return findings;
|
|
1809
|
+
}
|
|
1810
|
+
async function readConfigSnapshotForAudit(params) {
|
|
1811
|
+
return await createConfigIO({
|
|
1812
|
+
env: params.env,
|
|
1813
|
+
configPath: params.configPath
|
|
1814
|
+
}).readConfigFileSnapshot();
|
|
1815
|
+
}
|
|
1816
|
+
async function collectPluginsCodeSafetyFindings(params) {
|
|
1817
|
+
const findings = [];
|
|
1818
|
+
const extensionsDir = path.join(params.stateDir, "extensions");
|
|
1819
|
+
const st = await safeStat(extensionsDir);
|
|
1820
|
+
if (!st.ok || !st.isDir) return findings;
|
|
1821
|
+
const pluginDirs = (await fs.readdir(extensionsDir, { withFileTypes: true }).catch((err) => {
|
|
1822
|
+
findings.push({
|
|
1823
|
+
checkId: "plugins.code_safety.scan_failed",
|
|
1824
|
+
severity: "warn",
|
|
1825
|
+
title: "Plugin extensions directory scan failed",
|
|
1826
|
+
detail: `Static code scan could not list extensions directory: ${String(err)}`,
|
|
1827
|
+
remediation: "Check file permissions and plugin layout, then rerun `anima security audit --deep`."
|
|
1828
|
+
});
|
|
1829
|
+
return [];
|
|
1830
|
+
})).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1831
|
+
for (const pluginName of pluginDirs) {
|
|
1832
|
+
const pluginPath = path.join(extensionsDir, pluginName);
|
|
1833
|
+
const extensionEntries = await readPluginManifestExtensions(pluginPath).catch(() => []);
|
|
1834
|
+
const forcedScanEntries = [];
|
|
1835
|
+
const escapedEntries = [];
|
|
1836
|
+
for (const entry of extensionEntries) {
|
|
1837
|
+
const resolvedEntry = path.resolve(pluginPath, entry);
|
|
1838
|
+
if (!isPathInside(pluginPath, resolvedEntry)) {
|
|
1839
|
+
escapedEntries.push(entry);
|
|
1840
|
+
continue;
|
|
1841
|
+
}
|
|
1842
|
+
if (extensionUsesSkippedScannerPath(entry)) findings.push({
|
|
1843
|
+
checkId: "plugins.code_safety.entry_path",
|
|
1844
|
+
severity: "warn",
|
|
1845
|
+
title: `Plugin "${pluginName}" entry path is hidden or node_modules`,
|
|
1846
|
+
detail: `Extension entry "${entry}" points to a hidden or node_modules path. Deep code scan will cover this entry explicitly, but review this path choice carefully.`,
|
|
1847
|
+
remediation: "Prefer extension entrypoints under normal source paths like dist/ or src/."
|
|
1848
|
+
});
|
|
1849
|
+
forcedScanEntries.push(resolvedEntry);
|
|
1850
|
+
}
|
|
1851
|
+
if (escapedEntries.length > 0) findings.push({
|
|
1852
|
+
checkId: "plugins.code_safety.entry_escape",
|
|
1853
|
+
severity: "critical",
|
|
1854
|
+
title: `Plugin "${pluginName}" has extension entry path traversal`,
|
|
1855
|
+
detail: `Found extension entries that escape the plugin directory:\n${escapedEntries.map((entry) => ` - ${entry}`).join("\n")}`,
|
|
1856
|
+
remediation: "Update the plugin manifest so all anima.extensions entries stay inside the plugin directory."
|
|
1857
|
+
});
|
|
1858
|
+
const summary = await scanDirectoryWithSummary(pluginPath, { includeFiles: forcedScanEntries }).catch((err) => {
|
|
1859
|
+
findings.push({
|
|
1860
|
+
checkId: "plugins.code_safety.scan_failed",
|
|
1861
|
+
severity: "warn",
|
|
1862
|
+
title: `Plugin "${pluginName}" code scan failed`,
|
|
1863
|
+
detail: `Static code scan could not complete: ${String(err)}`,
|
|
1864
|
+
remediation: "Check file permissions and plugin layout, then rerun `anima security audit --deep`."
|
|
1865
|
+
});
|
|
1866
|
+
return null;
|
|
1867
|
+
});
|
|
1868
|
+
if (!summary) continue;
|
|
1869
|
+
if (summary.critical > 0) {
|
|
1870
|
+
const details = formatCodeSafetyDetails(summary.findings.filter((f) => f.severity === "critical"), pluginPath);
|
|
1871
|
+
findings.push({
|
|
1872
|
+
checkId: "plugins.code_safety",
|
|
1873
|
+
severity: "critical",
|
|
1874
|
+
title: `Plugin "${pluginName}" contains dangerous code patterns`,
|
|
1875
|
+
detail: `Found ${summary.critical} critical issue(s) in ${summary.scannedFiles} scanned file(s):\n${details}`,
|
|
1876
|
+
remediation: "Review the plugin source code carefully before use. If untrusted, remove the plugin from your Anima extensions state directory."
|
|
1877
|
+
});
|
|
1878
|
+
} else if (summary.warn > 0) {
|
|
1879
|
+
const details = formatCodeSafetyDetails(summary.findings.filter((f) => f.severity === "warn"), pluginPath);
|
|
1880
|
+
findings.push({
|
|
1881
|
+
checkId: "plugins.code_safety",
|
|
1882
|
+
severity: "warn",
|
|
1883
|
+
title: `Plugin "${pluginName}" contains suspicious code patterns`,
|
|
1884
|
+
detail: `Found ${summary.warn} warning(s) in ${summary.scannedFiles} scanned file(s):\n${details}`,
|
|
1885
|
+
remediation: `Review the flagged code to ensure it is intentional and safe.`
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
return findings;
|
|
1890
|
+
}
|
|
1891
|
+
async function collectInstalledSkillsCodeSafetyFindings(params) {
|
|
1892
|
+
const findings = [];
|
|
1893
|
+
const pluginExtensionsDir = path.join(params.stateDir, "extensions");
|
|
1894
|
+
const scannedSkillDirs = /* @__PURE__ */ new Set();
|
|
1895
|
+
const workspaceDirs = listWorkspaceDirs(params.cfg);
|
|
1896
|
+
for (const workspaceDir of workspaceDirs) {
|
|
1897
|
+
const entries = loadWorkspaceSkillEntries(workspaceDir, { config: params.cfg });
|
|
1898
|
+
for (const entry of entries) {
|
|
1899
|
+
if (entry.skill.source === "anima-bundled") continue;
|
|
1900
|
+
const skillDir = path.resolve(entry.skill.baseDir);
|
|
1901
|
+
if (isPathInside(pluginExtensionsDir, skillDir)) continue;
|
|
1902
|
+
if (scannedSkillDirs.has(skillDir)) continue;
|
|
1903
|
+
scannedSkillDirs.add(skillDir);
|
|
1904
|
+
const skillName = entry.skill.name;
|
|
1905
|
+
const summary = await scanDirectoryWithSummary(skillDir).catch((err) => {
|
|
1906
|
+
findings.push({
|
|
1907
|
+
checkId: "skills.code_safety.scan_failed",
|
|
1908
|
+
severity: "warn",
|
|
1909
|
+
title: `Skill "${skillName}" code scan failed`,
|
|
1910
|
+
detail: `Static code scan could not complete for ${skillDir}: ${String(err)}`,
|
|
1911
|
+
remediation: "Check file permissions and skill layout, then rerun `anima security audit --deep`."
|
|
1912
|
+
});
|
|
1913
|
+
return null;
|
|
1914
|
+
});
|
|
1915
|
+
if (!summary) continue;
|
|
1916
|
+
if (summary.critical > 0) {
|
|
1917
|
+
const details = formatCodeSafetyDetails(summary.findings.filter((finding) => finding.severity === "critical"), skillDir);
|
|
1918
|
+
findings.push({
|
|
1919
|
+
checkId: "skills.code_safety",
|
|
1920
|
+
severity: "critical",
|
|
1921
|
+
title: `Skill "${skillName}" contains dangerous code patterns`,
|
|
1922
|
+
detail: `Found ${summary.critical} critical issue(s) in ${summary.scannedFiles} scanned file(s) under ${skillDir}:\n${details}`,
|
|
1923
|
+
remediation: `Review the skill source code before use. If untrusted, remove "${skillDir}".`
|
|
1924
|
+
});
|
|
1925
|
+
} else if (summary.warn > 0) {
|
|
1926
|
+
const details = formatCodeSafetyDetails(summary.findings.filter((finding) => finding.severity === "warn"), skillDir);
|
|
1927
|
+
findings.push({
|
|
1928
|
+
checkId: "skills.code_safety",
|
|
1929
|
+
severity: "warn",
|
|
1930
|
+
title: `Skill "${skillName}" contains suspicious code patterns`,
|
|
1931
|
+
detail: `Found ${summary.warn} warning(s) in ${summary.scannedFiles} scanned file(s) under ${skillDir}:\n${details}`,
|
|
1932
|
+
remediation: "Review flagged lines to ensure the behavior is intentional and safe."
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
return findings;
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
//#endregion
|
|
1941
|
+
//#region src/security/audit.ts
|
|
1942
|
+
function countBySeverity(findings) {
|
|
1943
|
+
let critical = 0;
|
|
1944
|
+
let warn = 0;
|
|
1945
|
+
let info = 0;
|
|
1946
|
+
for (const f of findings) if (f.severity === "critical") critical += 1;
|
|
1947
|
+
else if (f.severity === "warn") warn += 1;
|
|
1948
|
+
else info += 1;
|
|
1949
|
+
return {
|
|
1950
|
+
critical,
|
|
1951
|
+
warn,
|
|
1952
|
+
info
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
function normalizeAllowFromList(list) {
|
|
1956
|
+
if (!Array.isArray(list)) return [];
|
|
1957
|
+
return list.map((v) => String(v).trim()).filter(Boolean);
|
|
1958
|
+
}
|
|
1959
|
+
async function collectFilesystemFindings(params) {
|
|
1960
|
+
const findings = [];
|
|
1961
|
+
const stateDirPerms = await inspectPathPermissions(params.stateDir, {
|
|
1962
|
+
env: params.env,
|
|
1963
|
+
platform: params.platform,
|
|
1964
|
+
exec: params.execIcacls
|
|
1965
|
+
});
|
|
1966
|
+
if (stateDirPerms.ok) {
|
|
1967
|
+
if (stateDirPerms.isSymlink) findings.push({
|
|
1968
|
+
checkId: "fs.state_dir.symlink",
|
|
1969
|
+
severity: "warn",
|
|
1970
|
+
title: "State dir is a symlink",
|
|
1971
|
+
detail: `${params.stateDir} is a symlink; treat this as an extra trust boundary.`
|
|
1972
|
+
});
|
|
1973
|
+
if (stateDirPerms.worldWritable) findings.push({
|
|
1974
|
+
checkId: "fs.state_dir.perms_world_writable",
|
|
1975
|
+
severity: "critical",
|
|
1976
|
+
title: "State dir is world-writable",
|
|
1977
|
+
detail: `${formatPermissionDetail(params.stateDir, stateDirPerms)}; other users can write into your Anima state.`,
|
|
1978
|
+
remediation: formatPermissionRemediation({
|
|
1979
|
+
targetPath: params.stateDir,
|
|
1980
|
+
perms: stateDirPerms,
|
|
1981
|
+
isDir: true,
|
|
1982
|
+
posixMode: 448,
|
|
1983
|
+
env: params.env
|
|
1984
|
+
})
|
|
1985
|
+
});
|
|
1986
|
+
else if (stateDirPerms.groupWritable) findings.push({
|
|
1987
|
+
checkId: "fs.state_dir.perms_group_writable",
|
|
1988
|
+
severity: "warn",
|
|
1989
|
+
title: "State dir is group-writable",
|
|
1990
|
+
detail: `${formatPermissionDetail(params.stateDir, stateDirPerms)}; group users can write into your Anima state.`,
|
|
1991
|
+
remediation: formatPermissionRemediation({
|
|
1992
|
+
targetPath: params.stateDir,
|
|
1993
|
+
perms: stateDirPerms,
|
|
1994
|
+
isDir: true,
|
|
1995
|
+
posixMode: 448,
|
|
1996
|
+
env: params.env
|
|
1997
|
+
})
|
|
1998
|
+
});
|
|
1999
|
+
else if (stateDirPerms.groupReadable || stateDirPerms.worldReadable) findings.push({
|
|
2000
|
+
checkId: "fs.state_dir.perms_readable",
|
|
2001
|
+
severity: "warn",
|
|
2002
|
+
title: "State dir is readable by others",
|
|
2003
|
+
detail: `${formatPermissionDetail(params.stateDir, stateDirPerms)}; consider restricting to 700.`,
|
|
2004
|
+
remediation: formatPermissionRemediation({
|
|
2005
|
+
targetPath: params.stateDir,
|
|
2006
|
+
perms: stateDirPerms,
|
|
2007
|
+
isDir: true,
|
|
2008
|
+
posixMode: 448,
|
|
2009
|
+
env: params.env
|
|
2010
|
+
})
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
const configPerms = await inspectPathPermissions(params.configPath, {
|
|
2014
|
+
env: params.env,
|
|
2015
|
+
platform: params.platform,
|
|
2016
|
+
exec: params.execIcacls
|
|
2017
|
+
});
|
|
2018
|
+
if (configPerms.ok) {
|
|
2019
|
+
if (configPerms.isSymlink) findings.push({
|
|
2020
|
+
checkId: "fs.config.symlink",
|
|
2021
|
+
severity: "warn",
|
|
2022
|
+
title: "Config file is a symlink",
|
|
2023
|
+
detail: `${params.configPath} is a symlink; make sure you trust its target.`
|
|
2024
|
+
});
|
|
2025
|
+
if (configPerms.worldWritable || configPerms.groupWritable) findings.push({
|
|
2026
|
+
checkId: "fs.config.perms_writable",
|
|
2027
|
+
severity: "critical",
|
|
2028
|
+
title: "Config file is writable by others",
|
|
2029
|
+
detail: `${formatPermissionDetail(params.configPath, configPerms)}; another user could change gateway/auth/tool policies.`,
|
|
2030
|
+
remediation: formatPermissionRemediation({
|
|
2031
|
+
targetPath: params.configPath,
|
|
2032
|
+
perms: configPerms,
|
|
2033
|
+
isDir: false,
|
|
2034
|
+
posixMode: 384,
|
|
2035
|
+
env: params.env
|
|
2036
|
+
})
|
|
2037
|
+
});
|
|
2038
|
+
else if (configPerms.worldReadable) findings.push({
|
|
2039
|
+
checkId: "fs.config.perms_world_readable",
|
|
2040
|
+
severity: "critical",
|
|
2041
|
+
title: "Config file is world-readable",
|
|
2042
|
+
detail: `${formatPermissionDetail(params.configPath, configPerms)}; config can contain tokens and private settings.`,
|
|
2043
|
+
remediation: formatPermissionRemediation({
|
|
2044
|
+
targetPath: params.configPath,
|
|
2045
|
+
perms: configPerms,
|
|
2046
|
+
isDir: false,
|
|
2047
|
+
posixMode: 384,
|
|
2048
|
+
env: params.env
|
|
2049
|
+
})
|
|
2050
|
+
});
|
|
2051
|
+
else if (configPerms.groupReadable) findings.push({
|
|
2052
|
+
checkId: "fs.config.perms_group_readable",
|
|
2053
|
+
severity: "warn",
|
|
2054
|
+
title: "Config file is group-readable",
|
|
2055
|
+
detail: `${formatPermissionDetail(params.configPath, configPerms)}; config can contain tokens and private settings.`,
|
|
2056
|
+
remediation: formatPermissionRemediation({
|
|
2057
|
+
targetPath: params.configPath,
|
|
2058
|
+
perms: configPerms,
|
|
2059
|
+
isDir: false,
|
|
2060
|
+
posixMode: 384,
|
|
2061
|
+
env: params.env
|
|
2062
|
+
})
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
return findings;
|
|
2066
|
+
}
|
|
2067
|
+
function collectGatewayConfigFindings(cfg, env) {
|
|
2068
|
+
const findings = [];
|
|
2069
|
+
const bind = typeof cfg.gateway?.bind === "string" ? cfg.gateway.bind : "loopback";
|
|
2070
|
+
const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off";
|
|
2071
|
+
const auth = resolveGatewayAuth({
|
|
2072
|
+
authConfig: cfg.gateway?.auth,
|
|
2073
|
+
tailscaleMode,
|
|
2074
|
+
env
|
|
2075
|
+
});
|
|
2076
|
+
const controlUiEnabled = cfg.gateway?.controlUi?.enabled !== false;
|
|
2077
|
+
const trustedProxies = Array.isArray(cfg.gateway?.trustedProxies) ? cfg.gateway.trustedProxies : [];
|
|
2078
|
+
const hasToken = typeof auth.token === "string" && auth.token.trim().length > 0;
|
|
2079
|
+
const hasPassword = typeof auth.password === "string" && auth.password.trim().length > 0;
|
|
2080
|
+
const hasSharedSecret = auth.mode === "token" && hasToken || auth.mode === "password" && hasPassword;
|
|
2081
|
+
const hasTailscaleAuth = auth.allowTailscale && tailscaleMode === "serve";
|
|
2082
|
+
const hasGatewayAuth = hasSharedSecret || hasTailscaleAuth;
|
|
2083
|
+
const gatewayToolsAllowRaw = Array.isArray(cfg.gateway?.tools?.allow) ? cfg.gateway?.tools?.allow : [];
|
|
2084
|
+
const gatewayToolsAllow = new Set(gatewayToolsAllowRaw.map((v) => typeof v === "string" ? v.trim().toLowerCase() : "").filter(Boolean));
|
|
2085
|
+
const reenabledOverHttp = DEFAULT_GATEWAY_HTTP_TOOL_DENY.filter((name) => gatewayToolsAllow.has(name));
|
|
2086
|
+
if (reenabledOverHttp.length > 0) {
|
|
2087
|
+
const extraRisk = bind !== "loopback" || tailscaleMode === "funnel";
|
|
2088
|
+
findings.push({
|
|
2089
|
+
checkId: "gateway.tools_invoke_http.dangerous_allow",
|
|
2090
|
+
severity: extraRisk ? "critical" : "warn",
|
|
2091
|
+
title: "Gateway HTTP /tools/invoke re-enables dangerous tools",
|
|
2092
|
+
detail: `gateway.tools.allow includes ${reenabledOverHttp.join(", ")} which removes them from the default HTTP deny list. This can allow remote session spawning / control-plane actions via HTTP and increases RCE blast radius if the gateway is reachable.`,
|
|
2093
|
+
remediation: "Remove these entries from gateway.tools.allow (recommended). If you keep them enabled, keep gateway.bind loopback-only (or tailnet-only), restrict network exposure, and treat the gateway token/password as full-admin."
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
if (bind !== "loopback" && !hasSharedSecret && auth.mode !== "trusted-proxy") findings.push({
|
|
2097
|
+
checkId: "gateway.bind_no_auth",
|
|
2098
|
+
severity: "critical",
|
|
2099
|
+
title: "Gateway binds beyond loopback without auth",
|
|
2100
|
+
detail: `gateway.bind="${bind}" but no gateway.auth token/password is configured.`,
|
|
2101
|
+
remediation: `Set gateway.auth (token recommended) or bind to loopback.`
|
|
2102
|
+
});
|
|
2103
|
+
if (bind === "loopback" && controlUiEnabled && trustedProxies.length === 0) findings.push({
|
|
2104
|
+
checkId: "gateway.trusted_proxies_missing",
|
|
2105
|
+
severity: "warn",
|
|
2106
|
+
title: "Reverse proxy headers are not trusted",
|
|
2107
|
+
detail: "gateway.bind is loopback and gateway.trustedProxies is empty. If you expose the Control UI through a reverse proxy, configure trusted proxies so local-client checks cannot be spoofed.",
|
|
2108
|
+
remediation: "Set gateway.trustedProxies to your proxy IPs or keep the Control UI local-only."
|
|
2109
|
+
});
|
|
2110
|
+
if (bind === "loopback" && controlUiEnabled && !hasGatewayAuth) findings.push({
|
|
2111
|
+
checkId: "gateway.loopback_no_auth",
|
|
2112
|
+
severity: "critical",
|
|
2113
|
+
title: "Gateway auth missing on loopback",
|
|
2114
|
+
detail: "gateway.bind is loopback but no gateway auth secret is configured. If the Control UI is exposed through a reverse proxy, unauthenticated access is possible.",
|
|
2115
|
+
remediation: "Set gateway.auth (token recommended) or keep the Control UI local-only."
|
|
2116
|
+
});
|
|
2117
|
+
if (tailscaleMode === "funnel") findings.push({
|
|
2118
|
+
checkId: "gateway.tailscale_funnel",
|
|
2119
|
+
severity: "critical",
|
|
2120
|
+
title: "Tailscale Funnel exposure enabled",
|
|
2121
|
+
detail: `gateway.tailscale.mode="funnel" exposes the Gateway publicly; keep auth strict and treat it as internet-facing.`,
|
|
2122
|
+
remediation: `Prefer tailscale.mode="serve" (tailnet-only) or set tailscale.mode="off".`
|
|
2123
|
+
});
|
|
2124
|
+
else if (tailscaleMode === "serve") findings.push({
|
|
2125
|
+
checkId: "gateway.tailscale_serve",
|
|
2126
|
+
severity: "info",
|
|
2127
|
+
title: "Tailscale Serve exposure enabled",
|
|
2128
|
+
detail: `gateway.tailscale.mode="serve" exposes the Gateway to your tailnet (loopback behind Tailscale).`
|
|
2129
|
+
});
|
|
2130
|
+
if (cfg.gateway?.controlUi?.allowInsecureAuth === true) findings.push({
|
|
2131
|
+
checkId: "gateway.control_ui.insecure_auth",
|
|
2132
|
+
severity: "critical",
|
|
2133
|
+
title: "Control UI allows insecure HTTP auth",
|
|
2134
|
+
detail: "gateway.controlUi.allowInsecureAuth=true allows token-only auth over HTTP and skips device identity.",
|
|
2135
|
+
remediation: "Disable it or switch to HTTPS (Tailscale Serve) or localhost."
|
|
2136
|
+
});
|
|
2137
|
+
if (cfg.gateway?.controlUi?.dangerouslyDisableDeviceAuth === true) findings.push({
|
|
2138
|
+
checkId: "gateway.control_ui.device_auth_disabled",
|
|
2139
|
+
severity: "critical",
|
|
2140
|
+
title: "DANGEROUS: Control UI device auth disabled",
|
|
2141
|
+
detail: "gateway.controlUi.dangerouslyDisableDeviceAuth=true disables device identity checks for the Control UI.",
|
|
2142
|
+
remediation: "Disable it unless you are in a short-lived break-glass scenario."
|
|
2143
|
+
});
|
|
2144
|
+
const token = typeof auth.token === "string" && auth.token.trim().length > 0 ? auth.token.trim() : null;
|
|
2145
|
+
if (auth.mode === "token" && token && token.length < 24) findings.push({
|
|
2146
|
+
checkId: "gateway.token_too_short",
|
|
2147
|
+
severity: "warn",
|
|
2148
|
+
title: "Gateway token looks short",
|
|
2149
|
+
detail: `gateway auth token is ${token.length} chars; prefer a long random token.`
|
|
2150
|
+
});
|
|
2151
|
+
if (auth.mode === "trusted-proxy") {
|
|
2152
|
+
const trustedProxies = cfg.gateway?.trustedProxies ?? [];
|
|
2153
|
+
const trustedProxyConfig = cfg.gateway?.auth?.trustedProxy;
|
|
2154
|
+
findings.push({
|
|
2155
|
+
checkId: "gateway.trusted_proxy_auth",
|
|
2156
|
+
severity: "critical",
|
|
2157
|
+
title: "Trusted-proxy auth mode enabled",
|
|
2158
|
+
detail: "gateway.auth.mode=\"trusted-proxy\" delegates authentication to a reverse proxy. Ensure your proxy (Pomerium, Caddy, nginx) handles auth correctly and that gateway.trustedProxies only contains IPs of your actual proxy servers.",
|
|
2159
|
+
remediation: "Verify: (1) Your proxy terminates TLS and authenticates users. (2) gateway.trustedProxies is restricted to proxy IPs only. (3) Direct access to the Gateway port is blocked by firewall. See /gateway/trusted-proxy-auth for setup guidance."
|
|
2160
|
+
});
|
|
2161
|
+
if (trustedProxies.length === 0) findings.push({
|
|
2162
|
+
checkId: "gateway.trusted_proxy_no_proxies",
|
|
2163
|
+
severity: "critical",
|
|
2164
|
+
title: "Trusted-proxy auth enabled but no trusted proxies configured",
|
|
2165
|
+
detail: "gateway.auth.mode=\"trusted-proxy\" but gateway.trustedProxies is empty. All requests will be rejected.",
|
|
2166
|
+
remediation: "Set gateway.trustedProxies to the IP(s) of your reverse proxy."
|
|
2167
|
+
});
|
|
2168
|
+
if (!trustedProxyConfig?.userHeader) findings.push({
|
|
2169
|
+
checkId: "gateway.trusted_proxy_no_user_header",
|
|
2170
|
+
severity: "critical",
|
|
2171
|
+
title: "Trusted-proxy auth missing userHeader config",
|
|
2172
|
+
detail: "gateway.auth.mode=\"trusted-proxy\" but gateway.auth.trustedProxy.userHeader is not configured.",
|
|
2173
|
+
remediation: "Set gateway.auth.trustedProxy.userHeader to the header name your proxy uses (e.g., \"x-forwarded-user\", \"x-pomerium-claim-email\")."
|
|
2174
|
+
});
|
|
2175
|
+
if ((trustedProxyConfig?.allowUsers ?? []).length === 0) findings.push({
|
|
2176
|
+
checkId: "gateway.trusted_proxy_no_allowlist",
|
|
2177
|
+
severity: "warn",
|
|
2178
|
+
title: "Trusted-proxy auth allows all authenticated users",
|
|
2179
|
+
detail: "gateway.auth.trustedProxy.allowUsers is empty, so any user authenticated by your proxy can access the Gateway.",
|
|
2180
|
+
remediation: "Consider setting gateway.auth.trustedProxy.allowUsers to restrict access to specific users (e.g., [\"nick@example.com\"])."
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
if (bind !== "loopback" && auth.mode !== "trusted-proxy" && !cfg.gateway?.auth?.rateLimit) findings.push({
|
|
2184
|
+
checkId: "gateway.auth_no_rate_limit",
|
|
2185
|
+
severity: "warn",
|
|
2186
|
+
title: "No auth rate limiting configured",
|
|
2187
|
+
detail: "gateway.bind is not loopback but no gateway.auth.rateLimit is configured. Without rate limiting, brute-force auth attacks are not mitigated.",
|
|
2188
|
+
remediation: "Set gateway.auth.rateLimit (e.g. { maxAttempts: 10, windowMs: 60000, lockoutMs: 300000 })."
|
|
2189
|
+
});
|
|
2190
|
+
return findings;
|
|
2191
|
+
}
|
|
2192
|
+
function collectBrowserControlFindings(cfg, env) {
|
|
2193
|
+
const findings = [];
|
|
2194
|
+
let resolved;
|
|
2195
|
+
try {
|
|
2196
|
+
resolved = resolveBrowserConfig(cfg.browser, cfg);
|
|
2197
|
+
} catch (err) {
|
|
2198
|
+
findings.push({
|
|
2199
|
+
checkId: "browser.control_invalid_config",
|
|
2200
|
+
severity: "warn",
|
|
2201
|
+
title: "Browser control config looks invalid",
|
|
2202
|
+
detail: String(err),
|
|
2203
|
+
remediation: `Fix browser.cdpUrl in ${resolveConfigPath()} and re-run "${formatCliCommand("anima security audit --deep")}".`
|
|
2204
|
+
});
|
|
2205
|
+
return findings;
|
|
2206
|
+
}
|
|
2207
|
+
if (!resolved.enabled) return findings;
|
|
2208
|
+
const browserAuth = resolveBrowserControlAuth(cfg, env);
|
|
2209
|
+
if (!browserAuth.token && !browserAuth.password) findings.push({
|
|
2210
|
+
checkId: "browser.control_no_auth",
|
|
2211
|
+
severity: "critical",
|
|
2212
|
+
title: "Browser control has no auth",
|
|
2213
|
+
detail: "Browser control HTTP routes are enabled but no gateway.auth token/password is configured. Any local process (or SSRF to loopback) can call browser control endpoints.",
|
|
2214
|
+
remediation: "Set gateway.auth.token (recommended) or gateway.auth.password so browser control HTTP routes require authentication. Restarting the gateway will auto-generate gateway.auth.token when browser control is enabled."
|
|
2215
|
+
});
|
|
2216
|
+
for (const name of Object.keys(resolved.profiles)) {
|
|
2217
|
+
const profile = resolveProfile(resolved, name);
|
|
2218
|
+
if (!profile || profile.cdpIsLoopback) continue;
|
|
2219
|
+
let url;
|
|
2220
|
+
try {
|
|
2221
|
+
url = new URL(profile.cdpUrl);
|
|
2222
|
+
} catch {
|
|
2223
|
+
continue;
|
|
2224
|
+
}
|
|
2225
|
+
if (url.protocol === "http:") findings.push({
|
|
2226
|
+
checkId: "browser.remote_cdp_http",
|
|
2227
|
+
severity: "warn",
|
|
2228
|
+
title: "Remote CDP uses HTTP",
|
|
2229
|
+
detail: `browser profile "${name}" uses http CDP (${profile.cdpUrl}); this is OK only if it's tailnet-only or behind an encrypted tunnel.`,
|
|
2230
|
+
remediation: `Prefer HTTPS/TLS or a tailnet-only endpoint for remote CDP.`
|
|
2231
|
+
});
|
|
2232
|
+
}
|
|
2233
|
+
return findings;
|
|
2234
|
+
}
|
|
2235
|
+
function collectLoggingFindings(cfg) {
|
|
2236
|
+
if (cfg.logging?.redactSensitive !== "off") return [];
|
|
2237
|
+
return [{
|
|
2238
|
+
checkId: "logging.redact_off",
|
|
2239
|
+
severity: "warn",
|
|
2240
|
+
title: "Tool summary redaction is disabled",
|
|
2241
|
+
detail: `logging.redactSensitive="off" can leak secrets into logs and status output.`,
|
|
2242
|
+
remediation: `Set logging.redactSensitive="tools".`
|
|
2243
|
+
}];
|
|
2244
|
+
}
|
|
2245
|
+
function collectElevatedFindings(cfg) {
|
|
2246
|
+
const findings = [];
|
|
2247
|
+
const enabled = cfg.tools?.elevated?.enabled;
|
|
2248
|
+
const allowFrom = cfg.tools?.elevated?.allowFrom ?? {};
|
|
2249
|
+
const anyAllowFromKeys = Object.keys(allowFrom).length > 0;
|
|
2250
|
+
if (enabled === false) return findings;
|
|
2251
|
+
if (!anyAllowFromKeys) return findings;
|
|
2252
|
+
for (const [provider, list] of Object.entries(allowFrom)) {
|
|
2253
|
+
const normalized = normalizeAllowFromList(list);
|
|
2254
|
+
if (normalized.includes("*")) findings.push({
|
|
2255
|
+
checkId: `tools.elevated.allowFrom.${provider}.wildcard`,
|
|
2256
|
+
severity: "critical",
|
|
2257
|
+
title: "Elevated exec allowlist contains wildcard",
|
|
2258
|
+
detail: `tools.elevated.allowFrom.${provider} includes "*" which effectively approves everyone on that channel for elevated mode.`
|
|
2259
|
+
});
|
|
2260
|
+
else if (normalized.length > 25) findings.push({
|
|
2261
|
+
checkId: `tools.elevated.allowFrom.${provider}.large`,
|
|
2262
|
+
severity: "warn",
|
|
2263
|
+
title: "Elevated exec allowlist is large",
|
|
2264
|
+
detail: `tools.elevated.allowFrom.${provider} has ${normalized.length} entries; consider tightening elevated access.`
|
|
2265
|
+
});
|
|
2266
|
+
}
|
|
2267
|
+
return findings;
|
|
2268
|
+
}
|
|
2269
|
+
async function maybeProbeGateway(params) {
|
|
2270
|
+
const url = buildGatewayConnectionDetails({ config: params.cfg }).url;
|
|
2271
|
+
const isRemoteMode = params.cfg.gateway?.mode === "remote";
|
|
2272
|
+
const remoteUrlRaw = typeof params.cfg.gateway?.remote?.url === "string" ? params.cfg.gateway.remote.url.trim() : "";
|
|
2273
|
+
const auth = !isRemoteMode || isRemoteMode && !remoteUrlRaw ? resolveGatewayProbeAuth({
|
|
2274
|
+
cfg: params.cfg,
|
|
2275
|
+
mode: "local"
|
|
2276
|
+
}) : resolveGatewayProbeAuth({
|
|
2277
|
+
cfg: params.cfg,
|
|
2278
|
+
mode: "remote"
|
|
2279
|
+
});
|
|
2280
|
+
const res = await params.probe({
|
|
2281
|
+
url,
|
|
2282
|
+
auth,
|
|
2283
|
+
timeoutMs: params.timeoutMs
|
|
2284
|
+
}).catch((err) => ({
|
|
2285
|
+
ok: false,
|
|
2286
|
+
url,
|
|
2287
|
+
connectLatencyMs: null,
|
|
2288
|
+
error: String(err),
|
|
2289
|
+
close: null,
|
|
2290
|
+
health: null,
|
|
2291
|
+
status: null,
|
|
2292
|
+
presence: null,
|
|
2293
|
+
configSnapshot: null
|
|
2294
|
+
}));
|
|
2295
|
+
return { gateway: {
|
|
2296
|
+
attempted: true,
|
|
2297
|
+
url,
|
|
2298
|
+
ok: res.ok,
|
|
2299
|
+
error: res.ok ? null : res.error,
|
|
2300
|
+
close: res.close ? {
|
|
2301
|
+
code: res.close.code,
|
|
2302
|
+
reason: res.close.reason
|
|
2303
|
+
} : null
|
|
2304
|
+
} };
|
|
2305
|
+
}
|
|
2306
|
+
async function runSecurityAudit(opts) {
|
|
2307
|
+
const findings = [];
|
|
2308
|
+
const cfg = opts.config;
|
|
2309
|
+
const env = opts.env ?? process.env;
|
|
2310
|
+
const platform = opts.platform ?? process.platform;
|
|
2311
|
+
const execIcacls = opts.execIcacls;
|
|
2312
|
+
const stateDir = opts.stateDir ?? resolveStateDir(env);
|
|
2313
|
+
const configPath = opts.configPath ?? resolveConfigPath(env, stateDir);
|
|
2314
|
+
findings.push(...collectAttackSurfaceSummaryFindings(cfg));
|
|
2315
|
+
findings.push(...collectSyncedFolderFindings({
|
|
2316
|
+
stateDir,
|
|
2317
|
+
configPath
|
|
2318
|
+
}));
|
|
2319
|
+
findings.push(...collectGatewayConfigFindings(cfg, env));
|
|
2320
|
+
findings.push(...collectBrowserControlFindings(cfg, env));
|
|
2321
|
+
findings.push(...collectLoggingFindings(cfg));
|
|
2322
|
+
findings.push(...collectElevatedFindings(cfg));
|
|
2323
|
+
findings.push(...collectHooksHardeningFindings(cfg, env));
|
|
2324
|
+
findings.push(...collectGatewayHttpSessionKeyOverrideFindings(cfg));
|
|
2325
|
+
findings.push(...collectSandboxDockerNoopFindings(cfg));
|
|
2326
|
+
findings.push(...collectNodeDenyCommandPatternFindings(cfg));
|
|
2327
|
+
findings.push(...collectMinimalProfileOverrideFindings(cfg));
|
|
2328
|
+
findings.push(...collectSecretsInConfigFindings(cfg));
|
|
2329
|
+
findings.push(...collectModelHygieneFindings(cfg));
|
|
2330
|
+
findings.push(...collectSmallModelRiskFindings({
|
|
2331
|
+
cfg,
|
|
2332
|
+
env
|
|
2333
|
+
}));
|
|
2334
|
+
findings.push(...collectExposureMatrixFindings(cfg));
|
|
2335
|
+
const configSnapshot = opts.includeFilesystem !== false ? await readConfigSnapshotForAudit({
|
|
2336
|
+
env,
|
|
2337
|
+
configPath
|
|
2338
|
+
}).catch(() => null) : null;
|
|
2339
|
+
if (opts.includeFilesystem !== false) {
|
|
2340
|
+
findings.push(...await collectFilesystemFindings({
|
|
2341
|
+
stateDir,
|
|
2342
|
+
configPath,
|
|
2343
|
+
env,
|
|
2344
|
+
platform,
|
|
2345
|
+
execIcacls
|
|
2346
|
+
}));
|
|
2347
|
+
if (configSnapshot) findings.push(...await collectIncludeFilePermFindings({
|
|
2348
|
+
configSnapshot,
|
|
2349
|
+
env,
|
|
2350
|
+
platform,
|
|
2351
|
+
execIcacls
|
|
2352
|
+
}));
|
|
2353
|
+
findings.push(...await collectStateDeepFilesystemFindings({
|
|
2354
|
+
cfg,
|
|
2355
|
+
env,
|
|
2356
|
+
stateDir,
|
|
2357
|
+
platform,
|
|
2358
|
+
execIcacls
|
|
2359
|
+
}));
|
|
2360
|
+
findings.push(...await collectPluginsTrustFindings({
|
|
2361
|
+
cfg,
|
|
2362
|
+
stateDir
|
|
2363
|
+
}));
|
|
2364
|
+
if (opts.deep === true) {
|
|
2365
|
+
findings.push(...await collectPluginsCodeSafetyFindings({ stateDir }));
|
|
2366
|
+
findings.push(...await collectInstalledSkillsCodeSafetyFindings({
|
|
2367
|
+
cfg,
|
|
2368
|
+
stateDir
|
|
2369
|
+
}));
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
if (opts.includeChannelSecurity !== false) {
|
|
2373
|
+
const plugins = opts.plugins ?? listChannelPlugins();
|
|
2374
|
+
findings.push(...await collectChannelSecurityFindings({
|
|
2375
|
+
cfg,
|
|
2376
|
+
plugins
|
|
2377
|
+
}));
|
|
2378
|
+
}
|
|
2379
|
+
const deep = opts.deep === true ? await maybeProbeGateway({
|
|
2380
|
+
cfg,
|
|
2381
|
+
timeoutMs: Math.max(250, opts.deepTimeoutMs ?? 5e3),
|
|
2382
|
+
probe: opts.probeGatewayFn ?? probeGateway
|
|
2383
|
+
}) : void 0;
|
|
2384
|
+
if (deep?.gateway?.attempted && !deep.gateway.ok) findings.push({
|
|
2385
|
+
checkId: "gateway.probe_failed",
|
|
2386
|
+
severity: "warn",
|
|
2387
|
+
title: "Gateway probe failed (deep)",
|
|
2388
|
+
detail: deep.gateway.error ?? "gateway unreachable",
|
|
2389
|
+
remediation: `Run "${formatCliCommand("anima status --all")}" to debug connectivity/auth, then re-run "${formatCliCommand("anima security audit --deep")}".`
|
|
2390
|
+
});
|
|
2391
|
+
const summary = countBySeverity(findings);
|
|
2392
|
+
return {
|
|
2393
|
+
ts: Date.now(),
|
|
2394
|
+
summary,
|
|
2395
|
+
findings,
|
|
2396
|
+
deep
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
//#endregion
|
|
2401
|
+
export { resolveGatewayProbeAuth as a, probeGateway as c, collectIncludePathsRecursive as i, createIcaclsResetCommand as n, isNodeCommandAllowed as o, formatIcaclsResetCommand as r, resolveNodeCommandAllowlist as s, runSecurityAudit as t };
|