@minionry/minion 0.7.36
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/LICENSE +14 -0
- package/PRIVACY.md +357 -0
- package/README.md +57 -0
- package/THIRD_PARTY_NOTICES.md +245 -0
- package/bin/branding.js +4 -0
- package/bin/commands/browser-install.js +483 -0
- package/bin/commands/create-sandbox.js +542 -0
- package/bin/commands/fingerprint.js +101 -0
- package/bin/commands/login.js +400 -0
- package/bin/commands/logout.js +93 -0
- package/bin/commands/minions.js +211 -0
- package/bin/commands/sandboxes.js +309 -0
- package/bin/commands/status.js +298 -0
- package/bin/commands/whoami.js +153 -0
- package/bin/launch-env.js +35 -0
- package/bin/machine-identity.js +221 -0
- package/bin/machine-layout.js +200 -0
- package/bin/minion.js +839 -0
- package/bin/platform-endpoints.js +8 -0
- package/bin/postinstall.js +52 -0
- package/bin/pty-doctor.js +326 -0
- package/bin/pty-locator.js +110 -0
- package/bin/pty-provision.js +186 -0
- package/bin/sandbox/Dockerfile +78 -0
- package/bin/sandbox/entrypoint.sh +14 -0
- package/bin/sandbox/install-repo.sh +53 -0
- package/bin/sandbox/seccomp-userns.json +883 -0
- package/bin/sandbox-config.js +306 -0
- package/dist/bin/machine-identity.js +221 -0
- package/dist/bin/machine-layout.js +200 -0
- package/dist/electron-host/agent-lease.js +141 -0
- package/dist/electron-host/dom-driver.js +642 -0
- package/dist/electron-host/ipc-protocol.js +89 -0
- package/dist/electron-host/js-driver.js +216 -0
- package/dist/electron-host/view-manager.js +254 -0
- package/dist/server/cli/engine-resume.js +49 -0
- package/dist/server/cli/eta-estimator.js +278 -0
- package/dist/server/cli/headless/background-activity.js +109 -0
- package/dist/server/cli/headless/claude-invoker-process.js +614 -0
- package/dist/server/cli/headless/claude-invoker-stall.js +150 -0
- package/dist/server/cli/headless/claude-invoker-stream.js +555 -0
- package/dist/server/cli/headless/claude-invoker-tools.js +148 -0
- package/dist/server/cli/headless/claude-invoker.js +151 -0
- package/dist/server/cli/headless/engine-availability.js +92 -0
- package/dist/server/cli/headless/engine-notice.js +185 -0
- package/dist/server/cli/headless/haiku-assessments.js +293 -0
- package/dist/server/cli/headless/headless-logger.js +75 -0
- package/dist/server/cli/headless/index.js +9 -0
- package/dist/server/cli/headless/mcp-config.js +314 -0
- package/dist/server/cli/headless/native-timeout-detector.js +72 -0
- package/dist/server/cli/headless/output-utils.js +71 -0
- package/dist/server/cli/headless/prompt-utils.js +82 -0
- package/dist/server/cli/headless/resilient-runner.js +326 -0
- package/dist/server/cli/headless/retry-strategies.js +292 -0
- package/dist/server/cli/headless/runner.js +245 -0
- package/dist/server/cli/headless/stall-assessor.js +295 -0
- package/dist/server/cli/headless/tool-watchdog.js +447 -0
- package/dist/server/cli/headless/types.js +18 -0
- package/dist/server/cli/history-portability.js +85 -0
- package/dist/server/cli/improvisation-attachments.js +99 -0
- package/dist/server/cli/improvisation-history-store.js +121 -0
- package/dist/server/cli/improvisation-movements.js +137 -0
- package/dist/server/cli/improvisation-output-queue.js +34 -0
- package/dist/server/cli/improvisation-retry.js +8 -0
- package/dist/server/cli/improvisation-session-manager.js +1016 -0
- package/dist/server/cli/improvisation-types.js +31 -0
- package/dist/server/cli/plan-lifecycle.js +72 -0
- package/dist/server/cli/prompt-builders.js +371 -0
- package/dist/server/cli/retry/retry-best-result.js +28 -0
- package/dist/server/cli/retry/retry-context-loss.js +75 -0
- package/dist/server/cli/retry/retry-recovery-strategies.js +213 -0
- package/dist/server/cli/retry/retry-resume-strategy.js +20 -0
- package/dist/server/cli/retry/retry-runner-factory.js +92 -0
- package/dist/server/cli/retry/retry-tool-results.js +22 -0
- package/dist/server/cli/retry/retry-types.js +3 -0
- package/dist/server/engines/EngineEvent.js +57 -0
- package/dist/server/engines/claude/ClaudeCodeEngine.js +316 -0
- package/dist/server/engines/claude/claude-command.js +32 -0
- package/dist/server/engines/codex/CodexEngine.js +469 -0
- package/dist/server/engines/codex/codex-auth.js +203 -0
- package/dist/server/engines/codex/codex-invoker.js +205 -0
- package/dist/server/engines/codex/codex-provision.js +215 -0
- package/dist/server/engines/credentials/credential-probe.js +88 -0
- package/dist/server/engines/credentials/model-profiles.js +249 -0
- package/dist/server/engines/credentials/provider-credentials.js +146 -0
- package/dist/server/engines/engine-event-stream.js +52 -0
- package/dist/server/engines/execution/engine-session.js +380 -0
- package/dist/server/engines/factory.js +42 -0
- package/dist/server/engines/hermes/HermesEngine.js +628 -0
- package/dist/server/engines/hermes/hermes-auth.js +159 -0
- package/dist/server/engines/hermes/hermes-gateway-home.js +271 -0
- package/dist/server/engines/hermes/hermes-invoker.js +350 -0
- package/dist/server/engines/hermes/hermes-provision.js +318 -0
- package/dist/server/engines/read-only.js +20 -0
- package/dist/server/engines/types.js +27 -0
- package/dist/server/handlers/company-execution-registry.js +67 -0
- package/dist/server/handlers/company-handlers.js +514 -0
- package/dist/server/handlers/company-triggers.js +189 -0
- package/dist/server/handlers/company-types.js +3 -0
- package/dist/server/index.js +244 -0
- package/dist/server/mcp/app-tools-bridge.js +110 -0
- package/dist/server/mcp/app-tools-mcp-server.js +99 -0
- package/dist/server/mcp/bouncer-integration.js +744 -0
- package/dist/server/mcp/browser-mcp-server.js +915 -0
- package/dist/server/mcp/browser-redaction.js +103 -0
- package/dist/server/mcp/classifier/BouncerClassifier.js +3 -0
- package/dist/server/mcp/classifier/ClaudeBouncerClassifier.js +212 -0
- package/dist/server/mcp/classifier/factory.js +124 -0
- package/dist/server/mcp/classifier/health.js +54 -0
- package/dist/server/mcp/classifier/telemetry.js +0 -0
- package/dist/server/mcp/company-patterns.js +104 -0
- package/dist/server/mcp/department-profiles.js +133 -0
- package/dist/server/mcp/quality-bridge.js +153 -0
- package/dist/server/mcp/quality-mcp-server.js +105 -0
- package/dist/server/mcp/security-analysis.js +384 -0
- package/dist/server/mcp/security-audit.js +152 -0
- package/dist/server/mcp/security-patterns.js +314 -0
- package/dist/server/mcp/server.js +194 -0
- package/dist/server/mcp/test-utils/agent-simulator.js +301 -0
- package/dist/server/mcp/test-utils/e2e-agent.js +103 -0
- package/dist/server/mcp/test-utils/e2e-capture.js +92 -0
- package/dist/server/mcp/test-utils/e2e-client.js +116 -0
- package/dist/server/mcp/test-utils/e2e-fixtures.js +87 -0
- package/dist/server/mcp/test-utils/e2e-gating.js +8 -0
- package/dist/server/mcp/test-utils/e2e-harness.js +8 -0
- package/dist/server/mcp/test-utils/e2e-scenarios.js +105 -0
- package/dist/server/routes/files.js +18 -0
- package/dist/server/routes/improvise.js +64 -0
- package/dist/server/routes/index.js +7 -0
- package/dist/server/routes/instances.js +41 -0
- package/dist/server/routes/internal.js +900 -0
- package/dist/server/routes/notifications.js +110 -0
- package/dist/server/server-setup.js +282 -0
- package/dist/server/services/agent-push.js +148 -0
- package/dist/server/services/analytics.js +197 -0
- package/dist/server/services/apps/build.js +249 -0
- package/dist/server/services/apps/greenlight.js +199 -0
- package/dist/server/services/apps/install.js +101 -0
- package/dist/server/services/apps/layout.js +35 -0
- package/dist/server/services/apps/serve.js +665 -0
- package/dist/server/services/apps/space-apps.js +248 -0
- package/dist/server/services/auth.js +48 -0
- package/dist/server/services/browser/chrome-sandbox.js +245 -0
- package/dist/server/services/browser/chromium-launch-spec.js +51 -0
- package/dist/server/services/browser/claude-reasoner.js +669 -0
- package/dist/server/services/browser/debug-log.js +3 -0
- package/dist/server/services/browser/driver-state.js +546 -0
- package/dist/server/services/browser/electron-provision.js +256 -0
- package/dist/server/services/browser/host-internal.js +245 -0
- package/dist/server/services/browser/host-mock.js +334 -0
- package/dist/server/services/browser/host-types.js +3 -0
- package/dist/server/services/browser/host.js +1067 -0
- package/dist/server/services/browser/index.js +4 -0
- package/dist/server/services/browser/ipc-client.js +354 -0
- package/dist/server/services/browser/pipewire-sink.js +199 -0
- package/dist/server/services/browser/preflight.js +590 -0
- package/dist/server/services/browser/reasoning-agent.js +659 -0
- package/dist/server/services/browser/sandbox-policy.js +169 -0
- package/dist/server/services/browser/stderr-noise-filter.js +26 -0
- package/dist/server/services/browser/telemetry.js +107 -0
- package/dist/server/services/browser/xvfb-supervisor.js +139 -0
- package/dist/server/services/browser-frame-policy.js +56 -0
- package/dist/server/services/client-id.js +32 -0
- package/dist/server/services/collab/registry.js +408 -0
- package/dist/server/services/collab/session.js +489 -0
- package/dist/server/services/collab/text-diff.js +28 -0
- package/dist/server/services/collab/types.js +19 -0
- package/dist/server/services/e2ee-group-key.js +197 -0
- package/dist/server/services/e2ee-identity.js +203 -0
- package/dist/server/services/file-explorer-ops.js +328 -0
- package/dist/server/services/file-transfer-zip.js +188 -0
- package/dist/server/services/files.js +111 -0
- package/dist/server/services/git/branch-ops.js +62 -0
- package/dist/server/services/git/command.js +68 -0
- package/dist/server/services/git/commit-message.js +74 -0
- package/dist/server/services/git/diff-ops.js +56 -0
- package/dist/server/services/git/haiku.js +76 -0
- package/dist/server/services/git/head-watcher.js +226 -0
- package/dist/server/services/git/index.js +17 -0
- package/dist/server/services/git/log-ops.js +98 -0
- package/dist/server/services/git/merge-ops.js +314 -0
- package/dist/server/services/git/parsers.js +401 -0
- package/dist/server/services/git/remote-ops.js +192 -0
- package/dist/server/services/git/status-ops.js +105 -0
- package/dist/server/services/git/suggestion-ops.js +80 -0
- package/dist/server/services/git/tag-ops.js +36 -0
- package/dist/server/services/git/types.js +4 -0
- package/dist/server/services/git/worktree-ops.js +114 -0
- package/dist/server/services/instances.js +337 -0
- package/dist/server/services/model-catalog.js +247 -0
- package/dist/server/services/observability/posthog.js +153 -0
- package/dist/server/services/pathUtils.js +114 -0
- package/dist/server/services/plan/agent-loader.js +97 -0
- package/dist/server/services/plan/agent-manager.js +363 -0
- package/dist/server/services/plan/agent-resolver.js +74 -0
- package/dist/server/services/plan/agents/assess-stall.md +33 -0
- package/dist/server/services/plan/agents/browser-planner.md +53 -0
- package/dist/server/services/plan/agents/browser-reasoner.md +114 -0
- package/dist/server/services/plan/agents/check-injection.md +95 -0
- package/dist/server/services/plan/agents/classify-error.md +30 -0
- package/dist/server/services/plan/agents/code-review.md +142 -0
- package/dist/server/services/plan/agents/commit-message.md +28 -0
- package/dist/server/services/plan/agents/detect-context-loss.md +30 -0
- package/dist/server/services/plan/agents/execute-issue.md +58 -0
- package/dist/server/services/plan/agents/fix-quality.md +25 -0
- package/dist/server/services/plan/agents/plan-coordinator.md +72 -0
- package/dist/server/services/plan/agents/pr-description.md +29 -0
- package/dist/server/services/plan/agents/retry-task.md +27 -0
- package/dist/server/services/plan/agents/review-code.md +38 -0
- package/dist/server/services/plan/agents/review-quality.md +45 -0
- package/dist/server/services/plan/agents/triage-findings.md +78 -0
- package/dist/server/services/plan/agents/verify-review.md +57 -0
- package/dist/server/services/plan/approval-evaluator.js +143 -0
- package/dist/server/services/plan/board-archive.js +102 -0
- package/dist/server/services/plan/board-config.js +108 -0
- package/dist/server/services/plan/board-export.js +214 -0
- package/dist/server/services/plan/composer-prompt.js +115 -0
- package/dist/server/services/plan/composer.js +664 -0
- package/dist/server/services/plan/config-installer.js +103 -0
- package/dist/server/services/plan/dependency-resolver.js +137 -0
- package/dist/server/services/plan/executor.js +696 -0
- package/dist/server/services/plan/front-matter.js +43 -0
- package/dist/server/services/plan/issue-classification.js +10 -0
- package/dist/server/services/plan/issue-effort.js +182 -0
- package/dist/server/services/plan/issue-loader.js +35 -0
- package/dist/server/services/plan/issue-prompt-builder.js +152 -0
- package/dist/server/services/plan/issue-retry.js +42 -0
- package/dist/server/services/plan/issue-writer.js +102 -0
- package/dist/server/services/plan/metrics-collector.js +272 -0
- package/dist/server/services/plan/output-manager.js +77 -0
- package/dist/server/services/plan/parser-core.js +435 -0
- package/dist/server/services/plan/parser-migration.js +108 -0
- package/dist/server/services/plan/parser.js +258 -0
- package/dist/server/services/plan/plan-runner-factory.js +68 -0
- package/dist/server/services/plan/pm-timeline.js +320 -0
- package/dist/server/services/plan/progress-log.js +101 -0
- package/dist/server/services/plan/prompt-builder.js +142 -0
- package/dist/server/services/plan/quality-delta.js +274 -0
- package/dist/server/services/plan/readiness-planner.js +34 -0
- package/dist/server/services/plan/review-gate.js +409 -0
- package/dist/server/services/plan/review-report.js +134 -0
- package/dist/server/services/plan/review-verdict-stream.js +161 -0
- package/dist/server/services/plan/state-reconciler.js +238 -0
- package/dist/server/services/plan/template-diff.js +175 -0
- package/dist/server/services/plan/template-instantiator.js +516 -0
- package/dist/server/services/plan/template-manifest.js +56 -0
- package/dist/server/services/plan/template-types.js +3 -0
- package/dist/server/services/plan/trigger-evaluator.js +211 -0
- package/dist/server/services/plan/types.js +3 -0
- package/dist/server/services/plan/watcher.js +73 -0
- package/dist/server/services/platform-credentials.js +56 -0
- package/dist/server/services/platform-e2ee.js +421 -0
- package/dist/server/services/platform-envelope.js +102 -0
- package/dist/server/services/platform-heartbeat.js +88 -0
- package/dist/server/services/platform-reconnect.js +60 -0
- package/dist/server/services/platform-token-lifecycle.js +95 -0
- package/dist/server/services/platform-url.js +17 -0
- package/dist/server/services/platform.js +656 -0
- package/dist/server/services/runtime-info.js +9 -0
- package/dist/server/services/schedule/agent-schedule-store.js +232 -0
- package/dist/server/services/schedule/agent-scheduler.js +323 -0
- package/dist/server/services/schedule/daily-timing.js +105 -0
- package/dist/server/services/schedule/quality-schedule-launch.js +11 -0
- package/dist/server/services/schedule/quality-schedule-store.js +211 -0
- package/dist/server/services/schedule/quality-scheduler.js +215 -0
- package/dist/server/services/schedule/run-bootstrap.js +329 -0
- package/dist/server/services/schedule/schedule-store.js +238 -0
- package/dist/server/services/schedule/schedule-wire.js +77 -0
- package/dist/server/services/schedule/scheduler.js +310 -0
- package/dist/server/services/schedule/template-source.js +97 -0
- package/dist/server/services/sdk/agent-sessions.js +264 -0
- package/dist/server/services/sdk/agents.js +557 -0
- package/dist/server/services/sdk/app-endpoints.js +48 -0
- package/dist/server/services/sdk/app-tools.js +303 -0
- package/dist/server/services/sdk/audit.js +48 -0
- package/dist/server/services/sdk/browser-transport.js +188 -0
- package/dist/server/services/sdk/browser.js +605 -0
- package/dist/server/services/sdk/composer.js +129 -0
- package/dist/server/services/sdk/connect.js +273 -0
- package/dist/server/services/sdk/consent-cooldown.js +31 -0
- package/dist/server/services/sdk/endpoint-url.js +141 -0
- package/dist/server/services/sdk/endpoints.js +314 -0
- package/dist/server/services/sdk/file-change-notify.js +36 -0
- package/dist/server/services/sdk/files-app.js +163 -0
- package/dist/server/services/sdk/files-search.js +131 -0
- package/dist/server/services/sdk/files-transfer.js +279 -0
- package/dist/server/services/sdk/files-workspace.js +233 -0
- package/dist/server/services/sdk/git.js +520 -0
- package/dist/server/services/sdk/grant-store.js +318 -0
- package/dist/server/services/sdk/host-ops.js +84 -0
- package/dist/server/services/sdk/inference-claude.js +214 -0
- package/dist/server/services/sdk/inference.js +247 -0
- package/dist/server/services/sdk/live-registry.js +42 -0
- package/dist/server/services/sdk/local-app-bindings.js +71 -0
- package/dist/server/services/sdk/pipeline.js +190 -0
- package/dist/server/services/sdk/pm-execute.js +228 -0
- package/dist/server/services/sdk/pm-planning-tap.js +29 -0
- package/dist/server/services/sdk/pm-schedule.js +165 -0
- package/dist/server/services/sdk/pm.js +634 -0
- package/dist/server/services/sdk/quality-change-notify.js +20 -0
- package/dist/server/services/sdk/quality-schedule.js +165 -0
- package/dist/server/services/sdk/quality.js +602 -0
- package/dist/server/services/sdk/rate-limits.js +198 -0
- package/dist/server/services/sdk/registry.js +597 -0
- package/dist/server/services/sdk/revoke.js +86 -0
- package/dist/server/services/sdk/run-profile.js +41 -0
- package/dist/server/services/sdk/sandbox.js +89 -0
- package/dist/server/services/sdk/storage.js +110 -0
- package/dist/server/services/sdk/streams.js +147 -0
- package/dist/server/services/sdk/terminal.js +201 -0
- package/dist/server/services/sdk/test-utils/workshop-browser-app.js +466 -0
- package/dist/server/services/sentry.js +99 -0
- package/dist/server/services/settings.js +374 -0
- package/dist/server/services/snippets/expander.js +87 -0
- package/dist/server/services/snippets/index.js +6 -0
- package/dist/server/services/snippets/loader.js +119 -0
- package/dist/server/services/snippets/parser.js +52 -0
- package/dist/server/services/snippets/types.js +3 -0
- package/dist/server/services/snippets/watcher.js +60 -0
- package/dist/server/services/space-layout.js +80 -0
- package/dist/server/services/system-prompts.js +219 -0
- package/dist/server/services/terminal/pty-manager.js +206 -0
- package/dist/server/services/terminal/pty-utils.js +150 -0
- package/dist/server/services/timeline/assembler.js +890 -0
- package/dist/server/services/timeline/contract.js +196 -0
- package/dist/server/services/timeline/index.js +4 -0
- package/dist/server/services/websocket/agent-schedule-handlers.js +217 -0
- package/dist/server/services/websocket/apps-handlers.js +399 -0
- package/dist/server/services/websocket/ask-user-question-bridge.js +112 -0
- package/dist/server/services/websocket/autocomplete-index.js +415 -0
- package/dist/server/services/websocket/autocomplete-match.js +243 -0
- package/dist/server/services/websocket/autocomplete.js +407 -0
- package/dist/server/services/websocket/browser-agent-runtime.js +208 -0
- package/dist/server/services/websocket/browser-device-profile.js +46 -0
- package/dist/server/services/websocket/browser-handlers.js +377 -0
- package/dist/server/services/websocket/browser-host-broadcast.js +179 -0
- package/dist/server/services/websocket/browser-ice-config.js +121 -0
- package/dist/server/services/websocket/browser-payloads.js +222 -0
- package/dist/server/services/websocket/browser-viewer-lifecycle.js +84 -0
- package/dist/server/services/websocket/browser-viewer-registry.js +118 -0
- package/dist/server/services/websocket/collab-broadcast.js +11 -0
- package/dist/server/services/websocket/collab-handlers.js +116 -0
- package/dist/server/services/websocket/commit-suggestion-service.js +132 -0
- package/dist/server/services/websocket/debug-log.js +38 -0
- package/dist/server/services/websocket/dispatch-util.js +10 -0
- package/dist/server/services/websocket/engine-auth-handlers.js +323 -0
- package/dist/server/services/websocket/engine-credential-watcher.js +83 -0
- package/dist/server/services/websocket/engine-profile-handlers.js +92 -0
- package/dist/server/services/websocket/file-definition-handlers.js +180 -0
- package/dist/server/services/websocket/file-download-handler.js +291 -0
- package/dist/server/services/websocket/file-explorer-handlers.js +291 -0
- package/dist/server/services/websocket/file-search-handlers.js +267 -0
- package/dist/server/services/websocket/file-share-handlers.js +328 -0
- package/dist/server/services/websocket/file-transfer-http.js +361 -0
- package/dist/server/services/websocket/file-upload-handler.js +267 -0
- package/dist/server/services/websocket/file-utils.js +346 -0
- package/dist/server/services/websocket/git-branch-handlers.js +90 -0
- package/dist/server/services/websocket/git-diff-handlers.js +59 -0
- package/dist/server/services/websocket/git-handlers.js +212 -0
- package/dist/server/services/websocket/git-head-watcher.js +85 -0
- package/dist/server/services/websocket/git-log-handlers.js +104 -0
- package/dist/server/services/websocket/git-pr-handlers.js +56 -0
- package/dist/server/services/websocket/git-tag-handlers.js +53 -0
- package/dist/server/services/websocket/git-utils.js +11 -0
- package/dist/server/services/websocket/git-worktree-handlers.js +521 -0
- package/dist/server/services/websocket/handler-context.js +113 -0
- package/dist/server/services/websocket/handler.js +1197 -0
- package/dist/server/services/websocket/index.js +5 -0
- package/dist/server/services/websocket/movement-written-files.js +26 -0
- package/dist/server/services/websocket/msg-id-tracker.js +48 -0
- package/dist/server/services/websocket/plan-board-handlers.js +410 -0
- package/dist/server/services/websocket/plan-execution-handlers.js +244 -0
- package/dist/server/services/websocket/plan-handlers.js +67 -0
- package/dist/server/services/websocket/plan-helpers.js +161 -0
- package/dist/server/services/websocket/plan-issue-handlers.js +168 -0
- package/dist/server/services/websocket/plan-sprint-handlers.js +190 -0
- package/dist/server/services/websocket/plan-transfer-handlers.js +310 -0
- package/dist/server/services/websocket/pm-timeline-broadcast.js +28 -0
- package/dist/server/services/websocket/post-session-scan.js +132 -0
- package/dist/server/services/websocket/prompt-queue-handlers.js +96 -0
- package/dist/server/services/websocket/quality-agent-cost.js +229 -0
- package/dist/server/services/websocket/quality-agent-notes.js +113 -0
- package/dist/server/services/websocket/quality-change-activity.js +187 -0
- package/dist/server/services/websocket/quality-complexity.js +345 -0
- package/dist/server/services/websocket/quality-dependencies.js +445 -0
- package/dist/server/services/websocket/quality-duplication.js +162 -0
- package/dist/server/services/websocket/quality-eta.js +65 -0
- package/dist/server/services/websocket/quality-fingerprint.js +104 -0
- package/dist/server/services/websocket/quality-grading.js +575 -0
- package/dist/server/services/websocket/quality-handlers.js +48 -0
- package/dist/server/services/websocket/quality-linting.js +270 -0
- package/dist/server/services/websocket/quality-operations.js +54 -0
- package/dist/server/services/websocket/quality-persistence.js +410 -0
- package/dist/server/services/websocket/quality-review-agent.js +526 -0
- package/dist/server/services/websocket/quality-run.js +220 -0
- package/dist/server/services/websocket/quality-secrets.js +0 -0
- package/dist/server/services/websocket/quality-service.js +775 -0
- package/dist/server/services/websocket/quality-tools.js +383 -0
- package/dist/server/services/websocket/quality-triage-agent.js +101 -0
- package/dist/server/services/websocket/quality-types.js +149 -0
- package/dist/server/services/websocket/recommended-answers.js +28 -0
- package/dist/server/services/websocket/schedule-handlers.js +197 -0
- package/dist/server/services/websocket/sdk-agent-host.js +569 -0
- package/dist/server/services/websocket/sdk-browser-host.js +459 -0
- package/dist/server/services/websocket/sdk-composer-host.js +10 -0
- package/dist/server/services/websocket/sdk-git-host.js +115 -0
- package/dist/server/services/websocket/sdk-handlers.js +242 -0
- package/dist/server/services/websocket/sdk-pm-execution-host.js +63 -0
- package/dist/server/services/websocket/sdk-pm-schedule-host.js +21 -0
- package/dist/server/services/websocket/sdk-quality-schedule-host.js +34 -0
- package/dist/server/services/websocket/sdk-terminal-host.js +140 -0
- package/dist/server/services/websocket/session-handlers.js +565 -0
- package/dist/server/services/websocket/session-history.js +335 -0
- package/dist/server/services/websocket/session-initialization.js +315 -0
- package/dist/server/services/websocket/session-registry.js +340 -0
- package/dist/server/services/websocket/session-role-directory.js +39 -0
- package/dist/server/services/websocket/settings-handlers.js +276 -0
- package/dist/server/services/websocket/skill-handlers.js +324 -0
- package/dist/server/services/websocket/skill-watcher.js +83 -0
- package/dist/server/services/websocket/snippet-handlers.js +261 -0
- package/dist/server/services/websocket/tab-broadcast.js +35 -0
- package/dist/server/services/websocket/tab-event-buffer.js +110 -0
- package/dist/server/services/websocket/tab-event-replay.js +47 -0
- package/dist/server/services/websocket/tab-handlers.js +211 -0
- package/dist/server/services/websocket/terminal-handlers.js +268 -0
- package/dist/server/services/websocket/types.js +96 -0
- package/dist/server/types/company-messages.js +102 -0
- package/dist/server/utils/agent-manager.js +202 -0
- package/dist/server/utils/console-timestamps.js +24 -0
- package/dist/server/utils/file-store.js +112 -0
- package/dist/server/utils/paths.js +28 -0
- package/dist/server/utils/port-manager.js +43 -0
- package/dist/server/utils/port.js +204 -0
- package/electron-host/dist/agent-lease.js +141 -0
- package/electron-host/dist/capture-recovery.js +77 -0
- package/electron-host/dist/codec-policy.js +37 -0
- package/electron-host/dist/controller-channel.js +60 -0
- package/electron-host/dist/controller-handlers.js +153 -0
- package/electron-host/dist/device-emulator.js +106 -0
- package/electron-host/dist/dom-driver.js +642 -0
- package/electron-host/dist/domain-registry.js +159 -0
- package/electron-host/dist/domain-validators.js +308 -0
- package/electron-host/dist/editable-focus-reporter.js +143 -0
- package/electron-host/dist/fake-peer.test-util.js +105 -0
- package/electron-host/dist/host-log.js +31 -0
- package/electron-host/dist/host-wire.js +11 -0
- package/electron-host/dist/input-router.js +421 -0
- package/electron-host/dist/ipc-protocol.js +89 -0
- package/electron-host/dist/ipc-server.js +193 -0
- package/electron-host/dist/js-driver.js +216 -0
- package/electron-host/dist/main-guards.js +70 -0
- package/electron-host/dist/main.js +716 -0
- package/electron-host/dist/roster-sync.js +87 -0
- package/electron-host/dist/rpc-context.js +1 -0
- package/electron-host/dist/rpc-tab-methods.js +138 -0
- package/electron-host/dist/rpc-webrtc-methods.js +121 -0
- package/electron-host/dist/screen-capture-cache.js +147 -0
- package/electron-host/dist/sdk-injector.js +166 -0
- package/electron-host/dist/sdp.js +102 -0
- package/electron-host/dist/session-surface.js +78 -0
- package/electron-host/dist/surface-sizing.js +59 -0
- package/electron-host/dist/view-manager.js +254 -0
- package/electron-host/dist/viewer-registry.js +46 -0
- package/electron-host/dist/viewer-session.js +288 -0
- package/electron-host/dist/webrtc-channels.js +14 -0
- package/electron-host/dist/webrtc-controller.js +57 -0
- package/electron-host/dist/webrtc-ice-filter.js +13 -0
- package/electron-host/dist/webrtc-peer.js +45 -0
- package/electron-host/package.json +12 -0
- package/electron-host/tsconfig.json +23 -0
- package/electron-host/webrtc-controller-preload.cjs +3 -0
- package/electron-host/webrtc-controller.html +14 -0
- package/package.json +85 -0
- package/vendor/e2ee/LICENSE +14 -0
- package/vendor/e2ee/package.json +14 -0
- package/vendor/e2ee/src/index.js +310 -0
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
// Copyright (c) 2025-present Minionry, Inc.
|
|
2
|
+
// SPDX-License-Identifier: LicenseRef-Minionry-Software
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { basename, isAbsolute, join, resolve } from 'node:path';
|
|
5
|
+
import { AnalyticsEvents, trackEvent } from '../services/analytics.js';
|
|
6
|
+
import { getBrowserDriverState, INPUT_MUTATING_BROWSER_TOOLS } from '../services/browser/driver-state.js';
|
|
7
|
+
import { WORKSPACE_READ_TOOLS, WORKSPACE_SHELL_TOOLS, WORKSPACE_WRITE_TOOLS, } from '../services/sdk/run-profile.js';
|
|
8
|
+
import { resolveWithinRoot } from '../services/sdk/sandbox.js';
|
|
9
|
+
import { captureException } from '../services/sentry.js';
|
|
10
|
+
import { getBrowserAllowlist } from '../services/settings.js';
|
|
11
|
+
import { SPACE_DIRNAME, SPACE_LAYOUT, spacePath } from '../services/space-layout.js';
|
|
12
|
+
import { redactBeforeReview, redactToolInput } from './browser-redaction.js';
|
|
13
|
+
import { HAIKU_TIMEOUT_MS } from './classifier/ClaudeBouncerClassifier.js';
|
|
14
|
+
import { getConfiguredClassifierIdentity, resolveClassifier, } from './classifier/factory.js';
|
|
15
|
+
import { isCompanyMode } from './company-patterns.js';
|
|
16
|
+
import { departmentAllows, departmentDenies, getActiveDepartmentProfile, } from './department-profiles.js';
|
|
17
|
+
import { BROWSER_EVAL_THREATS, BROWSER_SAFE_TOOLS, CRITICAL_THREATS, classifyRisk, isDeployMode, isLocalhostUrl, isOriginAllowlisted, matchesPattern, normalizeOperation, requiresAIReview, SAFE_OPERATIONS, SENSITIVE_PATHS } from './security-patterns.js';
|
|
18
|
+
function untrustedModeRequiresReview(isSafe) {
|
|
19
|
+
return !isSafe && (isDeployMode() || isCompanyMode());
|
|
20
|
+
}
|
|
21
|
+
export function mapBouncerDecisionToOutcome(decision) {
|
|
22
|
+
switch (decision.decision) {
|
|
23
|
+
case 'allow':
|
|
24
|
+
return 'autoAllow';
|
|
25
|
+
case 'deny':
|
|
26
|
+
return 'autoDeny';
|
|
27
|
+
default:
|
|
28
|
+
return 'greenlight';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const CACHE_TTL_MS = parseInt(process.env.BOUNCER_CACHE_TTL_MS || '300000', 10);
|
|
32
|
+
const CACHE_MAX_SIZE = 200;
|
|
33
|
+
const decisionCache = new Map();
|
|
34
|
+
export function normalizeOperationForCacheKey(operation) {
|
|
35
|
+
const op = operation.replace(/\s+/g, ' ').trim();
|
|
36
|
+
const fileMatch = op.match(/^(Write|Edit|Read):\s*(\/\S+)$/i);
|
|
37
|
+
if (fileMatch && !matchesPattern(op, SENSITIVE_PATHS)) {
|
|
38
|
+
const [, tool, filePath] = fileMatch;
|
|
39
|
+
const slash = filePath.lastIndexOf('/');
|
|
40
|
+
const dir = slash > 0 ? filePath.slice(0, slash) : '';
|
|
41
|
+
return `${tool}: ${dir}/*`;
|
|
42
|
+
}
|
|
43
|
+
return op;
|
|
44
|
+
}
|
|
45
|
+
function buildCacheKey(operation, context) {
|
|
46
|
+
const sessionId = context?.sessionId ?? '_';
|
|
47
|
+
const sdkSalt = context?.source === 'sdk'
|
|
48
|
+
? `|sdk:${String(context.appId ?? '')}:${Array.isArray(context.grantedScopes) ? [...context.grantedScopes].sort().join('+') : ''}`
|
|
49
|
+
: '';
|
|
50
|
+
return `${sessionId}${sdkSalt}:${normalizeOperationForCacheKey(operation)}`;
|
|
51
|
+
}
|
|
52
|
+
function getCachedDecision(operation, context) {
|
|
53
|
+
const key = buildCacheKey(operation, context);
|
|
54
|
+
const entry = decisionCache.get(key);
|
|
55
|
+
if (!entry)
|
|
56
|
+
return null;
|
|
57
|
+
if (Date.now() > entry.expiresAt) {
|
|
58
|
+
decisionCache.delete(key);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return entry.decision;
|
|
62
|
+
}
|
|
63
|
+
export function clearDecisionCache() {
|
|
64
|
+
decisionCache.clear();
|
|
65
|
+
}
|
|
66
|
+
const REMOTE_CACHE_TTL_MS = parseInt(process.env.BOUNCER_REMOTE_CACHE_TTL_MS || String(30 * 60 * 1000), 10);
|
|
67
|
+
let remoteDecisionCache = null;
|
|
68
|
+
export function setRemoteDecisionCache(store) {
|
|
69
|
+
remoteDecisionCache = store;
|
|
70
|
+
}
|
|
71
|
+
async function getRemoteCachedDecision(operation, context) {
|
|
72
|
+
if (!remoteDecisionCache)
|
|
73
|
+
return null;
|
|
74
|
+
const key = buildCacheKey(operation, context);
|
|
75
|
+
try {
|
|
76
|
+
const decision = await remoteDecisionCache.get(key);
|
|
77
|
+
if (!decision)
|
|
78
|
+
return null;
|
|
79
|
+
decisionCache.set(key, { decision, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
80
|
+
return decision;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function lookupCachedDecision(operation, context) {
|
|
87
|
+
const local = getCachedDecision(operation, context);
|
|
88
|
+
if (local) {
|
|
89
|
+
console.error(`[Bouncer] ⚡ Cache hit: ${local.decision} (${local.confidence}%)`);
|
|
90
|
+
return local;
|
|
91
|
+
}
|
|
92
|
+
const crossTurn = await getRemoteCachedDecision(operation, context);
|
|
93
|
+
if (crossTurn) {
|
|
94
|
+
console.error(`[Bouncer] ⚡ Cross-turn cache hit: ${crossTurn.decision} (${crossTurn.confidence}%)`);
|
|
95
|
+
}
|
|
96
|
+
return crossTurn;
|
|
97
|
+
}
|
|
98
|
+
function cacheDecision(operation, context, decision, layer) {
|
|
99
|
+
if (decision.confidence < 50)
|
|
100
|
+
return;
|
|
101
|
+
if (decisionCache.size >= CACHE_MAX_SIZE) {
|
|
102
|
+
const firstKey = decisionCache.keys().next().value;
|
|
103
|
+
if (firstKey !== undefined)
|
|
104
|
+
decisionCache.delete(firstKey);
|
|
105
|
+
}
|
|
106
|
+
const key = buildCacheKey(operation, context);
|
|
107
|
+
decisionCache.set(key, { decision, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
108
|
+
if (remoteDecisionCache && layer === 'haiku-ai') {
|
|
109
|
+
remoteDecisionCache.set(key, decision, REMOTE_CACHE_TTL_MS).catch(() => {
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function redactForSink(sink, operation, context) {
|
|
114
|
+
const toolName = context?.toolName;
|
|
115
|
+
const toolInput = context?.toolInput;
|
|
116
|
+
if (typeof toolName !== 'string' || typeof toolInput !== 'object' || toolInput === null) {
|
|
117
|
+
return { operation, context };
|
|
118
|
+
}
|
|
119
|
+
const redactedInput = redactToolInput(toolName, toolInput, sink);
|
|
120
|
+
if (redactedInput === toolInput)
|
|
121
|
+
return { operation, context };
|
|
122
|
+
return {
|
|
123
|
+
operation: formatOperationForReview(toolName, redactedInput),
|
|
124
|
+
context: sink === 'audit' ? { ...context, toolInput: redactedInput } : context,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function redactRequestBeforeReview(request) {
|
|
128
|
+
const toolName = request.context?.toolName;
|
|
129
|
+
const toolInput = request.context?.toolInput;
|
|
130
|
+
if (typeof toolName !== 'string' || typeof toolInput !== 'object' || toolInput === null) {
|
|
131
|
+
return request;
|
|
132
|
+
}
|
|
133
|
+
const redactedInput = redactBeforeReview(toolName, toolInput);
|
|
134
|
+
if (redactedInput === toolInput)
|
|
135
|
+
return request;
|
|
136
|
+
return {
|
|
137
|
+
...request,
|
|
138
|
+
operation: formatOperationForReview(toolName, redactedInput),
|
|
139
|
+
context: { ...request.context, toolInput: redactedInput },
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
class DecisionFinalizer {
|
|
143
|
+
operation;
|
|
144
|
+
startTime;
|
|
145
|
+
context;
|
|
146
|
+
logFn;
|
|
147
|
+
constructor(operation, startTime, context, logFn) {
|
|
148
|
+
this.operation = operation;
|
|
149
|
+
this.startTime = startTime;
|
|
150
|
+
this.context = context;
|
|
151
|
+
this.logFn = logFn;
|
|
152
|
+
}
|
|
153
|
+
finalize(decision, layer, opts) {
|
|
154
|
+
const latencyMs = Math.round(performance.now() - this.startTime);
|
|
155
|
+
const needsClassifier = !opts?.skipLog || !opts?.skipAnalytics;
|
|
156
|
+
const classifier = needsClassifier
|
|
157
|
+
? (opts?.classifier ?? { ...getConfiguredClassifierIdentity(), fallback: false })
|
|
158
|
+
: null;
|
|
159
|
+
if (!opts?.skipLog) {
|
|
160
|
+
const audit = redactForSink('audit', this.operation, this.context);
|
|
161
|
+
this.logFn(audit.operation, decision.decision, decision.confidence, decision.reasoning, {
|
|
162
|
+
context: audit.context,
|
|
163
|
+
threatLevel: decision.threatLevel,
|
|
164
|
+
layer,
|
|
165
|
+
latencyMs,
|
|
166
|
+
...(classifier && {
|
|
167
|
+
classifierEngine: classifier.engine,
|
|
168
|
+
classifierModel: classifier.model,
|
|
169
|
+
...(classifier.fallback && { classifierFallback: true }),
|
|
170
|
+
}),
|
|
171
|
+
...(opts?.error && { error: opts.error }),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (!opts?.skipAnalytics) {
|
|
175
|
+
const event = decision.decision === 'deny' ? AnalyticsEvents.BOUNCER_TOOL_DENIED : AnalyticsEvents.BOUNCER_TOOL_ALLOWED;
|
|
176
|
+
trackEvent(event, {
|
|
177
|
+
layer,
|
|
178
|
+
operation_length: this.operation.length,
|
|
179
|
+
threat_level: decision.threatLevel,
|
|
180
|
+
confidence: decision.confidence,
|
|
181
|
+
latency_ms: latencyMs,
|
|
182
|
+
...(classifier && {
|
|
183
|
+
classifier_engine: classifier.engine,
|
|
184
|
+
classifier_model: classifier.model,
|
|
185
|
+
classifier_fallback: classifier.fallback,
|
|
186
|
+
}),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (!opts?.skipCache)
|
|
190
|
+
cacheDecision(this.operation, this.context, decision, layer);
|
|
191
|
+
return decision;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
export const LOW_CONFIDENCE_THRESHOLD = 50;
|
|
195
|
+
export function isSensitiveOperation(operation) {
|
|
196
|
+
const risk = classifyRisk(operation);
|
|
197
|
+
return (risk.riskLevel === 'high' ||
|
|
198
|
+
risk.riskLevel === 'critical' ||
|
|
199
|
+
!!matchesPattern(operation, SENSITIVE_PATHS));
|
|
200
|
+
}
|
|
201
|
+
function applySensitiveVerdictFloor(operation, decision, classifier, fin) {
|
|
202
|
+
if (decision.decision === 'deny')
|
|
203
|
+
return null;
|
|
204
|
+
if (!isSensitiveOperation(operation))
|
|
205
|
+
return null;
|
|
206
|
+
const confidence = decision.confidence;
|
|
207
|
+
const malformed = typeof confidence !== 'number' ||
|
|
208
|
+
!Number.isFinite(confidence) ||
|
|
209
|
+
confidence < 0 ||
|
|
210
|
+
confidence > 100;
|
|
211
|
+
if (!malformed && confidence >= LOW_CONFIDENCE_THRESHOLD)
|
|
212
|
+
return null;
|
|
213
|
+
const cause = malformed
|
|
214
|
+
? 'returned a malformed/unparseable verdict'
|
|
215
|
+
: `returned a low-confidence verdict (${confidence}% < ${LOW_CONFIDENCE_THRESHOLD}%)`;
|
|
216
|
+
console.error(`[Bouncer] 🚫 Classifier ${cause} on a sensitive/high-risk operation — DENYING for safety (fail-closed)`);
|
|
217
|
+
return fin.finalize({
|
|
218
|
+
decision: 'deny',
|
|
219
|
+
confidence: 0,
|
|
220
|
+
reasoning: `Security classifier ${cause} on a sensitive/high-risk operation. Denying for safety — the operation could not be verified with sufficient confidence.`,
|
|
221
|
+
threatLevel: 'high',
|
|
222
|
+
}, 'haiku-lowconf', { skipCache: true, classifier });
|
|
223
|
+
}
|
|
224
|
+
function resolveClassifierUnavailable(isTimeout, errorMessage, operation, maxAttempts, classifier, fin) {
|
|
225
|
+
const layer = isTimeout ? 'haiku-timeout' : 'ai-error';
|
|
226
|
+
const cause = isTimeout
|
|
227
|
+
? `timed out after ${maxAttempts} attempts (${HAIKU_TIMEOUT_MS}ms each)`
|
|
228
|
+
: `failed: ${errorMessage}`;
|
|
229
|
+
const risk = classifyRisk(operation);
|
|
230
|
+
const failClosed = isSensitiveOperation(operation);
|
|
231
|
+
if (failClosed) {
|
|
232
|
+
console.error(`[Bouncer] 🚫 Classifier unavailable (${cause}) on a ${risk.riskLevel}-risk operation — DENYING for safety`);
|
|
233
|
+
return fin.finalize({ decision: 'deny', confidence: 0, reasoning: `Security analysis ${cause} on a sensitive/high-risk operation. Denying for safety — operation could not be verified.`, threatLevel: 'high' }, layer, { skipCache: true, classifier, ...(isTimeout ? {} : { skipAnalytics: true, error: errorMessage }) });
|
|
234
|
+
}
|
|
235
|
+
console.error(`[Bouncer] ⚠️ Classifier unavailable (${cause}) on a ${risk.riskLevel}-risk operation — allowing with caution (fail-open)`);
|
|
236
|
+
return fin.finalize({ decision: 'warn_allow', confidence: 50, reasoning: `Security classifier was unavailable (${cause}). The operation cleared Layer-1 pattern checks and is ${risk.riskLevel}-risk, so it is allowed with caution rather than blocked on classifier latency. Verify it matches your intent.`, threatLevel: 'medium' }, layer, { skipCache: true, classifier, ...(isTimeout ? {} : { skipAnalytics: true, error: errorMessage }) });
|
|
237
|
+
}
|
|
238
|
+
function handleHaikuError(error, operation, attempt, maxAttempts, classifier, fin) {
|
|
239
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
240
|
+
const isTimeout = errorMessage.includes('timed out');
|
|
241
|
+
if (isTimeout && attempt < maxAttempts) {
|
|
242
|
+
console.error(`[Bouncer] ⚠️ Haiku timed out (attempt ${attempt}/${maxAttempts}), retrying...`);
|
|
243
|
+
captureException(error, { context: 'bouncer.haiku_timeout_retry', operation, attempt });
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
captureException(error, { context: isTimeout ? 'bouncer.haiku_timeout' : 'bouncer.haiku_analysis', operation });
|
|
247
|
+
return resolveClassifierUnavailable(isTimeout, errorMessage, operation, maxAttempts, classifier, fin);
|
|
248
|
+
}
|
|
249
|
+
let defaultClassifier = null;
|
|
250
|
+
function resolveActiveClassifier() {
|
|
251
|
+
if (defaultClassifier) {
|
|
252
|
+
return {
|
|
253
|
+
classifier: defaultClassifier,
|
|
254
|
+
identity: { engine: 'custom', model: 'custom', fallback: false },
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const resolution = resolveClassifier();
|
|
258
|
+
return {
|
|
259
|
+
classifier: resolution.classifier,
|
|
260
|
+
identity: { engine: resolution.engine, model: resolution.model, fallback: resolution.didFallback },
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
export function setBouncerClassifier(classifier) {
|
|
264
|
+
defaultClassifier = classifier;
|
|
265
|
+
}
|
|
266
|
+
const inflightClassifications = new Map();
|
|
267
|
+
function coalesceClassification(key, run) {
|
|
268
|
+
const joined = inflightClassifications.get(key);
|
|
269
|
+
if (joined) {
|
|
270
|
+
console.error('[Bouncer] ⏳ Identical classification already in flight — joining it');
|
|
271
|
+
return joined;
|
|
272
|
+
}
|
|
273
|
+
const started = run().finally(() => {
|
|
274
|
+
if (inflightClassifications.get(key) === started)
|
|
275
|
+
inflightClassifications.delete(key);
|
|
276
|
+
});
|
|
277
|
+
inflightClassifications.set(key, started);
|
|
278
|
+
return started;
|
|
279
|
+
}
|
|
280
|
+
async function runHaikuAnalysis(request, operation, startTime, fin) {
|
|
281
|
+
const aiDisabledByEnv = process.env.BOUNCER_USE_AI === 'false';
|
|
282
|
+
if (aiDisabledByEnv || request.context?._skipAI === true) {
|
|
283
|
+
console.error('[Bouncer] AI analysis disabled');
|
|
284
|
+
return fin.finalize({ decision: 'warn_allow', confidence: 60, reasoning: 'Operation requires review but AI analysis is disabled. Proceeding with caution.', threatLevel: 'medium' }, 'ai-disabled', { skipCache: true, skipAnalytics: true });
|
|
285
|
+
}
|
|
286
|
+
console.error('[Bouncer] 🤖 Invoking Haiku for AI analysis...');
|
|
287
|
+
trackEvent(AnalyticsEvents.BOUNCER_HAIKU_REVIEW, { operation_length: operation.length });
|
|
288
|
+
const { classifier, identity } = resolveActiveClassifier();
|
|
289
|
+
const MAX_ATTEMPTS = 2;
|
|
290
|
+
const classifierInput = redactForSink('classifier', request.operation, request.context);
|
|
291
|
+
const coalesceKey = buildCacheKey(operation, request.context);
|
|
292
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
293
|
+
try {
|
|
294
|
+
const decision = await coalesceClassification(coalesceKey, () => classifier.classify(classifierInput.operation, request.context));
|
|
295
|
+
console.error(`[Bouncer] ✓ Haiku decision: ${decision.decision} (${decision.confidence}% confidence) [${Math.round(performance.now() - startTime)}ms]`);
|
|
296
|
+
console.error(`[Bouncer] Reasoning: ${decision.reasoning}`);
|
|
297
|
+
const floored = applySensitiveVerdictFloor(operation, decision, identity, fin);
|
|
298
|
+
if (floored)
|
|
299
|
+
return floored;
|
|
300
|
+
return fin.finalize(decision, 'haiku-ai', { classifier: identity });
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
const result = handleHaikuError(error, operation, attempt, MAX_ATTEMPTS, identity, fin);
|
|
304
|
+
if (result)
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return fin.finalize({ decision: 'deny', confidence: 0, reasoning: 'Security analysis exhausted all attempts. Denying for safety.', threatLevel: 'critical' }, 'ai-error', { skipCache: true, classifier: identity });
|
|
309
|
+
}
|
|
310
|
+
const BROWSER_MCP_PREFIX_RE = /^mcp__minionry-browser__/i;
|
|
311
|
+
function extractBrowserNavigateUrl(operation) {
|
|
312
|
+
const match = operation.match(/^browser_navigate:\s*(\{[\s\S]*\})\s*$/i);
|
|
313
|
+
if (!match)
|
|
314
|
+
return null;
|
|
315
|
+
try {
|
|
316
|
+
const parsed = JSON.parse(match[1]);
|
|
317
|
+
if (parsed && typeof parsed === 'object' && typeof parsed.url === 'string') {
|
|
318
|
+
return parsed.url;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
}
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
function resolveBrowserAllowlist(context) {
|
|
326
|
+
const fromContext = context?.browserAllowlist;
|
|
327
|
+
if (Array.isArray(fromContext)) {
|
|
328
|
+
return fromContext.filter((e) => typeof e === 'string');
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
return getBrowserAllowlist();
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function browserTabOf(context) {
|
|
338
|
+
if (typeof context?.browserTabId === 'string' && context.browserTabId)
|
|
339
|
+
return context.browserTabId;
|
|
340
|
+
const input = context?.toolInput;
|
|
341
|
+
const named = typeof input === 'object' && input !== null ? input.tabId : undefined;
|
|
342
|
+
return typeof named === 'string' && named ? named : undefined;
|
|
343
|
+
}
|
|
344
|
+
function applyDriverStateGate(operation, context, fin) {
|
|
345
|
+
const bare = operation.replace(BROWSER_MCP_PREFIX_RE, '');
|
|
346
|
+
const toolName = bare.match(/^(browser_[a-z_]+)/i)?.[1] ?? '';
|
|
347
|
+
if (!INPUT_MUTATING_BROWSER_TOOLS.has(toolName))
|
|
348
|
+
return null;
|
|
349
|
+
const tabId = browserTabOf(context);
|
|
350
|
+
const driverState = getBrowserDriverState();
|
|
351
|
+
const gate = tabId ? driverState.gateSync(tabId, toolName) : driverState.anyTabBlocksAgent();
|
|
352
|
+
if (gate.allow)
|
|
353
|
+
return null;
|
|
354
|
+
console.error(`[Bouncer] ⚡ Driver-state gate: ${toolName} blocked (${gate.state}) on ${tabId ?? 'some tab'}`);
|
|
355
|
+
return fin.finalize({
|
|
356
|
+
decision: 'deny',
|
|
357
|
+
confidence: 95,
|
|
358
|
+
reasoning: `Driver arbitration: ${gate.hint}`,
|
|
359
|
+
threatLevel: 'low',
|
|
360
|
+
alternative: gate.state === 'automation'
|
|
361
|
+
? 'Wait for the user to resume the tab from the browser view before retrying.'
|
|
362
|
+
: 'Re-check the page state after the user finishes their action; do not blindly retry.',
|
|
363
|
+
enforceable: true,
|
|
364
|
+
}, 'pattern-arbitration', { skipCache: true });
|
|
365
|
+
}
|
|
366
|
+
function reviewBrowserTool(operation, context, fin) {
|
|
367
|
+
if (!/^(mcp__minionry-browser__)?browser_/i.test(operation))
|
|
368
|
+
return null;
|
|
369
|
+
const bare = operation.replace(BROWSER_MCP_PREFIX_RE, '');
|
|
370
|
+
const arbitration = applyDriverStateGate(operation, context, fin);
|
|
371
|
+
if (arbitration)
|
|
372
|
+
return arbitration;
|
|
373
|
+
const safeBrowser = matchesPattern(bare, BROWSER_SAFE_TOOLS);
|
|
374
|
+
if (safeBrowser) {
|
|
375
|
+
console.error('[Bouncer] ⚡ Browser fast path: read-only tool approved');
|
|
376
|
+
return fin.finalize({
|
|
377
|
+
decision: 'allow',
|
|
378
|
+
confidence: 95,
|
|
379
|
+
reasoning: `Read-only browser tool: ${safeBrowser.reason ?? 'no side effects'}`,
|
|
380
|
+
threatLevel: 'low',
|
|
381
|
+
}, 'pattern-safe');
|
|
382
|
+
}
|
|
383
|
+
if (/^browser_(evaluate|run_code_unsafe):/i.test(bare)) {
|
|
384
|
+
const threat = matchesPattern(bare, BROWSER_EVAL_THREATS);
|
|
385
|
+
if (threat) {
|
|
386
|
+
console.error('[Bouncer] ⚡ Browser fast path: JS payload threat detected');
|
|
387
|
+
return fin.finalize({
|
|
388
|
+
decision: 'deny',
|
|
389
|
+
confidence: 99,
|
|
390
|
+
reasoning: `🚨 Browser JS payload threat: ${threat.reason}`,
|
|
391
|
+
threatLevel: 'critical',
|
|
392
|
+
alternative: 'Avoid reading cookies, storage, or sending credentials from injected JS. If you need this data, ask the user to provide it directly.',
|
|
393
|
+
enforceable: true,
|
|
394
|
+
}, 'pattern-critical');
|
|
395
|
+
}
|
|
396
|
+
console.error('[Bouncer] ⚡ Browser fast path: JS payload — no threat patterns matched');
|
|
397
|
+
return fin.finalize({
|
|
398
|
+
decision: 'warn_allow',
|
|
399
|
+
confidence: 65,
|
|
400
|
+
reasoning: 'Browser JS evaluation — no known exfiltration patterns matched, but executing arbitrary JS in a logged-in page is inherently risky. Verify the payload before approving.',
|
|
401
|
+
threatLevel: 'medium',
|
|
402
|
+
}, 'pattern-default', { skipCache: true });
|
|
403
|
+
}
|
|
404
|
+
if (/^browser_navigate:/i.test(bare)) {
|
|
405
|
+
const url = extractBrowserNavigateUrl(bare);
|
|
406
|
+
if (!url) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
if (isLocalhostUrl(url)) {
|
|
410
|
+
console.error(`[Bouncer] ⚡ Browser fast path: localhost navigation approved (${url})`);
|
|
411
|
+
return fin.finalize({
|
|
412
|
+
decision: 'allow',
|
|
413
|
+
confidence: 95,
|
|
414
|
+
reasoning: 'Localhost / loopback navigation — target is the user\'s own machine, not a third party.',
|
|
415
|
+
threatLevel: 'low',
|
|
416
|
+
}, 'pattern-safe');
|
|
417
|
+
}
|
|
418
|
+
const allowlist = resolveBrowserAllowlist(context);
|
|
419
|
+
if (isOriginAllowlisted(url, allowlist)) {
|
|
420
|
+
console.error(`[Bouncer] ⚡ Browser fast path: allowlisted origin approved (${url})`);
|
|
421
|
+
return fin.finalize({
|
|
422
|
+
decision: 'allow',
|
|
423
|
+
confidence: 95,
|
|
424
|
+
reasoning: 'Navigation target matches the per-orchestra origin allowlist.',
|
|
425
|
+
threatLevel: 'low',
|
|
426
|
+
}, 'pattern-safe');
|
|
427
|
+
}
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
export function sdkRunReviewContextFromEnv(env) {
|
|
433
|
+
const appId = env.BOUNCER_SDK_APP_ID;
|
|
434
|
+
if (!appId)
|
|
435
|
+
return null;
|
|
436
|
+
return {
|
|
437
|
+
source: 'sdk',
|
|
438
|
+
appId,
|
|
439
|
+
appOrigin: env.BOUNCER_SDK_APP_ORIGIN ?? '',
|
|
440
|
+
grantedScopes: (env.BOUNCER_SDK_SCOPES ?? '')
|
|
441
|
+
.split(',')
|
|
442
|
+
.map((scope) => scope.trim())
|
|
443
|
+
.filter((scope) => scope.length > 0),
|
|
444
|
+
sdkWorkspaceDir: env.BOUNCER_SDK_WORKSPACE_DIR ?? '',
|
|
445
|
+
sdkSandboxDir: env.BOUNCER_SDK_SANDBOX_DIR ?? '',
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
const SDK_READ_TOOL_SET = new Set(WORKSPACE_READ_TOOLS.map((tool) => tool.toLowerCase()));
|
|
449
|
+
const SDK_WRITE_TOOL_SET = new Set(WORKSPACE_WRITE_TOOLS.map((tool) => tool.toLowerCase()));
|
|
450
|
+
const SDK_SHELL_TOOL_SET = new Set(WORKSPACE_SHELL_TOOLS.map((tool) => tool.toLowerCase()));
|
|
451
|
+
const SDK_SECRET_REFS = /\.minionry[\\/]+(credentials(?:-dev\.json|(?![-\w]))|(?:config[\\/]+)?settings\.json|session-token|e2ee-identity)/i;
|
|
452
|
+
const SDK_APPS_AREA = /[\\/]\.minionry[. ]*(?::[^\\/]*)?[\\/]+apps[. ]*(?::[^\\/]*)?(?:[\\/]|$)/i;
|
|
453
|
+
const SDK_APPS_ROSTER = /[\\/]\.minionry[. ]*(?::[^\\/]*)?[\\/]+apps\.json[. ]*(?::[^\\/]*)?$/i;
|
|
454
|
+
function sdkForeignSandboxRef(text, appId) {
|
|
455
|
+
const safeAppId = appId.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
456
|
+
if (!safeAppId)
|
|
457
|
+
return SDK_APPS_AREA.test(text);
|
|
458
|
+
const foreign = new RegExp(`\\.minionry[\\\\/]+apps(?![\\\\/]+${safeAppId}(?:[\\\\/]|['"\\s]|$))`, 'i');
|
|
459
|
+
return foreign.test(text);
|
|
460
|
+
}
|
|
461
|
+
function sdkTargetPath(input) {
|
|
462
|
+
for (const key of ['file_path', 'filePath', 'path', 'notebook_path']) {
|
|
463
|
+
const value = input[key];
|
|
464
|
+
if (typeof value === 'string' && value.length > 0)
|
|
465
|
+
return value;
|
|
466
|
+
}
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
function parseSdkCeiling(context) {
|
|
470
|
+
const scopes = Array.isArray(context.grantedScopes)
|
|
471
|
+
? context.grantedScopes.filter((scope) => typeof scope === 'string')
|
|
472
|
+
: [];
|
|
473
|
+
const workspaceWrite = scopes.includes('files:workspace:write');
|
|
474
|
+
return {
|
|
475
|
+
appId: typeof context.appId === 'string' ? context.appId : '',
|
|
476
|
+
scopes,
|
|
477
|
+
workspaceWrite,
|
|
478
|
+
workspaceRead: workspaceWrite || scopes.includes('files:workspace:read'),
|
|
479
|
+
sandboxDir: typeof context.sdkSandboxDir === 'string' && context.sdkSandboxDir ? context.sdkSandboxDir : null,
|
|
480
|
+
workspaceDir: typeof context.sdkWorkspaceDir === 'string' && context.sdkWorkspaceDir ? context.sdkWorkspaceDir : null,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
function confineSdkShell(toolName, command, ceiling) {
|
|
484
|
+
if (!ceiling.workspaceWrite) {
|
|
485
|
+
return {
|
|
486
|
+
reasoning: `shell tool '${toolName}' exceeds the granted scopes [${ceiling.scopes.join(', ') || 'none'}] — shell requires files:workspace:write at minimum`,
|
|
487
|
+
alternative: 'The app must request the files:workspace:write scope (re-consent) for shell access.',
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
if (SDK_SECRET_REFS.test(command)) {
|
|
491
|
+
return { reasoning: 'shell command references the Minionry credential/settings store — off-limits to app runs regardless of scopes' };
|
|
492
|
+
}
|
|
493
|
+
if (sdkForeignSandboxRef(command, ceiling.appId)) {
|
|
494
|
+
return { reasoning: "shell command references the app-sandbox area outside this app's own sandbox — other apps' sandboxes are excluded from workspace scopes" };
|
|
495
|
+
}
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
function sdkWithin(root, path) {
|
|
499
|
+
return root !== null && resolveWithinRoot(root, path, { quiet: true }).valid;
|
|
500
|
+
}
|
|
501
|
+
function sdkResolveTarget(rawPath, sandboxDir) {
|
|
502
|
+
if (isAbsolute(rawPath))
|
|
503
|
+
return resolve(rawPath);
|
|
504
|
+
return sandboxDir ? resolve(sandboxDir, rawPath) : null;
|
|
505
|
+
}
|
|
506
|
+
function sdkNamesAppsArea(path) {
|
|
507
|
+
return SDK_APPS_AREA.test(path) || SDK_APPS_ROSTER.test(path);
|
|
508
|
+
}
|
|
509
|
+
function sdkInAppsArea(resolved, workspaceDir) {
|
|
510
|
+
if (sdkNamesAppsArea(resolved))
|
|
511
|
+
return true;
|
|
512
|
+
const real = workspaceDir ? resolveWithinRoot(workspaceDir, resolved, { quiet: true }) : null;
|
|
513
|
+
if (real?.valid && sdkNamesAppsArea(real.resolvedPath))
|
|
514
|
+
return true;
|
|
515
|
+
return sdkWithin(workspaceDir ? spacePath(workspaceDir, 'apps') : null, resolved);
|
|
516
|
+
}
|
|
517
|
+
function sdkHoldsAppsArea(resolved, workspaceDir) {
|
|
518
|
+
const real = workspaceDir ? resolveWithinRoot(workspaceDir, resolved, { quiet: true }) : null;
|
|
519
|
+
if (!real?.valid)
|
|
520
|
+
return false;
|
|
521
|
+
const name = basename(real.resolvedPath).split(':')[0].replace(/[. ]+$/, '').toLowerCase();
|
|
522
|
+
if (name !== SPACE_DIRNAME)
|
|
523
|
+
return false;
|
|
524
|
+
return (existsSync(join(real.resolvedPath, SPACE_LAYOUT.apps)) || existsSync(join(real.resolvedPath, SPACE_LAYOUT.appsRoster)));
|
|
525
|
+
}
|
|
526
|
+
function confineSdkWorkspaceScope(toolName, kind, ceiling) {
|
|
527
|
+
const allowed = kind === 'write' ? ceiling.workspaceWrite : ceiling.workspaceRead;
|
|
528
|
+
if (allowed)
|
|
529
|
+
return null;
|
|
530
|
+
const needed = kind === 'write' ? 'files:workspace:write' : 'files:workspace:read';
|
|
531
|
+
return {
|
|
532
|
+
reasoning: `workspace ${kind} via '${toolName}' exceeds the granted scopes [${ceiling.scopes.join(', ') || 'none'}] — requires ${needed}`,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
function confineSdkFileTarget(toolName, kind, rawPath, ceiling) {
|
|
536
|
+
const resolved = sdkResolveTarget(rawPath, ceiling.sandboxDir);
|
|
537
|
+
if (resolved === null) {
|
|
538
|
+
return { reasoning: `relative path '${rawPath}' cannot be confined without the run's sandbox root (fail closed)` };
|
|
539
|
+
}
|
|
540
|
+
if (SDK_SECRET_REFS.test(resolved)) {
|
|
541
|
+
return { reasoning: `'${rawPath}' targets the Minionry credential/settings store — off-limits to app runs regardless of scopes` };
|
|
542
|
+
}
|
|
543
|
+
if (sdkWithin(ceiling.sandboxDir, resolved))
|
|
544
|
+
return null;
|
|
545
|
+
if (sdkInAppsArea(resolved, ceiling.workspaceDir)) {
|
|
546
|
+
return { reasoning: `'${rawPath}' is inside the app-sandbox area but outside this app's own sandbox — denied even with workspace scopes` };
|
|
547
|
+
}
|
|
548
|
+
if (sdkHoldsAppsArea(resolved, ceiling.workspaceDir)) {
|
|
549
|
+
return {
|
|
550
|
+
reasoning: `'${rawPath}' is a Space's state directory holding the app-sandbox area — a search or listing rooted there reaches other apps' sandboxes, so it is denied even with workspace scopes`,
|
|
551
|
+
alternative: "Target a subdirectory of .minionry (e.g. its specs) instead of .minionry itself.",
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
if (!sdkWithin(ceiling.workspaceDir, resolved)) {
|
|
555
|
+
return {
|
|
556
|
+
reasoning: `'${rawPath}' is outside the workspace and outside the app sandbox — no grantable scope reaches it`,
|
|
557
|
+
alternative: 'App runs may touch their own sandbox, and the workspace when files:workspace:* scopes are granted.',
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
return confineSdkWorkspaceScope(toolName, kind, ceiling);
|
|
561
|
+
}
|
|
562
|
+
function classifySdkTool(operation, context) {
|
|
563
|
+
const name = (typeof context.toolName === 'string' && context.toolName
|
|
564
|
+
? context.toolName
|
|
565
|
+
: (operation.split(':')[0] ?? ''))
|
|
566
|
+
.trim()
|
|
567
|
+
.toLowerCase();
|
|
568
|
+
if (SDK_SHELL_TOOL_SET.has(name))
|
|
569
|
+
return { name, kind: 'shell' };
|
|
570
|
+
if (SDK_WRITE_TOOL_SET.has(name))
|
|
571
|
+
return { name, kind: 'write' };
|
|
572
|
+
if (SDK_READ_TOOL_SET.has(name))
|
|
573
|
+
return { name, kind: 'read' };
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
function applySdkScopeCeiling(operation, context, fin) {
|
|
577
|
+
if (context?.source !== 'sdk')
|
|
578
|
+
return null;
|
|
579
|
+
const tool = classifySdkTool(operation, context);
|
|
580
|
+
if (!tool)
|
|
581
|
+
return null;
|
|
582
|
+
const ceiling = parseSdkCeiling(context);
|
|
583
|
+
const input = typeof context.toolInput === 'object' && context.toolInput !== null
|
|
584
|
+
? context.toolInput
|
|
585
|
+
: {};
|
|
586
|
+
let violation;
|
|
587
|
+
if (tool.kind === 'shell') {
|
|
588
|
+
violation = confineSdkShell(tool.name, typeof input.command === 'string' ? input.command : operation, ceiling);
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
const rawPath = sdkTargetPath(input);
|
|
592
|
+
violation = rawPath ? confineSdkFileTarget(tool.name, tool.kind, rawPath, ceiling) : null;
|
|
593
|
+
}
|
|
594
|
+
if (!violation)
|
|
595
|
+
return null;
|
|
596
|
+
console.error(`[Bouncer] 🚫 SDK scope ceiling: ${violation.reasoning}`);
|
|
597
|
+
return fin.finalize({
|
|
598
|
+
decision: 'deny',
|
|
599
|
+
confidence: 98,
|
|
600
|
+
reasoning: `App-run scope ceiling (app '${ceiling.appId}'): ${violation.reasoning}`,
|
|
601
|
+
threatLevel: 'high',
|
|
602
|
+
...(violation.alternative ? { alternative: violation.alternative } : {}),
|
|
603
|
+
enforceable: true,
|
|
604
|
+
}, 'sdk-scope-ceiling',
|
|
605
|
+
{ skipCache: true });
|
|
606
|
+
}
|
|
607
|
+
const NOOP_FASTPATH_EXEMPT_PATTERNS = [/^git_/i, /^(mcp__minionry-browser__)?browser_/i];
|
|
608
|
+
function isEmptyParamNoop(operation, toolInput) {
|
|
609
|
+
if (!toolInput || typeof toolInput !== 'object' || Object.keys(toolInput).length > 0)
|
|
610
|
+
return false;
|
|
611
|
+
return !NOOP_FASTPATH_EXEMPT_PATTERNS.some((pattern) => pattern.test(operation));
|
|
612
|
+
}
|
|
613
|
+
export async function reviewOperation(incoming) {
|
|
614
|
+
const { logBouncerDecision } = await import('./security-audit.js');
|
|
615
|
+
const startTime = performance.now();
|
|
616
|
+
const request = redactRequestBeforeReview(incoming);
|
|
617
|
+
const { operation: rawOperation } = request;
|
|
618
|
+
const operation = normalizeOperation(rawOperation);
|
|
619
|
+
const fin = new DecisionFinalizer(operation, startTime, request.context, logBouncerDecision);
|
|
620
|
+
const ceilingDenial = applySdkScopeCeiling(operation, request.context, fin);
|
|
621
|
+
if (ceilingDenial)
|
|
622
|
+
return ceilingDenial;
|
|
623
|
+
const cached = await lookupCachedDecision(operation, request.context);
|
|
624
|
+
if (cached)
|
|
625
|
+
return cached;
|
|
626
|
+
console.error('[Bouncer] Analyzing operation...');
|
|
627
|
+
console.error(`[Bouncer] Operation: ${operation}`);
|
|
628
|
+
if (request.context?.userRequest) {
|
|
629
|
+
console.error(`[Bouncer] User request: ${request.context.userRequest}`);
|
|
630
|
+
}
|
|
631
|
+
const stages = [
|
|
632
|
+
() => {
|
|
633
|
+
if (isEmptyParamNoop(operation, request.context?.toolInput)) {
|
|
634
|
+
console.error('[Bouncer] ⚡ Fast path: Empty tool parameters (no-op)');
|
|
635
|
+
return fin.finalize({ decision: 'allow', confidence: 95, reasoning: 'Empty tool parameters - operation is a no-op with no side effects.', threatLevel: 'low' }, 'pattern-noop', { skipAnalytics: true, skipLog: true });
|
|
636
|
+
}
|
|
637
|
+
return null;
|
|
638
|
+
},
|
|
639
|
+
() => reviewBrowserTool(operation, request.context, fin),
|
|
640
|
+
() => {
|
|
641
|
+
if (/^(mcp__minionry-browser__)?browser_navigate:/i.test(operation)) {
|
|
642
|
+
const enriched = enrichBrowserContext(request, operation);
|
|
643
|
+
return runHaikuAnalysis(enriched, operation, startTime, fin);
|
|
644
|
+
}
|
|
645
|
+
return null;
|
|
646
|
+
},
|
|
647
|
+
() => {
|
|
648
|
+
const criticalThreat = matchesPattern(operation, CRITICAL_THREATS);
|
|
649
|
+
if (criticalThreat) {
|
|
650
|
+
console.error('[Bouncer] ⚡ Fast path: CRITICAL THREAT detected');
|
|
651
|
+
return fin.finalize({
|
|
652
|
+
decision: 'deny', confidence: 99, reasoning: `🚨 CRITICAL THREAT: ${criticalThreat.reason}`, threatLevel: 'critical',
|
|
653
|
+
alternative: 'This operation should never be performed. If you need to accomplish a specific task, please describe your goal and I can suggest safe alternatives.',
|
|
654
|
+
enforceable: true,
|
|
655
|
+
}, 'pattern-critical');
|
|
656
|
+
}
|
|
657
|
+
return null;
|
|
658
|
+
},
|
|
659
|
+
() => {
|
|
660
|
+
const activeProfile = getActiveDepartmentProfile();
|
|
661
|
+
const profileDenied = departmentDenies(operation, activeProfile);
|
|
662
|
+
const needsReview = requiresAIReview(operation) || !!profileDenied;
|
|
663
|
+
if (needsReview)
|
|
664
|
+
return null;
|
|
665
|
+
const profileAllowed = departmentAllows(operation, activeProfile);
|
|
666
|
+
if (profileAllowed && activeProfile) {
|
|
667
|
+
console.error(`[Bouncer] ⚡ Fast path: ${activeProfile.label} department profile approved`);
|
|
668
|
+
return fin.finalize({
|
|
669
|
+
decision: 'allow',
|
|
670
|
+
confidence: 90,
|
|
671
|
+
reasoning: `Allowed by ${activeProfile.label} department profile: ${profileAllowed.reason}`,
|
|
672
|
+
threatLevel: 'low',
|
|
673
|
+
}, 'pattern-department');
|
|
674
|
+
}
|
|
675
|
+
const isSafe = matchesPattern(operation, SAFE_OPERATIONS);
|
|
676
|
+
if (untrustedModeRequiresReview(!!isSafe)) {
|
|
677
|
+
console.error('[Bouncer] ⚡ Untrusted-prompt mode: no safe-pattern match — escalating to AI review instead of pattern-default allow');
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
console.error(`[Bouncer] ⚡ Fast path: ${isSafe ? 'Safe operation approved' : 'No concerning patterns, allowing'}`);
|
|
681
|
+
return fin.finalize({
|
|
682
|
+
decision: 'allow',
|
|
683
|
+
confidence: isSafe ? 95 : 80,
|
|
684
|
+
reasoning: isSafe
|
|
685
|
+
? 'Operation matches known-safe patterns. No security concerns detected.'
|
|
686
|
+
: 'Operation appears safe based on pattern analysis. No obvious threats detected.',
|
|
687
|
+
threatLevel: 'low'
|
|
688
|
+
}, isSafe ? 'pattern-safe' : 'pattern-default');
|
|
689
|
+
},
|
|
690
|
+
];
|
|
691
|
+
for (const stage of stages) {
|
|
692
|
+
const decision = stage();
|
|
693
|
+
if (decision)
|
|
694
|
+
return decision;
|
|
695
|
+
}
|
|
696
|
+
const enrichedRequest = enrichBrowserContext(request, operation);
|
|
697
|
+
return runHaikuAnalysis(enrichedRequest, operation, startTime, fin);
|
|
698
|
+
}
|
|
699
|
+
function enrichBrowserContext(request, operation) {
|
|
700
|
+
const bare = operation.replace(BROWSER_MCP_PREFIX_RE, '');
|
|
701
|
+
if (!/^browser_/i.test(bare))
|
|
702
|
+
return request;
|
|
703
|
+
if (/^browser_(evaluate|run_code_unsafe):/i.test(bare))
|
|
704
|
+
return request;
|
|
705
|
+
const navUrl = extractBrowserNavigateUrl(bare);
|
|
706
|
+
const allowlist = resolveBrowserAllowlist(request.context);
|
|
707
|
+
return {
|
|
708
|
+
...request,
|
|
709
|
+
context: {
|
|
710
|
+
...request.context,
|
|
711
|
+
browserAllowlist: allowlist,
|
|
712
|
+
...(navUrl ? { browserNavigateUrl: navUrl } : {}),
|
|
713
|
+
},
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
export { classifyRisk as classifyOperationRisk } from './security-patterns.js';
|
|
717
|
+
export function formatOperationForReview(toolName, input) {
|
|
718
|
+
const getFilePath = (inp) => inp.file_path ?? inp.filePath ?? inp.path;
|
|
719
|
+
const lowered = toolName.toLowerCase();
|
|
720
|
+
if (lowered === 'bash' && typeof input.command === 'string' && input.command) {
|
|
721
|
+
return `${toolName}: ${input.command}`;
|
|
722
|
+
}
|
|
723
|
+
if (['write', 'edit', 'read'].includes(lowered)) {
|
|
724
|
+
const filePath = getFilePath(input);
|
|
725
|
+
return typeof filePath === 'string' && filePath
|
|
726
|
+
? `${toolName}: ${filePath}`
|
|
727
|
+
: `${toolName}: ${JSON.stringify(input)}`;
|
|
728
|
+
}
|
|
729
|
+
return `${toolName}: ${JSON.stringify(input)}`;
|
|
730
|
+
}
|
|
731
|
+
export async function reviewEnginePermission(request) {
|
|
732
|
+
const operation = formatOperationForReview(request.toolName, request.input);
|
|
733
|
+
return reviewOperation({
|
|
734
|
+
operation,
|
|
735
|
+
context: {
|
|
736
|
+
...request.context,
|
|
737
|
+
toolName: request.toolName,
|
|
738
|
+
toolInput: request.input,
|
|
739
|
+
},
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
export function formatDenialMessage(decision) {
|
|
743
|
+
return `🚫 ${decision.reasoning}${decision.alternative ? `\n\nAlternative: ${decision.alternative}` : ''}`;
|
|
744
|
+
}
|