@vellumai/assistant 0.7.1 → 0.7.2
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/ARCHITECTURE.md +32 -49
- package/Dockerfile +1 -0
- package/README.md +1 -2
- package/__tests__/permissions/gateway-threshold-reader.test.ts +9 -3
- package/bun.lock +26 -26
- package/docs/architecture/security.md +20 -0
- package/docs/plugins.md +7 -9
- package/knip.json +1 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +39 -1
- package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
- package/node_modules/@vellumai/service-contracts/package.json +2 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
- package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +9 -0
- package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
- package/node_modules/@vellumai/twilio-client/package.json +18 -0
- package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
- package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
- package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
- package/openapi.yaml +565 -12
- package/package.json +6 -3
- package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
- package/src/__tests__/app-bundler.test.ts +170 -1
- package/src/__tests__/app-control-flow.test.ts +374 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
- package/src/__tests__/app-executors.test.ts +30 -43
- package/src/__tests__/approval-routes-http.test.ts +23 -6
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
- package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
- package/src/__tests__/assistant-event-hub.test.ts +109 -2
- package/src/__tests__/assistant-event.test.ts +10 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -2
- package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -7
- package/src/__tests__/background-shell-host-bash.test.ts +14 -15
- package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
- package/src/__tests__/btw-routes.test.ts +13 -4
- package/src/__tests__/call-controller.test.ts +49 -1
- package/src/__tests__/call-domain.test.ts +0 -2
- package/src/__tests__/call-routes-http.test.ts +0 -2
- package/src/__tests__/channel-readiness-service.test.ts +59 -1
- package/src/__tests__/checker.test.ts +3 -4
- package/src/__tests__/config-loader-backfill.test.ts +90 -155
- package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-set-platform-guard.test.ts +48 -4
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +2 -2
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-lifecycle.test.ts +36 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-slash-commands.test.ts +0 -4
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +317 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
- package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
- package/src/__tests__/credentials-cli.test.ts +5 -12
- package/src/__tests__/cu-unified-flow.test.ts +185 -23
- package/src/__tests__/daemon-credential-client.test.ts +101 -19
- package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
- package/src/__tests__/heartbeat-service.test.ts +718 -1
- package/src/__tests__/helpers/call-route-handler.ts +7 -1
- package/src/__tests__/host-app-control-proxy.test.ts +602 -0
- package/src/__tests__/host-app-control-routes.test.ts +263 -0
- package/src/__tests__/host-bash-proxy.test.ts +246 -47
- package/src/__tests__/host-bash-routes.test.ts +294 -0
- package/src/__tests__/host-browser-proxy.test.ts +24 -22
- package/src/__tests__/host-browser-routes.test.ts +39 -13
- package/src/__tests__/host-cu-proxy.test.ts +41 -52
- package/src/__tests__/host-cu-routes-targeted.test.ts +300 -0
- package/src/__tests__/host-file-edit-tool.test.ts +47 -1
- package/src/__tests__/host-file-proxy-targeted.test.ts +339 -0
- package/src/__tests__/host-file-proxy.test.ts +37 -43
- package/src/__tests__/host-file-read-tool.test.ts +17 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +262 -0
- package/src/__tests__/host-file-write-tool.test.ts +42 -1
- package/src/__tests__/host-proxy-base.test.ts +312 -0
- package/src/__tests__/host-shell-tool.test.ts +22 -4
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +583 -0
- package/src/__tests__/host-transfer-proxy.test.ts +121 -22
- package/src/__tests__/host-transfer-routes-targeted.test.ts +447 -0
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/identity-intro-cache.test.ts +29 -0
- package/src/__tests__/identity-routes.test.ts +103 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
- package/src/__tests__/inline-command-runner.test.ts +0 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
- package/src/__tests__/integration-status.test.ts +85 -5
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
- package/src/__tests__/mcp-auth-routes.test.ts +197 -0
- package/src/__tests__/mcp-cli.test.ts +338 -2
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
- package/src/__tests__/migration-import-commit-http.test.ts +108 -2
- package/src/__tests__/mock-gateway-ipc.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +0 -2
- package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
- package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
- package/src/__tests__/prechat-onboarding-contract.test.ts +3 -1
- package/src/__tests__/process-message-background-slack.test.ts +2 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +97 -0
- package/src/__tests__/require-fresh-approval.test.ts +0 -1
- package/src/__tests__/retry-backoff.test.ts +87 -0
- package/src/__tests__/runtime-events-sse.test.ts +10 -6
- package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
- package/src/__tests__/schedule-retry.test.ts +715 -0
- package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
- package/src/__tests__/skill-feature-flags.test.ts +43 -41
- package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
- package/src/__tests__/skill-load-inline-command.test.ts +0 -51
- package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
- package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
- package/src/__tests__/slack-channel-config.test.ts +9 -14
- package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-config.test.ts +0 -1
- package/src/__tests__/test-preload.ts +8 -0
- package/src/__tests__/tool-approval-handler.test.ts +3 -4
- package/src/__tests__/tool-audit-listener.test.ts +48 -0
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/twilio-config.test.ts +3 -16
- package/src/__tests__/twilio-routes.test.ts +3 -5
- package/src/__tests__/twilio-validation.test.ts +93 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
- package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
- package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
- package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
- package/src/backup/__tests__/paths.test.ts +0 -22
- package/src/backup/__tests__/restore.test.ts +51 -151
- package/src/backup/paths.ts +2 -18
- package/src/backup/restore.ts +107 -231
- package/src/bundler/app-bundler.ts +51 -3
- package/src/calls/relay-server.ts +4 -44
- package/src/calls/twilio-config.ts +2 -17
- package/src/calls/twilio-rest.ts +33 -105
- package/src/calls/twilio-routes.ts +11 -12
- package/src/channels/types.ts +8 -7
- package/src/cli/commands/__tests__/backup.test.ts +6 -277
- package/src/cli/commands/__tests__/gateway.test.ts +288 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -1
- package/src/cli/commands/backup.ts +6 -331
- package/src/cli/commands/clients.ts +36 -37
- package/src/cli/commands/contacts.ts +73 -0
- package/src/cli/commands/conversations.ts +2 -5
- package/src/cli/commands/credentials.ts +15 -7
- package/src/cli/commands/domain.ts +66 -15
- package/src/cli/commands/gateway.ts +183 -0
- package/src/cli/commands/keys.ts +9 -6
- package/src/cli/commands/mcp.ts +116 -156
- package/src/cli/commands/memory-v2.ts +296 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +0 -2
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -2
- package/src/cli/commands/platform/__tests__/status.test.ts +13 -15
- package/src/cli/commands/platform/disconnect.ts +5 -4
- package/src/cli/commands/platform/index.ts +0 -18
- package/src/cli/lib/daemon-credential-client.ts +110 -28
- package/src/cli/program.ts +2 -0
- package/src/config/assistant-feature-flags.ts +67 -10
- package/src/config/bundled-skills/acp/SKILL.md +6 -0
- package/src/config/bundled-skills/acp/TOOLS.json +1 -22
- package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
- package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
- package/src/config/bundled-skills/app-control/SKILL.md +75 -0
- package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
- package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
- package/src/config/bundled-skills/document/TOOLS.json +0 -8
- package/src/config/bundled-skills/followups/TOOLS.json +0 -12
- package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
- package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
- package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
- package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
- package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +19 -4
- package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
- package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
- package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
- package/src/config/bundled-skills/settings/SKILL.md +4 -0
- package/src/config/bundled-skills/settings/TOOLS.json +0 -12
- package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
- package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
- package/src/config/bundled-skills/subagent/SKILL.md +6 -2
- package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
- package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
- package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
- package/src/config/bundled-tool-registry.ts +21 -0
- package/src/config/env-registry.ts +0 -2
- package/src/config/env.ts +19 -12
- package/src/config/feature-flag-registry.json +21 -133
- package/src/config/loader.ts +73 -99
- package/src/config/sanitize-for-transfer.ts +2 -0
- package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +7 -4
- package/src/config/schemas/calls.ts +0 -9
- package/src/config/schemas/heartbeat.ts +63 -0
- package/src/config/schemas/ingress.ts +10 -6
- package/src/config/schemas/llm.ts +5 -10
- package/src/config/schemas/memory-lifecycle.ts +77 -24
- package/src/config/schemas/memory-v2.ts +48 -4
- package/src/config/schemas/platform.ts +6 -0
- package/src/config/schemas/services.ts +1 -15
- package/src/config/schemas/skills.ts +0 -6
- package/src/config/seed-inference-profiles.ts +1 -1
- package/src/contacts/contact-store.ts +0 -30
- package/src/contacts/contacts-write.ts +0 -27
- package/src/context/window-manager.ts +1 -2
- package/src/credential-execution/feature-gates.ts +10 -10
- package/src/credential-execution/process-manager.ts +12 -41
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +126 -5
- package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
- package/src/daemon/config-watcher.ts +4 -3
- package/src/daemon/conversation-agent-loop-handlers.ts +21 -3
- package/src/daemon/conversation-agent-loop.ts +32 -28
- package/src/daemon/conversation-lifecycle.ts +8 -1
- package/src/daemon/conversation-process.ts +16 -11
- package/src/daemon/conversation-runtime-assembly.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +125 -4
- package/src/daemon/conversation-tool-setup.ts +16 -55
- package/src/daemon/conversation.ts +21 -2
- package/src/daemon/doordash-steps.ts +1 -1
- package/src/daemon/handlers/shared.ts +4 -1
- package/src/daemon/host-app-control-proxy.ts +293 -0
- package/src/daemon/host-bash-proxy.ts +84 -74
- package/src/daemon/host-browser-proxy.ts +67 -82
- package/src/daemon/host-cu-proxy.ts +81 -86
- package/src/daemon/host-file-proxy.ts +93 -69
- package/src/daemon/host-proxy-base.ts +294 -0
- package/src/daemon/host-proxy-preactivation.ts +82 -0
- package/src/daemon/host-transfer-proxy.ts +247 -129
- package/src/daemon/lifecycle.ts +115 -117
- package/src/daemon/message-protocol.ts +3 -8
- package/src/daemon/message-types/contacts.ts +23 -1
- package/src/daemon/message-types/conversations.ts +11 -8
- package/src/daemon/message-types/host-app-control.ts +150 -0
- package/src/daemon/message-types/host-bash.ts +4 -0
- package/src/daemon/message-types/host-cu.ts +2 -0
- package/src/daemon/message-types/host-file.ts +4 -0
- package/src/daemon/message-types/host-transfer.ts +3 -0
- package/src/daemon/message-types/schedules.ts +8 -3
- package/src/daemon/message-types/skills.ts +2 -2
- package/src/daemon/process-message.ts +18 -1
- package/src/daemon/shutdown-handlers.ts +0 -3
- package/src/daemon/tool-setup-types.ts +51 -0
- package/src/daemon/tool-side-effects.ts +1 -1
- package/src/events/tool-audit-listener.ts +2 -1
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +15 -7
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +216 -0
- package/src/heartbeat/heartbeat-run-store.ts +236 -0
- package/src/heartbeat/heartbeat-service.ts +280 -49
- package/src/home/__tests__/post-connect-feed.test.ts +99 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
- package/src/home/__tests__/suggested-prompts.test.ts +89 -0
- package/src/home/post-connect-feed.ts +68 -0
- package/src/home/relationship-state-writer.ts +17 -92
- package/src/home/suggested-prompts.ts +46 -10
- package/src/inbound/public-ingress-urls.ts +32 -34
- package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
- package/src/ipc/assistant-server.ts +14 -1
- package/src/ipc/cli-client.ts +32 -1
- package/src/live-voice/live-voice-metrics.ts +10 -10
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
- package/src/mcp/mcp-auth-orchestrator.ts +213 -0
- package/src/mcp/mcp-auth-state.ts +133 -0
- package/src/mcp/mcp-oauth-provider.ts +19 -0
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
- package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
- package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
- package/src/memory/anisotropy.test.ts +247 -0
- package/src/memory/anisotropy.ts +443 -0
- package/src/memory/auto-analysis-constants.ts +17 -0
- package/src/memory/auto-analysis-guard.ts +5 -15
- package/src/memory/canonical-guardian-store.ts +7 -7
- package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
- package/src/memory/context-search/agent-protocol.ts +6 -6
- package/src/memory/context-search/agent-runner.ts +32 -7
- package/src/memory/context-search/sources/memory-v2.ts +17 -5
- package/src/memory/conversation-crud.ts +1 -1
- package/src/memory/conversation-key-store.ts +2 -15
- package/src/memory/db-init.ts +4 -0
- package/src/memory/embedding-backend.ts +9 -21
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +49 -4
- package/src/memory/graph/conversation-graph-memory.ts +1 -24
- package/src/memory/graph/graph-search.ts +8 -0
- package/src/memory/graph/retriever.ts +28 -0
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
- package/src/memory/jobs/embed-concept-page.ts +28 -2
- package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
- package/src/memory/jobs-store.ts +66 -22
- package/src/memory/jobs-worker.ts +112 -63
- package/src/memory/memory-v2-activation-log-store.ts +1 -1
- package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
- package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/pkb/pkb-search.ts +7 -0
- package/src/memory/qdrant-client.ts +50 -20
- package/src/memory/schema/infrastructure.ts +15 -0
- package/src/memory/search/semantic.ts +7 -0
- package/src/memory/sparse-tokenize.ts +49 -0
- package/src/memory/v2/__tests__/activation.test.ts +77 -95
- package/src/memory/v2/__tests__/injection.test.ts +43 -21
- package/src/memory/v2/__tests__/sim.test.ts +166 -6
- package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
- package/src/memory/v2/__tests__/static-context.test.ts +0 -1
- package/src/memory/v2/activation.ts +69 -88
- package/src/memory/v2/consolidation-job.ts +3 -5
- package/src/memory/v2/constants.ts +7 -0
- package/src/memory/v2/injection.ts +86 -53
- package/src/memory/v2/prompts/consolidation.ts +312 -91
- package/src/memory/v2/qdrant.ts +99 -1
- package/src/memory/v2/sim.ts +126 -16
- package/src/memory/v2/skill-qdrant.ts +12 -3
- package/src/memory/v2/skill-store.ts +16 -1
- package/src/memory/v2/sparse-bm25.ts +245 -0
- package/src/memory/v2/static-context.ts +6 -5
- package/src/messaging/providers/gmail/types.ts +0 -49
- package/src/messaging/providers/slack/adapter.ts +1 -31
- package/src/messaging/providers/slack/types.ts +0 -32
- package/src/notifications/README.md +10 -10
- package/src/notifications/broadcaster.ts +1 -1
- package/src/notifications/guardian-question-mode.ts +5 -5
- package/src/oauth/connect-orchestrator.ts +4 -0
- package/src/oauth/credential-token-resolver.ts +1 -3
- package/src/oauth/manual-token-connection.ts +0 -4
- package/src/outbound-proxy/index.ts +1 -37
- package/src/outbound-proxy/logging.ts +1 -1
- package/src/outbound-proxy/policy.ts +6 -5
- package/src/outbound-proxy/router.ts +2 -1
- package/src/permissions/approval-policy.test.ts +6 -275
- package/src/permissions/approval-policy.ts +0 -51
- package/src/permissions/checker.test.ts +0 -1
- package/src/permissions/checker.ts +3 -17
- package/src/permissions/gateway-threshold-reader.ts +2 -0
- package/src/permissions/prompter.ts +34 -1
- package/src/permissions/secret-prompter.ts +6 -2
- package/src/prompts/bootstrap-cleanup.ts +27 -0
- package/src/prompts/system-prompt.ts +3 -18
- package/src/prompts/templates/SOUL.md +13 -1
- package/src/providers/speech-to-text/provider-catalog.ts +7 -8
- package/src/runtime/assistant-event-hub.ts +118 -96
- package/src/runtime/assistant-event.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
- package/src/runtime/auth/middleware.ts +0 -96
- package/src/runtime/auth/route-policy.ts +19 -0
- package/src/runtime/btw-sidechain.ts +2 -3
- package/src/runtime/channel-invite-transport.ts +2 -48
- package/src/runtime/channel-invite-transports/email.ts +1 -1
- package/src/runtime/channel-invite-transports/slack.ts +1 -1
- package/src/runtime/channel-invite-transports/telegram.ts +1 -1
- package/src/runtime/channel-invite-transports/voice.ts +1 -1
- package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
- package/src/runtime/channel-invite-types.ts +54 -0
- package/src/runtime/channel-readiness-service.ts +32 -13
- package/src/runtime/http-server.ts +3 -329
- package/src/runtime/http-types.ts +0 -5
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
- package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
- package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
- package/src/runtime/migrations/migration-transport.ts +7 -7
- package/src/runtime/migrations/vbundle-builder.ts +327 -60
- package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
- package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
- package/src/runtime/migrations/vbundle-importer.ts +245 -68
- package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
- package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
- package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
- package/src/runtime/migrations/vbundle-validator.ts +114 -0
- package/src/runtime/pending-interactions.ts +35 -9
- package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
- package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
- package/src/runtime/routes/approval-interception-types.ts +13 -0
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
- package/src/runtime/routes/backup-routes.ts +15 -38
- package/src/runtime/routes/btw-routes.ts +14 -37
- package/src/runtime/routes/client-routes.ts +1 -0
- package/src/runtime/routes/contact-prompt-routes.ts +183 -0
- package/src/runtime/routes/conversation-query-routes.ts +36 -1
- package/src/runtime/routes/conversation-routes.ts +30 -13
- package/src/runtime/routes/document-pdf-renderer.ts +165 -0
- package/src/runtime/routes/documents-routes.ts +30 -0
- package/src/runtime/routes/errors.ts +19 -4
- package/src/runtime/routes/events-routes.ts +12 -6
- package/src/runtime/routes/gateway-log-routes.ts +79 -0
- package/src/runtime/routes/guardian-approval-interception.ts +2 -8
- package/src/runtime/routes/heartbeat-routes.ts +103 -38
- package/src/runtime/routes/host-app-control-routes.ts +134 -0
- package/src/runtime/routes/host-bash-routes.ts +36 -6
- package/src/runtime/routes/host-browser-routes.ts +108 -13
- package/src/runtime/routes/host-cu-routes.ts +44 -14
- package/src/runtime/routes/host-file-routes.ts +33 -10
- package/src/runtime/routes/host-transfer-routes.ts +64 -24
- package/src/runtime/routes/http-adapter.ts +1 -0
- package/src/runtime/routes/identity-intro-cache.ts +30 -0
- package/src/runtime/routes/identity-routes.ts +15 -43
- package/src/runtime/routes/inbound-message-handler.ts +1 -9
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
- package/src/runtime/routes/index.ts +8 -0
- package/src/runtime/routes/mcp-auth-routes.ts +132 -0
- package/src/runtime/routes/memory-item-routes.ts +10 -12
- package/src/runtime/routes/memory-v2-routes.ts +441 -1
- package/src/runtime/routes/migration-routes.ts +96 -0
- package/src/runtime/routes/schedule-routes.ts +7 -0
- package/src/runtime/verification-templates.ts +4 -7
- package/src/schedule/integration-status.ts +66 -2
- package/src/schedule/recurrence-engine.ts +4 -1
- package/src/schedule/retry-backoff.ts +18 -0
- package/src/schedule/retry-policy.ts +82 -0
- package/src/schedule/schedule-recovery.ts +64 -0
- package/src/schedule/schedule-store.ts +106 -2
- package/src/schedule/scheduler-types.ts +25 -0
- package/src/schedule/scheduler.ts +63 -38
- package/src/security/oauth-callback-registry.ts +8 -0
- package/src/sequence/analytics.ts +5 -5
- package/src/sequence/engine.ts +1 -1
- package/src/skills/catalog-files.ts +2 -8
- package/src/skills/include-graph.ts +5 -5
- package/src/skills/remote-skill-policy.ts +5 -5
- package/src/skills/skill-file-provider.ts +1 -1
- package/src/skills/skill-file-types.ts +13 -0
- package/src/skills/skillssh-audit-types.ts +28 -0
- package/src/skills/skillssh-registry.ts +8 -21
- package/src/telemetry/types.ts +2 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
- package/src/tools/apps/executors.ts +56 -69
- package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
- package/src/tools/browser/browser-execution.ts +2 -2
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
- package/src/tools/browser/cdp-client/factory.ts +23 -24
- package/src/tools/browser/cdp-client/index.ts +1 -14
- package/src/tools/computer-use/definitions.ts +42 -20
- package/src/tools/executor.ts +2 -0
- package/src/tools/host-filesystem/edit.ts +26 -0
- package/src/tools/host-filesystem/read.ts +26 -0
- package/src/tools/host-filesystem/transfer.ts +31 -1
- package/src/tools/host-filesystem/write.ts +26 -0
- package/src/tools/host-terminal/host-shell.ts +58 -0
- package/src/tools/schedule/create.ts +6 -0
- package/src/tools/schedule/list.ts +2 -0
- package/src/tools/schedule/update.ts +10 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
- package/src/tools/shared/filesystem/path-policy.ts +25 -1
- package/src/tools/skills/load.ts +0 -32
- package/src/tools/tool-approval-handler.ts +1 -5
- package/src/tools/types.ts +4 -0
- package/src/usage/pricing.ts +1 -1
- package/src/workspace/hatched-date.ts +86 -0
- package/src/workspace/migrations/003-seed-device-id.ts +1 -1
- package/src/workspace/migrations/006-services-config.ts +8 -5
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
- package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
- package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
- package/src/workspace/migrations/utils.ts +21 -0
- package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
- package/src/__tests__/twilio-rest.test.ts +0 -34
- package/src/backup/__tests__/backup-key.test.ts +0 -152
- package/src/backup/__tests__/backup-worker.test.ts +0 -782
- package/src/backup/__tests__/offsite-writer.test.ts +0 -641
- package/src/backup/__tests__/stream-crypt.test.ts +0 -228
- package/src/backup/backup-key.ts +0 -137
- package/src/backup/backup-worker.ts +0 -472
- package/src/backup/offsite-writer.ts +0 -222
- package/src/backup/stream-crypt.ts +0 -263
- package/src/daemon/message-types/pairing.ts +0 -58
- package/src/outbound-proxy/config.ts +0 -20
- package/src/outbound-proxy/health.ts +0 -18
- package/src/outbound-proxy/types.ts +0 -150
- package/src/runtime/capability-tokens.ts +0 -190
- package/src/signals/mcp-reload.ts +0 -18
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { open } from "node:fs/promises";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
decryptFile,
|
|
10
|
-
ENCRYPTED_HEADER_SIZE,
|
|
11
|
-
encryptFile,
|
|
12
|
-
GCM_TAG_SIZE,
|
|
13
|
-
verifyEncryptedFile,
|
|
14
|
-
} from "../stream-crypt.js";
|
|
15
|
-
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Fixtures
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
let TEST_DIR: string;
|
|
21
|
-
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
TEST_DIR = join(
|
|
24
|
-
tmpdir(),
|
|
25
|
-
`vellum-stream-crypt-test-${randomBytes(6).toString("hex")}`,
|
|
26
|
-
);
|
|
27
|
-
mkdirSync(TEST_DIR, { recursive: true });
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
try {
|
|
32
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
33
|
-
} catch {
|
|
34
|
-
// best-effort
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
function makeKey(seed: number): Buffer {
|
|
39
|
-
const buf = Buffer.alloc(32);
|
|
40
|
-
for (let i = 0; i < 32; i++) {
|
|
41
|
-
buf[i] = (seed + i) & 0xff;
|
|
42
|
-
}
|
|
43
|
-
return buf;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
// Tests
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
|
|
50
|
-
describe("stream-crypt", () => {
|
|
51
|
-
test("round-trips a 1 KB random file", async () => {
|
|
52
|
-
const key = randomBytes(32);
|
|
53
|
-
const plaintext = randomBytes(1024);
|
|
54
|
-
const plainPath = join(TEST_DIR, "plain.bin");
|
|
55
|
-
const encPath = join(TEST_DIR, "enc.bin");
|
|
56
|
-
const roundTripPath = join(TEST_DIR, "roundtrip.bin");
|
|
57
|
-
|
|
58
|
-
writeFileSync(plainPath, plaintext);
|
|
59
|
-
await encryptFile(plainPath, encPath, key);
|
|
60
|
-
await decryptFile(encPath, roundTripPath, key);
|
|
61
|
-
|
|
62
|
-
const result = readFileSync(roundTripPath);
|
|
63
|
-
expect(result.equals(plaintext)).toBe(true);
|
|
64
|
-
|
|
65
|
-
// Encrypted file has the IV + tag overhead
|
|
66
|
-
const encBytes = readFileSync(encPath);
|
|
67
|
-
expect(encBytes.length).toBe(
|
|
68
|
-
plaintext.length + ENCRYPTED_HEADER_SIZE + GCM_TAG_SIZE,
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("round-trips a 10 MB file (streaming across chunk boundaries)", async () => {
|
|
73
|
-
const key = randomBytes(32);
|
|
74
|
-
const size = 10 * 1024 * 1024;
|
|
75
|
-
const plaintext = randomBytes(size);
|
|
76
|
-
const plainPath = join(TEST_DIR, "plain.bin");
|
|
77
|
-
const encPath = join(TEST_DIR, "enc.bin");
|
|
78
|
-
const roundTripPath = join(TEST_DIR, "roundtrip.bin");
|
|
79
|
-
|
|
80
|
-
writeFileSync(plainPath, plaintext);
|
|
81
|
-
await encryptFile(plainPath, encPath, key);
|
|
82
|
-
await decryptFile(encPath, roundTripPath, key);
|
|
83
|
-
|
|
84
|
-
const result = readFileSync(roundTripPath);
|
|
85
|
-
expect(result.length).toBe(size);
|
|
86
|
-
expect(result.equals(plaintext)).toBe(true);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("auth tag verification: flipping a byte in the ciphertext causes decrypt to throw", async () => {
|
|
90
|
-
const key = randomBytes(32);
|
|
91
|
-
const plaintext = randomBytes(2048);
|
|
92
|
-
const plainPath = join(TEST_DIR, "plain.bin");
|
|
93
|
-
const encPath = join(TEST_DIR, "enc.bin");
|
|
94
|
-
const outPath = join(TEST_DIR, "out.bin");
|
|
95
|
-
|
|
96
|
-
writeFileSync(plainPath, plaintext);
|
|
97
|
-
await encryptFile(plainPath, encPath, key);
|
|
98
|
-
|
|
99
|
-
// Flip one byte somewhere in the middle of the ciphertext body
|
|
100
|
-
// (not in the IV header or the trailing auth tag).
|
|
101
|
-
const ciphertextByteOffset =
|
|
102
|
-
ENCRYPTED_HEADER_SIZE + Math.floor(plaintext.length / 2);
|
|
103
|
-
const fh = await open(encPath, "r+");
|
|
104
|
-
try {
|
|
105
|
-
const one = Buffer.alloc(1);
|
|
106
|
-
await fh.read(one, 0, 1, ciphertextByteOffset);
|
|
107
|
-
one[0] = one[0] ^ 0xff;
|
|
108
|
-
await fh.write(one, 0, 1, ciphertextByteOffset);
|
|
109
|
-
} finally {
|
|
110
|
-
await fh.close();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
await expect(decryptFile(encPath, outPath, key)).rejects.toThrow();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test("decrypting with the wrong key throws", async () => {
|
|
117
|
-
const keyA = makeKey(1);
|
|
118
|
-
const keyB = makeKey(99);
|
|
119
|
-
const plaintext = randomBytes(4096);
|
|
120
|
-
const plainPath = join(TEST_DIR, "plain.bin");
|
|
121
|
-
const encPath = join(TEST_DIR, "enc.bin");
|
|
122
|
-
const outPath = join(TEST_DIR, "out.bin");
|
|
123
|
-
|
|
124
|
-
writeFileSync(plainPath, plaintext);
|
|
125
|
-
await encryptFile(plainPath, encPath, keyA);
|
|
126
|
-
|
|
127
|
-
await expect(decryptFile(encPath, outPath, keyB)).rejects.toThrow();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test("passing a 16-byte key throws the typed error", async () => {
|
|
131
|
-
const badKey = randomBytes(16);
|
|
132
|
-
const plainPath = join(TEST_DIR, "plain.bin");
|
|
133
|
-
const encPath = join(TEST_DIR, "enc.bin");
|
|
134
|
-
|
|
135
|
-
writeFileSync(plainPath, Buffer.from("hello world", "utf-8"));
|
|
136
|
-
|
|
137
|
-
await expect(encryptFile(plainPath, encPath, badKey)).rejects.toThrow(
|
|
138
|
-
"Backup encryption key must be 32 bytes",
|
|
139
|
-
);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("IV uniqueness: encrypting the same file twice yields different outputs", async () => {
|
|
143
|
-
const key = randomBytes(32);
|
|
144
|
-
const plaintext = randomBytes(4096);
|
|
145
|
-
const plainPath = join(TEST_DIR, "plain.bin");
|
|
146
|
-
const encPathA = join(TEST_DIR, "enc-a.bin");
|
|
147
|
-
const encPathB = join(TEST_DIR, "enc-b.bin");
|
|
148
|
-
|
|
149
|
-
writeFileSync(plainPath, plaintext);
|
|
150
|
-
await encryptFile(plainPath, encPathA, key);
|
|
151
|
-
await encryptFile(plainPath, encPathB, key);
|
|
152
|
-
|
|
153
|
-
const a = readFileSync(encPathA);
|
|
154
|
-
const b = readFileSync(encPathB);
|
|
155
|
-
|
|
156
|
-
expect(a.equals(b)).toBe(false);
|
|
157
|
-
// The first 12 bytes are the IV — they must differ with overwhelming
|
|
158
|
-
// probability (collision chance is 1/2^96 for random 12-byte IVs).
|
|
159
|
-
expect(
|
|
160
|
-
a.subarray(0, ENCRYPTED_HEADER_SIZE).equals(
|
|
161
|
-
b.subarray(0, ENCRYPTED_HEADER_SIZE),
|
|
162
|
-
),
|
|
163
|
-
).toBe(false);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test("verifyEncryptedFile returns true for a valid bundle and false for a tampered one", async () => {
|
|
167
|
-
const key = randomBytes(32);
|
|
168
|
-
const plaintext = randomBytes(1024);
|
|
169
|
-
const plainPath = join(TEST_DIR, "plain.bin");
|
|
170
|
-
const encPath = join(TEST_DIR, "enc.bin");
|
|
171
|
-
|
|
172
|
-
writeFileSync(plainPath, plaintext);
|
|
173
|
-
await encryptFile(plainPath, encPath, key);
|
|
174
|
-
|
|
175
|
-
expect(await verifyEncryptedFile(encPath, key)).toBe(true);
|
|
176
|
-
|
|
177
|
-
// Tamper the ciphertext and re-verify
|
|
178
|
-
const fh = await open(encPath, "r+");
|
|
179
|
-
try {
|
|
180
|
-
const flipOffset = ENCRYPTED_HEADER_SIZE + 10;
|
|
181
|
-
const one = Buffer.alloc(1);
|
|
182
|
-
await fh.read(one, 0, 1, flipOffset);
|
|
183
|
-
one[0] = one[0] ^ 0x01;
|
|
184
|
-
await fh.write(one, 0, 1, flipOffset);
|
|
185
|
-
} finally {
|
|
186
|
-
await fh.close();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
expect(await verifyEncryptedFile(encPath, key)).toBe(false);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test("verifyEncryptedFile returns true for a valid bundle even when tmpdir is unwritable", async () => {
|
|
193
|
-
const key = randomBytes(32);
|
|
194
|
-
const plaintext = randomBytes(2048);
|
|
195
|
-
const plainPath = join(TEST_DIR, "plain.bin");
|
|
196
|
-
const encPath = join(TEST_DIR, "enc.bin");
|
|
197
|
-
|
|
198
|
-
writeFileSync(plainPath, plaintext);
|
|
199
|
-
await encryptFile(plainPath, encPath, key);
|
|
200
|
-
|
|
201
|
-
// Point tmpdir at a path that cannot be written to. The implementation
|
|
202
|
-
// must authenticate the bundle without writing any scratch file — a full
|
|
203
|
-
// or read-only /tmp must not block restore for healthy backups.
|
|
204
|
-
const originalTmpdir = process.env.TMPDIR;
|
|
205
|
-
const originalTmp = process.env.TMP;
|
|
206
|
-
const originalTemp = process.env.TEMP;
|
|
207
|
-
process.env.TMPDIR = "/dev/null/does-not-exist";
|
|
208
|
-
process.env.TMP = "/dev/null/does-not-exist";
|
|
209
|
-
process.env.TEMP = "/dev/null/does-not-exist";
|
|
210
|
-
try {
|
|
211
|
-
expect(await verifyEncryptedFile(encPath, key)).toBe(true);
|
|
212
|
-
} finally {
|
|
213
|
-
if (originalTmpdir === undefined) delete process.env.TMPDIR;
|
|
214
|
-
else process.env.TMPDIR = originalTmpdir;
|
|
215
|
-
if (originalTmp === undefined) delete process.env.TMP;
|
|
216
|
-
else process.env.TMP = originalTmp;
|
|
217
|
-
if (originalTemp === undefined) delete process.env.TEMP;
|
|
218
|
-
else process.env.TEMP = originalTemp;
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
test("verifyEncryptedFile rethrows filesystem errors (e.g. ENOENT) instead of masking them as tamper", async () => {
|
|
223
|
-
const key = randomBytes(32);
|
|
224
|
-
const missingPath = join(TEST_DIR, "does-not-exist.bin");
|
|
225
|
-
|
|
226
|
-
await expect(verifyEncryptedFile(missingPath, key)).rejects.toThrow();
|
|
227
|
-
});
|
|
228
|
-
});
|
package/src/backup/backup-key.ts
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Backup key management.
|
|
3
|
-
*
|
|
4
|
-
* The backup key is a 32-byte random secret used to authenticate / encrypt
|
|
5
|
-
* workspace backups. It is generated once per install and persisted to disk
|
|
6
|
-
* so subsequent backup/restore operations reuse the same key.
|
|
7
|
-
*
|
|
8
|
-
* This module is intentionally pure: callers pass the full `keyPath` rather
|
|
9
|
-
* than resolving a default location. That keeps the helpers trivially
|
|
10
|
-
* testable against temp directories and avoids any coupling to daemon
|
|
11
|
-
* startup, workspace layout, or global path helpers.
|
|
12
|
-
*
|
|
13
|
-
* On-disk invariants:
|
|
14
|
-
* - Parent directory is created with mode `0o700`.
|
|
15
|
-
* - Key file is written atomically (temp + `link`) with mode `0o600`, so
|
|
16
|
-
* concurrent callers converge on the first winner's bytes.
|
|
17
|
-
* - Key file is exactly 32 bytes; any other size is treated as corruption.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { randomBytes } from "node:crypto";
|
|
21
|
-
import {
|
|
22
|
-
chmod,
|
|
23
|
-
link,
|
|
24
|
-
mkdir,
|
|
25
|
-
readFile,
|
|
26
|
-
stat,
|
|
27
|
-
unlink,
|
|
28
|
-
writeFile,
|
|
29
|
-
} from "node:fs/promises";
|
|
30
|
-
import { dirname } from "node:path";
|
|
31
|
-
|
|
32
|
-
/** Required length of the backup key file, in bytes. */
|
|
33
|
-
const BACKUP_KEY_LENGTH = 32;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Check whether a filesystem path exists without throwing.
|
|
37
|
-
*
|
|
38
|
-
* Only `ENOENT` is treated as "missing". Any other errno (EIO, ESTALE,
|
|
39
|
-
* EACCES, ...) is rethrown — we must not silently treat a transient I/O
|
|
40
|
-
* failure as "file is absent" because that can cause an existing backup
|
|
41
|
-
* key to be rotated away under the caller's feet, breaking decryption of
|
|
42
|
-
* data encrypted with the prior key.
|
|
43
|
-
*/
|
|
44
|
-
async function pathExists(path: string): Promise<boolean> {
|
|
45
|
-
try {
|
|
46
|
-
await stat(path);
|
|
47
|
-
return true;
|
|
48
|
-
} catch (err) {
|
|
49
|
-
if ((err as NodeJS.ErrnoException)?.code === "ENOENT") return false;
|
|
50
|
-
throw err;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Read the backup key from disk if it exists.
|
|
56
|
-
*
|
|
57
|
-
* Returns the raw 32-byte buffer, or `null` if the file is missing. Intended
|
|
58
|
-
* for read-only callers (e.g. restore paths) that should not create a new
|
|
59
|
-
* key as a side effect.
|
|
60
|
-
*
|
|
61
|
-
* Throws if the file exists but is not exactly 32 bytes -- callers should
|
|
62
|
-
* treat that as a corruption signal rather than silently regenerating.
|
|
63
|
-
*/
|
|
64
|
-
export async function readBackupKey(keyPath: string): Promise<Buffer | null> {
|
|
65
|
-
if (!(await pathExists(keyPath))) return null;
|
|
66
|
-
const buf = await readFile(keyPath);
|
|
67
|
-
if (buf.length !== BACKUP_KEY_LENGTH) {
|
|
68
|
-
throw new Error(
|
|
69
|
-
`Backup key at ${keyPath} has invalid length ${buf.length} (expected ${BACKUP_KEY_LENGTH})`,
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
return buf;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Ensure a backup key exists at `keyPath`, returning its bytes.
|
|
77
|
-
*
|
|
78
|
-
* - If the file exists, it is read and validated. A wrong-size file throws,
|
|
79
|
-
* so a corrupt key is never silently replaced.
|
|
80
|
-
* - Otherwise, the parent directory is created (mode `0o700`), a fresh
|
|
81
|
-
* 32-byte random key is generated, written to a unique tmp file, and
|
|
82
|
-
* atomically published to `keyPath` via `link()`.
|
|
83
|
-
*
|
|
84
|
-
* Concurrency: callers that race here must all converge on the same bytes
|
|
85
|
-
* — otherwise one caller encrypts data with bytes that will never be
|
|
86
|
-
* persisted and can never be decrypted.
|
|
87
|
-
*
|
|
88
|
-
* We use the canonical Unix atomic-create idiom: write full contents to
|
|
89
|
-
* a per-call tmp file, then `link(tmp, keyPath)`. `link` fails with
|
|
90
|
-
* `EEXIST` if `keyPath` already exists, which makes exactly one racing
|
|
91
|
-
* caller the winner; the rest read the winner's bytes. `rename(2)` by
|
|
92
|
-
* contrast overwrites the destination and is not race-safe here — two
|
|
93
|
-
* renames can leave either caller's bytes on disk regardless of who
|
|
94
|
-
* generated them, so a lost caller would return bytes that don't match
|
|
95
|
-
* what's persisted. `link` avoids that entirely.
|
|
96
|
-
*/
|
|
97
|
-
export async function ensureBackupKey(keyPath: string): Promise<Buffer> {
|
|
98
|
-
const existing = await readBackupKey(keyPath);
|
|
99
|
-
if (existing) return existing;
|
|
100
|
-
|
|
101
|
-
const parent = dirname(keyPath);
|
|
102
|
-
await mkdir(parent, { recursive: true, mode: 0o700 });
|
|
103
|
-
|
|
104
|
-
const key = randomBytes(BACKUP_KEY_LENGTH);
|
|
105
|
-
const tmpPath = `${keyPath}.tmp.${process.pid}.${randomBytes(8).toString("hex")}`;
|
|
106
|
-
try {
|
|
107
|
-
// `wx` fails if tmpPath somehow exists (stale orphan or collision) so
|
|
108
|
-
// we never silently overwrite another writer's in-flight tmp file.
|
|
109
|
-
await writeFile(tmpPath, key, { flag: "wx", mode: 0o600 });
|
|
110
|
-
// Some platforms / umasks ignore the `mode` option on writeFile, so
|
|
111
|
-
// enforce 0o600 explicitly before publishing.
|
|
112
|
-
await chmod(tmpPath, 0o600);
|
|
113
|
-
try {
|
|
114
|
-
// Atomic publish: only one racing caller's link() succeeds.
|
|
115
|
-
await link(tmpPath, keyPath);
|
|
116
|
-
return key;
|
|
117
|
-
} catch (err) {
|
|
118
|
-
if ((err as NodeJS.ErrnoException)?.code !== "EEXIST") throw err;
|
|
119
|
-
// Another caller won the race. Return their bytes, not ours.
|
|
120
|
-
const winner = await readBackupKey(keyPath);
|
|
121
|
-
if (!winner) {
|
|
122
|
-
throw new Error(
|
|
123
|
-
`link() reported EEXIST but ${keyPath} is unreadable`,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
return winner;
|
|
127
|
-
}
|
|
128
|
-
} finally {
|
|
129
|
-
// Remove our tmp file whether we won (tmp is a hard link to keyPath,
|
|
130
|
-
// safe to unlink), lost, or errored. Best-effort.
|
|
131
|
-
try {
|
|
132
|
-
await unlink(tmpPath);
|
|
133
|
-
} catch {
|
|
134
|
-
// ignore
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|