@theowlops/boombot 0.0.0-stage → 1.1.0
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 +69 -3
- package/dist/LICENSE +21 -0
- package/dist/bin/boombot.js +16 -0
- package/dist/cli/api-key.js +123 -0
- package/dist/cli/bin-bun.js +17 -0
- package/dist/cli/bin.js +34 -0
- package/dist/cli/cli/cli-error.js +61 -0
- package/dist/cli/cli/dispatch.js +138 -0
- package/dist/cli/cli/help.js +83 -0
- package/dist/cli/cli/parser.js +89 -0
- package/dist/cli/cli/registry.js +185 -0
- package/dist/cli/cli/spec.js +41 -0
- package/dist/cli/cli/suggest.js +125 -0
- package/dist/cli/cli/version.js +30 -0
- package/dist/cli/cli/vocabulary.js +87 -0
- package/dist/cli/commands/doctor.js +365 -0
- package/dist/cli/commands/gateway.js +290 -0
- package/dist/cli/commands/migrate.js +270 -0
- package/dist/cli/commands/plugin.js +97 -0
- package/dist/cli/commands/setup.js +230 -0
- package/dist/cli/config-file.js +133 -0
- package/dist/cli/master-key.js +78 -0
- package/dist/cli/open-browser.js +59 -0
- package/dist/cli/web-assets.js +58 -0
- package/dist/gateway/auth.js +46 -0
- package/dist/gateway/config-format.js +202 -0
- package/dist/gateway/config.js +427 -0
- package/dist/gateway/context.js +1 -0
- package/dist/gateway/env-file.js +129 -0
- package/dist/gateway/events.js +172 -0
- package/dist/gateway/guard/tagging.js +505 -0
- package/dist/gateway/handlers/index.js +66 -0
- package/dist/gateway/handlers/memory.js +54 -0
- package/dist/gateway/handlers/providers.js +73 -0
- package/dist/gateway/handlers/registry.js +39 -0
- package/dist/gateway/handlers/sessions.js +188 -0
- package/dist/gateway/handlers/system.js +12 -0
- package/dist/gateway/handlers/tasks.js +191 -0
- package/dist/gateway/handlers/turn.js +100 -0
- package/dist/gateway/index.js +126 -0
- package/dist/gateway/main.js +23 -0
- package/dist/gateway/server.js +354 -0
- package/dist/gateway/services/context-limits.js +75 -0
- package/dist/gateway/services/history-normalizer.js +42 -0
- package/dist/gateway/services/image-pipeline.js +56 -0
- package/dist/gateway/services/key-roulette-client.js +43 -0
- package/dist/gateway/services/llm-client.js +593 -0
- package/dist/gateway/services/llm-decoder.js +200 -0
- package/dist/gateway/services/local-fallback.js +135 -0
- package/dist/gateway/services/memory.js +511 -0
- package/dist/gateway/services/persistent-memory.js +196 -0
- package/dist/gateway/services/providers.js +44 -0
- package/dist/gateway/services/tasks.js +237 -0
- package/dist/gateway/services/verification.js +124 -0
- package/dist/gateway/services/vision-payload.js +48 -0
- package/dist/gateway/services.js +257 -0
- package/dist/gateway/session-snapshot.js +19 -0
- package/dist/gateway/sessions.js +701 -0
- package/dist/gateway/static.js +239 -0
- package/dist/gateway/turns/clarify-tool.js +57 -0
- package/dist/gateway/turns/cronjob-tool.js +96 -0
- package/dist/gateway/turns/deferred-tools.js +154 -0
- package/dist/gateway/turns/execute-code-tool.js +84 -0
- package/dist/gateway/turns/file-tool-defs.js +178 -0
- package/dist/gateway/turns/file-tool-ops.js +178 -0
- package/dist/gateway/turns/file-tool-scan.js +182 -0
- package/dist/gateway/turns/file-tool.js +302 -0
- package/dist/gateway/turns/index.js +416 -0
- package/dist/gateway/turns/jev-middleware.js +91 -0
- package/dist/gateway/turns/local-reply.js +156 -0
- package/dist/gateway/turns/mcp-client.js +643 -0
- package/dist/gateway/turns/memory-tool.js +171 -0
- package/dist/gateway/turns/middleware.js +96 -0
- package/dist/gateway/turns/python-core.js +397 -0
- package/dist/gateway/turns/routing/index.js +3 -0
- package/dist/gateway/turns/routing/intent-classifier.js +60 -0
- package/dist/gateway/turns/routing/planner-adapter.js +91 -0
- package/dist/gateway/turns/routing/types.js +1 -0
- package/dist/gateway/turns/runtime-deps.js +228 -0
- package/dist/gateway/turns/self-config-tool.js +277 -0
- package/dist/gateway/turns/service-types.js +11 -0
- package/dist/gateway/turns/service.js +820 -0
- package/dist/gateway/turns/session-adapter.js +158 -0
- package/dist/gateway/turns/session-tool.js +49 -0
- package/dist/gateway/turns/shell-tool.js +190 -0
- package/dist/gateway/turns/skill-tool.js +239 -0
- package/dist/gateway/turns/spillover.js +201 -0
- package/dist/gateway/turns/subagent-tool.js +140 -0
- package/dist/gateway/turns/system-prompt.js +121 -0
- package/dist/gateway/turns/tool-schema.js +90 -0
- package/dist/gateway/turns/turn-utils.js +58 -0
- package/dist/gateway/turns/vision-tool.js +120 -0
- package/dist/gateway/types.js +1 -0
- package/dist/native/win32-x64/boombot-native-host.exe +0 -0
- package/dist/node_modules/@boombot/channels/handlers.d.ts +9 -0
- package/dist/node_modules/@boombot/channels/handlers.js +109 -0
- package/dist/node_modules/@boombot/channels/index.d.ts +12 -0
- package/dist/node_modules/@boombot/channels/index.js +12 -0
- package/dist/node_modules/@boombot/channels/package.json +9 -0
- package/dist/node_modules/@boombot/channels/service.d.ts +62 -0
- package/dist/node_modules/@boombot/channels/service.js +608 -0
- package/dist/node_modules/@boombot/channels/tool.d.ts +10 -0
- package/dist/node_modules/@boombot/channels/tool.js +46 -0
- package/dist/node_modules/@boombot/channels/transports/common/dedup.d.ts +17 -0
- package/dist/node_modules/@boombot/channels/transports/common/dedup.js +42 -0
- package/dist/node_modules/@boombot/channels/transports/common/handoff.d.ts +19 -0
- package/dist/node_modules/@boombot/channels/transports/common/handoff.js +32 -0
- package/dist/node_modules/@boombot/channels/transports/common/index.d.ts +3 -0
- package/dist/node_modules/@boombot/channels/transports/common/index.js +3 -0
- package/dist/node_modules/@boombot/channels/transports/common/types.d.ts +99 -0
- package/dist/node_modules/@boombot/channels/transports/common/types.js +54 -0
- package/dist/node_modules/@boombot/channels/transports/common.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/common.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/dedup/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/dedup/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/dedup.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/dedup.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/discord/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/discord/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/discord/transport.d.ts +36 -0
- package/dist/node_modules/@boombot/channels/transports/discord/transport.js +199 -0
- package/dist/node_modules/@boombot/channels/transports/discord.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/discord.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/handoff.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/handoff.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/messenger/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/messenger/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/messenger/transport.d.ts +34 -0
- package/dist/node_modules/@boombot/channels/transports/messenger/transport.js +180 -0
- package/dist/node_modules/@boombot/channels/transports/messenger.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/messenger.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/slack/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/slack/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/slack/transport.d.ts +35 -0
- package/dist/node_modules/@boombot/channels/transports/slack/transport.js +183 -0
- package/dist/node_modules/@boombot/channels/transports/slack.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/slack.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/streamer/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/streamer/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/streamer/streamer.d.ts +31 -0
- package/dist/node_modules/@boombot/channels/transports/streamer/streamer.js +141 -0
- package/dist/node_modules/@boombot/channels/transports/streamer.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/streamer.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/telegram/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/telegram/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/telegram/transport.d.ts +75 -0
- package/dist/node_modules/@boombot/channels/transports/telegram/transport.js +395 -0
- package/dist/node_modules/@boombot/channels/transports/telegram.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/telegram.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/tiktok/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/tiktok/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/tiktok/transport.d.ts +27 -0
- package/dist/node_modules/@boombot/channels/transports/tiktok/transport.js +97 -0
- package/dist/node_modules/@boombot/channels/transports/tiktok.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/tiktok.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/whatsapp/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/whatsapp/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/whatsapp/transport.d.ts +38 -0
- package/dist/node_modules/@boombot/channels/transports/whatsapp/transport.js +189 -0
- package/dist/node_modules/@boombot/channels/transports/whatsapp.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/whatsapp.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/zalo/index.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/zalo/index.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/zalo/oa.d.ts +28 -0
- package/dist/node_modules/@boombot/channels/transports/zalo/oa.js +94 -0
- package/dist/node_modules/@boombot/channels/transports/zalo/personal.d.ts +106 -0
- package/dist/node_modules/@boombot/channels/transports/zalo/personal.js +171 -0
- package/dist/node_modules/@boombot/channels/transports/zalo-personal.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/zalo-personal.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/zalo.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/zalo.js +2 -0
- package/dist/node_modules/@boombot/core/agent-core/index.d.ts +12 -0
- package/dist/node_modules/@boombot/core/agent-core/index.js +12 -0
- package/dist/node_modules/@boombot/core/agent-core/middleware.d.ts +60 -0
- package/dist/node_modules/@boombot/core/agent-core/middleware.js +152 -0
- package/dist/node_modules/@boombot/core/agent-core/routing.d.ts +128 -0
- package/dist/node_modules/@boombot/core/agent-core/routing.js +437 -0
- package/dist/node_modules/@boombot/core/agent-core/runtime-deps.d.ts +88 -0
- package/dist/node_modules/@boombot/core/agent-core/runtime-deps.js +8 -0
- package/dist/node_modules/@boombot/core/agent-core/tool-errors.d.ts +44 -0
- package/dist/node_modules/@boombot/core/agent-core/tool-errors.js +60 -0
- package/dist/node_modules/@boombot/core/agent-core/turn-loop.d.ts +21 -0
- package/dist/node_modules/@boombot/core/agent-core/turn-loop.js +561 -0
- package/dist/node_modules/@boombot/core/agent-core/types.d.ts +189 -0
- package/dist/node_modules/@boombot/core/agent-core/types.js +7 -0
- package/dist/node_modules/@boombot/core/compaction/compactor.d.ts +19 -0
- package/dist/node_modules/@boombot/core/compaction/compactor.js +127 -0
- package/dist/node_modules/@boombot/core/compaction/estimator.d.ts +10 -0
- package/dist/node_modules/@boombot/core/compaction/estimator.js +48 -0
- package/dist/node_modules/@boombot/core/compaction/index.d.ts +3 -0
- package/dist/node_modules/@boombot/core/compaction/index.js +3 -0
- package/dist/node_modules/@boombot/core/compaction/types.d.ts +31 -0
- package/dist/node_modules/@boombot/core/compaction/types.js +7 -0
- package/dist/node_modules/@boombot/core/index.d.ts +9 -0
- package/dist/node_modules/@boombot/core/index.js +9 -0
- package/dist/node_modules/@boombot/core/jev/index.d.ts +2 -0
- package/dist/node_modules/@boombot/core/jev/index.js +2 -0
- package/dist/node_modules/@boombot/core/jev/jev.d.ts +30 -0
- package/dist/node_modules/@boombot/core/jev/jev.js +164 -0
- package/dist/node_modules/@boombot/core/jev/types.d.ts +22 -0
- package/dist/node_modules/@boombot/core/jev/types.js +1 -0
- package/dist/node_modules/@boombot/core/memory/consolidation/budget.d.ts +63 -0
- package/dist/node_modules/@boombot/core/memory/consolidation/budget.js +94 -0
- package/dist/node_modules/@boombot/core/memory/consolidation/consolidator.d.ts +152 -0
- package/dist/node_modules/@boombot/core/memory/consolidation/consolidator.js +318 -0
- package/dist/node_modules/@boombot/core/memory/consolidation/index.d.ts +12 -0
- package/dist/node_modules/@boombot/core/memory/consolidation/index.js +12 -0
- package/dist/node_modules/@boombot/core/memory/curation.d.ts +86 -0
- package/dist/node_modules/@boombot/core/memory/curation.js +120 -0
- package/dist/node_modules/@boombot/core/memory/errors.d.ts +12 -0
- package/dist/node_modules/@boombot/core/memory/errors.js +20 -0
- package/dist/node_modules/@boombot/core/memory/hybrid-search.d.ts +29 -0
- package/dist/node_modules/@boombot/core/memory/hybrid-search.js +52 -0
- package/dist/node_modules/@boombot/core/memory/index.d.ts +23 -0
- package/dist/node_modules/@boombot/core/memory/index.js +23 -0
- package/dist/node_modules/@boombot/core/memory/indexer.d.ts +47 -0
- package/dist/node_modules/@boombot/core/memory/indexer.js +138 -0
- package/dist/node_modules/@boombot/core/memory/lifecycle.d.ts +12 -0
- package/dist/node_modules/@boombot/core/memory/lifecycle.js +66 -0
- package/dist/node_modules/@boombot/core/memory/provider.d.ts +84 -0
- package/dist/node_modules/@boombot/core/memory/provider.js +117 -0
- package/dist/node_modules/@boombot/core/memory/recall.d.ts +7 -0
- package/dist/node_modules/@boombot/core/memory/recall.js +111 -0
- package/dist/node_modules/@boombot/core/memory/scope-resolver.d.ts +27 -0
- package/dist/node_modules/@boombot/core/memory/scope-resolver.js +60 -0
- package/dist/node_modules/@boombot/core/memory/search.d.ts +37 -0
- package/dist/node_modules/@boombot/core/memory/search.js +158 -0
- package/dist/node_modules/@boombot/core/memory/store.d.ts +60 -0
- package/dist/node_modules/@boombot/core/memory/store.js +289 -0
- package/dist/node_modules/@boombot/core/memory/types.d.ts +121 -0
- package/dist/node_modules/@boombot/core/memory/types.js +21 -0
- package/dist/node_modules/@boombot/core/memory/vector-index.d.ts +34 -0
- package/dist/node_modules/@boombot/core/memory/vector-index.js +65 -0
- package/dist/node_modules/@boombot/core/package.json +9 -0
- package/dist/node_modules/@boombot/core/reflection/classifier.d.ts +13 -0
- package/dist/node_modules/@boombot/core/reflection/classifier.js +77 -0
- package/dist/node_modules/@boombot/core/reflection/index.d.ts +2 -0
- package/dist/node_modules/@boombot/core/reflection/index.js +2 -0
- package/dist/node_modules/@boombot/core/reflection/types.d.ts +13 -0
- package/dist/node_modules/@boombot/core/reflection/types.js +4 -0
- package/dist/node_modules/@boombot/core/research/config.d.ts +89 -0
- package/dist/node_modules/@boombot/core/research/config.js +206 -0
- package/dist/node_modules/@boombot/core/research/crawl.d.ts +36 -0
- package/dist/node_modules/@boombot/core/research/crawl.js +179 -0
- package/dist/node_modules/@boombot/core/research/factory.d.ts +26 -0
- package/dist/node_modules/@boombot/core/research/factory.js +46 -0
- package/dist/node_modules/@boombot/core/research/http.d.ts +87 -0
- package/dist/node_modules/@boombot/core/research/http.js +250 -0
- package/dist/node_modules/@boombot/core/research/index.d.ts +17 -0
- package/dist/node_modules/@boombot/core/research/index.js +10 -0
- package/dist/node_modules/@boombot/core/research/orchestrator.d.ts +113 -0
- package/dist/node_modules/@boombot/core/research/orchestrator.js +252 -0
- package/dist/node_modules/@boombot/core/research/parse/docx.d.ts +24 -0
- package/dist/node_modules/@boombot/core/research/parse/docx.js +183 -0
- package/dist/node_modules/@boombot/core/research/parse/guards.d.ts +89 -0
- package/dist/node_modules/@boombot/core/research/parse/guards.js +171 -0
- package/dist/node_modules/@boombot/core/research/parse/index.d.ts +68 -0
- package/dist/node_modules/@boombot/core/research/parse/index.js +219 -0
- package/dist/node_modules/@boombot/core/research/parse/pdf.d.ts +38 -0
- package/dist/node_modules/@boombot/core/research/parse/pdf.js +567 -0
- package/dist/node_modules/@boombot/core/research/providers/arxiv.d.ts +9 -0
- package/dist/node_modules/@boombot/core/research/providers/arxiv.js +72 -0
- package/dist/node_modules/@boombot/core/research/providers/brave.d.ts +11 -0
- package/dist/node_modules/@boombot/core/research/providers/brave.js +36 -0
- package/dist/node_modules/@boombot/core/research/providers/duckduckgo.d.ts +9 -0
- package/dist/node_modules/@boombot/core/research/providers/duckduckgo.js +78 -0
- package/dist/node_modules/@boombot/core/research/providers/exa.d.ts +11 -0
- package/dist/node_modules/@boombot/core/research/providers/exa.js +34 -0
- package/dist/node_modules/@boombot/core/research/providers/firecrawl.d.ts +16 -0
- package/dist/node_modules/@boombot/core/research/providers/firecrawl.js +47 -0
- package/dist/node_modules/@boombot/core/research/providers/index.d.ts +17 -0
- package/dist/node_modules/@boombot/core/research/providers/index.js +11 -0
- package/dist/node_modules/@boombot/core/research/providers/jina.d.ts +13 -0
- package/dist/node_modules/@boombot/core/research/providers/jina.js +114 -0
- package/dist/node_modules/@boombot/core/research/providers/openalex.d.ts +14 -0
- package/dist/node_modules/@boombot/core/research/providers/openalex.js +66 -0
- package/dist/node_modules/@boombot/core/research/providers/semantic-scholar.d.ts +9 -0
- package/dist/node_modules/@boombot/core/research/providers/semantic-scholar.js +56 -0
- package/dist/node_modules/@boombot/core/research/providers/tavily.d.ts +11 -0
- package/dist/node_modules/@boombot/core/research/providers/tavily.js +35 -0
- package/dist/node_modules/@boombot/core/research/providers/wikipedia.d.ts +12 -0
- package/dist/node_modules/@boombot/core/research/providers/wikipedia.js +47 -0
- package/dist/node_modules/@boombot/core/research/tools.d.ts +27 -0
- package/dist/node_modules/@boombot/core/research/tools.js +251 -0
- package/dist/node_modules/@boombot/core/research/types.d.ts +133 -0
- package/dist/node_modules/@boombot/core/research/types.js +8 -0
- package/dist/node_modules/@boombot/core/research/wiring.d.ts +25 -0
- package/dist/node_modules/@boombot/core/research/wiring.js +51 -0
- package/dist/node_modules/@boombot/core/scheduler/cron.d.ts +34 -0
- package/dist/node_modules/@boombot/core/scheduler/cron.js +147 -0
- package/dist/node_modules/@boombot/core/scheduler/evaluator.d.ts +42 -0
- package/dist/node_modules/@boombot/core/scheduler/evaluator.js +148 -0
- package/dist/node_modules/@boombot/core/scheduler/index.d.ts +15 -0
- package/dist/node_modules/@boombot/core/scheduler/index.js +15 -0
- package/dist/node_modules/@boombot/core/scheduler/registry.d.ts +81 -0
- package/dist/node_modules/@boombot/core/scheduler/registry.js +263 -0
- package/dist/node_modules/@boombot/core/scheduler/runner.d.ts +59 -0
- package/dist/node_modules/@boombot/core/scheduler/runner.js +257 -0
- package/dist/node_modules/@boombot/core/scheduler/store.d.ts +150 -0
- package/dist/node_modules/@boombot/core/scheduler/store.js +526 -0
- package/dist/node_modules/@boombot/core/scheduler/timezone.d.ts +35 -0
- package/dist/node_modules/@boombot/core/scheduler/timezone.js +96 -0
- package/dist/node_modules/@boombot/core/scheduler/types.d.ts +157 -0
- package/dist/node_modules/@boombot/core/scheduler/types.js +89 -0
- package/dist/node_modules/@boombot/core/task-runner/index.d.ts +7 -0
- package/dist/node_modules/@boombot/core/task-runner/index.js +5 -0
- package/dist/node_modules/@boombot/core/task-runner/planner.d.ts +37 -0
- package/dist/node_modules/@boombot/core/task-runner/planner.js +154 -0
- package/dist/node_modules/@boombot/core/task-runner/runner.d.ts +8 -0
- package/dist/node_modules/@boombot/core/task-runner/runner.js +564 -0
- package/dist/node_modules/@boombot/core/task-runner/store.d.ts +28 -0
- package/dist/node_modules/@boombot/core/task-runner/store.js +126 -0
- package/dist/node_modules/@boombot/core/task-runner/types.d.ts +138 -0
- package/dist/node_modules/@boombot/core/task-runner/types.js +11 -0
- package/dist/node_modules/@boombot/core/workflow/dag.d.ts +16 -0
- package/dist/node_modules/@boombot/core/workflow/dag.js +109 -0
- package/dist/node_modules/@boombot/core/workflow/engine.d.ts +34 -0
- package/dist/node_modules/@boombot/core/workflow/engine.js +304 -0
- package/dist/node_modules/@boombot/core/workflow/index.d.ts +3 -0
- package/dist/node_modules/@boombot/core/workflow/index.js +3 -0
- package/dist/node_modules/@boombot/core/workflow/types.d.ts +98 -0
- package/dist/node_modules/@boombot/core/workflow/types.js +21 -0
- package/dist/node_modules/@boombot/cronjobs/handlers.d.ts +11 -0
- package/dist/node_modules/@boombot/cronjobs/handlers.js +174 -0
- package/dist/node_modules/@boombot/cronjobs/index.d.ts +2 -0
- package/dist/node_modules/@boombot/cronjobs/index.js +2 -0
- package/dist/node_modules/@boombot/cronjobs/package.json +9 -0
- package/dist/node_modules/@boombot/cronjobs/service.d.ts +53 -0
- package/dist/node_modules/@boombot/cronjobs/service.js +200 -0
- package/dist/node_modules/@boombot/plugins/index.d.ts +2 -0
- package/dist/node_modules/@boombot/plugins/index.js +2 -0
- package/dist/node_modules/@boombot/plugins/installer.d.ts +45 -0
- package/dist/node_modules/@boombot/plugins/installer.js +487 -0
- package/dist/node_modules/@boombot/plugins/manager.d.ts +23 -0
- package/dist/node_modules/@boombot/plugins/manager.js +243 -0
- package/dist/node_modules/@boombot/plugins/package.json +9 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-channels.d.ts +125 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-channels.js +76 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-plugins.d.ts +220 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-plugins.js +60 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-scheduler.d.ts +33 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-scheduler.js +25 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands.d.ts +4349 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands.js +563 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/envelope.d.ts +32 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/envelope.js +61 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/errors.d.ts +13 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/errors.js +16 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/events.d.ts +567 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/events.js +395 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/index.d.ts +20 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/index.js +13 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/types.d.ts +91 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/types.js +7 -0
- package/dist/node_modules/@boombot/protocol/index.d.ts +4 -0
- package/dist/node_modules/@boombot/protocol/index.js +3 -0
- package/dist/node_modules/@boombot/protocol/package.json +9 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/channel.d.ts +41 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/channel.js +17 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/index.d.ts +10 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/index.js +10 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/manifest.d.ts +58 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/manifest.js +116 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/plugin.d.ts +25 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/plugin.js +1 -0
- package/dist/node_modules/@boombot/protocol/providers/cipher.d.ts +7 -0
- package/dist/node_modules/@boombot/protocol/providers/cipher.js +52 -0
- package/dist/node_modules/@boombot/protocol/providers/index.d.ts +19 -0
- package/dist/node_modules/@boombot/protocol/providers/index.js +19 -0
- package/dist/node_modules/@boombot/protocol/providers/key-roulette.d.ts +79 -0
- package/dist/node_modules/@boombot/protocol/providers/key-roulette.js +81 -0
- package/dist/node_modules/@boombot/protocol/providers/probe.d.ts +29 -0
- package/dist/node_modules/@boombot/protocol/providers/probe.js +96 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/anthropic.d.ts +11 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/anthropic.js +11 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/custom.d.ts +22 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/custom.js +222 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/deepseek.d.ts +7 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/deepseek.js +9 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/google.d.ts +10 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/google.js +10 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/index.d.ts +31 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/index.js +56 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/local.d.ts +10 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/local.js +11 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/mistral.d.ts +7 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/mistral.js +9 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/openai-compatible.d.ts +10 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/openai-compatible.js +10 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/openai.d.ts +7 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/openai.js +11 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/types.d.ts +58 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/types.js +1 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/xai.d.ts +7 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/xai.js +10 -0
- package/dist/node_modules/@boombot/protocol/providers/request-profile.d.ts +102 -0
- package/dist/node_modules/@boombot/protocol/providers/request-profile.js +118 -0
- package/dist/node_modules/@boombot/protocol/providers/service.d.ts +2 -0
- package/dist/node_modules/@boombot/protocol/providers/service.js +418 -0
- package/dist/node_modules/@boombot/protocol/providers/types.d.ts +141 -0
- package/dist/node_modules/@boombot/protocol/providers/types.js +16 -0
- package/dist/node_modules/@boombot/sandbox/fs-path.d.ts +13 -0
- package/dist/node_modules/@boombot/sandbox/fs-path.js +79 -0
- package/dist/node_modules/@boombot/sandbox/index.d.ts +13 -0
- package/dist/node_modules/@boombot/sandbox/index.js +13 -0
- package/dist/node_modules/@boombot/sandbox/native.d.ts +39 -0
- package/dist/node_modules/@boombot/sandbox/native.js +224 -0
- package/dist/node_modules/@boombot/sandbox/package.json +9 -0
- package/dist/node_modules/@boombot/sandbox/sandbox.d.ts +92 -0
- package/dist/node_modules/@boombot/sandbox/sandbox.js +229 -0
- package/dist/node_modules/@boombot/security/harness-guard/boundary.d.ts +22 -0
- package/dist/node_modules/@boombot/security/harness-guard/boundary.js +105 -0
- package/dist/node_modules/@boombot/security/harness-guard/gates.d.ts +90 -0
- package/dist/node_modules/@boombot/security/harness-guard/gates.js +791 -0
- package/dist/node_modules/@boombot/security/harness-guard/index.d.ts +23 -0
- package/dist/node_modules/@boombot/security/harness-guard/index.js +23 -0
- package/dist/node_modules/@boombot/security/harness-guard/policy.d.ts +20 -0
- package/dist/node_modules/@boombot/security/harness-guard/policy.js +70 -0
- package/dist/node_modules/@boombot/security/harness-guard/sanitizer.d.ts +53 -0
- package/dist/node_modules/@boombot/security/harness-guard/sanitizer.js +145 -0
- package/dist/node_modules/@boombot/security/harness-guard/types.d.ts +71 -0
- package/dist/node_modules/@boombot/security/harness-guard/types.js +10 -0
- package/dist/node_modules/@boombot/security/harness-guard/verifier.d.ts +59 -0
- package/dist/node_modules/@boombot/security/harness-guard/verifier.js +169 -0
- package/dist/node_modules/@boombot/security/index.d.ts +5 -0
- package/dist/node_modules/@boombot/security/index.js +9 -0
- package/dist/node_modules/@boombot/security/package.json +9 -0
- package/dist/node_modules/@boombot/security/policy/approval.d.ts +117 -0
- package/dist/node_modules/@boombot/security/policy/approval.js +223 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/claim-extractor.d.ts +10 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/claim-extractor.js +124 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/evidence-ledger.d.ts +25 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/evidence-ledger.js +70 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/gate.d.ts +38 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/gate.js +61 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/grounding-check.d.ts +12 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/grounding-check.js +234 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/index.d.ts +17 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/index.js +13 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/policy.d.ts +15 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/policy.js +39 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/types.d.ts +46 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/types.js +7 -0
- package/dist/node_modules/@boombot/security/security/approval.d.ts +63 -0
- package/dist/node_modules/@boombot/security/security/approval.js +140 -0
- package/dist/node_modules/@boombot/security/security/audit.d.ts +39 -0
- package/dist/node_modules/@boombot/security/security/audit.js +70 -0
- package/dist/node_modules/@boombot/security/security/index.d.ts +12 -0
- package/dist/node_modules/@boombot/security/security/index.js +12 -0
- package/dist/node_modules/@boombot/security/security/trust.d.ts +79 -0
- package/dist/node_modules/@boombot/security/security/trust.js +87 -0
- package/dist/package.json +5 -0
- package/dist/proto/agent_core.proto +56 -0
- package/dist/python/agent-core-python/pyproject.toml +40 -0
- package/dist/python/agent-core-python/src/boombot_agent/__init__.py +5 -0
- package/dist/python/agent-core-python/src/boombot_agent/generated/__init__.py +0 -0
- package/dist/python/agent-core-python/src/boombot_agent/generated/agent_core_pb2.py +54 -0
- package/dist/python/agent-core-python/src/boombot_agent/generated/agent_core_pb2_grpc.py +140 -0
- package/dist/python/agent-core-python/src/boombot_agent/rpc.py +265 -0
- package/dist/python/agent-core-python/src/boombot_agent/turn.py +577 -0
- package/dist/python/agent-core-python/src/boombot_ml/__init__.py +12 -0
- package/dist/python/agent-core-python/src/boombot_ml/embeddings.py +58 -0
- package/dist/python/agent-core-python/src/boombot_ml/training.py +156 -0
- package/dist/python/agent-core-python/uv.lock +2856 -0
- package/dist/web/assets/ChatView-BZrSD-64.js +262 -0
- package/dist/web/assets/ChatView-Cahbjez6.css +1 -0
- package/dist/web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/web/assets/MemoryView-BJbXqqs7.css +1 -0
- package/dist/web/assets/MemoryView-BfMq_6CD.js +1 -0
- package/dist/web/assets/SchedulesView-B22QmPR-.js +1 -0
- package/dist/web/assets/SchedulesView-C6XK7A8a.css +1 -0
- package/dist/web/assets/SessionsView-C1dqdhxI.css +1 -0
- package/dist/web/assets/SessionsView-DS0-aH2a.js +1 -0
- package/dist/web/assets/TasksView-Dtasg4hd.css +1 -0
- package/dist/web/assets/TasksView-ey0FLsW8.js +2 -0
- package/dist/web/assets/Textarea-DN3Zybl1.js +1 -0
- package/dist/web/assets/Tooltip-Bn2cqFPz.css +1 -0
- package/dist/web/assets/Tooltip-MzV2Gqnc.js +1 -0
- package/dist/web/assets/browser-Bnt73-Ub.js +8 -0
- package/dist/web/assets/index-BL8bi7Yt.css +1 -0
- package/dist/web/assets/index-CuPR73Tn.js +73 -0
- package/dist/web/favicon.svg +4 -0
- package/dist/web/index.html +26 -0
- package/dist/web/logo.jpg +0 -0
- package/dist/web/logo.png +0 -0
- package/package.json +51 -4
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@boombot/security` — deterministic tool gates (layer: `'gates'`).
|
|
3
|
+
*
|
|
4
|
+
* Fail-closed static checks that run *before* a tool executes:
|
|
5
|
+
*
|
|
6
|
+
* - `tokenizeBash` / `analyzeBash` — a hand-written bash tokenizer (quotes,
|
|
7
|
+
* escapes, comments, operators, `$(...)`, backticks, `${...}`) plus a
|
|
8
|
+
* rule-based risk classifier. Deterministic: the same command always yields
|
|
9
|
+
* the same risk. When in doubt it escalates (fail-closed).
|
|
10
|
+
* - `checkHttpUrl` — outbound URL gate: protocol check, userinfo (credential
|
|
11
|
+
* in URL) check, and hostname allowlist with `*.example.com` wildcards.
|
|
12
|
+
* - `evaluateToolCall` — the entry point the verifier imports. Routes a tool
|
|
13
|
+
* call through deny-list / approval-list / bash-like / http-like gates.
|
|
14
|
+
*
|
|
15
|
+
* Zero dependencies. Findings are mapped to a severity and passed to `decide()`
|
|
16
|
+
* from `./policy.js` so `GuardMode` semantics stay in one place; clean checks
|
|
17
|
+
* (no finding at all) allow directly, so strict mode never denies a command
|
|
18
|
+
* or URL that nothing flagged. Every finding's reason names the exact
|
|
19
|
+
* construct that was caught.
|
|
20
|
+
*/
|
|
21
|
+
import { isAbsolute, resolve, sep } from 'node:path';
|
|
22
|
+
import { decide } from './policy.js';
|
|
23
|
+
/** Layer tag attached to every verdict produced by this module. */
|
|
24
|
+
const LAYER = 'gates';
|
|
25
|
+
const RISK_ORDER = ['low', 'medium', 'high', 'critical'];
|
|
26
|
+
function maxRisk(a, b) {
|
|
27
|
+
return RISK_ORDER.indexOf(a) >= RISK_ORDER.indexOf(b) ? a : b;
|
|
28
|
+
}
|
|
29
|
+
function truncate(s, max = 160) {
|
|
30
|
+
return s.length > max ? `${s.slice(0, max - 3)}...` : s;
|
|
31
|
+
}
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// tokenizeBash
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
/**
|
|
36
|
+
* Tokenize a bash command line.
|
|
37
|
+
*
|
|
38
|
+
* Handles single/double quotes (quote-aware, quotes stripped from the token
|
|
39
|
+
* value), backslash escapes, `#` comments (only at the start of a word,
|
|
40
|
+
* outside quotes), and emits operators as standalone tokens: `|`, `||`,
|
|
41
|
+
* `&&`, `;`, `;;`, `&`, `>`, `>>`, `<`, `<<`, `(`, `)`, `{`, `}`, `` ` ``,
|
|
42
|
+
* `$(`, `${`.
|
|
43
|
+
*
|
|
44
|
+
* This is a real tokenizer, not a naive whitespace split: `echo "a b" | cat`
|
|
45
|
+
* yields `['echo', 'a b', '|', 'cat']`, and `#` inside quotes never starts a
|
|
46
|
+
* comment.
|
|
47
|
+
*/
|
|
48
|
+
export function tokenizeBash(command) {
|
|
49
|
+
const tokens = [];
|
|
50
|
+
let cur = '';
|
|
51
|
+
const push = () => {
|
|
52
|
+
if (cur !== '') {
|
|
53
|
+
tokens.push(cur);
|
|
54
|
+
cur = '';
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const two = (i) => command.charAt(i) + command.charAt(i + 1);
|
|
58
|
+
let i = 0;
|
|
59
|
+
const n = command.length;
|
|
60
|
+
while (i < n) {
|
|
61
|
+
const c = command.charAt(i);
|
|
62
|
+
// whitespace separates tokens
|
|
63
|
+
if (c === ' ' || c === '\t' || c === '\r' || c === '\n') {
|
|
64
|
+
push();
|
|
65
|
+
i++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// `#` starts a comment only at the start of a word, outside quotes
|
|
69
|
+
if (c === '#' && cur === '') {
|
|
70
|
+
while (i < n && command.charAt(i) !== '\n')
|
|
71
|
+
i++;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
// single quotes: everything literal until the closing quote
|
|
75
|
+
if (c === "'") {
|
|
76
|
+
i++;
|
|
77
|
+
while (i < n) {
|
|
78
|
+
const q = command.charAt(i);
|
|
79
|
+
if (q === "'") {
|
|
80
|
+
i++;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
cur += q;
|
|
84
|
+
i++;
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
// double quotes: `\` only escapes `$`, `` ` ``, `"`, `\` and newline
|
|
89
|
+
if (c === '"') {
|
|
90
|
+
i++;
|
|
91
|
+
while (i < n) {
|
|
92
|
+
const q = command.charAt(i);
|
|
93
|
+
if (q === '"') {
|
|
94
|
+
i++;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
if (q === '\\') {
|
|
98
|
+
const nxt = command.charAt(i + 1);
|
|
99
|
+
if (nxt === '$' || nxt === '`' || nxt === '"' || nxt === '\\') {
|
|
100
|
+
cur += nxt;
|
|
101
|
+
i += 2;
|
|
102
|
+
}
|
|
103
|
+
else if (nxt === '\n') {
|
|
104
|
+
i += 2; // line continuation inside double quotes
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
cur += '\\';
|
|
108
|
+
i++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
cur += q;
|
|
113
|
+
i++;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
// backslash escape outside quotes
|
|
119
|
+
if (c === '\\') {
|
|
120
|
+
const nxt = command.charAt(i + 1);
|
|
121
|
+
if (nxt === '') {
|
|
122
|
+
i++; // trailing backslash: drop it
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (nxt === '\n') {
|
|
126
|
+
i += 2; // line continuation
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
cur += nxt;
|
|
130
|
+
i += 2;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
// two-character operators
|
|
134
|
+
const t2 = two(i);
|
|
135
|
+
if (t2 === '||' ||
|
|
136
|
+
t2 === '&&' ||
|
|
137
|
+
t2 === '>>' ||
|
|
138
|
+
t2 === '<<' ||
|
|
139
|
+
t2 === '$(' ||
|
|
140
|
+
t2 === '${' ||
|
|
141
|
+
t2 === ';;') {
|
|
142
|
+
push();
|
|
143
|
+
tokens.push(t2);
|
|
144
|
+
i += 2;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
// single-character operators
|
|
148
|
+
if (c === '|' ||
|
|
149
|
+
c === '&' ||
|
|
150
|
+
c === ';' ||
|
|
151
|
+
c === '>' ||
|
|
152
|
+
c === '<' ||
|
|
153
|
+
c === '(' ||
|
|
154
|
+
c === ')' ||
|
|
155
|
+
c === '{' ||
|
|
156
|
+
c === '}' ||
|
|
157
|
+
c === '`') {
|
|
158
|
+
push();
|
|
159
|
+
tokens.push(c);
|
|
160
|
+
i++;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
cur += c;
|
|
164
|
+
i++;
|
|
165
|
+
}
|
|
166
|
+
push();
|
|
167
|
+
return tokens;
|
|
168
|
+
}
|
|
169
|
+
const SHELL_NAMES = new Set(['sh', 'bash', 'zsh']);
|
|
170
|
+
const DOWNLOADER_NAMES = new Set(['curl', 'wget']);
|
|
171
|
+
const ENV_NAMES = new Set(['env', 'printenv', 'set']);
|
|
172
|
+
const NET_NAMES = new Set(['curl', 'wget', 'nc', 'ncat', 'ssh', 'scp']);
|
|
173
|
+
const NC_NAMES = new Set(['nc', 'ncat', 'netcat']);
|
|
174
|
+
const SYSTEM_PATH_PREFIXES = [
|
|
175
|
+
'/etc/',
|
|
176
|
+
'/usr/bin/',
|
|
177
|
+
'/usr/sbin/',
|
|
178
|
+
'/bin/',
|
|
179
|
+
'/sbin/',
|
|
180
|
+
'/boot/',
|
|
181
|
+
];
|
|
182
|
+
const DISK_DEVICE_RE = /^\/dev\/(sd[a-z]+|hd[a-z]+|vd[a-z]+|nvme[0-9]+n[0-9]+|mmcblk[0-9]+)/;
|
|
183
|
+
/** Split a token stream on command separators (`;`, `&&`, `||`, `&`). */
|
|
184
|
+
function splitCommands(tokens) {
|
|
185
|
+
const cmds = [];
|
|
186
|
+
let cur = [];
|
|
187
|
+
for (const t of tokens) {
|
|
188
|
+
if (t === ';' || t === '&&' || t === '||' || t === '&') {
|
|
189
|
+
if (cur.length > 0)
|
|
190
|
+
cmds.push(cur);
|
|
191
|
+
cur = [];
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
cur.push(t);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (cur.length > 0)
|
|
198
|
+
cmds.push(cur);
|
|
199
|
+
return cmds;
|
|
200
|
+
}
|
|
201
|
+
/** Split one command into pipeline segments on `|`. */
|
|
202
|
+
function splitPipeline(cmd) {
|
|
203
|
+
const segs = [];
|
|
204
|
+
let cur = [];
|
|
205
|
+
for (const t of cmd) {
|
|
206
|
+
if (t === '|') {
|
|
207
|
+
if (cur.length > 0)
|
|
208
|
+
segs.push(cur);
|
|
209
|
+
cur = [];
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
cur.push(t);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (cur.length > 0)
|
|
216
|
+
segs.push(cur);
|
|
217
|
+
return segs;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Resolve the command a pipeline segment invokes: the first token, skipping
|
|
221
|
+
* a leading `sudo` (plus its flags) and stripping any `/path/` prefix.
|
|
222
|
+
*/
|
|
223
|
+
function invokedCommand(seg) {
|
|
224
|
+
let i = 0;
|
|
225
|
+
if (seg[0] === 'sudo') {
|
|
226
|
+
i = 1;
|
|
227
|
+
while (i < seg.length) {
|
|
228
|
+
const t = seg[i];
|
|
229
|
+
if (t === undefined || !t.startsWith('-') || t === '--')
|
|
230
|
+
break;
|
|
231
|
+
if (t === '-u' || t === '--user' || t === '-g' || t === '--group')
|
|
232
|
+
i += 2;
|
|
233
|
+
else
|
|
234
|
+
i += 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const raw = seg[i];
|
|
238
|
+
if (raw === undefined || raw === '')
|
|
239
|
+
return null;
|
|
240
|
+
const slash = raw.lastIndexOf('/');
|
|
241
|
+
const name = slash >= 0 ? raw.slice(slash + 1) : raw;
|
|
242
|
+
if (name === '')
|
|
243
|
+
return null;
|
|
244
|
+
return { name, args: seg.slice(i + 1) };
|
|
245
|
+
}
|
|
246
|
+
/** Merge `${` `HOME` `}` token triples — and the single token `'${HOME}'`
|
|
247
|
+
* (from quoted `"${HOME}"`) — back into `$HOME` for analysis. */
|
|
248
|
+
function normalizeTokens(tokens) {
|
|
249
|
+
const out = [];
|
|
250
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
251
|
+
const t = tokens[i];
|
|
252
|
+
if (t === '${' && tokens[i + 1] === 'HOME' && tokens[i + 2] === '}') {
|
|
253
|
+
out.push('$HOME');
|
|
254
|
+
i += 2;
|
|
255
|
+
}
|
|
256
|
+
else if (t === '${HOME}') {
|
|
257
|
+
out.push('$HOME');
|
|
258
|
+
}
|
|
259
|
+
else if (t !== undefined) {
|
|
260
|
+
out.push(t);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
function isDestructiveRmTarget(t) {
|
|
266
|
+
return (t === '/' ||
|
|
267
|
+
t.startsWith('/*') ||
|
|
268
|
+
t === '~' ||
|
|
269
|
+
t.startsWith('~/') ||
|
|
270
|
+
/^~[A-Za-z_]/.test(t) || // ~otheruser
|
|
271
|
+
t === '$HOME' ||
|
|
272
|
+
t.startsWith('$HOME/'));
|
|
273
|
+
}
|
|
274
|
+
function isSystemPath(p) {
|
|
275
|
+
return SYSTEM_PATH_PREFIXES.some((pre) => p === pre.slice(0, -1) || p.startsWith(pre));
|
|
276
|
+
}
|
|
277
|
+
function checkRm(inv, raise) {
|
|
278
|
+
if (inv.name !== 'rm')
|
|
279
|
+
return;
|
|
280
|
+
let recursive = false;
|
|
281
|
+
let force = false;
|
|
282
|
+
const flagToks = [];
|
|
283
|
+
const targets = [];
|
|
284
|
+
let endOfFlags = false;
|
|
285
|
+
for (const a of inv.args) {
|
|
286
|
+
if (!endOfFlags && a === '--') {
|
|
287
|
+
endOfFlags = true;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (!endOfFlags && a.startsWith('-') && a.length > 1) {
|
|
291
|
+
flagToks.push(a);
|
|
292
|
+
if (a === '--recursive')
|
|
293
|
+
recursive = true;
|
|
294
|
+
else if (a === '--force')
|
|
295
|
+
force = true;
|
|
296
|
+
else if (!a.startsWith('--')) {
|
|
297
|
+
if (a.includes('r') || a.includes('R'))
|
|
298
|
+
recursive = true;
|
|
299
|
+
if (a.includes('f'))
|
|
300
|
+
force = true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
targets.push(a);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (!recursive || !force)
|
|
308
|
+
return;
|
|
309
|
+
for (const t of targets) {
|
|
310
|
+
if (isDestructiveRmTarget(t)) {
|
|
311
|
+
raise('critical', `destructive delete: "rm ${flagToks.join(' ')} ${t}" targets a filesystem root`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function checkMkfs(inv, seg, raise) {
|
|
317
|
+
if (inv.name === 'mkfs' || inv.name.startsWith('mkfs.')) {
|
|
318
|
+
raise('critical', `filesystem formatter "mkfs" detected: "${truncate(seg.join(' '))}"`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function checkDd(inv, raise) {
|
|
322
|
+
if (inv.name !== 'dd')
|
|
323
|
+
return;
|
|
324
|
+
const of = inv.args.find((a) => a.startsWith('of=/dev/'));
|
|
325
|
+
if (of !== undefined) {
|
|
326
|
+
raise('critical', `raw disk write via "dd": "${of}"`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function checkNcListener(seg, raise) {
|
|
330
|
+
const hasNc = seg.some((t) => NC_NAMES.has(t));
|
|
331
|
+
const hasListen = seg.includes('-l') || seg.includes('--listen');
|
|
332
|
+
if (hasNc && hasListen) {
|
|
333
|
+
raise('medium', `network listener: "${truncate(seg.join(' '))}"`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function checkChmod(inv, seg, raise) {
|
|
337
|
+
if (inv.name !== 'chmod')
|
|
338
|
+
return;
|
|
339
|
+
let recursive = false;
|
|
340
|
+
let mode777 = false;
|
|
341
|
+
let rootTarget = null;
|
|
342
|
+
for (const a of inv.args) {
|
|
343
|
+
if (a === '--recursive')
|
|
344
|
+
recursive = true;
|
|
345
|
+
else if (a.startsWith('-') && !a.startsWith('--') && /[rR]/.test(a))
|
|
346
|
+
recursive = true;
|
|
347
|
+
else if (a === '777')
|
|
348
|
+
mode777 = true;
|
|
349
|
+
else if (a === '/' || a.startsWith('/*'))
|
|
350
|
+
rootTarget = a;
|
|
351
|
+
}
|
|
352
|
+
if (recursive && mode777 && rootTarget !== null) {
|
|
353
|
+
raise('high', `recursive permission wipe: "chmod -R 777 ${rootTarget}" (${truncate(seg.join(' '))})`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function checkFindDelete(inv, seg, raise) {
|
|
357
|
+
if (inv.name !== 'find')
|
|
358
|
+
return;
|
|
359
|
+
if (inv.args.includes('/') && inv.args.includes('-delete')) {
|
|
360
|
+
raise('medium', `mass delete via find: "${truncate(seg.join(' '))}"`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function checkTeeSystemPath(inv, seg, raise) {
|
|
364
|
+
if (inv.name !== 'tee')
|
|
365
|
+
return;
|
|
366
|
+
for (const a of inv.args) {
|
|
367
|
+
if (a.startsWith('-'))
|
|
368
|
+
continue;
|
|
369
|
+
if (isSystemPath(a)) {
|
|
370
|
+
raise('high', `write to system path via "tee": "${truncate(seg.join(' '))}"`);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function checkCopyToSystemPath(inv, seg, raise) {
|
|
376
|
+
if (inv.name !== 'cp' && inv.name !== 'mv' && inv.name !== 'install')
|
|
377
|
+
return;
|
|
378
|
+
const operands = inv.args.filter((a) => !a.startsWith('-'));
|
|
379
|
+
const dest = operands[operands.length - 1];
|
|
380
|
+
if (dest !== undefined && isSystemPath(dest)) {
|
|
381
|
+
raise('high', `write to system path via "${inv.name}": "${truncate(seg.join(' '))}"`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function checkPipeToShell(pipeline, raise) {
|
|
385
|
+
let downloaderSeg = null;
|
|
386
|
+
for (const seg of pipeline) {
|
|
387
|
+
const inv = invokedCommand(seg);
|
|
388
|
+
const cmdName = inv === null ? seg[0] : inv.name;
|
|
389
|
+
if (cmdName !== undefined && SHELL_NAMES.has(cmdName) && downloaderSeg !== null) {
|
|
390
|
+
raise('high', `piping a remote download into a shell: "${truncate(`${downloaderSeg.join(' ')} | ... | ${seg.join(' ')}`)}"`);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (seg.some((t) => DOWNLOADER_NAMES.has(t)))
|
|
394
|
+
downloaderSeg = [...seg];
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function checkEnvExfil(pipeline, raise) {
|
|
398
|
+
let envSeg = null;
|
|
399
|
+
for (const seg of pipeline) {
|
|
400
|
+
const inv = invokedCommand(seg);
|
|
401
|
+
const cmdName = inv === null ? seg[0] : inv.name;
|
|
402
|
+
if (cmdName === undefined)
|
|
403
|
+
continue;
|
|
404
|
+
if (envSeg === null && ENV_NAMES.has(cmdName)) {
|
|
405
|
+
envSeg = [...seg];
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
if (envSeg !== null && NET_NAMES.has(cmdName)) {
|
|
409
|
+
raise('high', `environment exfiltration: "${truncate(`${envSeg.join(' ')} | ... | ${seg.join(' ')}`)}"`);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function checkForkBomb(raw, raise) {
|
|
415
|
+
const compact = raw.replace(/\s+/g, '');
|
|
416
|
+
// classic `:(){ :|:& };:` (whitespace variants collapse to the same shape)
|
|
417
|
+
if (/:\(\)\{[^}]*\|[^}]*&[^}]*\}/.test(compact)) {
|
|
418
|
+
raise('critical', 'fork bomb detected: ":(){ :|:& };:"');
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
// named-function variant: name(){ ... name ... | ... & ... } then invoked
|
|
422
|
+
const m = /([A-Za-z_]\w*)\(\)\{([^}]*)\}/.exec(compact);
|
|
423
|
+
if (m !== null) {
|
|
424
|
+
const name = m[1];
|
|
425
|
+
const body = m[2];
|
|
426
|
+
const words = body.split(/[^A-Za-z0-9_]/);
|
|
427
|
+
const tail = compact.slice(m.index + m[0].length);
|
|
428
|
+
const tailWords = tail.split(/[^A-Za-z0-9_]/);
|
|
429
|
+
if (words.includes(name) &&
|
|
430
|
+
body.includes('|') &&
|
|
431
|
+
body.includes('&') &&
|
|
432
|
+
tailWords.includes(name)) {
|
|
433
|
+
raise('critical', `fork bomb detected: "${truncate(`${name}(){ ... }; ${name}`)}"`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
function checkCommandSubstitution(tokens, raise) {
|
|
438
|
+
let i = 0;
|
|
439
|
+
while (i < tokens.length) {
|
|
440
|
+
const t = tokens[i];
|
|
441
|
+
if (t === '$(') {
|
|
442
|
+
let depth = 1;
|
|
443
|
+
let j = i + 1;
|
|
444
|
+
while (j < tokens.length && depth > 0) {
|
|
445
|
+
const u = tokens[j];
|
|
446
|
+
if (u === '$(')
|
|
447
|
+
depth++;
|
|
448
|
+
else if (u === ')')
|
|
449
|
+
depth--;
|
|
450
|
+
j++;
|
|
451
|
+
}
|
|
452
|
+
const inside = tokens.slice(i + 1, j - 1);
|
|
453
|
+
if (inside.some((x) => x === 'curl' || x === 'wget')) {
|
|
454
|
+
raise('high', 'command substitution downloads remote content: "$(curl ...)"');
|
|
455
|
+
}
|
|
456
|
+
i = j;
|
|
457
|
+
}
|
|
458
|
+
else if (t === '`') {
|
|
459
|
+
let j = i + 1;
|
|
460
|
+
while (j < tokens.length && tokens[j] !== '`')
|
|
461
|
+
j++;
|
|
462
|
+
const inside = tokens.slice(i + 1, j);
|
|
463
|
+
if (inside.some((x) => x === 'curl' || x === 'wget')) {
|
|
464
|
+
raise('high', 'command substitution downloads remote content: "`curl ...`"');
|
|
465
|
+
}
|
|
466
|
+
i = j + 1;
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
i++;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
function checkRedirects(tokens, raise) {
|
|
474
|
+
for (let i = 0; i + 1 < tokens.length; i++) {
|
|
475
|
+
const t = tokens[i];
|
|
476
|
+
if (t !== '>' && t !== '>>')
|
|
477
|
+
continue;
|
|
478
|
+
const target = tokens[i + 1];
|
|
479
|
+
if (target === undefined)
|
|
480
|
+
continue;
|
|
481
|
+
if (DISK_DEVICE_RE.test(target)) {
|
|
482
|
+
raise('critical', `raw disk overwrite via redirect: "${t} ${target}"`);
|
|
483
|
+
}
|
|
484
|
+
else if (isSystemPath(target)) {
|
|
485
|
+
raise('high', `write to system path via redirect: "${t} ${target}"`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function checkSudo(tokens, raise) {
|
|
490
|
+
if (tokens.includes('sudo')) {
|
|
491
|
+
raise('medium', 'privilege escalation via "sudo"');
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function checkChmodPlusXDownloaded(tokens, raise) {
|
|
495
|
+
if (!tokens.some((t) => t === 'curl' || t === 'wget'))
|
|
496
|
+
return;
|
|
497
|
+
for (const cmd of splitCommands(tokens)) {
|
|
498
|
+
for (const seg of splitPipeline(cmd)) {
|
|
499
|
+
const inv = invokedCommand(seg);
|
|
500
|
+
if (inv !== null && inv.name === 'chmod' && inv.args.includes('+x')) {
|
|
501
|
+
raise('medium', `chmod +x on a downloaded file: "${truncate(seg.join(' '))}"`);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Analyze a bash command and classify its risk.
|
|
509
|
+
*
|
|
510
|
+
* - `critical`: `rm -rf /` (and `/*`, `~`, `$HOME` variants, `--recursive`
|
|
511
|
+
* `--force` spellings), `mkfs`, `dd … of=/dev/…`, fork bombs
|
|
512
|
+
* (`:(){ :|:& };:` and whitespace/named variants), `> /dev/sdX`.
|
|
513
|
+
* - `high`: piping a download into a shell (`curl … | sh`, `… | sudo sh`),
|
|
514
|
+
* `$(curl …)` / backtick command substitution fetching remote content,
|
|
515
|
+
* env exfiltration (`env | curl …`), writes to system paths (`/etc/`,
|
|
516
|
+
* `/usr/bin`, …), `chmod -R 777 /`.
|
|
517
|
+
* - `medium`: any `sudo`, `nc -l` listeners, `chmod +x` on a downloaded
|
|
518
|
+
* file, `find / … -delete`.
|
|
519
|
+
* - `low`: everything else.
|
|
520
|
+
*
|
|
521
|
+
* Every finding appends a reason quoting the exact construct caught; the
|
|
522
|
+
* overall risk is the maximum over all findings (fail-closed).
|
|
523
|
+
*/
|
|
524
|
+
export function analyzeBash(command) {
|
|
525
|
+
const reasons = [];
|
|
526
|
+
let risk = 'low';
|
|
527
|
+
const raise = (r, reason) => {
|
|
528
|
+
risk = maxRisk(risk, r);
|
|
529
|
+
reasons.push(reason);
|
|
530
|
+
};
|
|
531
|
+
const tokens = normalizeTokens(tokenizeBash(command));
|
|
532
|
+
if (tokens.length === 0)
|
|
533
|
+
return { risk: 'low', reasons: [] };
|
|
534
|
+
for (const cmd of splitCommands(tokens)) {
|
|
535
|
+
const pipeline = splitPipeline(cmd);
|
|
536
|
+
for (const seg of pipeline) {
|
|
537
|
+
const inv = invokedCommand(seg);
|
|
538
|
+
if (inv !== null) {
|
|
539
|
+
checkRm(inv, raise);
|
|
540
|
+
checkMkfs(inv, seg, raise);
|
|
541
|
+
checkDd(inv, raise);
|
|
542
|
+
checkChmod(inv, seg, raise);
|
|
543
|
+
checkFindDelete(inv, seg, raise);
|
|
544
|
+
checkTeeSystemPath(inv, seg, raise);
|
|
545
|
+
checkCopyToSystemPath(inv, seg, raise);
|
|
546
|
+
}
|
|
547
|
+
checkNcListener(seg, raise);
|
|
548
|
+
}
|
|
549
|
+
checkPipeToShell(pipeline, raise);
|
|
550
|
+
checkEnvExfil(pipeline, raise);
|
|
551
|
+
}
|
|
552
|
+
checkForkBomb(command, raise);
|
|
553
|
+
checkCommandSubstitution(tokens, raise);
|
|
554
|
+
checkRedirects(tokens, raise);
|
|
555
|
+
checkSudo(tokens, raise);
|
|
556
|
+
checkChmodPlusXDownloaded(tokens, raise);
|
|
557
|
+
return { risk, reasons };
|
|
558
|
+
}
|
|
559
|
+
// ---------------------------------------------------------------------------
|
|
560
|
+
// checkHttpUrl
|
|
561
|
+
// ---------------------------------------------------------------------------
|
|
562
|
+
function hostMatchesAllowlist(host, entry) {
|
|
563
|
+
let e = entry.trim().toLowerCase();
|
|
564
|
+
if (e.endsWith('.'))
|
|
565
|
+
e = e.slice(0, -1);
|
|
566
|
+
// strip an optional `:port` suffix
|
|
567
|
+
if (!e.startsWith('[')) {
|
|
568
|
+
const portMatch = /:(\d+)$/.exec(e);
|
|
569
|
+
if (portMatch !== null)
|
|
570
|
+
e = e.slice(0, e.length - portMatch[0].length);
|
|
571
|
+
}
|
|
572
|
+
if (e.startsWith('*.')) {
|
|
573
|
+
const base = e.slice(2);
|
|
574
|
+
// wildcard matches subdomains only, never the apex itself
|
|
575
|
+
return host.length > base.length && host.endsWith(`.${base}`);
|
|
576
|
+
}
|
|
577
|
+
return host === e;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Gate an outbound URL (layer: `'gates'`).
|
|
581
|
+
*
|
|
582
|
+
* - Unparseable URL → deny, severity `critical`.
|
|
583
|
+
* - Non-http(s) protocol → deny, severity `high`.
|
|
584
|
+
* - Userinfo (`user:pass@host`) → deny, severity `high` ("credential in URL").
|
|
585
|
+
* - `httpMode: 'off'` → allow. `'warn'` → off-list hosts still allow, but the
|
|
586
|
+
* reason flags them (severity `low`). `'allowlist'` → off-list hosts deny
|
|
587
|
+
* with severity `high`.
|
|
588
|
+
*
|
|
589
|
+
* Host matching is case-insensitive, ignores ports, and supports exact and
|
|
590
|
+
* `*.example.com` wildcard entries.
|
|
591
|
+
*/
|
|
592
|
+
export function checkHttpUrl(rawUrl, policy) {
|
|
593
|
+
// guard off: allow unconditionally (types.ts: "off: every layer allows unconditionally")
|
|
594
|
+
if (policy.mode === 'off') {
|
|
595
|
+
return { decision: 'allow', reason: 'guard off', layer: LAYER };
|
|
596
|
+
}
|
|
597
|
+
let url;
|
|
598
|
+
try {
|
|
599
|
+
url = new URL(rawUrl);
|
|
600
|
+
}
|
|
601
|
+
catch {
|
|
602
|
+
return decide(policy, LAYER, 'critical', `unparseable URL: "${truncate(rawUrl, 120)}"`);
|
|
603
|
+
}
|
|
604
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
605
|
+
return decide(policy, LAYER, 'high', `disallowed URL protocol "${url.protocol}" (only http/https allowed): "${truncate(rawUrl, 120)}"`);
|
|
606
|
+
}
|
|
607
|
+
if (url.username !== '' || url.password !== '') {
|
|
608
|
+
return decide(policy, LAYER, 'high', 'credential in URL (userinfo present)');
|
|
609
|
+
}
|
|
610
|
+
// No finding from here on: httpMode 'off' and allowlist hits allow directly
|
|
611
|
+
// so a clean check is never denied by `decide()` in strict mode.
|
|
612
|
+
if (policy.httpMode === 'off') {
|
|
613
|
+
return { decision: 'allow', reason: 'http checks disabled by policy (httpMode=off)', layer: LAYER };
|
|
614
|
+
}
|
|
615
|
+
const host = url.hostname.toLowerCase();
|
|
616
|
+
const allowed = policy.httpAllowlist.some((entry) => hostMatchesAllowlist(host, entry));
|
|
617
|
+
if (allowed) {
|
|
618
|
+
return { decision: 'allow', reason: `URL host "${host}" is on the http allowlist`, layer: LAYER };
|
|
619
|
+
}
|
|
620
|
+
if (policy.httpMode === 'warn') {
|
|
621
|
+
// genuine finding (severity low): `decide()` allows it in warn mode with
|
|
622
|
+
// the reason attached, denies it in strict mode (fail-closed)
|
|
623
|
+
return decide(policy, LAYER, 'low', `URL host "${host}" is NOT on the http allowlist (warn mode: allowed with warning)`);
|
|
624
|
+
}
|
|
625
|
+
return decide(policy, LAYER, 'high', `URL host "${host}" is NOT on the http allowlist`);
|
|
626
|
+
}
|
|
627
|
+
// ---------------------------------------------------------------------------
|
|
628
|
+
// evaluateToolCall
|
|
629
|
+
// ---------------------------------------------------------------------------
|
|
630
|
+
/** Tools whose outbound HTTP is gated by name (in addition to the regex). */
|
|
631
|
+
const HTTP_TOOL_NAMES = new Set([
|
|
632
|
+
'web-fetch',
|
|
633
|
+
'research.search',
|
|
634
|
+
'research.extract',
|
|
635
|
+
'research.deep',
|
|
636
|
+
'research.academic',
|
|
637
|
+
'research.crawl',
|
|
638
|
+
]);
|
|
639
|
+
const BASH_NAME_RE = /(\b|_)(bash|sh|shell|exec|command|run)(s?\b|_)/i;
|
|
640
|
+
const HTTP_NAME_RE = /fetch|https?|request/i;
|
|
641
|
+
/** First string-valued argument among the given names, if any. */
|
|
642
|
+
function stringArg(call, names) {
|
|
643
|
+
for (const n of names) {
|
|
644
|
+
const v = call.arguments[n];
|
|
645
|
+
if (typeof v === 'string')
|
|
646
|
+
return v;
|
|
647
|
+
}
|
|
648
|
+
return undefined;
|
|
649
|
+
}
|
|
650
|
+
/** Tool names whose writes are zone-gated. */
|
|
651
|
+
const FILE_WRITE_NAMES = new Set(['file.write', 'file.edit']);
|
|
652
|
+
/**
|
|
653
|
+
* Lexical containment check: is `target` inside any of `zones`?
|
|
654
|
+
*
|
|
655
|
+
* Absolute targets are normalized and compared directly. Relative targets
|
|
656
|
+
* are resolved against each zone in turn — the file tools resolve relative
|
|
657
|
+
* paths under their root, so a relative target that climbs out (`../..`)
|
|
658
|
+
* fails closed. This is an approval gate, not the security boundary itself:
|
|
659
|
+
* the handlers still enforce symlink-aware containment via `resolveWithin`
|
|
660
|
+
* at execution time.
|
|
661
|
+
*/
|
|
662
|
+
function isWithinZones(target, zones) {
|
|
663
|
+
const trimmed = target.trim();
|
|
664
|
+
if (trimmed === '')
|
|
665
|
+
return false;
|
|
666
|
+
for (const zone of zones) {
|
|
667
|
+
const normalizedZone = resolve(zone);
|
|
668
|
+
if (normalizedZone === '' || !isAbsolute(normalizedZone))
|
|
669
|
+
continue;
|
|
670
|
+
const absolute = isAbsolute(trimmed) ? resolve(trimmed) : resolve(normalizedZone, trimmed);
|
|
671
|
+
if (absolute === normalizedZone || absolute.startsWith(normalizedZone + sep)) {
|
|
672
|
+
return true;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Zone gate for write-capable tools. Returns a `require-approval` verdict
|
|
679
|
+
* when the call targets a path outside every configured zone, or `undefined`
|
|
680
|
+
* to let evaluation continue.
|
|
681
|
+
*
|
|
682
|
+
* - `file.write` / `file.edit`: the `path` argument is checked. A missing
|
|
683
|
+
* or empty path fails closed (it cannot be proven in-zone).
|
|
684
|
+
* - `shell.exec`: an explicit `cwd` argument is checked. An absent `cwd`
|
|
685
|
+
* keeps the previous behavior (the handler defaults it to the workspace
|
|
686
|
+
* root, which the gateway puts in the zones).
|
|
687
|
+
* - Empty `writeZones` (the default): the gate is off entirely.
|
|
688
|
+
*/
|
|
689
|
+
function checkWriteZones(call, policy) {
|
|
690
|
+
if (policy.writeZones.length === 0)
|
|
691
|
+
return undefined;
|
|
692
|
+
if (FILE_WRITE_NAMES.has(call.name)) {
|
|
693
|
+
const target = stringArg(call, ['path']);
|
|
694
|
+
if (target === undefined || !isWithinZones(target, policy.writeZones)) {
|
|
695
|
+
return {
|
|
696
|
+
decision: 'require-approval',
|
|
697
|
+
reason: `file write "${call.name}" targets a path outside the configured write zones`,
|
|
698
|
+
layer: LAYER,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
return undefined;
|
|
702
|
+
}
|
|
703
|
+
if (call.name === 'shell.exec') {
|
|
704
|
+
const cwd = stringArg(call, ['cwd']);
|
|
705
|
+
if (cwd !== undefined && !isWithinZones(cwd, policy.writeZones)) {
|
|
706
|
+
return {
|
|
707
|
+
decision: 'require-approval',
|
|
708
|
+
reason: 'shell.exec cwd is outside the configured write zones',
|
|
709
|
+
layer: LAYER,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
return undefined;
|
|
713
|
+
}
|
|
714
|
+
return undefined;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Evaluate one tool call against the gates layer.
|
|
718
|
+
*
|
|
719
|
+
* Order (first match wins):
|
|
720
|
+
* 1. guard `off` → allow unconditionally;
|
|
721
|
+
* 2. name exactly in `denyTools` → deny (`critical`);
|
|
722
|
+
* 3. name exactly in `approvalTools` → require-approval;
|
|
723
|
+
* 4. write-zone gate — `file.write`/`file.edit` (by `path`) and `shell.exec`
|
|
724
|
+
* (by explicit `cwd`) outside every configured `writeZones` →
|
|
725
|
+
* require-approval (fail-closed); in-zone keeps the previous behavior;
|
|
726
|
+
* 5. bash-like — name matches the bash regex **or** a string arg named
|
|
727
|
+
* `command`/`script`/`cmd` → `analyzeBash`; risk `medium`/`high`/`critical`
|
|
728
|
+
* goes through `decide()`, risk `low` (no findings) allows directly;
|
|
729
|
+
* 6. http-like — name in the known research/fetch set, name matches
|
|
730
|
+
* `/fetch|https?|request/i`, **or** a string arg named `url`/`href`/`link`
|
|
731
|
+
* → `checkHttpUrl` on that URL argument;
|
|
732
|
+
* 7. anything else → allow.
|
|
733
|
+
*/
|
|
734
|
+
export function evaluateToolCall(call, policy) {
|
|
735
|
+
// guard off: allow unconditionally (types.ts: "off: every layer allows unconditionally")
|
|
736
|
+
if (policy.mode === 'off') {
|
|
737
|
+
return { decision: 'allow', reason: 'guard off', layer: LAYER };
|
|
738
|
+
}
|
|
739
|
+
if (policy.denyTools.includes(call.name)) {
|
|
740
|
+
return decide(policy, LAYER, 'critical', `tool "${call.name}" is on the deny list`);
|
|
741
|
+
}
|
|
742
|
+
if (policy.approvalTools.includes(call.name)) {
|
|
743
|
+
return { decision: 'require-approval', reason: `tool "${call.name}" requires human approval`, layer: LAYER };
|
|
744
|
+
}
|
|
745
|
+
// -- write zones ---------------------------------------------------------
|
|
746
|
+
const zoneVerdict = checkWriteZones(call, policy);
|
|
747
|
+
if (zoneVerdict !== undefined)
|
|
748
|
+
return zoneVerdict;
|
|
749
|
+
// -- bash-like -----------------------------------------------------------
|
|
750
|
+
const bashArg = stringArg(call, ['command', 'script', 'cmd']);
|
|
751
|
+
const bashByName = BASH_NAME_RE.test(call.name);
|
|
752
|
+
if (bashByName || bashArg !== undefined) {
|
|
753
|
+
const analyses = [];
|
|
754
|
+
if (bashArg !== undefined)
|
|
755
|
+
analyses.push(analyzeBash(bashArg));
|
|
756
|
+
if (bashByName) {
|
|
757
|
+
// a bash-named tool may carry the script under a different arg name
|
|
758
|
+
for (const v of Object.values(call.arguments)) {
|
|
759
|
+
if (typeof v === 'string' && v !== bashArg && v.trim() !== '') {
|
|
760
|
+
analyses.push(analyzeBash(v));
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
let risk = 'low';
|
|
765
|
+
const reasons = [];
|
|
766
|
+
for (const a of analyses) {
|
|
767
|
+
risk = maxRisk(risk, a.risk);
|
|
768
|
+
reasons.push(...a.reasons);
|
|
769
|
+
}
|
|
770
|
+
// No findings: allow directly so a clean command is never denied by
|
|
771
|
+
// `decide()` in strict mode. Findings map 1:1 onto severity.
|
|
772
|
+
if (risk === 'low') {
|
|
773
|
+
return { decision: 'allow', reason: `bash-like tool "${call.name}": no dangerous constructs detected`, layer: LAYER };
|
|
774
|
+
}
|
|
775
|
+
const severity = risk;
|
|
776
|
+
return decide(policy, LAYER, severity, reasons.join('; '));
|
|
777
|
+
}
|
|
778
|
+
// -- http-like -----------------------------------------------------------
|
|
779
|
+
// Only the url/href/link arguments are checked: a research tool carrying a
|
|
780
|
+
// plain-text `query` (no URL) must not be denied as an "unparseable URL".
|
|
781
|
+
const urlArg = stringArg(call, ['url', 'href', 'link']);
|
|
782
|
+
const httpByName = HTTP_TOOL_NAMES.has(call.name) || HTTP_NAME_RE.test(call.name);
|
|
783
|
+
if (httpByName || urlArg !== undefined) {
|
|
784
|
+
if (urlArg === undefined) {
|
|
785
|
+
return { decision: 'allow', reason: `http-like tool "${call.name}" has no URL argument to check`, layer: LAYER };
|
|
786
|
+
}
|
|
787
|
+
return checkHttpUrl(urlArg, policy);
|
|
788
|
+
}
|
|
789
|
+
// -- default --------------------------------------------------------------
|
|
790
|
+
return { decision: 'allow', reason: `tool "${call.name}" is not covered by any gate`, layer: LAYER };
|
|
791
|
+
}
|