@vellumai/assistant 0.3.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/.dockerignore +27 -0
- package/.env.example +22 -0
- package/Dockerfile +99 -0
- package/Dockerfile.sandbox +5 -0
- package/README.md +248 -0
- package/bun.lock +1723 -0
- package/bunfig.toml +2 -0
- package/docs/skills.md +158 -0
- package/drizzle/0000_dizzy_maggott.sql +301 -0
- package/drizzle/meta/0000_snapshot.json +1999 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +7 -0
- package/eslint.config.mjs +17 -0
- package/hook-templates/debug-prompt-logger/hook.json +7 -0
- package/hook-templates/debug-prompt-logger/run.sh +68 -0
- package/knip.json +9 -0
- package/package.json +70 -0
- package/scripts/capture-x-graphql.ts +545 -0
- package/scripts/ipc/check-contract-inventory.ts +104 -0
- package/scripts/ipc/check-swift-decoder-drift.ts +166 -0
- package/scripts/ipc/generate-swift.ts +492 -0
- package/scripts/test-filesystem-tools.sh +48 -0
- package/scripts/test.sh +127 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +2485 -0
- package/src/__tests__/account-registry.test.ts +245 -0
- package/src/__tests__/active-skill-tools.test.ts +378 -0
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/agent-loop-thinking.test.ts +81 -0
- package/src/__tests__/agent-loop.test.ts +1135 -0
- package/src/__tests__/anthropic-provider.test.ts +778 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +290 -0
- package/src/__tests__/app-bundler.test.ts +292 -0
- package/src/__tests__/app-executors.test.ts +613 -0
- package/src/__tests__/app-git-history.test.ts +176 -0
- package/src/__tests__/app-git-service.test.ts +169 -0
- package/src/__tests__/app-open-proxy.test.ts +62 -0
- package/src/__tests__/asset-materialize-tool.test.ts +452 -0
- package/src/__tests__/asset-search-tool.test.ts +477 -0
- package/src/__tests__/assistant-attachment-directive.test.ts +401 -0
- package/src/__tests__/assistant-attachments.test.ts +437 -0
- package/src/__tests__/assistant-event-hub.test.ts +226 -0
- package/src/__tests__/assistant-event.test.ts +123 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +315 -0
- package/src/__tests__/attachments-store.test.ts +476 -0
- package/src/__tests__/attachments.test.ts +134 -0
- package/src/__tests__/audit-log-rotation.test.ts +154 -0
- package/src/__tests__/browser-fill-credential.test.ts +309 -0
- package/src/__tests__/browser-manager.test.ts +203 -0
- package/src/__tests__/browser-runtime-check.test.ts +55 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +68 -0
- package/src/__tests__/browser-skill-endstate.test.ts +195 -0
- package/src/__tests__/bundle-scanner.test.ts +313 -0
- package/src/__tests__/call-bridge.test.ts +517 -0
- package/src/__tests__/call-constants.test.ts +40 -0
- package/src/__tests__/call-domain.test.ts +163 -0
- package/src/__tests__/call-orchestrator.test.ts +625 -0
- package/src/__tests__/call-recovery.test.ts +518 -0
- package/src/__tests__/call-routes-http.test.ts +699 -0
- package/src/__tests__/call-state-machine.test.ts +143 -0
- package/src/__tests__/call-state.test.ts +174 -0
- package/src/__tests__/call-store.test.ts +691 -0
- package/src/__tests__/channel-approval-routes.test.ts +2356 -0
- package/src/__tests__/channel-approval.test.ts +299 -0
- package/src/__tests__/channel-approvals.test.ts +521 -0
- package/src/__tests__/channel-delivery-store.test.ts +447 -0
- package/src/__tests__/channel-guardian.test.ts +1005 -0
- package/src/__tests__/checker.test.ts +3519 -0
- package/src/__tests__/clarification-resolver.test.ts +159 -0
- package/src/__tests__/classifier.test.ts +67 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +127 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +88 -0
- package/src/__tests__/cli-discover.test.ts +85 -0
- package/src/__tests__/cli.test.ts +26 -0
- package/src/__tests__/clipboard.test.ts +80 -0
- package/src/__tests__/commit-guarantee.test.ts +335 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
- package/src/__tests__/compaction.benchmark.test.ts +176 -0
- package/src/__tests__/computer-use-session-compaction.test.ts +132 -0
- package/src/__tests__/computer-use-session-lifecycle.test.ts +293 -0
- package/src/__tests__/computer-use-session-working-dir.test.ts +117 -0
- package/src/__tests__/computer-use-skill-baseline.test.ts +74 -0
- package/src/__tests__/computer-use-skill-endstate.test.ts +89 -0
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +217 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +107 -0
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +54 -0
- package/src/__tests__/computer-use-tools.test.ts +250 -0
- package/src/__tests__/config-schema.test.ts +1462 -0
- package/src/__tests__/conflict-intent-tokenization.test.ts +141 -0
- package/src/__tests__/conflict-policy.test.ts +121 -0
- package/src/__tests__/conflict-store.test.ts +332 -0
- package/src/__tests__/connection-policy.test.ts +102 -0
- package/src/__tests__/contacts-tools.test.ts +331 -0
- package/src/__tests__/context-memory-e2e.test.ts +434 -0
- package/src/__tests__/context-token-estimator.test.ts +135 -0
- package/src/__tests__/context-window-manager.test.ts +376 -0
- package/src/__tests__/contradiction-checker.test.ts +314 -0
- package/src/__tests__/conversation-store.test.ts +612 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +517 -0
- package/src/__tests__/credential-broker-server-use.test.ts +554 -0
- package/src/__tests__/credential-broker.test.ts +167 -0
- package/src/__tests__/credential-host-pattern-match.test.ts +104 -0
- package/src/__tests__/credential-metadata-store.test.ts +779 -0
- package/src/__tests__/credential-policy-validate.test.ts +121 -0
- package/src/__tests__/credential-resolve.test.ts +328 -0
- package/src/__tests__/credential-security-e2e.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +583 -0
- package/src/__tests__/credential-selection.test.ts +354 -0
- package/src/__tests__/credential-vault-unit.test.ts +780 -0
- package/src/__tests__/credential-vault.test.ts +852 -0
- package/src/__tests__/daemon-assistant-events.test.ts +164 -0
- package/src/__tests__/daemon-server-session-init.test.ts +522 -0
- package/src/__tests__/date-context.test.ts +373 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
- package/src/__tests__/delete-managed-skill-tool.test.ts +97 -0
- package/src/__tests__/diff.test.ts +121 -0
- package/src/__tests__/domain-normalize.test.ts +112 -0
- package/src/__tests__/domain-policy.test.ts +124 -0
- package/src/__tests__/doordash-client.test.ts +186 -0
- package/src/__tests__/doordash-session.test.ts +152 -0
- package/src/__tests__/dynamic-page-surface.test.ts +91 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +132 -0
- package/src/__tests__/edit-engine.test.ts +180 -0
- package/src/__tests__/elevenlabs-client.test.ts +271 -0
- package/src/__tests__/email-cli.test.ts +283 -0
- package/src/__tests__/encrypted-store.test.ts +332 -0
- package/src/__tests__/entity-extractor.test.ts +190 -0
- package/src/__tests__/ephemeral-permissions.test.ts +362 -0
- package/src/__tests__/evaluate-typescript-tool.test.ts +286 -0
- package/src/__tests__/event-bus.test.ts +222 -0
- package/src/__tests__/file-edit-tool.test.ts +122 -0
- package/src/__tests__/file-ops-service.test.ts +330 -0
- package/src/__tests__/file-read-tool.test.ts +75 -0
- package/src/__tests__/file-write-tool.test.ts +113 -0
- package/src/__tests__/filesystem-tools.test.ts +579 -0
- package/src/__tests__/fixtures/credential-security-fixtures.ts +181 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +126 -0
- package/src/__tests__/fixtures/mock-signup-server.ts +387 -0
- package/src/__tests__/fixtures/proxy-fixtures.ts +147 -0
- package/src/__tests__/followup-tools.test.ts +303 -0
- package/src/__tests__/forbidden-legacy-symbols.test.ts +71 -0
- package/src/__tests__/fuzzy-match-property.test.ts +216 -0
- package/src/__tests__/fuzzy-match.test.ts +138 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +631 -0
- package/src/__tests__/gemini-image-service.test.ts +261 -0
- package/src/__tests__/gemini-provider.test.ts +651 -0
- package/src/__tests__/get-weather.test.ts +318 -0
- package/src/__tests__/gmail-integration.test.ts +73 -0
- package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +202 -0
- package/src/__tests__/handlers-cu-observation-blob.test.ts +352 -0
- package/src/__tests__/handlers-ipc-blob-probe.test.ts +191 -0
- package/src/__tests__/handlers-slack-config.test.ts +200 -0
- package/src/__tests__/handlers-task-submit-slash.test.ts +38 -0
- package/src/__tests__/handlers-telegram-config.test.ts +968 -0
- package/src/__tests__/handlers-twilio-config.test.ts +659 -0
- package/src/__tests__/handlers-twitter-config.test.ts +858 -0
- package/src/__tests__/headless-browser-interactions.test.ts +536 -0
- package/src/__tests__/headless-browser-navigate.test.ts +211 -0
- package/src/__tests__/headless-browser-read-tools.test.ts +261 -0
- package/src/__tests__/headless-browser-snapshot.test.ts +185 -0
- package/src/__tests__/history-repair-observability.test.ts +56 -0
- package/src/__tests__/history-repair.test.ts +510 -0
- package/src/__tests__/home-base-bootstrap.test.ts +82 -0
- package/src/__tests__/hooks-blocking.test.ts +128 -0
- package/src/__tests__/hooks-cli.test.ts +144 -0
- package/src/__tests__/hooks-config.test.ts +93 -0
- package/src/__tests__/hooks-discovery.test.ts +199 -0
- package/src/__tests__/hooks-integration.test.ts +189 -0
- package/src/__tests__/hooks-manager.test.ts +187 -0
- package/src/__tests__/hooks-runner.test.ts +182 -0
- package/src/__tests__/hooks-settings.test.ts +154 -0
- package/src/__tests__/hooks-templates.test.ts +137 -0
- package/src/__tests__/hooks-ts-runner.test.ts +125 -0
- package/src/__tests__/hooks-watch.test.ts +100 -0
- package/src/__tests__/host-file-edit-tool.test.ts +228 -0
- package/src/__tests__/host-file-read-tool.test.ts +123 -0
- package/src/__tests__/host-file-write-tool.test.ts +136 -0
- package/src/__tests__/host-shell-tool.test.ts +562 -0
- package/src/__tests__/ingress-reconcile.test.ts +581 -0
- package/src/__tests__/ingress-url-consistency.test.ts +214 -0
- package/src/__tests__/intent-routing.test.ts +259 -0
- package/src/__tests__/ipc-blob-store.test.ts +315 -0
- package/src/__tests__/ipc-contract-inventory.test.ts +54 -0
- package/src/__tests__/ipc-contract.test.ts +74 -0
- package/src/__tests__/ipc-protocol.test.ts +113 -0
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
- package/src/__tests__/ipc-snapshot.test.ts +1769 -0
- package/src/__tests__/ipc-validate.test.ts +407 -0
- package/src/__tests__/key-migration.test.ts +206 -0
- package/src/__tests__/keychain.test.ts +258 -0
- package/src/__tests__/llm-usage-store.test.ts +221 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +257 -0
- package/src/__tests__/managed-store.test.ts +608 -0
- package/src/__tests__/media-generate-image.test.ts +238 -0
- package/src/__tests__/media-reuse-story.e2e.test.ts +676 -0
- package/src/__tests__/media-visibility-policy.test.ts +141 -0
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +235 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +481 -0
- package/src/__tests__/memory-query-builder.test.ts +59 -0
- package/src/__tests__/memory-recall-quality.test.ts +846 -0
- package/src/__tests__/memory-regressions.experimental.test.ts +538 -0
- package/src/__tests__/memory-regressions.test.ts +4435 -0
- package/src/__tests__/memory-retrieval-budget.test.ts +49 -0
- package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
- package/src/__tests__/migration-cli-flows.test.ts +169 -0
- package/src/__tests__/migration-ordering.test.ts +249 -0
- package/src/__tests__/mock-signup-server.test.ts +528 -0
- package/src/__tests__/oauth-callback-registry.test.ts +92 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +285 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +176 -0
- package/src/__tests__/onboarding-template-contract.test.ts +58 -0
- package/src/__tests__/openai-provider.test.ts +753 -0
- package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
- package/src/__tests__/parser.test.ts +472 -0
- package/src/__tests__/path-classifier.test.ts +73 -0
- package/src/__tests__/path-policy.test.ts +435 -0
- package/src/__tests__/platform-move-helper.test.ts +99 -0
- package/src/__tests__/platform-socket-path.test.ts +52 -0
- package/src/__tests__/platform-workspace-migration.test.ts +1000 -0
- package/src/__tests__/platform.test.ts +131 -0
- package/src/__tests__/playbook-execution.test.ts +502 -0
- package/src/__tests__/playbook-tools.test.ts +340 -0
- package/src/__tests__/prebuilt-home-base-seed.test.ts +75 -0
- package/src/__tests__/pricing.test.ts +256 -0
- package/src/__tests__/profile-compiler.test.ts +374 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +342 -0
- package/src/__tests__/provider-registry-ollama.test.ts +16 -0
- package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
- package/src/__tests__/proxy-approval-callback.test.ts +601 -0
- package/src/__tests__/public-ingress-urls.test.ts +256 -0
- package/src/__tests__/qdrant-manager.test.ts +267 -0
- package/src/__tests__/ratelimit.test.ts +297 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +175 -0
- package/src/__tests__/recurrence-engine.test.ts +78 -0
- package/src/__tests__/recurrence-types.test.ts +79 -0
- package/src/__tests__/registry.test.ts +494 -0
- package/src/__tests__/relay-server.test.ts +688 -0
- package/src/__tests__/reminder-store.test.ts +223 -0
- package/src/__tests__/reminder.test.ts +229 -0
- package/src/__tests__/request-file-tool.test.ts +158 -0
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +227 -0
- package/src/__tests__/run-orchestrator.test.ts +425 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +189 -0
- package/src/__tests__/runtime-events-sse-parity.test.ts +343 -0
- package/src/__tests__/runtime-events-sse.test.ts +162 -0
- package/src/__tests__/runtime-runs-http.test.ts +438 -0
- package/src/__tests__/runtime-runs.test.ts +260 -0
- package/src/__tests__/sandbox-diagnostics.test.ts +408 -0
- package/src/__tests__/sandbox-host-parity.test.ts +950 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +253 -0
- package/src/__tests__/schedule-store.test.ts +484 -0
- package/src/__tests__/schedule-tools.test.ts +783 -0
- package/src/__tests__/scheduler-recurrence.test.ts +430 -0
- package/src/__tests__/script-proxy-certs.test.ts +90 -0
- package/src/__tests__/script-proxy-connect-tunnel.test.ts +177 -0
- package/src/__tests__/script-proxy-decision-trace.test.ts +156 -0
- package/src/__tests__/script-proxy-http-forwarder.test.ts +281 -0
- package/src/__tests__/script-proxy-injection-runtime.test.ts +401 -0
- package/src/__tests__/script-proxy-mitm-handler.test.ts +407 -0
- package/src/__tests__/script-proxy-policy-runtime.test.ts +287 -0
- package/src/__tests__/script-proxy-policy.test.ts +310 -0
- package/src/__tests__/script-proxy-rewrite-specificity.test.ts +135 -0
- package/src/__tests__/script-proxy-router.test.ts +180 -0
- package/src/__tests__/script-proxy-session-manager.test.ts +382 -0
- package/src/__tests__/script-proxy-session-runtime.test.ts +113 -0
- package/src/__tests__/secret-allowlist.test.ts +230 -0
- package/src/__tests__/secret-ingress-handler.test.ts +110 -0
- package/src/__tests__/secret-onetime-send.test.ts +130 -0
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +106 -0
- package/src/__tests__/secret-response-routing.test.ts +93 -0
- package/src/__tests__/secret-scanner-executor.test.ts +348 -0
- package/src/__tests__/secret-scanner.test.ts +900 -0
- package/src/__tests__/secure-keys.test.ts +323 -0
- package/src/__tests__/server-history-render.test.ts +431 -0
- package/src/__tests__/session-abort-tool-results.test.ts +240 -0
- package/src/__tests__/session-conflict-gate.test.ts +1136 -0
- package/src/__tests__/session-error.test.ts +369 -0
- package/src/__tests__/session-evictor.test.ts +188 -0
- package/src/__tests__/session-init.benchmark.test.ts +465 -0
- package/src/__tests__/session-load-history-repair.test.ts +222 -0
- package/src/__tests__/session-pre-run-repair.test.ts +213 -0
- package/src/__tests__/session-process-bridge.test.ts +242 -0
- package/src/__tests__/session-profile-injection.test.ts +444 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +306 -0
- package/src/__tests__/session-queue.test.ts +1535 -0
- package/src/__tests__/session-runtime-assembly.test.ts +476 -0
- package/src/__tests__/session-runtime-workspace.test.ts +183 -0
- package/src/__tests__/session-skill-tools.test.ts +2431 -0
- package/src/__tests__/session-slash-known.test.ts +368 -0
- package/src/__tests__/session-slash-queue.test.ts +288 -0
- package/src/__tests__/session-slash-unknown.test.ts +271 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +473 -0
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +140 -0
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +140 -0
- package/src/__tests__/session-undo.test.ts +75 -0
- package/src/__tests__/session-workspace-cache-state.test.ts +246 -0
- package/src/__tests__/session-workspace-injection.test.ts +327 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +240 -0
- package/src/__tests__/shared-filesystem-errors.test.ts +78 -0
- package/src/__tests__/shell-credential-ref.test.ts +187 -0
- package/src/__tests__/shell-identity.test.ts +256 -0
- package/src/__tests__/shell-parser-fuzz.test.ts +544 -0
- package/src/__tests__/shell-parser-property.test.ts +433 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +272 -0
- package/src/__tests__/signup-e2e.test.ts +353 -0
- package/src/__tests__/size-guard.test.ts +117 -0
- package/src/__tests__/skill-include-graph.test.ts +303 -0
- package/src/__tests__/skill-load-tool.test.ts +409 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +338 -0
- package/src/__tests__/skill-script-runner-host.test.ts +489 -0
- package/src/__tests__/skill-script-runner-sandbox.test.ts +349 -0
- package/src/__tests__/skill-script-runner.test.ts +159 -0
- package/src/__tests__/skill-tool-factory.test.ts +252 -0
- package/src/__tests__/skill-tool-manifest.test.ts +658 -0
- package/src/__tests__/skill-version-hash.test.ts +182 -0
- package/src/__tests__/skills.test.ts +680 -0
- package/src/__tests__/slash-commands-catalog.test.ts +86 -0
- package/src/__tests__/slash-commands-parser.test.ts +119 -0
- package/src/__tests__/slash-commands-resolver.test.ts +193 -0
- package/src/__tests__/slash-commands-rewrite.test.ts +39 -0
- package/src/__tests__/speaker-identification.test.ts +52 -0
- package/src/__tests__/starter-bundle.test.ts +136 -0
- package/src/__tests__/starter-task-flow.test.ts +143 -0
- package/src/__tests__/subagent-manager-notify.test.ts +404 -0
- package/src/__tests__/subagent-tools.test.ts +801 -0
- package/src/__tests__/subagent-types.test.ts +78 -0
- package/src/__tests__/swarm-orchestrator.test.ts +428 -0
- package/src/__tests__/swarm-plan-validator.test.ts +330 -0
- package/src/__tests__/swarm-recursion.test.ts +165 -0
- package/src/__tests__/swarm-router-planner.test.ts +208 -0
- package/src/__tests__/swarm-session-integration.test.ts +274 -0
- package/src/__tests__/swarm-tool.test.ts +145 -0
- package/src/__tests__/swarm-worker-backend.test.ts +129 -0
- package/src/__tests__/swarm-worker-runner.test.ts +272 -0
- package/src/__tests__/system-prompt.test.ts +439 -0
- package/src/__tests__/task-compiler.test.ts +284 -0
- package/src/__tests__/task-management-tools.test.ts +936 -0
- package/src/__tests__/task-runner.test.ts +216 -0
- package/src/__tests__/task-scheduler.test.ts +217 -0
- package/src/__tests__/task-tools.test.ts +595 -0
- package/src/__tests__/terminal-sandbox-docker.test.ts +1064 -0
- package/src/__tests__/terminal-sandbox.integration.test.ts +178 -0
- package/src/__tests__/terminal-sandbox.test.ts +202 -0
- package/src/__tests__/terminal-tools.test.ts +840 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +90 -0
- package/src/__tests__/test-support/computer-use-skill-harness.ts +45 -0
- package/src/__tests__/tool-audit-listener.test.ts +113 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +253 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +516 -0
- package/src/__tests__/tool-executor-redaction.test.ts +289 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +301 -0
- package/src/__tests__/tool-executor.test.ts +1989 -0
- package/src/__tests__/tool-metrics-listener.test.ts +225 -0
- package/src/__tests__/tool-notification-listener.test.ts +49 -0
- package/src/__tests__/tool-permission-simulate-handler.test.ts +336 -0
- package/src/__tests__/tool-policy.test.ts +54 -0
- package/src/__tests__/tool-profiling-listener.test.ts +268 -0
- package/src/__tests__/tool-result-truncation.test.ts +217 -0
- package/src/__tests__/tool-trace-listener.test.ts +226 -0
- package/src/__tests__/top-level-renderer.test.ts +121 -0
- package/src/__tests__/top-level-scanner.test.ts +141 -0
- package/src/__tests__/trace-emitter.test.ts +173 -0
- package/src/__tests__/trust-store.test.ts +1605 -0
- package/src/__tests__/turn-commit.test.ts +554 -0
- package/src/__tests__/twilio-provider.test.ts +329 -0
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +375 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +127 -0
- package/src/__tests__/twilio-routes.test.ts +577 -0
- package/src/__tests__/twitter-auth-handler.test.ts +667 -0
- package/src/__tests__/twitter-cli-error-shaping.test.ts +208 -0
- package/src/__tests__/twitter-cli-routing.test.ts +252 -0
- package/src/__tests__/twitter-oauth-client.test.ts +209 -0
- package/src/__tests__/url-safety.test.ts +418 -0
- package/src/__tests__/view-image-tool.test.ts +217 -0
- package/src/__tests__/weather-skill-regression.test.ts +225 -0
- package/src/__tests__/web-fetch.test.ts +869 -0
- package/src/__tests__/web-search.test.ts +584 -0
- package/src/__tests__/workspace-git-service.test.ts +1153 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +486 -0
- package/src/__tests__/workspace-lifecycle.test.ts +292 -0
- package/src/__tests__/workspace-policy.test.ts +213 -0
- package/src/agent/attachments.ts +35 -0
- package/src/agent/loop.ts +500 -0
- package/src/agent/message-types.ts +17 -0
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/autonomy/autonomy-resolver.ts +60 -0
- package/src/autonomy/autonomy-store.ts +122 -0
- package/src/autonomy/disposition-mapper.ts +31 -0
- package/src/autonomy/index.ts +11 -0
- package/src/autonomy/types.ts +39 -0
- package/src/bundler/app-bundler.ts +295 -0
- package/src/bundler/bundle-scanner.ts +535 -0
- package/src/bundler/bundle-signer.ts +124 -0
- package/src/bundler/manifest.ts +21 -0
- package/src/bundler/signature-verifier.ts +184 -0
- package/src/calls/call-bridge.ts +168 -0
- package/src/calls/call-constants.ts +48 -0
- package/src/calls/call-domain.ts +430 -0
- package/src/calls/call-orchestrator.ts +498 -0
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-state.ts +87 -0
- package/src/calls/call-store.ts +422 -0
- package/src/calls/elevenlabs-client.ts +97 -0
- package/src/calls/elevenlabs-config.ts +31 -0
- package/src/calls/relay-server.ts +390 -0
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-config.ts +45 -0
- package/src/calls/twilio-provider.ts +263 -0
- package/src/calls/twilio-rest.ts +156 -0
- package/src/calls/twilio-routes.ts +311 -0
- package/src/calls/types.ts +39 -0
- package/src/calls/voice-provider.ts +14 -0
- package/src/calls/voice-quality.ts +114 -0
- package/src/cli/autonomy.ts +188 -0
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/contacts.ts +149 -0
- package/src/cli/core-commands.ts +784 -0
- package/src/cli/doordash.ts +1055 -0
- package/src/cli/email-guardrails.ts +200 -0
- package/src/cli/email.ts +405 -0
- package/src/cli/ipc-client.ts +82 -0
- package/src/cli/main-screen.tsx +53 -0
- package/src/cli/map.ts +270 -0
- package/src/cli/twitter.ts +754 -0
- package/src/cli.ts +918 -0
- package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
- package/src/commands/cc-command-registry.ts +209 -0
- package/src/config/bundled-skills/.gitkeep +0 -0
- package/src/config/bundled-skills/agentmail/SKILL.md +128 -0
- package/src/config/bundled-skills/agentmail/icon.svg +21 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +1404 -0
- package/src/config/bundled-skills/app-builder/TOOLS.json +279 -0
- package/src/config/bundled-skills/app-builder/icon.svg +9 -0
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +15 -0
- package/src/config/bundled-skills/app-builder/tools/app-delete.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +11 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +18 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +11 -0
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-query.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +20 -0
- package/src/config/bundled-skills/browser/SKILL.md +28 -0
- package/src/config/bundled-skills/browser/TOOLS.json +234 -0
- package/src/config/bundled-skills/browser/tools/browser-click.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-close.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-type.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +9 -0
- package/src/config/bundled-skills/claude-code/SKILL.md +50 -0
- package/src/config/bundled-skills/claude-code/TOOLS.json +40 -0
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +9 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +17 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +326 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-done.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-double-click.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-drag.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-key.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-open-app.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-respond.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-right-click.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-run-applescript.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-scroll.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-type-text.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-wait.ts +9 -0
- package/src/config/bundled-skills/contacts/SKILL.md +39 -0
- package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +57 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +60 -0
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +66 -0
- package/src/config/bundled-skills/document/SKILL.md +26 -0
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
- package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
- package/src/config/bundled-skills/doordash/SKILL.md +163 -0
- package/src/config/bundled-skills/followups/SKILL.md +32 -0
- package/src/config/bundled-skills/followups/TOOLS.json +100 -0
- package/src/config/bundled-skills/followups/icon.svg +24 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
- package/src/config/bundled-skills/google-calendar/SKILL.md +51 -0
- package/src/config/bundled-skills/google-calendar/TOOLS.json +108 -0
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +165 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +21 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +42 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +13 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +30 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +41 -0
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +18 -0
- package/src/config/bundled-skills/google-calendar/types.ts +97 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +32 -0
- package/src/config/bundled-skills/image-studio/TOOLS.json +42 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +115 -0
- package/src/config/bundled-skills/macos-automation/SKILL.md +66 -0
- package/src/config/bundled-skills/messaging/SKILL.md +153 -0
- package/src/config/bundled-skills/messaging/TOOLS.json +357 -0
- package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +23 -0
- package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +23 -0
- package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +26 -0
- package/src/config/bundled-skills/messaging/tools/gmail-label.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/gmail-trash.ts +23 -0
- package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +84 -0
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +18 -0
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +125 -0
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +16 -0
- package/src/config/bundled-skills/messaging/tools/messaging-draft.ts +49 -0
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +21 -0
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +28 -0
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +32 -0
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +22 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +31 -0
- package/src/config/bundled-skills/messaging/tools/shared.ts +76 -0
- package/src/config/bundled-skills/messaging/tools/slack-add-reaction.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/slack-leave-channel.ts +23 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +533 -0
- package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
- package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +98 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +54 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +76 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +113 -0
- package/src/config/bundled-skills/public-ingress/SKILL.md +200 -0
- package/src/config/bundled-skills/reminder/SKILL.md +20 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
- package/src/config/bundled-skills/schedule/SKILL.md +74 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
- package/src/config/bundled-skills/self-upgrade/SKILL.md +68 -0
- package/src/config/bundled-skills/start-the-day/SKILL.md +70 -0
- package/src/config/bundled-skills/start-the-day/icon.svg +13 -0
- package/src/config/bundled-skills/subagent/SKILL.md +25 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
- package/src/config/bundled-skills/tasks/SKILL.md +28 -0
- package/src/config/bundled-skills/tasks/TOOLS.json +281 -0
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +25 -0
- package/src/config/bundled-skills/transcribe/TOOLS.json +32 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +370 -0
- package/src/config/bundled-skills/twitter/SKILL.md +220 -0
- package/src/config/bundled-skills/watcher/SKILL.md +27 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
- package/src/config/bundled-skills/weather/SKILL.md +37 -0
- package/src/config/bundled-skills/weather/TOOLS.json +32 -0
- package/src/config/bundled-skills/weather/icon.svg +24 -0
- package/src/config/bundled-skills/weather/tools/get-weather.ts +9 -0
- package/src/config/computer-use-prompt.ts +97 -0
- package/src/config/defaults.ts +263 -0
- package/src/config/loader.ts +339 -0
- package/src/config/schema.ts +1436 -0
- package/src/config/skill-state.ts +95 -0
- package/src/config/skills.ts +972 -0
- package/src/config/system-prompt.ts +675 -0
- package/src/config/templates/BOOTSTRAP.md +70 -0
- package/src/config/templates/IDENTITY.md +25 -0
- package/src/config/templates/LOOKS.md +25 -0
- package/src/config/templates/SOUL.md +37 -0
- package/src/config/templates/USER.md +19 -0
- package/src/config/types.ts +42 -0
- package/src/config/vellum-skills/chatgpt-import/SKILL.md +24 -0
- package/src/config/vellum-skills/chatgpt-import/TOOLS.json +23 -0
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +284 -0
- package/src/config/vellum-skills/deploy-fullstack-vercel/SKILL.md +179 -0
- package/src/config/vellum-skills/document-writer/SKILL.md +195 -0
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +199 -0
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +153 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +143 -0
- package/src/config/vellum-skills/twilio-setup/SKILL.md +213 -0
- package/src/contacts/contact-store.ts +410 -0
- package/src/contacts/index.ts +11 -0
- package/src/contacts/types.ts +28 -0
- package/src/context/token-estimator.ts +108 -0
- package/src/context/tool-result-truncation.ts +128 -0
- package/src/context/window-manager.ts +531 -0
- package/src/daemon/assistant-attachments.ts +691 -0
- package/src/daemon/classifier.ts +110 -0
- package/src/daemon/computer-use-session.ts +903 -0
- package/src/daemon/connection-policy.ts +41 -0
- package/src/daemon/date-context.ts +136 -0
- package/src/daemon/handlers/apps.ts +530 -0
- package/src/daemon/handlers/browser.ts +54 -0
- package/src/daemon/handlers/computer-use.ts +187 -0
- package/src/daemon/handlers/config.ts +1517 -0
- package/src/daemon/handlers/diagnostics.ts +338 -0
- package/src/daemon/handlers/documents.ts +173 -0
- package/src/daemon/handlers/home-base.ts +78 -0
- package/src/daemon/handlers/identity.ts +127 -0
- package/src/daemon/handlers/index.ts +129 -0
- package/src/daemon/handlers/misc.ts +331 -0
- package/src/daemon/handlers/open-bundle-handler.ts +80 -0
- package/src/daemon/handlers/publish.ts +187 -0
- package/src/daemon/handlers/sessions.ts +555 -0
- package/src/daemon/handlers/shared.ts +570 -0
- package/src/daemon/handlers/signing.ts +37 -0
- package/src/daemon/handlers/skills.ts +486 -0
- package/src/daemon/handlers/subagents.ts +210 -0
- package/src/daemon/handlers/twitter-auth.ts +198 -0
- package/src/daemon/handlers/work-items.ts +632 -0
- package/src/daemon/handlers/workspace-files.ts +75 -0
- package/src/daemon/handlers.ts +17 -0
- package/src/daemon/history-repair.ts +214 -0
- package/src/daemon/ipc-blob-store.ts +231 -0
- package/src/daemon/ipc-contract-inventory.json +495 -0
- package/src/daemon/ipc-contract-inventory.ts +126 -0
- package/src/daemon/ipc-contract.ts +2551 -0
- package/src/daemon/ipc-protocol.ts +75 -0
- package/src/daemon/ipc-validate.ts +188 -0
- package/src/daemon/lifecycle.ts +582 -0
- package/src/daemon/main.ts +21 -0
- package/src/daemon/media-visibility-policy.ts +57 -0
- package/src/daemon/ride-shotgun-handler.ts +309 -0
- package/src/daemon/server.ts +1215 -0
- package/src/daemon/session-agent-loop.ts +922 -0
- package/src/daemon/session-attachments.ts +196 -0
- package/src/daemon/session-conflict-gate.ts +184 -0
- package/src/daemon/session-dynamic-profile.ts +63 -0
- package/src/daemon/session-error.ts +290 -0
- package/src/daemon/session-evictor.ts +196 -0
- package/src/daemon/session-history.ts +437 -0
- package/src/daemon/session-lifecycle.ts +147 -0
- package/src/daemon/session-media-retry.ts +147 -0
- package/src/daemon/session-memory.ts +212 -0
- package/src/daemon/session-messaging.ts +145 -0
- package/src/daemon/session-notifiers.ts +193 -0
- package/src/daemon/session-process.ts +323 -0
- package/src/daemon/session-queue-manager.ts +82 -0
- package/src/daemon/session-runtime-assembly.ts +447 -0
- package/src/daemon/session-skill-tools.ts +356 -0
- package/src/daemon/session-slash.ts +305 -0
- package/src/daemon/session-surfaces.ts +702 -0
- package/src/daemon/session-tool-setup.ts +523 -0
- package/src/daemon/session-usage.ts +72 -0
- package/src/daemon/session-workspace.ts +19 -0
- package/src/daemon/session.ts +400 -0
- package/src/daemon/tls-certs.ts +189 -0
- package/src/daemon/trace-emitter.ts +82 -0
- package/src/daemon/video-thumbnail.ts +62 -0
- package/src/daemon/watch-handler.ts +274 -0
- package/src/doordash/client.ts +999 -0
- package/src/doordash/queries.ts +1311 -0
- package/src/doordash/query-extractor.ts +93 -0
- package/src/doordash/session.ts +82 -0
- package/src/email/provider.ts +117 -0
- package/src/email/providers/agentmail.ts +317 -0
- package/src/email/providers/index.ts +58 -0
- package/src/email/service.ts +303 -0
- package/src/email/types.ts +126 -0
- package/src/events/bus.ts +157 -0
- package/src/events/domain-events.ts +83 -0
- package/src/events/index.ts +18 -0
- package/src/events/tool-audit-listener.ts +80 -0
- package/src/events/tool-domain-event-publisher.ts +111 -0
- package/src/events/tool-metrics-listener.ts +159 -0
- package/src/events/tool-notification-listener.ts +17 -0
- package/src/events/tool-profiling-listener.ts +158 -0
- package/src/events/tool-trace-listener.ts +75 -0
- package/src/export/formatter.ts +98 -0
- package/src/followups/followup-store.ts +168 -0
- package/src/followups/index.ts +10 -0
- package/src/followups/types.ts +29 -0
- package/src/gallery/default-gallery.ts +795 -0
- package/src/gallery/gallery-manifest.ts +24 -0
- package/src/home-base/app-link-store.ts +82 -0
- package/src/home-base/bootstrap.ts +68 -0
- package/src/home-base/prebuilt/index.html +662 -0
- package/src/home-base/prebuilt/seed-metadata.json +21 -0
- package/src/home-base/prebuilt/seed.ts +112 -0
- package/src/home-base/prebuilt-home-base-updater.ts +30 -0
- package/src/hooks/cli.ts +163 -0
- package/src/hooks/config.ts +88 -0
- package/src/hooks/discovery.ts +110 -0
- package/src/hooks/manager.ts +124 -0
- package/src/hooks/runner.ts +123 -0
- package/src/hooks/templates.ts +52 -0
- package/src/hooks/types.ts +72 -0
- package/src/inbound/public-ingress-urls.ts +123 -0
- package/src/index.ts +81 -0
- package/src/instrument.ts +60 -0
- package/src/logfire.ts +99 -0
- package/src/media/gemini-image-service.ts +136 -0
- package/src/memory/account-store.ts +108 -0
- package/src/memory/admin.ts +211 -0
- package/src/memory/app-git-service.ts +295 -0
- package/src/memory/app-store.ts +577 -0
- package/src/memory/attachments-store.ts +397 -0
- package/src/memory/channel-delivery-store.ts +353 -0
- package/src/memory/channel-guardian-store.ts +669 -0
- package/src/memory/checkpoints.ts +52 -0
- package/src/memory/clarification-resolver.ts +298 -0
- package/src/memory/conflict-intent.ts +157 -0
- package/src/memory/conflict-policy.ts +73 -0
- package/src/memory/conflict-store.ts +350 -0
- package/src/memory/contradiction-checker.ts +358 -0
- package/src/memory/conversation-key-store.ts +122 -0
- package/src/memory/conversation-store.ts +470 -0
- package/src/memory/db.ts +1991 -0
- package/src/memory/embedding-backend.ts +229 -0
- package/src/memory/embedding-gemini.ts +52 -0
- package/src/memory/embedding-local.ts +65 -0
- package/src/memory/embedding-ollama.ts +55 -0
- package/src/memory/embedding-openai.ts +25 -0
- package/src/memory/entity-extractor.ts +474 -0
- package/src/memory/external-conversation-store.ts +234 -0
- package/src/memory/fingerprint.ts +20 -0
- package/src/memory/indexer.ts +156 -0
- package/src/memory/items-extractor.ts +461 -0
- package/src/memory/job-handlers/backfill.ts +139 -0
- package/src/memory/job-handlers/cleanup.ts +58 -0
- package/src/memory/job-handlers/conflict.ts +141 -0
- package/src/memory/job-handlers/embedding.ts +61 -0
- package/src/memory/job-handlers/extraction.ts +123 -0
- package/src/memory/job-handlers/index-maintenance.ts +54 -0
- package/src/memory/job-handlers/summarization.ts +286 -0
- package/src/memory/job-utils.ts +170 -0
- package/src/memory/jobs-store.ts +401 -0
- package/src/memory/jobs-worker.ts +313 -0
- package/src/memory/llm-request-log-store.ts +45 -0
- package/src/memory/llm-usage-store.ts +60 -0
- package/src/memory/message-content.ts +54 -0
- package/src/memory/profile-compiler.ts +160 -0
- package/src/memory/published-pages-store.ts +137 -0
- package/src/memory/qdrant-client.ts +366 -0
- package/src/memory/qdrant-manager.ts +242 -0
- package/src/memory/query-builder.ts +45 -0
- package/src/memory/retrieval-budget.ts +30 -0
- package/src/memory/retriever.ts +653 -0
- package/src/memory/runs-store.ts +305 -0
- package/src/memory/schema.ts +677 -0
- package/src/memory/search/entity.ts +298 -0
- package/src/memory/search/formatting.ts +207 -0
- package/src/memory/search/lexical.ts +227 -0
- package/src/memory/search/ranking.ts +401 -0
- package/src/memory/search/semantic.ts +121 -0
- package/src/memory/search/types.ts +137 -0
- package/src/memory/segmenter.ts +68 -0
- package/src/memory/shared-app-links-store.ts +138 -0
- package/src/memory/tool-usage-store.ts +62 -0
- package/src/messaging/activity-analyzer.ts +76 -0
- package/src/messaging/draft-store.ts +88 -0
- package/src/messaging/index.ts +3 -0
- package/src/messaging/provider-types.ts +80 -0
- package/src/messaging/provider.ts +52 -0
- package/src/messaging/providers/gmail/adapter.ts +193 -0
- package/src/messaging/providers/gmail/client.ts +204 -0
- package/src/messaging/providers/gmail/types.ts +90 -0
- package/src/messaging/providers/slack/adapter.ts +202 -0
- package/src/messaging/providers/slack/client.ts +198 -0
- package/src/messaging/providers/slack/types.ts +119 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +162 -0
- package/src/messaging/providers/telegram-bot/client.ts +104 -0
- package/src/messaging/providers/telegram-bot/types.ts +15 -0
- package/src/messaging/registry.ts +35 -0
- package/src/messaging/style-analyzer.ts +159 -0
- package/src/messaging/thread-summarizer.ts +306 -0
- package/src/messaging/triage-engine.ts +323 -0
- package/src/messaging/types.ts +55 -0
- package/src/permissions/checker.ts +640 -0
- package/src/permissions/defaults.ts +254 -0
- package/src/permissions/prompter.ts +98 -0
- package/src/permissions/secret-prompter.ts +114 -0
- package/src/permissions/shell-identity.ts +227 -0
- package/src/permissions/trust-store.ts +607 -0
- package/src/permissions/types.ts +43 -0
- package/src/permissions/workspace-policy.ts +114 -0
- package/src/playbooks/index.ts +2 -0
- package/src/playbooks/playbook-compiler.ts +90 -0
- package/src/playbooks/types.ts +55 -0
- package/src/providers/anthropic/client.ts +751 -0
- package/src/providers/failover.ts +129 -0
- package/src/providers/fireworks/client.ts +20 -0
- package/src/providers/gemini/client.ts +285 -0
- package/src/providers/ollama/client.ts +30 -0
- package/src/providers/openai/client.ts +337 -0
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/ratelimit.ts +93 -0
- package/src/providers/registry.ts +146 -0
- package/src/providers/retry.ts +81 -0
- package/src/providers/stream-timeout.ts +38 -0
- package/src/providers/types.ts +109 -0
- package/src/runtime/assistant-event-hub.ts +157 -0
- package/src/runtime/assistant-event.ts +82 -0
- package/src/runtime/channel-approval-parser.ts +60 -0
- package/src/runtime/channel-approval-types.ts +73 -0
- package/src/runtime/channel-approvals.ts +206 -0
- package/src/runtime/channel-guardian-service.ts +212 -0
- package/src/runtime/gateway-client.ts +58 -0
- package/src/runtime/http-server.ts +1076 -0
- package/src/runtime/http-types.ts +66 -0
- package/src/runtime/routes/app-routes.ts +174 -0
- package/src/runtime/routes/attachment-routes.ts +133 -0
- package/src/runtime/routes/call-routes.ts +190 -0
- package/src/runtime/routes/channel-routes.ts +1404 -0
- package/src/runtime/routes/conversation-routes.ts +352 -0
- package/src/runtime/routes/events-routes.ts +148 -0
- package/src/runtime/routes/run-routes.ts +257 -0
- package/src/runtime/routes/secret-routes.ts +76 -0
- package/src/runtime/run-orchestrator.ts +330 -0
- package/src/schedule/recurrence-engine.ts +162 -0
- package/src/schedule/recurrence-types.ts +67 -0
- package/src/schedule/schedule-store.ts +506 -0
- package/src/schedule/scheduler.ts +171 -0
- package/src/security/encrypted-store.ts +238 -0
- package/src/security/keychain.ts +252 -0
- package/src/security/oauth-callback-registry.ts +66 -0
- package/src/security/oauth2.ts +274 -0
- package/src/security/redaction.ts +89 -0
- package/src/security/secret-allowlist.ts +164 -0
- package/src/security/secret-ingress.ts +57 -0
- package/src/security/secret-scanner.ts +550 -0
- package/src/security/secure-keys.ts +180 -0
- package/src/security/token-manager.ts +141 -0
- package/src/services/published-app-updater.ts +69 -0
- package/src/services/vercel-deploy.ts +73 -0
- package/src/skills/active-skill-tools.ts +81 -0
- package/src/skills/clawhub.ts +414 -0
- package/src/skills/include-graph.ts +146 -0
- package/src/skills/managed-store.ts +233 -0
- package/src/skills/path-classifier.ts +128 -0
- package/src/skills/slash-commands.ts +174 -0
- package/src/skills/tool-manifest.ts +165 -0
- package/src/skills/version-hash.ts +110 -0
- package/src/slack/slack-webhook.ts +61 -0
- package/src/subagent/index.ts +19 -0
- package/src/subagent/manager.ts +511 -0
- package/src/subagent/types.ts +69 -0
- package/src/swarm/backend-claude-code.ts +145 -0
- package/src/swarm/index.ts +44 -0
- package/src/swarm/limits.ts +37 -0
- package/src/swarm/orchestrator.ts +279 -0
- package/src/swarm/plan-validator.ts +151 -0
- package/src/swarm/router-planner.ts +100 -0
- package/src/swarm/router-prompts.ts +36 -0
- package/src/swarm/synthesizer.ts +62 -0
- package/src/swarm/types.ts +62 -0
- package/src/swarm/worker-backend.ts +121 -0
- package/src/swarm/worker-prompts.ts +79 -0
- package/src/swarm/worker-runner.ts +164 -0
- package/src/tasks/SPEC.md +139 -0
- package/src/tasks/candidate-store.ts +86 -0
- package/src/tasks/ephemeral-permissions.ts +48 -0
- package/src/tasks/task-compiler.ts +199 -0
- package/src/tasks/task-runner.ts +90 -0
- package/src/tasks/task-scheduler.ts +21 -0
- package/src/tasks/task-store.ts +127 -0
- package/src/tasks/tool-sanitizer.ts +36 -0
- package/src/tools/apps/definitions.ts +59 -0
- package/src/tools/apps/executors.ts +313 -0
- package/src/tools/apps/open-proxy.ts +43 -0
- package/src/tools/apps/registry.ts +16 -0
- package/src/tools/assets/materialize.ts +218 -0
- package/src/tools/assets/search.ts +361 -0
- package/src/tools/browser/__tests__/auth-cache.test.ts +219 -0
- package/src/tools/browser/__tests__/auth-detector.test.ts +362 -0
- package/src/tools/browser/__tests__/jit-auth.test.ts +189 -0
- package/src/tools/browser/api-map.ts +293 -0
- package/src/tools/browser/auth-cache.ts +149 -0
- package/src/tools/browser/auth-detector.ts +347 -0
- package/src/tools/browser/auto-navigate.ts +270 -0
- package/src/tools/browser/browser-execution.ts +980 -0
- package/src/tools/browser/browser-handoff.ts +79 -0
- package/src/tools/browser/browser-manager.ts +715 -0
- package/src/tools/browser/browser-screencast.ts +217 -0
- package/src/tools/browser/headless-browser.ts +450 -0
- package/src/tools/browser/jit-auth.ts +51 -0
- package/src/tools/browser/network-recorder.ts +349 -0
- package/src/tools/browser/network-recording-types.ts +49 -0
- package/src/tools/browser/recording-store.ts +49 -0
- package/src/tools/browser/runtime-check.ts +43 -0
- package/src/tools/browser/x-auto-navigate.ts +207 -0
- package/src/tools/calls/call-end.ts +67 -0
- package/src/tools/calls/call-start.ts +81 -0
- package/src/tools/calls/call-status.ts +81 -0
- package/src/tools/claude-code/claude-code.ts +428 -0
- package/src/tools/computer-use/definitions.ts +443 -0
- package/src/tools/computer-use/registry.ts +22 -0
- package/src/tools/computer-use/request-computer-control.ts +53 -0
- package/src/tools/computer-use/skill-proxy-bridge.ts +28 -0
- package/src/tools/credentials/account-registry.ts +127 -0
- package/src/tools/credentials/broker-types.ts +107 -0
- package/src/tools/credentials/broker.ts +372 -0
- package/src/tools/credentials/domain-policy.ts +51 -0
- package/src/tools/credentials/host-pattern-match.ts +60 -0
- package/src/tools/credentials/metadata-store.ts +335 -0
- package/src/tools/credentials/policy-types.ts +52 -0
- package/src/tools/credentials/policy-validate.ts +80 -0
- package/src/tools/credentials/resolve.ts +122 -0
- package/src/tools/credentials/selection.ts +159 -0
- package/src/tools/credentials/tool-policy.ts +25 -0
- package/src/tools/credentials/vault.ts +657 -0
- package/src/tools/document/document-tool.ts +92 -0
- package/src/tools/document/editor-template.ts +237 -0
- package/src/tools/execution-target.ts +21 -0
- package/src/tools/execution-timeout.ts +49 -0
- package/src/tools/executor.ts +815 -0
- package/src/tools/filesystem/edit.ts +127 -0
- package/src/tools/filesystem/fuzzy-match.ts +202 -0
- package/src/tools/filesystem/read.ts +71 -0
- package/src/tools/filesystem/view-image.ts +199 -0
- package/src/tools/filesystem/write.ts +79 -0
- package/src/tools/followups/followup_create.ts +76 -0
- package/src/tools/followups/followup_list.ts +60 -0
- package/src/tools/followups/followup_resolve.ts +56 -0
- package/src/tools/host-filesystem/edit.ts +125 -0
- package/src/tools/host-filesystem/read.ts +80 -0
- package/src/tools/host-filesystem/write.ts +76 -0
- package/src/tools/host-terminal/cli-discover.ts +180 -0
- package/src/tools/host-terminal/host-shell.ts +191 -0
- package/src/tools/memory/definitions.ts +69 -0
- package/src/tools/memory/handlers.ts +246 -0
- package/src/tools/memory/register.ts +66 -0
- package/src/tools/network/__tests__/web-search.test.ts +427 -0
- package/src/tools/network/domain-normalize.ts +85 -0
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
- package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
- package/src/tools/network/script-proxy/certs.ts +237 -0
- package/src/tools/network/script-proxy/connect-tunnel.ts +82 -0
- package/src/tools/network/script-proxy/http-forwarder.ts +151 -0
- package/src/tools/network/script-proxy/index.ts +28 -0
- package/src/tools/network/script-proxy/logging.ts +196 -0
- package/src/tools/network/script-proxy/mitm-handler.ts +269 -0
- package/src/tools/network/script-proxy/policy.ts +152 -0
- package/src/tools/network/script-proxy/router.ts +60 -0
- package/src/tools/network/script-proxy/server.ts +136 -0
- package/src/tools/network/script-proxy/session-manager.ts +534 -0
- package/src/tools/network/script-proxy/types.ts +125 -0
- package/src/tools/network/url-safety.ts +227 -0
- package/src/tools/network/web-fetch.ts +713 -0
- package/src/tools/network/web-search.ts +296 -0
- package/src/tools/policy-context.ts +29 -0
- package/src/tools/registry.ts +295 -0
- package/src/tools/reminder/reminder-store.ts +148 -0
- package/src/tools/reminder/reminder.ts +80 -0
- package/src/tools/schedule/create.ts +81 -0
- package/src/tools/schedule/delete.ts +28 -0
- package/src/tools/schedule/list.ts +69 -0
- package/src/tools/schedule/update.ts +97 -0
- package/src/tools/shared/filesystem/edit-engine.ts +56 -0
- package/src/tools/shared/filesystem/errors.ts +85 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +215 -0
- package/src/tools/shared/filesystem/format-diff.ts +35 -0
- package/src/tools/shared/filesystem/path-policy.ts +125 -0
- package/src/tools/shared/filesystem/size-guard.ts +41 -0
- package/src/tools/shared/filesystem/types.ts +80 -0
- package/src/tools/shared/shell-output.ts +52 -0
- package/src/tools/skills/delete-managed.ts +60 -0
- package/src/tools/skills/load.ts +139 -0
- package/src/tools/skills/sandbox-runner.ts +279 -0
- package/src/tools/skills/scaffold-managed.ts +150 -0
- package/src/tools/skills/script-contract.ts +6 -0
- package/src/tools/skills/skill-script-runner.ts +86 -0
- package/src/tools/skills/skill-tool-factory.ts +64 -0
- package/src/tools/skills/vellum-catalog.ts +217 -0
- package/src/tools/subagent/abort.ts +33 -0
- package/src/tools/subagent/message.ts +39 -0
- package/src/tools/subagent/read.ts +67 -0
- package/src/tools/subagent/spawn.ts +46 -0
- package/src/tools/subagent/status.ts +45 -0
- package/src/tools/swarm/delegate.ts +183 -0
- package/src/tools/system/request-permission.ts +98 -0
- package/src/tools/system/version.ts +43 -0
- package/src/tools/tasks/index.ts +27 -0
- package/src/tools/tasks/task-delete.ts +82 -0
- package/src/tools/tasks/task-list.ts +44 -0
- package/src/tools/tasks/task-run.ts +97 -0
- package/src/tools/tasks/task-save.ts +47 -0
- package/src/tools/tasks/work-item-enqueue.ts +234 -0
- package/src/tools/tasks/work-item-list.ts +55 -0
- package/src/tools/tasks/work-item-remove.ts +60 -0
- package/src/tools/tasks/work-item-run.ts +78 -0
- package/src/tools/tasks/work-item-update.ts +114 -0
- package/src/tools/terminal/backends/docker.ts +372 -0
- package/src/tools/terminal/backends/native.ts +190 -0
- package/src/tools/terminal/backends/types.ts +26 -0
- package/src/tools/terminal/evaluate-typescript.ts +275 -0
- package/src/tools/terminal/parser.ts +413 -0
- package/src/tools/terminal/safe-env.ts +37 -0
- package/src/tools/terminal/sandbox-diagnostics.ts +149 -0
- package/src/tools/terminal/sandbox.ts +44 -0
- package/src/tools/terminal/shell.ts +257 -0
- package/src/tools/tool-manifest.ts +198 -0
- package/src/tools/types.ts +176 -0
- package/src/tools/ui-surface/definitions.ts +244 -0
- package/src/tools/ui-surface/registry.ts +14 -0
- package/src/tools/watch/screen-watch.ts +130 -0
- package/src/tools/watch/watch-state.ts +119 -0
- package/src/tools/watcher/create.ts +64 -0
- package/src/tools/watcher/delete.ts +27 -0
- package/src/tools/watcher/digest.ts +50 -0
- package/src/tools/watcher/list.ts +60 -0
- package/src/tools/watcher/update.ts +56 -0
- package/src/tools/weather/service.ts +551 -0
- package/src/twitter/client.ts +690 -0
- package/src/twitter/oauth-client.ts +102 -0
- package/src/twitter/router.ts +101 -0
- package/src/twitter/session.ts +91 -0
- package/src/usage/actors.ts +24 -0
- package/src/usage/types.ts +37 -0
- package/src/util/clipboard.ts +33 -0
- package/src/util/content-id.ts +16 -0
- package/src/util/debounce.ts +88 -0
- package/src/util/diff.ts +181 -0
- package/src/util/errors.ts +129 -0
- package/src/util/logger.ts +243 -0
- package/src/util/network-info.ts +47 -0
- package/src/util/platform.ts +632 -0
- package/src/util/pricing.ts +150 -0
- package/src/util/promise-guard.ts +37 -0
- package/src/util/retry.ts +98 -0
- package/src/util/spinner.ts +51 -0
- package/src/util/time.ts +16 -0
- package/src/util/truncate.ts +6 -0
- package/src/util/xml.ts +4 -0
- package/src/version.ts +3 -0
- package/src/watcher/constants.ts +11 -0
- package/src/watcher/engine.ts +199 -0
- package/src/watcher/provider-registry.ts +15 -0
- package/src/watcher/provider-types.ts +48 -0
- package/src/watcher/providers/gmail.ts +198 -0
- package/src/watcher/providers/google-calendar.ts +228 -0
- package/src/watcher/providers/slack.ts +129 -0
- package/src/watcher/watcher-store.ts +419 -0
- package/src/work-items/work-item-runner.ts +171 -0
- package/src/work-items/work-item-store.ts +325 -0
- package/src/workspace/commit-message-enrichment-service.ts +284 -0
- package/src/workspace/commit-message-provider.ts +95 -0
- package/src/workspace/git-service.ts +857 -0
- package/src/workspace/heartbeat-service.ts +345 -0
- package/src/workspace/provider-commit-message-generator.ts +285 -0
- package/src/workspace/top-level-renderer.ts +19 -0
- package/src/workspace/top-level-scanner.ts +41 -0
- package/src/workspace/turn-commit.ts +175 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,1064 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from 'bun:test';
|
|
2
|
+
import { realpathSync, mkdirSync, existsSync } from 'node:fs';
|
|
3
|
+
import * as realChildProcess from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
const execSyncMock = mock((_command: string, _opts?: unknown): unknown => undefined);
|
|
6
|
+
const execFileSyncMock = mock(
|
|
7
|
+
(_file: string, _args?: readonly string[], _opts?: unknown): unknown => undefined,
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
mock.module('node:child_process', () => ({
|
|
11
|
+
...realChildProcess,
|
|
12
|
+
execSync: execSyncMock,
|
|
13
|
+
execFileSync: execFileSyncMock,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
mock.module('../util/logger.js', () => ({
|
|
17
|
+
getLogger: () => ({
|
|
18
|
+
error: () => {},
|
|
19
|
+
warn: () => {},
|
|
20
|
+
info: () => {},
|
|
21
|
+
debug: () => {},
|
|
22
|
+
}),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
const { DockerBackend, _resetDockerChecks } = await import(
|
|
26
|
+
'../tools/terminal/backends/docker.js'
|
|
27
|
+
);
|
|
28
|
+
const { ToolError } = await import('../util/errors.js');
|
|
29
|
+
const { DEFAULT_CONFIG } = await import('../config/defaults.js');
|
|
30
|
+
|
|
31
|
+
const defaultImage = DEFAULT_CONFIG.sandbox.docker.image;
|
|
32
|
+
|
|
33
|
+
// Use a real temp dir so realpathSync resolves correctly.
|
|
34
|
+
const sandboxRoot = realpathSync('/tmp');
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
_resetDockerChecks();
|
|
38
|
+
execSyncMock.mockReset();
|
|
39
|
+
execFileSyncMock.mockReset();
|
|
40
|
+
// Default: all preflight checks pass.
|
|
41
|
+
execSyncMock.mockImplementation(() => undefined);
|
|
42
|
+
execFileSyncMock.mockImplementation(() => undefined);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('DockerBackend — argument construction', () => {
|
|
46
|
+
test('returns docker as the command', () => {
|
|
47
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
48
|
+
const result = backend.wrap('echo hi', sandboxRoot);
|
|
49
|
+
expect(result.command).toBe('docker');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('sandboxed flag is always true', () => {
|
|
53
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
54
|
+
const result = backend.wrap('pwd', sandboxRoot);
|
|
55
|
+
expect(result.sandboxed).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('uses --rm for ephemeral containers', () => {
|
|
59
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
60
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
61
|
+
expect(result.args).toContain('--rm');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('drops all capabilities', () => {
|
|
65
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
66
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
67
|
+
expect(result.args).toContain('--cap-drop=ALL');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('sets no-new-privileges security option', () => {
|
|
71
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
72
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
73
|
+
expect(result.args).toContain('--security-opt=no-new-privileges');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('disables network by default', () => {
|
|
77
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
78
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
79
|
+
expect(result.args).toContain('--network=none');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('applies default resource limits', () => {
|
|
83
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
84
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
85
|
+
expect(result.args).toContain('--cpus=1');
|
|
86
|
+
expect(result.args).toContain('--memory=512m');
|
|
87
|
+
expect(result.args).toContain('--pids-limit=256');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('passes host UID:GID via --user', () => {
|
|
91
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
92
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
93
|
+
expect(result.args).toContain('--user');
|
|
94
|
+
const userIdx = result.args.indexOf('--user');
|
|
95
|
+
expect(result.args[userIdx + 1]).toBe('1000:1000');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('bind-mounts sandbox root to /workspace', () => {
|
|
99
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
100
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
101
|
+
expect(result.args).toContain('--mount');
|
|
102
|
+
const mountIdx = result.args.indexOf('--mount');
|
|
103
|
+
expect(result.args[mountIdx + 1]).toBe(
|
|
104
|
+
`type=bind,src=${sandboxRoot},dst=/workspace`,
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('uses default image from config', () => {
|
|
109
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
110
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
111
|
+
expect(result.args).toContain(defaultImage);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('wraps command with bash -c by default', () => {
|
|
115
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
116
|
+
const cmd = 'cat /etc/passwd | wc -l';
|
|
117
|
+
const result = backend.wrap(cmd, sandboxRoot);
|
|
118
|
+
const bashIdx = result.args.indexOf('bash');
|
|
119
|
+
expect(bashIdx).toBeGreaterThan(0);
|
|
120
|
+
expect(result.args.slice(bashIdx)).toEqual(['bash', '-c', cmd]);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('DockerBackend — read-only root and tmpfs', () => {
|
|
125
|
+
test('sets --read-only flag for container root filesystem', () => {
|
|
126
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
127
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
128
|
+
expect(result.args).toContain('--read-only');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('mounts writable tmpfs at /tmp', () => {
|
|
132
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
133
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
134
|
+
expect(result.args).toContain('--tmpfs');
|
|
135
|
+
const tmpfsIdx = result.args.indexOf('--tmpfs');
|
|
136
|
+
expect(result.args[tmpfsIdx + 1]).toBe('/tmp:rw,nosuid,nodev,noexec');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('--read-only appears before image name', () => {
|
|
140
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
141
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
142
|
+
const readOnlyIdx = result.args.indexOf('--read-only');
|
|
143
|
+
const imageIdx = result.args.indexOf(defaultImage);
|
|
144
|
+
expect(readOnlyIdx).toBeLessThan(imageIdx);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('--tmpfs appears before image name', () => {
|
|
148
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
149
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
150
|
+
const tmpfsIdx = result.args.indexOf('--tmpfs');
|
|
151
|
+
const imageIdx = result.args.indexOf(defaultImage);
|
|
152
|
+
expect(tmpfsIdx).toBeLessThan(imageIdx);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('DockerBackend — path mapping', () => {
|
|
157
|
+
test('maps sandbox root to /workspace workdir', () => {
|
|
158
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
159
|
+
const result = backend.wrap('pwd', sandboxRoot);
|
|
160
|
+
const wdIdx = result.args.indexOf('--workdir');
|
|
161
|
+
expect(result.args[wdIdx + 1]).toBe('/workspace');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('maps subdirectory to /workspace/<subdir>', () => {
|
|
165
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
166
|
+
const subDir = `${sandboxRoot}/docker-sandbox-test-sub`;
|
|
167
|
+
if (!existsSync(subDir)) {
|
|
168
|
+
mkdirSync(subDir, { recursive: true });
|
|
169
|
+
}
|
|
170
|
+
const result = backend.wrap('pwd', subDir);
|
|
171
|
+
const wdIdx = result.args.indexOf('--workdir');
|
|
172
|
+
expect(result.args[wdIdx + 1]).toBe('/workspace/docker-sandbox-test-sub');
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('DockerBackend — fail-closed on escape', () => {
|
|
177
|
+
test('throws ToolError when workdir is outside sandbox root', () => {
|
|
178
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
179
|
+
// /var is not under /tmp
|
|
180
|
+
const outsideDir = realpathSync('/var');
|
|
181
|
+
expect(() => backend.wrap('ls', outsideDir)).toThrow(ToolError);
|
|
182
|
+
expect(() => backend.wrap('ls', outsideDir)).toThrow(
|
|
183
|
+
'outside the sandbox root',
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('error message does not leak host working directory path', () => {
|
|
188
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
189
|
+
const outsideDir = realpathSync('/var');
|
|
190
|
+
try {
|
|
191
|
+
backend.wrap('ls', outsideDir);
|
|
192
|
+
throw new Error('should have thrown');
|
|
193
|
+
} catch (err) {
|
|
194
|
+
expect(err).toBeInstanceOf(ToolError);
|
|
195
|
+
// Should NOT contain the actual host paths
|
|
196
|
+
expect((err as Error).message).not.toContain(outsideDir);
|
|
197
|
+
expect((err as Error).message).not.toContain(sandboxRoot);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('rejects path traversal via ../ in working directory', () => {
|
|
202
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
203
|
+
const subDir = `${sandboxRoot}/docker-sandbox-test-sub`;
|
|
204
|
+
if (!existsSync(subDir)) {
|
|
205
|
+
mkdirSync(subDir, { recursive: true });
|
|
206
|
+
}
|
|
207
|
+
// ../../var should resolve outside sandbox root
|
|
208
|
+
const traversalDir = `${subDir}/../../var`;
|
|
209
|
+
expect(() => backend.wrap('ls', traversalDir)).toThrow(ToolError);
|
|
210
|
+
expect(() => backend.wrap('ls', traversalDir)).toThrow(
|
|
211
|
+
'outside the sandbox root',
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('DockerBackend — unsafe path character rejection', () => {
|
|
217
|
+
test('rejects sandbox root containing a comma (mount argument injection)', () => {
|
|
218
|
+
const dirWithComma = `${sandboxRoot}/evil,dst=/etc,src=/`;
|
|
219
|
+
if (!existsSync(dirWithComma)) {
|
|
220
|
+
mkdirSync(dirWithComma, { recursive: true });
|
|
221
|
+
}
|
|
222
|
+
expect(() => new DockerBackend(dirWithComma, undefined, 1000, 1000)).toThrow(ToolError);
|
|
223
|
+
expect(() => new DockerBackend(dirWithComma, undefined, 1000, 1000)).toThrow(
|
|
224
|
+
'unsafe for Docker mount arguments',
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('rejects working directory containing a comma', () => {
|
|
229
|
+
const dirWithComma = `${sandboxRoot}/work,dir`;
|
|
230
|
+
if (!existsSync(dirWithComma)) {
|
|
231
|
+
mkdirSync(dirWithComma, { recursive: true });
|
|
232
|
+
}
|
|
233
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
234
|
+
expect(() => backend.wrap('ls', dirWithComma)).toThrow(ToolError);
|
|
235
|
+
expect(() => backend.wrap('ls', dirWithComma)).toThrow(
|
|
236
|
+
'unsafe for Docker mount arguments',
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('DockerBackend — special characters in paths', () => {
|
|
242
|
+
test('handles spaces in sandbox root path', () => {
|
|
243
|
+
const dirWithSpaces = `${sandboxRoot}/docker sandbox test spaces`;
|
|
244
|
+
if (!existsSync(dirWithSpaces)) {
|
|
245
|
+
mkdirSync(dirWithSpaces, { recursive: true });
|
|
246
|
+
}
|
|
247
|
+
const backend = new DockerBackend(dirWithSpaces, undefined, 1000, 1000);
|
|
248
|
+
const result = backend.wrap('ls', dirWithSpaces);
|
|
249
|
+
// Args are separate argv segments, so spaces are safe
|
|
250
|
+
expect(result.args).toContain('--mount');
|
|
251
|
+
const mountIdx = result.args.indexOf('--mount');
|
|
252
|
+
expect(result.args[mountIdx + 1]).toContain(dirWithSpaces);
|
|
253
|
+
expect(result.sandboxed).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('handles quotes in working directory name', () => {
|
|
257
|
+
const dirWithQuotes = `${sandboxRoot}/docker'sandbox"test`;
|
|
258
|
+
if (!existsSync(dirWithQuotes)) {
|
|
259
|
+
mkdirSync(dirWithQuotes, { recursive: true });
|
|
260
|
+
}
|
|
261
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
262
|
+
const result = backend.wrap('ls', dirWithQuotes);
|
|
263
|
+
const wdIdx = result.args.indexOf('--workdir');
|
|
264
|
+
expect(result.args[wdIdx + 1]).toContain("docker'sandbox\"test");
|
|
265
|
+
expect(result.sandboxed).toBe(true);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('handles dollar signs and backticks in paths', () => {
|
|
269
|
+
const dirWithShellChars = `${sandboxRoot}/docker$sandbox\`test`;
|
|
270
|
+
if (!existsSync(dirWithShellChars)) {
|
|
271
|
+
mkdirSync(dirWithShellChars, { recursive: true });
|
|
272
|
+
}
|
|
273
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
274
|
+
const result = backend.wrap('ls', dirWithShellChars);
|
|
275
|
+
// Because we use argv segments (not shell interpolation), these are safe
|
|
276
|
+
expect(result.sandboxed).toBe(true);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('DockerBackend — argv segment safety', () => {
|
|
281
|
+
test('all args are discrete strings — no shell metacharacters are interpreted', () => {
|
|
282
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
283
|
+
const result = backend.wrap('echo $(whoami)', sandboxRoot);
|
|
284
|
+
// The command is passed as a single argv element after 'sh -c'
|
|
285
|
+
const cIdx = result.args.indexOf('-c');
|
|
286
|
+
expect(cIdx).toBeGreaterThan(0);
|
|
287
|
+
expect(result.args[cIdx + 1]).toBe('echo $(whoami)');
|
|
288
|
+
// The command itself is a single string, not split by the shell
|
|
289
|
+
expect(result.args.filter((a: string) => a === '$(whoami)').length).toBe(0);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('every arg is a string type', () => {
|
|
293
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
294
|
+
const result = backend.wrap('ls -la', sandboxRoot);
|
|
295
|
+
for (const arg of result.args) {
|
|
296
|
+
expect(typeof arg).toBe('string');
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test('no arg contains unintended shell operators', () => {
|
|
301
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
302
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
303
|
+
// None of the docker flag args (before the image) should contain ; | && etc.
|
|
304
|
+
const imageIdx = result.args.indexOf(defaultImage);
|
|
305
|
+
const flagArgs = result.args.slice(0, imageIdx);
|
|
306
|
+
for (const arg of flagArgs) {
|
|
307
|
+
expect(arg).not.toContain(';');
|
|
308
|
+
expect(arg).not.toContain('|');
|
|
309
|
+
expect(arg).not.toContain('&&');
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe('DockerBackend — UID:GID mapping', () => {
|
|
315
|
+
test('always includes --user flag with UID:GID', () => {
|
|
316
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 501, 20);
|
|
317
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
318
|
+
expect(result.args).toContain('--user');
|
|
319
|
+
const userIdx = result.args.indexOf('--user');
|
|
320
|
+
expect(result.args[userIdx + 1]).toBe('501:20');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test('defaults to process UID:GID when not specified', () => {
|
|
324
|
+
const backend = new DockerBackend(sandboxRoot);
|
|
325
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
326
|
+
expect(result.args).toContain('--user');
|
|
327
|
+
const userIdx = result.args.indexOf('--user');
|
|
328
|
+
const expected = `${process.getuid!()}:${process.getgid!()}`;
|
|
329
|
+
expect(result.args[userIdx + 1]).toBe(expected);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test('UID:GID format is always numeric colon-separated', () => {
|
|
333
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 0, 0);
|
|
334
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
335
|
+
const userIdx = result.args.indexOf('--user');
|
|
336
|
+
expect(result.args[userIdx + 1]).toMatch(/^\d+:\d+$/);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe('DockerBackend — custom config', () => {
|
|
341
|
+
test('accepts custom image', () => {
|
|
342
|
+
const backend = new DockerBackend(
|
|
343
|
+
sandboxRoot,
|
|
344
|
+
{ image: 'alpine:3.19' },
|
|
345
|
+
1000,
|
|
346
|
+
1000,
|
|
347
|
+
);
|
|
348
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
349
|
+
expect(result.args).toContain('alpine:3.19');
|
|
350
|
+
expect(result.args).not.toContain(defaultImage);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('accepts custom resource limits', () => {
|
|
354
|
+
const backend = new DockerBackend(
|
|
355
|
+
sandboxRoot,
|
|
356
|
+
{ cpus: 4, memoryMb: 1024, pidsLimit: 512 },
|
|
357
|
+
1000,
|
|
358
|
+
1000,
|
|
359
|
+
);
|
|
360
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
361
|
+
expect(result.args).toContain('--cpus=4');
|
|
362
|
+
expect(result.args).toContain('--memory=1024m');
|
|
363
|
+
expect(result.args).toContain('--pids-limit=512');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('accepts custom shell', () => {
|
|
367
|
+
const backend = new DockerBackend(
|
|
368
|
+
sandboxRoot,
|
|
369
|
+
{ shell: 'bash' },
|
|
370
|
+
1000,
|
|
371
|
+
1000,
|
|
372
|
+
);
|
|
373
|
+
const result = backend.wrap('echo hi', sandboxRoot);
|
|
374
|
+
const bashIdx = result.args.indexOf('bash');
|
|
375
|
+
expect(bashIdx).toBeGreaterThan(0);
|
|
376
|
+
expect(result.args.slice(bashIdx)).toEqual(['bash', '-c', 'echo hi']);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('accepts custom network mode', () => {
|
|
380
|
+
const backend = new DockerBackend(
|
|
381
|
+
sandboxRoot,
|
|
382
|
+
{ network: 'bridge' },
|
|
383
|
+
1000,
|
|
384
|
+
1000,
|
|
385
|
+
);
|
|
386
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
387
|
+
expect(result.args).toContain('--network=bridge');
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
describe('DockerBackend — preflight: Docker CLI check', () => {
|
|
392
|
+
test('throws ToolError with install hint when docker CLI is missing', () => {
|
|
393
|
+
execFileSyncMock.mockImplementation(
|
|
394
|
+
(file: string, args?: readonly string[]) => {
|
|
395
|
+
if (
|
|
396
|
+
file === 'docker' &&
|
|
397
|
+
Array.isArray(args) &&
|
|
398
|
+
args.includes('--version')
|
|
399
|
+
) {
|
|
400
|
+
throw new Error('command not found: docker');
|
|
401
|
+
}
|
|
402
|
+
return undefined;
|
|
403
|
+
},
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
407
|
+
expect(() => backend.wrap('ls', sandboxRoot)).toThrow(ToolError);
|
|
408
|
+
expect(() => backend.wrap('ls', sandboxRoot)).toThrow(
|
|
409
|
+
'Docker CLI is not installed',
|
|
410
|
+
);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test('caches successful CLI check', () => {
|
|
414
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
415
|
+
backend.wrap('ls', sandboxRoot);
|
|
416
|
+
backend.wrap('ls', sandboxRoot);
|
|
417
|
+
|
|
418
|
+
// docker --version should only be called once (cached after success).
|
|
419
|
+
const versionCalls = execFileSyncMock.mock.calls.filter(
|
|
420
|
+
(args) =>
|
|
421
|
+
args[0] === 'docker' &&
|
|
422
|
+
Array.isArray(args[1]) &&
|
|
423
|
+
(args[1] as string[]).includes('--version'),
|
|
424
|
+
);
|
|
425
|
+
expect(versionCalls.length).toBe(1);
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
describe('DockerBackend — preflight: Docker daemon check', () => {
|
|
430
|
+
test('throws ToolError with start hint when daemon is unreachable', () => {
|
|
431
|
+
execFileSyncMock.mockImplementation(
|
|
432
|
+
(file: string, args?: readonly string[]) => {
|
|
433
|
+
if (
|
|
434
|
+
file === 'docker' &&
|
|
435
|
+
Array.isArray(args) &&
|
|
436
|
+
args.includes('info')
|
|
437
|
+
) {
|
|
438
|
+
throw new Error('Cannot connect to the Docker daemon');
|
|
439
|
+
}
|
|
440
|
+
return undefined;
|
|
441
|
+
},
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
445
|
+
expect(() => backend.wrap('ls', sandboxRoot)).toThrow(ToolError);
|
|
446
|
+
expect(() => backend.wrap('ls', sandboxRoot)).toThrow(
|
|
447
|
+
'Docker daemon is not running',
|
|
448
|
+
);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
test('caches successful daemon check', () => {
|
|
452
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
453
|
+
backend.wrap('ls', sandboxRoot);
|
|
454
|
+
backend.wrap('ls', sandboxRoot);
|
|
455
|
+
|
|
456
|
+
const infoCalls = execFileSyncMock.mock.calls.filter(
|
|
457
|
+
(args) =>
|
|
458
|
+
args[0] === 'docker' &&
|
|
459
|
+
Array.isArray(args[1]) &&
|
|
460
|
+
(args[1] as string[]).includes('info'),
|
|
461
|
+
);
|
|
462
|
+
expect(infoCalls.length).toBe(1);
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
describe('DockerBackend — preflight: image availability check', () => {
|
|
467
|
+
test('auto-pulls image when not available locally', () => {
|
|
468
|
+
const pullImage = 'alpine:3.19';
|
|
469
|
+
execFileSyncMock.mockImplementation(
|
|
470
|
+
(file: string, args?: readonly string[]) => {
|
|
471
|
+
if (
|
|
472
|
+
file === 'docker' &&
|
|
473
|
+
Array.isArray(args) &&
|
|
474
|
+
args.includes('inspect')
|
|
475
|
+
) {
|
|
476
|
+
throw new Error('No such image');
|
|
477
|
+
}
|
|
478
|
+
return undefined;
|
|
479
|
+
},
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
const backend = new DockerBackend(sandboxRoot, { image: pullImage }, 1000, 1000);
|
|
483
|
+
// Should succeed — auto-pull kicks in after inspect fails.
|
|
484
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
485
|
+
expect(result.command).toBe('docker');
|
|
486
|
+
|
|
487
|
+
// Verify docker pull was called with the image.
|
|
488
|
+
const pullCalls = execFileSyncMock.mock.calls.filter(
|
|
489
|
+
(args) =>
|
|
490
|
+
args[0] === 'docker' &&
|
|
491
|
+
Array.isArray(args[1]) &&
|
|
492
|
+
(args[1] as string[]).includes('pull'),
|
|
493
|
+
);
|
|
494
|
+
expect(pullCalls.length).toBe(1);
|
|
495
|
+
expect((pullCalls[0]![1] as string[])).toContain(pullImage);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test('throws ToolError when auto-pull also fails', () => {
|
|
499
|
+
execFileSyncMock.mockImplementation(
|
|
500
|
+
(file: string, args?: readonly string[]) => {
|
|
501
|
+
if (
|
|
502
|
+
file === 'docker' &&
|
|
503
|
+
Array.isArray(args) &&
|
|
504
|
+
(args.includes('inspect') || args.includes('pull'))
|
|
505
|
+
) {
|
|
506
|
+
throw new Error('No such image / pull failed');
|
|
507
|
+
}
|
|
508
|
+
return undefined;
|
|
509
|
+
},
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
const backend = new DockerBackend(sandboxRoot, { image: 'alpine:3.19' }, 1000, 1000);
|
|
513
|
+
expect(() => backend.wrap('ls', sandboxRoot)).toThrow(ToolError);
|
|
514
|
+
expect(() => backend.wrap('ls', sandboxRoot)).toThrow(
|
|
515
|
+
'Failed to pull Docker image',
|
|
516
|
+
);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
test('includes image name in error message when pull fails', () => {
|
|
520
|
+
execFileSyncMock.mockImplementation(
|
|
521
|
+
(file: string, args?: readonly string[]) => {
|
|
522
|
+
if (
|
|
523
|
+
file === 'docker' &&
|
|
524
|
+
Array.isArray(args) &&
|
|
525
|
+
(args.includes('inspect') || args.includes('pull'))
|
|
526
|
+
) {
|
|
527
|
+
throw new Error('No such image / pull failed');
|
|
528
|
+
}
|
|
529
|
+
return undefined;
|
|
530
|
+
},
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
const backend = new DockerBackend(
|
|
534
|
+
sandboxRoot,
|
|
535
|
+
{ image: 'alpine:3.19' },
|
|
536
|
+
1000,
|
|
537
|
+
1000,
|
|
538
|
+
);
|
|
539
|
+
try {
|
|
540
|
+
backend.wrap('ls', sandboxRoot);
|
|
541
|
+
throw new Error('should have thrown');
|
|
542
|
+
} catch (err) {
|
|
543
|
+
expect(err).toBeInstanceOf(ToolError);
|
|
544
|
+
expect((err as Error).message).toContain('alpine:3.19');
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test('uses execFileSync (not execSync) for image check — no shell interpolation', () => {
|
|
549
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
550
|
+
backend.wrap('ls', sandboxRoot);
|
|
551
|
+
|
|
552
|
+
// execFileSync should have been called with 'docker' and argv array
|
|
553
|
+
const imageCalls = execFileSyncMock.mock.calls.filter(
|
|
554
|
+
(args) =>
|
|
555
|
+
args[0] === 'docker' &&
|
|
556
|
+
Array.isArray(args[1]) &&
|
|
557
|
+
(args[1] as string[]).includes('inspect'),
|
|
558
|
+
);
|
|
559
|
+
expect(imageCalls.length).toBe(1);
|
|
560
|
+
// Verify the image name is a separate argv element, not interpolated into a string
|
|
561
|
+
const argv = imageCalls[0]![1] as string[];
|
|
562
|
+
expect(argv).toContain('image');
|
|
563
|
+
expect(argv).toContain('inspect');
|
|
564
|
+
expect(argv).toContain(defaultImage);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test('caches successful image check per image', () => {
|
|
568
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
569
|
+
backend.wrap('ls', sandboxRoot);
|
|
570
|
+
backend.wrap('ls', sandboxRoot);
|
|
571
|
+
|
|
572
|
+
const inspectCalls = execFileSyncMock.mock.calls.filter(
|
|
573
|
+
(args) =>
|
|
574
|
+
args[0] === 'docker' &&
|
|
575
|
+
Array.isArray(args[1]) &&
|
|
576
|
+
(args[1] as string[]).includes('inspect'),
|
|
577
|
+
);
|
|
578
|
+
expect(inspectCalls.length).toBe(1);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
test('caches successful auto-pull so subsequent calls skip pull', () => {
|
|
582
|
+
let inspectCallCount = 0;
|
|
583
|
+
execFileSyncMock.mockImplementation(
|
|
584
|
+
(file: string, args?: readonly string[]) => {
|
|
585
|
+
if (
|
|
586
|
+
file === 'docker' &&
|
|
587
|
+
Array.isArray(args) &&
|
|
588
|
+
args.includes('inspect')
|
|
589
|
+
) {
|
|
590
|
+
inspectCallCount++;
|
|
591
|
+
if (inspectCallCount <= 1) {
|
|
592
|
+
throw new Error('No such image');
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return undefined;
|
|
596
|
+
},
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
const backend = new DockerBackend(sandboxRoot, { image: 'alpine:3.19' }, 1000, 1000);
|
|
600
|
+
backend.wrap('ls', sandboxRoot); // triggers inspect → pull → cache
|
|
601
|
+
backend.wrap('ls', sandboxRoot); // should use cache, no inspect or pull
|
|
602
|
+
|
|
603
|
+
const pullCalls = execFileSyncMock.mock.calls.filter(
|
|
604
|
+
(args) =>
|
|
605
|
+
args[0] === 'docker' &&
|
|
606
|
+
Array.isArray(args[1]) &&
|
|
607
|
+
(args[1] as string[]).includes('pull'),
|
|
608
|
+
);
|
|
609
|
+
expect(pullCalls.length).toBe(1);
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
describe('DockerBackend — preflight: mount probe', () => {
|
|
614
|
+
test('throws ToolError with file sharing hint when mount fails', () => {
|
|
615
|
+
// Mount probe now uses execFileSync.
|
|
616
|
+
execFileSyncMock.mockImplementation(
|
|
617
|
+
(file: string, args?: readonly string[]) => {
|
|
618
|
+
if (
|
|
619
|
+
file === 'docker' &&
|
|
620
|
+
Array.isArray(args) &&
|
|
621
|
+
args.includes('run')
|
|
622
|
+
) {
|
|
623
|
+
throw new Error('mount failed');
|
|
624
|
+
}
|
|
625
|
+
return undefined;
|
|
626
|
+
},
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
630
|
+
expect(() => backend.wrap('ls', sandboxRoot)).toThrow(ToolError);
|
|
631
|
+
expect(() => backend.wrap('ls', sandboxRoot)).toThrow(
|
|
632
|
+
'File Sharing',
|
|
633
|
+
);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
test('mount probe error does not leak host sandbox root path', () => {
|
|
637
|
+
execFileSyncMock.mockImplementation(
|
|
638
|
+
(file: string, args?: readonly string[]) => {
|
|
639
|
+
if (
|
|
640
|
+
file === 'docker' &&
|
|
641
|
+
Array.isArray(args) &&
|
|
642
|
+
args.includes('run')
|
|
643
|
+
) {
|
|
644
|
+
throw new Error('mount failed');
|
|
645
|
+
}
|
|
646
|
+
return undefined;
|
|
647
|
+
},
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
651
|
+
try {
|
|
652
|
+
backend.wrap('ls', sandboxRoot);
|
|
653
|
+
throw new Error('should have thrown');
|
|
654
|
+
} catch (err) {
|
|
655
|
+
expect(err).toBeInstanceOf(ToolError);
|
|
656
|
+
// Error message should be generic, not revealing the host path
|
|
657
|
+
expect((err as Error).message).not.toContain(sandboxRoot);
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
test('uses execFileSync (not execSync) for mount probe — no shell interpolation', () => {
|
|
662
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
663
|
+
backend.wrap('ls', sandboxRoot);
|
|
664
|
+
|
|
665
|
+
const mountCalls = execFileSyncMock.mock.calls.filter(
|
|
666
|
+
(args) =>
|
|
667
|
+
args[0] === 'docker' &&
|
|
668
|
+
Array.isArray(args[1]) &&
|
|
669
|
+
(args[1] as string[]).includes('run') &&
|
|
670
|
+
(args[1] as string[]).includes('test'),
|
|
671
|
+
);
|
|
672
|
+
expect(mountCalls.length).toBe(1);
|
|
673
|
+
// Verify mount arg is passed as a single argv element
|
|
674
|
+
const argv = mountCalls[0]![1] as string[];
|
|
675
|
+
expect(argv).toContain('--rm');
|
|
676
|
+
expect(argv).toContain('--mount');
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
test('mount probe uses configured image, not hardcoded ubuntu:22.04', () => {
|
|
680
|
+
const backend = new DockerBackend(
|
|
681
|
+
sandboxRoot,
|
|
682
|
+
{ image: 'alpine:3.19' },
|
|
683
|
+
1000,
|
|
684
|
+
1000,
|
|
685
|
+
);
|
|
686
|
+
backend.wrap('ls', sandboxRoot);
|
|
687
|
+
|
|
688
|
+
const mountCalls = execFileSyncMock.mock.calls.filter(
|
|
689
|
+
(args) =>
|
|
690
|
+
args[0] === 'docker' &&
|
|
691
|
+
Array.isArray(args[1]) &&
|
|
692
|
+
(args[1] as string[]).includes('run') &&
|
|
693
|
+
(args[1] as string[]).includes('test'),
|
|
694
|
+
);
|
|
695
|
+
expect(mountCalls.length).toBe(1);
|
|
696
|
+
const argv = mountCalls[0]![1] as string[];
|
|
697
|
+
expect(argv).toContain('alpine:3.19');
|
|
698
|
+
expect(argv).not.toContain('ubuntu:22.04');
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
test('caches successful mount probe per sandbox root', () => {
|
|
702
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
703
|
+
backend.wrap('ls', sandboxRoot);
|
|
704
|
+
backend.wrap('ls', sandboxRoot);
|
|
705
|
+
|
|
706
|
+
const mountCalls = execFileSyncMock.mock.calls.filter(
|
|
707
|
+
(args) =>
|
|
708
|
+
args[0] === 'docker' &&
|
|
709
|
+
Array.isArray(args[1]) &&
|
|
710
|
+
(args[1] as string[]).includes('run') &&
|
|
711
|
+
(args[1] as string[]).includes('test'),
|
|
712
|
+
);
|
|
713
|
+
expect(mountCalls.length).toBe(1);
|
|
714
|
+
});
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
describe('DockerBackend — preflight: shell resolution', () => {
|
|
718
|
+
test('falls back to sh when configured shell is not available in image', () => {
|
|
719
|
+
execFileSyncMock.mockImplementation(
|
|
720
|
+
(file: string, args?: readonly string[]) => {
|
|
721
|
+
if (
|
|
722
|
+
file === 'docker' &&
|
|
723
|
+
Array.isArray(args) &&
|
|
724
|
+
args.includes('run') &&
|
|
725
|
+
args.includes('bash')
|
|
726
|
+
) {
|
|
727
|
+
throw new Error('executable file not found');
|
|
728
|
+
}
|
|
729
|
+
return undefined;
|
|
730
|
+
},
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
734
|
+
const result = backend.wrap('echo hi', sandboxRoot);
|
|
735
|
+
// Should fall back to 'sh' instead of 'bash'
|
|
736
|
+
const imageIdx = result.args.indexOf(defaultImage);
|
|
737
|
+
expect(result.args[imageIdx + 1]).toBe('sh');
|
|
738
|
+
expect(result.args[imageIdx + 2]).toBe('-c');
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
test('throws when neither configured shell nor sh is available', () => {
|
|
742
|
+
execFileSyncMock.mockImplementation(
|
|
743
|
+
(file: string, args?: readonly string[]) => {
|
|
744
|
+
if (
|
|
745
|
+
file === 'docker' &&
|
|
746
|
+
Array.isArray(args) &&
|
|
747
|
+
args.includes('run') &&
|
|
748
|
+
(args.includes('bash') || args.includes('sh'))
|
|
749
|
+
) {
|
|
750
|
+
throw new Error('executable file not found');
|
|
751
|
+
}
|
|
752
|
+
return undefined;
|
|
753
|
+
},
|
|
754
|
+
);
|
|
755
|
+
|
|
756
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
757
|
+
expect(() => backend.wrap('ls', sandboxRoot)).toThrow(ToolError);
|
|
758
|
+
expect(() => backend.wrap('ls', sandboxRoot)).toThrow(
|
|
759
|
+
'is available in Docker image',
|
|
760
|
+
);
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
test('caches resolved shell per image', () => {
|
|
764
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
765
|
+
backend.wrap('ls', sandboxRoot);
|
|
766
|
+
backend.wrap('ls', sandboxRoot);
|
|
767
|
+
|
|
768
|
+
// Shell resolution docker run (with '-c' and 'true') should only happen once
|
|
769
|
+
const shellCalls = execFileSyncMock.mock.calls.filter(
|
|
770
|
+
(args) =>
|
|
771
|
+
args[0] === 'docker' &&
|
|
772
|
+
Array.isArray(args[1]) &&
|
|
773
|
+
(args[1] as string[]).includes('run') &&
|
|
774
|
+
(args[1] as string[]).includes('true'),
|
|
775
|
+
);
|
|
776
|
+
expect(shellCalls.length).toBe(1);
|
|
777
|
+
});
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
describe('DockerBackend — preflight check order', () => {
|
|
781
|
+
test('checks CLI before daemon', () => {
|
|
782
|
+
// All docker execFileSync calls fail, but we expect the CLI error first.
|
|
783
|
+
execFileSyncMock.mockImplementation(
|
|
784
|
+
(file: string) => {
|
|
785
|
+
if (file === 'docker') {
|
|
786
|
+
throw new Error('docker not available');
|
|
787
|
+
}
|
|
788
|
+
return undefined;
|
|
789
|
+
},
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
793
|
+
try {
|
|
794
|
+
backend.wrap('ls', sandboxRoot);
|
|
795
|
+
throw new Error('should have thrown');
|
|
796
|
+
} catch (err) {
|
|
797
|
+
expect(err).toBeInstanceOf(ToolError);
|
|
798
|
+
expect((err as Error).message).toContain('Docker CLI is not installed');
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
test('does not retry positive checks on subsequent calls', () => {
|
|
803
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
804
|
+
// First call succeeds and caches all checks.
|
|
805
|
+
backend.wrap('ls', sandboxRoot);
|
|
806
|
+
|
|
807
|
+
// Now make all docker commands fail.
|
|
808
|
+
execFileSyncMock.mockImplementation(
|
|
809
|
+
(file: string) => {
|
|
810
|
+
if (file === 'docker') {
|
|
811
|
+
throw new Error('docker unavailable');
|
|
812
|
+
}
|
|
813
|
+
return undefined;
|
|
814
|
+
},
|
|
815
|
+
);
|
|
816
|
+
|
|
817
|
+
// Should still succeed — all checks are cached.
|
|
818
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
819
|
+
expect(result.command).toBe('docker');
|
|
820
|
+
expect(result.sandboxed).toBe(true);
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
test('re-checks negative results on subsequent calls', () => {
|
|
824
|
+
// Start with CLI failing.
|
|
825
|
+
execFileSyncMock.mockImplementation(
|
|
826
|
+
(file: string, args?: readonly string[]) => {
|
|
827
|
+
if (
|
|
828
|
+
file === 'docker' &&
|
|
829
|
+
Array.isArray(args) &&
|
|
830
|
+
args.includes('--version')
|
|
831
|
+
) {
|
|
832
|
+
throw new Error('not found');
|
|
833
|
+
}
|
|
834
|
+
return undefined;
|
|
835
|
+
},
|
|
836
|
+
);
|
|
837
|
+
|
|
838
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
839
|
+
expect(() => backend.wrap('ls', sandboxRoot)).toThrow(
|
|
840
|
+
'Docker CLI is not installed',
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
// Now make it succeed.
|
|
844
|
+
execFileSyncMock.mockImplementation(() => undefined);
|
|
845
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
846
|
+
expect(result.command).toBe('docker');
|
|
847
|
+
});
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
describe('DockerBackend — no unsandboxed fallback', () => {
|
|
851
|
+
test('wrap never returns sandboxed=false', () => {
|
|
852
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
853
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
854
|
+
expect(result.sandboxed).toBe(true);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
test('preflight failure always throws, never returns unsandboxed result', () => {
|
|
858
|
+
execFileSyncMock.mockImplementation(
|
|
859
|
+
(file: string) => {
|
|
860
|
+
if (file === 'docker') {
|
|
861
|
+
throw new Error('not available');
|
|
862
|
+
}
|
|
863
|
+
return undefined;
|
|
864
|
+
},
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
868
|
+
let threw = false;
|
|
869
|
+
try {
|
|
870
|
+
backend.wrap('ls', sandboxRoot);
|
|
871
|
+
} catch (err) {
|
|
872
|
+
threw = true;
|
|
873
|
+
expect(err).toBeInstanceOf(ToolError);
|
|
874
|
+
}
|
|
875
|
+
expect(threw).toBe(true);
|
|
876
|
+
});
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
describe('DockerBackend — complete hardening profile verification', () => {
|
|
880
|
+
test('all security flags are present in correct order before image', () => {
|
|
881
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
882
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
883
|
+
const imageIdx = result.args.indexOf(defaultImage);
|
|
884
|
+
|
|
885
|
+
// All of these must appear before the image name
|
|
886
|
+
const securityFlags = [
|
|
887
|
+
'--network=none',
|
|
888
|
+
'--cap-drop=ALL',
|
|
889
|
+
'--security-opt=no-new-privileges',
|
|
890
|
+
'--read-only',
|
|
891
|
+
];
|
|
892
|
+
for (const flag of securityFlags) {
|
|
893
|
+
const idx = result.args.indexOf(flag);
|
|
894
|
+
expect(idx).toBeGreaterThan(-1);
|
|
895
|
+
expect(idx).toBeLessThan(imageIdx);
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
test('resource limits are all applied', () => {
|
|
900
|
+
const backend = new DockerBackend(
|
|
901
|
+
sandboxRoot,
|
|
902
|
+
{ cpus: 1, memoryMb: 256, pidsLimit: 64 },
|
|
903
|
+
1000,
|
|
904
|
+
1000,
|
|
905
|
+
);
|
|
906
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
907
|
+
expect(result.args).toContain('--cpus=1');
|
|
908
|
+
expect(result.args).toContain('--memory=256m');
|
|
909
|
+
expect(result.args).toContain('--pids-limit=64');
|
|
910
|
+
});
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
// ---------------------------------------------------------------------------
|
|
914
|
+
// Baseline: default sandboxed bash uses network-disabled isolation
|
|
915
|
+
// ---------------------------------------------------------------------------
|
|
916
|
+
|
|
917
|
+
describe('DockerBackend — baseline: network isolation defaults', () => {
|
|
918
|
+
test('default config sets network to "none"', () => {
|
|
919
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
920
|
+
const result = backend.wrap('curl https://example.com', sandboxRoot);
|
|
921
|
+
expect(result.args).toContain('--network=none');
|
|
922
|
+
expect(result.sandboxed).toBe(true);
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
test('--network=none appears before the image argument', () => {
|
|
926
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
927
|
+
const result = backend.wrap('wget http://evil.com', sandboxRoot);
|
|
928
|
+
const networkIdx = result.args.indexOf('--network=none');
|
|
929
|
+
const imageIdx = result.args.indexOf(defaultImage);
|
|
930
|
+
expect(networkIdx).toBeGreaterThan(-1);
|
|
931
|
+
expect(networkIdx).toBeLessThan(imageIdx);
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
test('no other --network flag is present when using defaults', () => {
|
|
935
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
936
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
937
|
+
const networkArgs = result.args.filter((a: string) => a.startsWith('--network='));
|
|
938
|
+
expect(networkArgs).toEqual(['--network=none']);
|
|
939
|
+
});
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
// ---------------------------------------------------------------------------
|
|
943
|
+
// Per-invocation network override (proxied bash)
|
|
944
|
+
// ---------------------------------------------------------------------------
|
|
945
|
+
|
|
946
|
+
describe('DockerBackend — per-invocation network override', () => {
|
|
947
|
+
test('networkMode "proxied" overrides default to --network=bridge', () => {
|
|
948
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
949
|
+
const result = backend.wrap('curl http://127.0.0.1:8080', sandboxRoot, {
|
|
950
|
+
networkMode: 'proxied',
|
|
951
|
+
});
|
|
952
|
+
expect(result.args).toContain('--network=bridge');
|
|
953
|
+
expect(result.args).not.toContain('--network=none');
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
test('networkMode "off" preserves --network=none', () => {
|
|
957
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
958
|
+
const result = backend.wrap('ls', sandboxRoot, { networkMode: 'off' });
|
|
959
|
+
expect(result.args).toContain('--network=none');
|
|
960
|
+
expect(result.args).not.toContain('--network=bridge');
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
test('no networkMode option preserves config default (none)', () => {
|
|
964
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
965
|
+
const result = backend.wrap('ls', sandboxRoot, {});
|
|
966
|
+
expect(result.args).toContain('--network=none');
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
test('undefined options preserves config default (none)', () => {
|
|
970
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
971
|
+
const result = backend.wrap('ls', sandboxRoot, undefined);
|
|
972
|
+
expect(result.args).toContain('--network=none');
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
test('proxied mode overrides config-level network=none', () => {
|
|
976
|
+
// Explicitly set config network to 'none', then override per-invocation
|
|
977
|
+
const backend = new DockerBackend(
|
|
978
|
+
sandboxRoot,
|
|
979
|
+
{ network: 'none' },
|
|
980
|
+
1000,
|
|
981
|
+
1000,
|
|
982
|
+
);
|
|
983
|
+
const result = backend.wrap('wget http://proxy:3128', sandboxRoot, {
|
|
984
|
+
networkMode: 'proxied',
|
|
985
|
+
});
|
|
986
|
+
expect(result.args).toContain('--network=bridge');
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
test('proxied mode with config network=bridge still uses bridge', () => {
|
|
990
|
+
const backend = new DockerBackend(
|
|
991
|
+
sandboxRoot,
|
|
992
|
+
{ network: 'bridge' },
|
|
993
|
+
1000,
|
|
994
|
+
1000,
|
|
995
|
+
);
|
|
996
|
+
const result = backend.wrap('ls', sandboxRoot, { networkMode: 'proxied' });
|
|
997
|
+
expect(result.args).toContain('--network=bridge');
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
test('off mode with config network=bridge preserves config bridge', () => {
|
|
1001
|
+
// When networkMode is 'off', we defer to the config-level network value,
|
|
1002
|
+
// which happens to be 'bridge' here.
|
|
1003
|
+
const backend = new DockerBackend(
|
|
1004
|
+
sandboxRoot,
|
|
1005
|
+
{ network: 'bridge' },
|
|
1006
|
+
1000,
|
|
1007
|
+
1000,
|
|
1008
|
+
);
|
|
1009
|
+
const result = backend.wrap('ls', sandboxRoot, { networkMode: 'off' });
|
|
1010
|
+
// 'off' means "no override" — config-level 'bridge' is used
|
|
1011
|
+
expect(result.args).toContain('--network=bridge');
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
test('only one --network flag is present in proxied mode', () => {
|
|
1015
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
1016
|
+
const result = backend.wrap('ls', sandboxRoot, { networkMode: 'proxied' });
|
|
1017
|
+
const networkArgs = result.args.filter((a: string) =>
|
|
1018
|
+
a.startsWith('--network='),
|
|
1019
|
+
);
|
|
1020
|
+
expect(networkArgs).toEqual(['--network=bridge']);
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
test('all other security flags remain intact in proxied mode', () => {
|
|
1024
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
1025
|
+
const result = backend.wrap('ls', sandboxRoot, { networkMode: 'proxied' });
|
|
1026
|
+
expect(result.args).toContain('--cap-drop=ALL');
|
|
1027
|
+
expect(result.args).toContain('--security-opt=no-new-privileges');
|
|
1028
|
+
expect(result.args).toContain('--read-only');
|
|
1029
|
+
expect(result.args).toContain('--rm');
|
|
1030
|
+
expect(result.sandboxed).toBe(true);
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
test('resource limits still apply in proxied mode', () => {
|
|
1034
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
1035
|
+
const result = backend.wrap('ls', sandboxRoot, { networkMode: 'proxied' });
|
|
1036
|
+
expect(result.args).toContain('--cpus=1');
|
|
1037
|
+
expect(result.args).toContain('--memory=512m');
|
|
1038
|
+
expect(result.args).toContain('--pids-limit=256');
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
test('proxied mode adds --add-host=host.docker.internal:host-gateway', () => {
|
|
1042
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
1043
|
+
const result = backend.wrap('curl http://proxy:3128', sandboxRoot, {
|
|
1044
|
+
networkMode: 'proxied',
|
|
1045
|
+
});
|
|
1046
|
+
expect(result.args).toContain('--add-host=host.docker.internal:host-gateway');
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
test('non-proxied mode does not add --add-host flag', () => {
|
|
1050
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
1051
|
+
const result = backend.wrap('ls', sandboxRoot);
|
|
1052
|
+
const addHostArgs = result.args.filter((a: string) => a.startsWith('--add-host'));
|
|
1053
|
+
expect(addHostArgs).toHaveLength(0);
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
test('--add-host appears before the image argument in proxied mode', () => {
|
|
1057
|
+
const backend = new DockerBackend(sandboxRoot, undefined, 1000, 1000);
|
|
1058
|
+
const result = backend.wrap('ls', sandboxRoot, { networkMode: 'proxied' });
|
|
1059
|
+
const addHostIdx = result.args.indexOf('--add-host=host.docker.internal:host-gateway');
|
|
1060
|
+
const imageIdx = result.args.indexOf(defaultImage);
|
|
1061
|
+
expect(addHostIdx).toBeGreaterThan(-1);
|
|
1062
|
+
expect(addHostIdx).toBeLessThan(imageIdx);
|
|
1063
|
+
});
|
|
1064
|
+
});
|