@openparachute/agent 0.1.2 → 0.2.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/.parachute/module.json +124 -8
- package/LICENSE +2 -16
- package/README.md +118 -166
- package/package.json +32 -43
- package/scripts/spawn-agent.ts +371 -0
- package/src/_parked/interactive-spawn.test.ts +324 -0
- package/src/_parked/interactive-spawn.ts +701 -0
- package/src/agent-defs.test.ts +1504 -0
- package/src/agent-defs.ts +1702 -0
- package/src/agent-mcp-config.test.ts +115 -0
- package/src/agent-mcp-config.ts +115 -0
- package/src/agents.test.ts +360 -0
- package/src/agents.ts +379 -0
- package/src/auth.test.ts +46 -0
- package/src/auth.ts +140 -0
- package/src/backends/attached-queue.test.ts +376 -0
- package/src/backends/attached-queue.ts +372 -0
- package/src/backends/programmatic.test.ts +1715 -0
- package/src/backends/programmatic.ts +927 -0
- package/src/backends/registry.test.ts +1494 -0
- package/src/backends/registry.ts +1202 -0
- package/src/backends/stream-json.test.ts +570 -0
- package/src/backends/stream-json.ts +392 -0
- package/src/backends/types.ts +223 -0
- package/src/bridge.ts +417 -0
- package/src/channel-backend-wiring.test.ts +237 -0
- package/src/credentials.test.ts +274 -0
- package/src/credentials.ts +380 -0
- package/src/cron.test.ts +342 -0
- package/src/cron.ts +380 -0
- package/src/daemon-agent-def-api.test.ts +166 -0
- package/src/daemon-agent-defs-api.test.ts +953 -0
- package/src/daemon-agent-env-api.test.ts +338 -0
- package/src/daemon-attached-queue-store.test.ts +65 -0
- package/src/daemon-config-api.test.ts +962 -0
- package/src/daemon-jobs-api.test.ts +271 -0
- package/src/daemon-vault-chat.test.ts +250 -0
- package/src/daemon.test.ts +746 -0
- package/src/daemon.ts +3314 -0
- package/src/def-vaults.test.ts +136 -0
- package/src/def-vaults.ts +165 -0
- package/src/delivery-state.test.ts +110 -0
- package/src/delivery-state.ts +154 -0
- package/src/effective-env.test.ts +114 -0
- package/src/effective-env.ts +184 -0
- package/src/env-compat.ts +39 -0
- package/src/grants.test.ts +638 -0
- package/src/grants.ts +675 -0
- package/src/hub-jwt.test.ts +161 -0
- package/src/hub-jwt.ts +182 -0
- package/src/jobs.test.ts +245 -0
- package/src/jobs.ts +266 -0
- package/src/mcp-http.test.ts +265 -0
- package/src/mcp-http.ts +771 -0
- package/src/mint-token.test.ts +152 -0
- package/src/mint-token.ts +139 -0
- package/src/module-manifest.test.ts +158 -0
- package/src/oauth-discovery.ts +134 -0
- package/src/programmatic-wiring.test.ts +838 -0
- package/src/registry.test.ts +227 -0
- package/src/registry.ts +228 -0
- package/src/resolve-port.test.ts +64 -0
- package/src/routing.test.ts +184 -0
- package/src/routing.ts +76 -0
- package/src/runner.test.ts +506 -0
- package/src/runner.ts +255 -0
- package/src/sandbox/config.test.ts +150 -0
- package/src/sandbox/config.ts +102 -0
- package/src/sandbox/egress.test.ts +113 -0
- package/src/sandbox/egress.ts +123 -0
- package/src/sandbox/index.ts +180 -0
- package/src/sandbox/live-seatbelt.test.ts +277 -0
- package/src/sandbox/mounts.test.ts +154 -0
- package/src/sandbox/mounts.ts +133 -0
- package/src/sandbox/sandbox.test.ts +168 -0
- package/src/sandbox/types.ts +382 -0
- package/src/services-manifest.test.ts +106 -0
- package/src/services-manifest.ts +95 -0
- package/src/spa-serve.test.ts +116 -0
- package/src/spa-serve.ts +116 -0
- package/src/spawn-agent-cli.test.ts +172 -0
- package/src/spawn-agent.test.ts +1218 -0
- package/src/spawn-agent.ts +569 -0
- package/src/spawn-deps.test.ts +54 -0
- package/src/spawn-deps.ts +166 -0
- package/src/telegram/api.ts +153 -0
- package/src/terminal-assets.test.ts +50 -0
- package/src/terminal-assets.ts +79 -0
- package/src/terminal-ui.ts +305 -0
- package/src/terminal.test.ts +530 -0
- package/src/terminal.ts +458 -0
- package/src/transport.ts +270 -0
- package/src/transports/http-ui.test.ts +455 -0
- package/src/transports/http-ui.ts +201 -0
- package/src/transports/telegram.test.ts +174 -0
- package/src/transports/telegram.ts +426 -0
- package/src/transports/vault.test.ts +2011 -0
- package/src/transports/vault.ts +1790 -0
- package/src/ui-kit.test.ts +178 -0
- package/src/ui-kit.ts +402 -0
- package/tsconfig.json +8 -14
- package/web/ui/tsconfig.json +2 -1
- package/.claude/scheduled_tasks.lock +0 -1
- package/.claude/settings.json +0 -5
- package/.claude/skills/add-atomic-chat-tool/SKILL.md +0 -243
- package/.claude/skills/add-atomic-chat-tool/atomic-chat-mcp-stdio.ts +0 -229
- package/.claude/skills/add-codex/SKILL.md +0 -161
- package/.claude/skills/add-dashboard/SKILL.md +0 -138
- package/.claude/skills/add-dashboard/resources/dashboard-pusher.ts +0 -495
- package/.claude/skills/add-emacs/SKILL.md +0 -296
- package/.claude/skills/add-gcal-tool/SKILL.md +0 -210
- package/.claude/skills/add-gchat/REMOVE.md +0 -6
- package/.claude/skills/add-gchat/SKILL.md +0 -92
- package/.claude/skills/add-gchat/VERIFY.md +0 -3
- package/.claude/skills/add-github/REMOVE.md +0 -6
- package/.claude/skills/add-github/SKILL.md +0 -148
- package/.claude/skills/add-github/VERIFY.md +0 -3
- package/.claude/skills/add-gmail-tool/SKILL.md +0 -229
- package/.claude/skills/add-imessage/REMOVE.md +0 -6
- package/.claude/skills/add-imessage/SKILL.md +0 -113
- package/.claude/skills/add-imessage/VERIFY.md +0 -3
- package/.claude/skills/add-karpathy-llm-wiki/SKILL.md +0 -110
- package/.claude/skills/add-karpathy-llm-wiki/llm-wiki.md +0 -75
- package/.claude/skills/add-linear/REMOVE.md +0 -6
- package/.claude/skills/add-linear/SKILL.md +0 -168
- package/.claude/skills/add-linear/VERIFY.md +0 -3
- package/.claude/skills/add-macos-statusbar/SKILL.md +0 -133
- package/.claude/skills/add-macos-statusbar/add/src/statusbar.swift +0 -147
- package/.claude/skills/add-matrix/REMOVE.md +0 -6
- package/.claude/skills/add-matrix/SKILL.md +0 -148
- package/.claude/skills/add-matrix/VERIFY.md +0 -3
- package/.claude/skills/add-ollama-provider/SKILL.md +0 -179
- package/.claude/skills/add-ollama-tool/SKILL.md +0 -193
- package/.claude/skills/add-opencode/SKILL.md +0 -229
- package/.claude/skills/add-parallel/SKILL.md +0 -290
- package/.claude/skills/add-resend/REMOVE.md +0 -6
- package/.claude/skills/add-resend/SKILL.md +0 -93
- package/.claude/skills/add-resend/VERIFY.md +0 -3
- package/.claude/skills/add-signal/REMOVE.md +0 -13
- package/.claude/skills/add-signal/SKILL.md +0 -318
- package/.claude/skills/add-signal/VERIFY.md +0 -5
- package/.claude/skills/add-slack/REMOVE.md +0 -6
- package/.claude/skills/add-slack/SKILL.md +0 -112
- package/.claude/skills/add-slack/VERIFY.md +0 -3
- package/.claude/skills/add-teams/REMOVE.md +0 -6
- package/.claude/skills/add-teams/SKILL.md +0 -207
- package/.claude/skills/add-teams/VERIFY.md +0 -3
- package/.claude/skills/add-vercel/SKILL.md +0 -147
- package/.claude/skills/add-vercel/container-skills/vercel-cli/SKILL.md +0 -103
- package/.claude/skills/add-webex/REMOVE.md +0 -6
- package/.claude/skills/add-webex/SKILL.md +0 -88
- package/.claude/skills/add-webex/VERIFY.md +0 -3
- package/.claude/skills/add-wechat/REMOVE.md +0 -49
- package/.claude/skills/add-wechat/SKILL.md +0 -170
- package/.claude/skills/add-wechat/scripts/wire-dm.ts +0 -172
- package/.claude/skills/add-whatsapp/SKILL.md +0 -264
- package/.claude/skills/add-whatsapp-cloud/REMOVE.md +0 -6
- package/.claude/skills/add-whatsapp-cloud/SKILL.md +0 -95
- package/.claude/skills/add-whatsapp-cloud/VERIFY.md +0 -3
- package/.claude/skills/claw/SKILL.md +0 -131
- package/.claude/skills/claw/scripts/claw +0 -374
- package/.claude/skills/convert-to-apple-container/SKILL.md +0 -212
- package/.claude/skills/customize/SKILL.md +0 -110
- package/.claude/skills/debug/SKILL.md +0 -349
- package/.claude/skills/get-qodo-rules/SKILL.md +0 -122
- package/.claude/skills/get-qodo-rules/references/output-format.md +0 -41
- package/.claude/skills/get-qodo-rules/references/pagination.md +0 -33
- package/.claude/skills/get-qodo-rules/references/repository-scope.md +0 -26
- package/.claude/skills/init-first-agent/SKILL.md +0 -120
- package/.claude/skills/init-onecli/SKILL.md +0 -270
- package/.claude/skills/manage-channels/SKILL.md +0 -87
- package/.claude/skills/manage-mounts/SKILL.md +0 -47
- package/.claude/skills/migrate-from-openclaw/MIGRATE_CRONS.md +0 -100
- package/.claude/skills/migrate-from-openclaw/SKILL.md +0 -447
- package/.claude/skills/migrate-from-openclaw/scripts/discover-openclaw.ts +0 -734
- package/.claude/skills/migrate-from-openclaw/scripts/extract-channel-credentials.ts +0 -476
- package/.claude/skills/migrate-nanoclaw/SKILL.md +0 -484
- package/.claude/skills/migrate-nanoclaw/diagnostics.md +0 -51
- package/.claude/skills/qodo-pr-resolver/SKILL.md +0 -326
- package/.claude/skills/qodo-pr-resolver/resources/providers.md +0 -329
- package/.claude/skills/update-nanoclaw/SKILL.md +0 -243
- package/.claude/skills/update-nanoclaw/diagnostics.md +0 -48
- package/.claude/skills/update-skills/SKILL.md +0 -130
- package/.claude/skills/use-native-credential-proxy/SKILL.md +0 -167
- package/.claude/skills/x-integration/SKILL.md +0 -417
- package/.claude/skills/x-integration/agent.ts +0 -243
- package/.claude/skills/x-integration/host.ts +0 -155
- package/.claude/skills/x-integration/lib/browser.ts +0 -148
- package/.claude/skills/x-integration/lib/config.ts +0 -62
- package/.claude/skills/x-integration/scripts/like.ts +0 -56
- package/.claude/skills/x-integration/scripts/post.ts +0 -66
- package/.claude/skills/x-integration/scripts/quote.ts +0 -80
- package/.claude/skills/x-integration/scripts/reply.ts +0 -74
- package/.claude/skills/x-integration/scripts/retweet.ts +0 -62
- package/.claude/skills/x-integration/scripts/setup.ts +0 -87
- package/.github/CODEOWNERS +0 -10
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -18
- package/.github/workflows/bump-version.yml +0 -35
- package/.github/workflows/ci.yml +0 -39
- package/.github/workflows/label-pr.yml +0 -40
- package/.github/workflows/update-tokens.yml +0 -43
- package/.husky/pre-commit +0 -1
- package/.mcp.json +0 -3
- package/.nvmrc +0 -1
- package/.prettierrc +0 -4
- package/CHANGELOG.md +0 -263
- package/CLAUDE.md +0 -307
- package/CODE_OF_CONDUCT.md +0 -128
- package/CONTRIBUTING.md +0 -159
- package/CONTRIBUTORS.md +0 -26
- package/LICENSE-NANOCLAW-MIT +0 -21
- package/README_ja.md +0 -194
- package/README_zh.md +0 -194
- package/assets/nanoclaw-favicon.png +0 -0
- package/assets/nanoclaw-icon.png +0 -0
- package/assets/nanoclaw-logo-dark.png +0 -0
- package/assets/nanoclaw-logo.png +0 -0
- package/assets/nanoclaw-profile.jpeg +0 -0
- package/assets/nanoclaw-sales.png +0 -0
- package/assets/social-preview.jpg +0 -0
- package/config-examples/mount-allowlist.json +0 -25
- package/container/.dockerignore +0 -2
- package/container/CLAUDE.md +0 -21
- package/container/Dockerfile +0 -121
- package/container/agent-runner/bun.lock +0 -243
- package/container/agent-runner/package.json +0 -22
- package/container/agent-runner/scripts/sdk-signal-probe.ts +0 -169
- package/container/agent-runner/src/config.ts +0 -55
- package/container/agent-runner/src/db/connection.ts +0 -267
- package/container/agent-runner/src/db/index.ts +0 -20
- package/container/agent-runner/src/db/messages-in.ts +0 -138
- package/container/agent-runner/src/db/messages-out.ts +0 -143
- package/container/agent-runner/src/db/session-routing.ts +0 -30
- package/container/agent-runner/src/db/session-state.test.ts +0 -100
- package/container/agent-runner/src/db/session-state.ts +0 -79
- package/container/agent-runner/src/destinations.ts +0 -135
- package/container/agent-runner/src/formatter.test.ts +0 -167
- package/container/agent-runner/src/formatter.ts +0 -260
- package/container/agent-runner/src/index.ts +0 -110
- package/container/agent-runner/src/integration.test.ts +0 -121
- package/container/agent-runner/src/mcp-tools/agents.instructions.md +0 -26
- package/container/agent-runner/src/mcp-tools/agents.ts +0 -66
- package/container/agent-runner/src/mcp-tools/core.instructions.md +0 -27
- package/container/agent-runner/src/mcp-tools/core.ts +0 -262
- package/container/agent-runner/src/mcp-tools/index.ts +0 -22
- package/container/agent-runner/src/mcp-tools/interactive.instructions.md +0 -22
- package/container/agent-runner/src/mcp-tools/interactive.ts +0 -169
- package/container/agent-runner/src/mcp-tools/scheduling.instructions.md +0 -40
- package/container/agent-runner/src/mcp-tools/scheduling.ts +0 -299
- package/container/agent-runner/src/mcp-tools/self-mod.instructions.md +0 -25
- package/container/agent-runner/src/mcp-tools/self-mod.ts +0 -120
- package/container/agent-runner/src/mcp-tools/server.ts +0 -54
- package/container/agent-runner/src/mcp-tools/types.ts +0 -6
- package/container/agent-runner/src/poll-loop.test.ts +0 -248
- package/container/agent-runner/src/poll-loop.ts +0 -437
- package/container/agent-runner/src/providers/claude.ts +0 -379
- package/container/agent-runner/src/providers/factory.test.ts +0 -19
- package/container/agent-runner/src/providers/factory.ts +0 -13
- package/container/agent-runner/src/providers/index.ts +0 -6
- package/container/agent-runner/src/providers/mock.ts +0 -77
- package/container/agent-runner/src/providers/provider-registry.ts +0 -33
- package/container/agent-runner/src/providers/types.ts +0 -82
- package/container/agent-runner/src/scheduling/task-script.ts +0 -121
- package/container/agent-runner/src/timezone.test.ts +0 -93
- package/container/agent-runner/src/timezone.ts +0 -107
- package/container/agent-runner/tsconfig.json +0 -14
- package/container/build.sh +0 -48
- package/container/entrypoint.sh +0 -16
- package/container/skills/agent-browser/SKILL.md +0 -159
- package/container/skills/frontend-engineer/SKILL.md +0 -157
- package/container/skills/self-customize/SKILL.md +0 -87
- package/container/skills/slack-formatting/SKILL.md +0 -94
- package/container/skills/vercel-cli/SKILL.md +0 -111
- package/container/skills/welcome/SKILL.md +0 -85
- package/docs/APPLE-CONTAINER-NETWORKING.md +0 -90
- package/docs/BRANCH-FORK-MAINTENANCE.md +0 -81
- package/docs/README.md +0 -25
- package/docs/SDK_DEEP_DIVE.md +0 -643
- package/docs/SECURITY.md +0 -162
- package/docs/agent-runner-details.md +0 -749
- package/docs/api-details.md +0 -365
- package/docs/architecture-diagram.html +0 -422
- package/docs/architecture-diagram.md +0 -215
- package/docs/architecture.md +0 -751
- package/docs/audit/2026-04-30-channel-endpoint-audit.md +0 -36
- package/docs/build-and-runtime.md +0 -80
- package/docs/cross-mount-stress/README.md +0 -112
- package/docs/cross-mount-stress/container-writer-retry.mjs +0 -55
- package/docs/cross-mount-stress/container-writer-slow.mjs +0 -42
- package/docs/cross-mount-stress/container-writer.mjs +0 -47
- package/docs/cross-mount-stress/host-writer-retry.mjs +0 -55
- package/docs/cross-mount-stress/host-writer-slow.mjs +0 -43
- package/docs/cross-mount-stress/host-writer.mjs +0 -47
- package/docs/db-central.md +0 -316
- package/docs/db-session.md +0 -183
- package/docs/db.md +0 -119
- package/docs/design/2026-04-29-vault-management-ui.md +0 -231
- package/docs/design/2026-04-30-channel-wiring-rework.md +0 -234
- package/docs/design/2026-05-01-channel-wiring-approvals-deep-dive.md +0 -272
- package/docs/design/2026-05-02-channel-policy-and-approval-routing.md +0 -250
- package/docs/docker-sandboxes.md +0 -359
- package/docs/isolation-model.md +0 -88
- package/docs/ollama.md +0 -79
- package/docs/parachute-integration.md +0 -109
- package/docs/post-night-rebirth-reflections.md +0 -151
- package/eslint.config.js +0 -32
- package/pnpm-workspace.yaml +0 -8
- package/repo-tokens/README.md +0 -113
- package/repo-tokens/action.yml +0 -186
- package/repo-tokens/badge.svg +0 -23
- package/repo-tokens/examples/green.svg +0 -14
- package/repo-tokens/examples/red.svg +0 -14
- package/repo-tokens/examples/yellow-green.svg +0 -14
- package/repo-tokens/examples/yellow.svg +0 -14
- package/scripts/chat.ts +0 -101
- package/scripts/cleanup-sessions.sh +0 -150
- package/scripts/init-cli-agent.ts +0 -172
- package/scripts/init-first-agent.ts +0 -378
- package/scripts/parachute.ts +0 -158
- package/scripts/run-migrations.ts +0 -105
- package/scripts/sanity-live-poll.ts +0 -95
- package/scripts/seed-discord.ts +0 -80
- package/scripts/test-v2-agent.ts +0 -106
- package/scripts/test-v2-channel-e2e.ts +0 -265
- package/scripts/test-v2-host.ts +0 -184
- package/src/channels/adapter.ts +0 -214
- package/src/channels/api-translator.test.ts +0 -306
- package/src/channels/api-translator.ts +0 -214
- package/src/channels/ask-question.ts +0 -46
- package/src/channels/channel-registry.test.ts +0 -421
- package/src/channels/channel-registry.ts +0 -313
- package/src/channels/chat-sdk-bridge.test.ts +0 -84
- package/src/channels/chat-sdk-bridge.ts +0 -652
- package/src/channels/cli.ts +0 -276
- package/src/channels/discord.ts +0 -90
- package/src/channels/index.ts +0 -17
- package/src/channels/telegram-markdown-sanitize.test.ts +0 -78
- package/src/channels/telegram-markdown-sanitize.ts +0 -55
- package/src/channels/telegram-pairing.test.ts +0 -254
- package/src/channels/telegram-pairing.ts +0 -339
- package/src/channels/telegram.ts +0 -279
- package/src/channels/trust-hint.test.ts +0 -48
- package/src/channels/trust-hint.ts +0 -75
- package/src/claude-md-compose.migrate.test.ts +0 -64
- package/src/claude-md-compose.ts +0 -205
- package/src/command-gate.ts +0 -63
- package/src/config.test.ts +0 -93
- package/src/config.ts +0 -128
- package/src/container-config.ts +0 -167
- package/src/container-runner.test.ts +0 -32
- package/src/container-runner.ts +0 -576
- package/src/container-runtime.test.ts +0 -269
- package/src/container-runtime.ts +0 -167
- package/src/db/_bun-sqlite-shim.ts +0 -88
- package/src/db/agent-activity.test.ts +0 -155
- package/src/db/agent-activity.ts +0 -121
- package/src/db/agent-groups.ts +0 -77
- package/src/db/connection.migrate.test.ts +0 -176
- package/src/db/connection.ts +0 -259
- package/src/db/db-v2.test.ts +0 -440
- package/src/db/dropped-messages.ts +0 -44
- package/src/db/index.ts +0 -40
- package/src/db/messaging-groups.ts +0 -252
- package/src/db/migrations/001-initial.ts +0 -112
- package/src/db/migrations/002-chat-sdk-state.ts +0 -36
- package/src/db/migrations/008-dropped-messages.ts +0 -27
- package/src/db/migrations/009-drop-pending-credentials.ts +0 -13
- package/src/db/migrations/010-engage-modes.ts +0 -103
- package/src/db/migrations/011-pending-sender-approvals.ts +0 -40
- package/src/db/migrations/012-channel-registration.ts +0 -48
- package/src/db/migrations/013-approval-render-metadata.ts +0 -27
- package/src/db/migrations/014-secrets.ts +0 -44
- package/src/db/migrations/015-secrets-drop-host-pattern.ts +0 -18
- package/src/db/migrations/016-secret-assignments.ts +0 -30
- package/src/db/migrations/017-agent-activity.ts +0 -40
- package/src/db/migrations/018-oauth-app-configs.ts +0 -34
- package/src/db/migrations/019-oauth-app-connections.ts +0 -48
- package/src/db/migrations/020-agent-app-connections.ts +0 -28
- package/src/db/migrations/021-pending-oauth-states.ts +0 -35
- package/src/db/migrations/022-app-connections-provider.ts +0 -25
- package/src/db/migrations/023-agent-group-secret-mode.test.ts +0 -124
- package/src/db/migrations/023-agent-group-secret-mode.ts +0 -65
- package/src/db/migrations/024-collapse-approvals.test.ts +0 -249
- package/src/db/migrations/024-collapse-approvals.ts +0 -182
- package/src/db/migrations/025-secret-mode-check.test.ts +0 -155
- package/src/db/migrations/025-secret-mode-check.ts +0 -49
- package/src/db/migrations/026-user-dms-bot-id.test.ts +0 -116
- package/src/db/migrations/026-user-dms-bot-id.ts +0 -54
- package/src/db/migrations/027-provider-credentials.ts +0 -41
- package/src/db/migrations/_test-helpers.ts +0 -41
- package/src/db/migrations/index.ts +0 -127
- package/src/db/migrations/module-agent-to-agent-destinations.ts +0 -84
- package/src/db/migrations/module-approvals-pending-approvals.ts +0 -42
- package/src/db/migrations/module-approvals-title-options.ts +0 -40
- package/src/db/schema.ts +0 -258
- package/src/db/session-db.test.ts +0 -93
- package/src/db/session-db.ts +0 -325
- package/src/db/sessions.ts +0 -241
- package/src/delivery.test.ts +0 -148
- package/src/delivery.ts +0 -445
- package/src/env.ts +0 -74
- package/src/group-folder.test.ts +0 -35
- package/src/group-folder.ts +0 -44
- package/src/group-init.ts +0 -92
- package/src/host-core.test.ts +0 -456
- package/src/host-sweep.test.ts +0 -146
- package/src/host-sweep.ts +0 -287
- package/src/index.ts +0 -232
- package/src/install-slug.ts +0 -33
- package/src/log.test.ts +0 -81
- package/src/log.ts +0 -117
- package/src/mcp/http.ts +0 -72
- package/src/mcp/server.ts +0 -92
- package/src/mcp/stdio.ts +0 -51
- package/src/mcp/tools/activity.ts +0 -88
- package/src/mcp/tools/agent-groups.ts +0 -183
- package/src/mcp/tools/approvals.ts +0 -122
- package/src/mcp/tools/channels.test.ts +0 -126
- package/src/mcp/tools/channels.ts +0 -134
- package/src/mcp/tools/index.ts +0 -27
- package/src/mcp/tools/oauth.ts +0 -48
- package/src/mcp/tools/secrets.ts +0 -169
- package/src/mcp/tools/sessions.ts +0 -135
- package/src/mcp/types.ts +0 -51
- package/src/modules/agent-to-agent/agent-route.test.ts +0 -46
- package/src/modules/agent-to-agent/agent-route.ts +0 -223
- package/src/modules/agent-to-agent/create-agent.ts +0 -127
- package/src/modules/agent-to-agent/db/agent-destinations.ts +0 -135
- package/src/modules/agent-to-agent/index.ts +0 -22
- package/src/modules/agent-to-agent/write-destinations.ts +0 -59
- package/src/modules/approvals/agent.md +0 -45
- package/src/modules/approvals/index.ts +0 -21
- package/src/modules/approvals/picks.test.ts +0 -291
- package/src/modules/approvals/primitive.ts +0 -279
- package/src/modules/approvals/project.md +0 -27
- package/src/modules/approvals/response-handler.ts +0 -87
- package/src/modules/index.ts +0 -24
- package/src/modules/interactive/agent.md +0 -21
- package/src/modules/interactive/index.ts +0 -69
- package/src/modules/interactive/project.md +0 -12
- package/src/modules/mount-security/expand-path.test.ts +0 -82
- package/src/modules/mount-security/index.ts +0 -459
- package/src/modules/mount-security/migrate.test.ts +0 -91
- package/src/modules/permissions/access.ts +0 -28
- package/src/modules/permissions/channel-approval.test.ts +0 -389
- package/src/modules/permissions/channel-approval.ts +0 -188
- package/src/modules/permissions/db/agent-group-members.ts +0 -44
- package/src/modules/permissions/db/pending-channel-approvals.test.ts +0 -86
- package/src/modules/permissions/db/pending-channel-approvals.ts +0 -66
- package/src/modules/permissions/db/pending-sender-approvals.ts +0 -60
- package/src/modules/permissions/db/user-dms.ts +0 -58
- package/src/modules/permissions/db/user-roles.ts +0 -85
- package/src/modules/permissions/db/users.ts +0 -38
- package/src/modules/permissions/index.ts +0 -421
- package/src/modules/permissions/permissions.test.ts +0 -358
- package/src/modules/permissions/sender-approval.test.ts +0 -641
- package/src/modules/permissions/sender-approval.ts +0 -165
- package/src/modules/permissions/user-dm.ts +0 -200
- package/src/modules/provider-credentials/db.ts +0 -121
- package/src/modules/provider-credentials/index.ts +0 -12
- package/src/modules/provider-credentials/spawn.test.ts +0 -206
- package/src/modules/provider-credentials/spawn.ts +0 -114
- package/src/modules/scheduling/actions.ts +0 -113
- package/src/modules/scheduling/db.test.ts +0 -282
- package/src/modules/scheduling/db.ts +0 -148
- package/src/modules/scheduling/index.ts +0 -34
- package/src/modules/scheduling/recurrence.test.ts +0 -98
- package/src/modules/scheduling/recurrence.ts +0 -54
- package/src/modules/self-mod/agent.md +0 -30
- package/src/modules/self-mod/apply.ts +0 -85
- package/src/modules/self-mod/index.ts +0 -30
- package/src/modules/self-mod/project.md +0 -39
- package/src/modules/self-mod/request.ts +0 -91
- package/src/modules/typing/index.ts +0 -165
- package/src/oauth/agent-app-connections.ts +0 -103
- package/src/oauth/app-configs.test.ts +0 -64
- package/src/oauth/app-configs.ts +0 -114
- package/src/oauth/app-connections.test.ts +0 -109
- package/src/oauth/app-connections.ts +0 -178
- package/src/oauth/crypto.ts +0 -56
- package/src/oauth/flow.ts +0 -104
- package/src/oauth/providers/google.test.ts +0 -38
- package/src/oauth/providers/google.ts +0 -46
- package/src/oauth/providers/index.ts +0 -48
- package/src/oauth/state-store.test.ts +0 -54
- package/src/oauth/state-store.ts +0 -93
- package/src/parachute/README.md +0 -27
- package/src/parachute/create-agent.test.ts +0 -83
- package/src/parachute/create-agent.ts +0 -122
- package/src/parachute/group-status.test.ts +0 -165
- package/src/parachute/group-status.ts +0 -136
- package/src/parachute/types.ts +0 -41
- package/src/parachute/vault-mcp.test.ts +0 -251
- package/src/parachute/vault-mcp.ts +0 -232
- package/src/platform-id.test.ts +0 -104
- package/src/platform-id.ts +0 -109
- package/src/providers/index.ts +0 -6
- package/src/providers/provider-container-registry.ts +0 -58
- package/src/response-registry.ts +0 -45
- package/src/router.ts +0 -530
- package/src/secrets/crypto.test.ts +0 -45
- package/src/secrets/crypto.ts +0 -55
- package/src/secrets/index.ts +0 -461
- package/src/secrets/master-key.ts +0 -70
- package/src/secrets/secrets.test.ts +0 -651
- package/src/session-manager.attachments.test.ts +0 -171
- package/src/session-manager.dup-skip.test.ts +0 -173
- package/src/session-manager.migrate.test.ts +0 -59
- package/src/session-manager.ts +0 -451
- package/src/startup-bootstrap.test.ts +0 -226
- package/src/startup-bootstrap.ts +0 -207
- package/src/state-sqlite.ts +0 -182
- package/src/timezone.test.ts +0 -64
- package/src/timezone.ts +0 -37
- package/src/types.ts +0 -233
- package/src/web/auth.test.ts +0 -335
- package/src/web/auth.ts +0 -214
- package/src/web/discord-validate.test.ts +0 -77
- package/src/web/discord-validate.ts +0 -88
- package/src/web/hub-discovery.test.ts +0 -98
- package/src/web/hub-discovery.ts +0 -69
- package/src/web/routes/activity.ts +0 -106
- package/src/web/routes/agent-provider.test.ts +0 -282
- package/src/web/routes/agent-provider.ts +0 -309
- package/src/web/routes/approvals.ts +0 -185
- package/src/web/routes/apps.ts +0 -434
- package/src/web/routes/channels-mg-detail.test.ts +0 -324
- package/src/web/routes/channels-mga-detail.test.ts +0 -472
- package/src/web/routes/channels.ts +0 -311
- package/src/web/routes/oauth-providers.ts +0 -42
- package/src/web/routes/secrets.test.ts +0 -220
- package/src/web/routes/secrets.ts +0 -317
- package/src/web/routes/sessions.ts +0 -123
- package/src/web/routes/settings.test.ts +0 -106
- package/src/web/routes/settings.ts +0 -247
- package/src/web/routes/setup-status.ts +0 -205
- package/src/web/routes/vaults.test.ts +0 -389
- package/src/web/routes/vaults.ts +0 -225
- package/src/web/server-version.test.ts +0 -16
- package/src/web/server.ts +0 -1024
- package/src/web/services-manifest.test.ts +0 -148
- package/src/web/services-manifest.ts +0 -66
- package/src/web/static-serve.test.ts +0 -255
- package/src/web/static-serve.ts +0 -104
- package/src/web/telegram-validate.test.ts +0 -116
- package/src/web/telegram-validate.ts +0 -107
- package/src/web/vault-proxy.test.ts +0 -214
- package/src/web/vault-proxy.ts +0 -120
- package/src/web/wire-channel.ts +0 -181
- package/src/webhook-server.ts +0 -134
- package/vitest.config.ts +0 -18
- package/web/README.md +0 -63
- package/web/ui/index.html +0 -13
- package/web/ui/package.json +0 -35
- package/web/ui/pnpm-lock.yaml +0 -2164
- package/web/ui/scripts/verify-base.mjs +0 -31
- package/web/ui/src/App.tsx +0 -88
- package/web/ui/src/components/ActivityFeed.tsx +0 -444
- package/web/ui/src/components/AgentGroupPicker.tsx +0 -263
- package/web/ui/src/components/AgentProviderCards.tsx +0 -220
- package/web/ui/src/components/CredentialForm.tsx +0 -214
- package/web/ui/src/components/ScopeGrants.tsx +0 -74
- package/web/ui/src/components/StatusDot.tsx +0 -43
- package/web/ui/src/components/VaultPicker.tsx +0 -127
- package/web/ui/src/components/setup/AdapterInstallStep.tsx +0 -178
- package/web/ui/src/components/setup/AgentGroupStep.tsx +0 -43
- package/web/ui/src/components/setup/ChannelPickStep.tsx +0 -74
- package/web/ui/src/components/setup/DoneStep.tsx +0 -49
- package/web/ui/src/components/setup/PrereqStep.tsx +0 -129
- package/web/ui/src/components/setup/TestConnectionStep.tsx +0 -108
- package/web/ui/src/components/setup/TestMessageStep.tsx +0 -104
- package/web/ui/src/components/setup/WireChannelStep.tsx +0 -166
- package/web/ui/src/components/setup/types.ts +0 -105
- package/web/ui/src/lib/api.test.ts +0 -410
- package/web/ui/src/lib/api.ts +0 -1248
- package/web/ui/src/lib/auth.test.ts +0 -352
- package/web/ui/src/lib/auth.ts +0 -405
- package/web/ui/src/lib/channel-adapters.ts +0 -136
- package/web/ui/src/main.tsx +0 -19
- package/web/ui/src/routes/ApprovalsList.tsx +0 -294
- package/web/ui/src/routes/Apps.tsx +0 -613
- package/web/ui/src/routes/ChannelWireDetail.test.tsx +0 -233
- package/web/ui/src/routes/ChannelWireDetail.tsx +0 -403
- package/web/ui/src/routes/ChannelsList.tsx +0 -158
- package/web/ui/src/routes/GroupDetail.test.tsx +0 -206
- package/web/ui/src/routes/GroupDetail.tsx +0 -880
- package/web/ui/src/routes/GroupList.tsx +0 -187
- package/web/ui/src/routes/MessagingGroupDetail.test.tsx +0 -233
- package/web/ui/src/routes/MessagingGroupDetail.tsx +0 -306
- package/web/ui/src/routes/NewGroupWizard.tsx +0 -390
- package/web/ui/src/routes/OAuthCallback.tsx +0 -56
- package/web/ui/src/routes/SecretsList.tsx +0 -942
- package/web/ui/src/routes/SessionsList.tsx +0 -220
- package/web/ui/src/routes/SettingsAgentProvider.tsx +0 -109
- package/web/ui/src/routes/SettingsApprovals.tsx +0 -234
- package/web/ui/src/routes/SetupWizard.tsx +0 -219
- package/web/ui/src/routes/VaultDetail.test.tsx +0 -363
- package/web/ui/src/routes/VaultDetail.tsx +0 -960
- package/web/ui/src/routes/VaultsList.tsx +0 -295
- package/web/ui/src/routes/WireChannelPage.tsx +0 -413
- package/web/ui/src/styles.css +0 -608
- package/web/ui/src/test/setup.ts +0 -23
- package/web/ui/src/vite-env.d.ts +0 -10
- package/web/ui/vite.config.ts +0 -34
- package/web/ui/vitest.config.ts +0 -25
|
@@ -1,459 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mount Security Module for parachute-agent
|
|
3
|
-
*
|
|
4
|
-
* Validates additional mounts against an allowlist stored OUTSIDE the project root.
|
|
5
|
-
* This prevents container agents from modifying security configuration.
|
|
6
|
-
*
|
|
7
|
-
* Allowlist location: `<HOME>/.config/parachute-agent/mount-allowlist.json`
|
|
8
|
-
* (pre-0.1.0 installs auto-migrate from `<HOME>/.config/paraclaw/` on startup —
|
|
9
|
-
* see migrateLegacyAllowlistDir below). Path stays under `<HOME>/.config/`,
|
|
10
|
-
* NOT under `PARACHUTE_DIR`, because mount-allowlist is operator-host policy
|
|
11
|
-
* rather than per-install runtime state — see the doc-block on `ALLOWLIST_DIR`
|
|
12
|
-
* in src/config.ts (paraclaw#99).
|
|
13
|
-
*
|
|
14
|
-
* `HOME_DIR` is imported from src/config.ts so the precedence rule
|
|
15
|
-
* (`process.env.HOME` → `os.homedir()`) lives in one place. expandPath uses
|
|
16
|
-
* the same HOME_DIR for `~`-expansion in operator-supplied paths inside the
|
|
17
|
-
* allowlist (`~/projects` etc.), ensuring expansion agrees with the rest of
|
|
18
|
-
* the host process.
|
|
19
|
-
*/
|
|
20
|
-
import fs from 'fs';
|
|
21
|
-
import path from 'path';
|
|
22
|
-
import { ALLOWLIST_DIR, HOME_DIR, LEGACY_ALLOWLIST_DIR, MOUNT_ALLOWLIST_PATH } from '../../config.js';
|
|
23
|
-
import { log } from '../../log.js';
|
|
24
|
-
|
|
25
|
-
export interface AdditionalMount {
|
|
26
|
-
hostPath: string;
|
|
27
|
-
containerPath?: string;
|
|
28
|
-
readonly?: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface MountAllowlist {
|
|
32
|
-
allowedRoots: AllowedRoot[];
|
|
33
|
-
blockedPatterns: string[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface AllowedRoot {
|
|
37
|
-
path: string;
|
|
38
|
-
allowReadWrite: boolean;
|
|
39
|
-
description?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Cache the allowlist in memory - only reloads on process restart
|
|
43
|
-
let cachedAllowlist: MountAllowlist | null = null;
|
|
44
|
-
let allowlistLoadError: string | null = null;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Default blocked patterns - paths that should never be mounted
|
|
48
|
-
*/
|
|
49
|
-
const DEFAULT_BLOCKED_PATTERNS = [
|
|
50
|
-
'.ssh',
|
|
51
|
-
'.gnupg',
|
|
52
|
-
'.gpg',
|
|
53
|
-
'.aws',
|
|
54
|
-
'.azure',
|
|
55
|
-
'.gcloud',
|
|
56
|
-
'.kube',
|
|
57
|
-
'.docker',
|
|
58
|
-
'credentials',
|
|
59
|
-
'.env',
|
|
60
|
-
'.netrc',
|
|
61
|
-
'.npmrc',
|
|
62
|
-
'.pypirc',
|
|
63
|
-
'id_rsa',
|
|
64
|
-
'id_ed25519',
|
|
65
|
-
'private_key',
|
|
66
|
-
'.secret',
|
|
67
|
-
];
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Load the mount allowlist from the external config location.
|
|
71
|
-
* Returns null if the file doesn't exist or is invalid.
|
|
72
|
-
* Result is cached in memory for the lifetime of the process.
|
|
73
|
-
*/
|
|
74
|
-
export function loadMountAllowlist(): MountAllowlist | null {
|
|
75
|
-
if (cachedAllowlist !== null) {
|
|
76
|
-
return cachedAllowlist;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (allowlistLoadError !== null) {
|
|
80
|
-
// Already tried and failed, don't spam logs
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
if (!fs.existsSync(MOUNT_ALLOWLIST_PATH)) {
|
|
86
|
-
// Do NOT cache this as an error — file may be created later without restart.
|
|
87
|
-
// Only parse/structural errors are permanently cached.
|
|
88
|
-
log.warn(
|
|
89
|
-
'Mount allowlist not found - additional mounts will be BLOCKED. Create the file to enable additional mounts.',
|
|
90
|
-
{ path: MOUNT_ALLOWLIST_PATH },
|
|
91
|
-
);
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const content = fs.readFileSync(MOUNT_ALLOWLIST_PATH, 'utf-8');
|
|
96
|
-
const allowlist = JSON.parse(content) as MountAllowlist;
|
|
97
|
-
|
|
98
|
-
// Validate structure
|
|
99
|
-
if (!Array.isArray(allowlist.allowedRoots)) {
|
|
100
|
-
throw new Error('allowedRoots must be an array');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (!Array.isArray(allowlist.blockedPatterns)) {
|
|
104
|
-
throw new Error('blockedPatterns must be an array');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Merge with default blocked patterns
|
|
108
|
-
const mergedBlockedPatterns = [...new Set([...DEFAULT_BLOCKED_PATTERNS, ...allowlist.blockedPatterns])];
|
|
109
|
-
allowlist.blockedPatterns = mergedBlockedPatterns;
|
|
110
|
-
|
|
111
|
-
cachedAllowlist = allowlist;
|
|
112
|
-
log.info('Mount allowlist loaded successfully', {
|
|
113
|
-
path: MOUNT_ALLOWLIST_PATH,
|
|
114
|
-
allowedRoots: allowlist.allowedRoots.length,
|
|
115
|
-
blockedPatterns: allowlist.blockedPatterns.length,
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
return cachedAllowlist;
|
|
119
|
-
} catch (err) {
|
|
120
|
-
allowlistLoadError = err instanceof Error ? err.message : String(err);
|
|
121
|
-
log.error('Failed to load mount allowlist - additional mounts will be BLOCKED', {
|
|
122
|
-
path: MOUNT_ALLOWLIST_PATH,
|
|
123
|
-
error: allowlistLoadError,
|
|
124
|
-
});
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Expand ~ to home directory and resolve to absolute path. `HOME_DIR` comes
|
|
131
|
-
* from src/config.ts so a future change to the precedence rule
|
|
132
|
-
* (`HOME` → `os.homedir()`) is one edit upstream rather than redrawn here.
|
|
133
|
-
* Exported for direct test coverage of the expansion (paraclaw#99); not
|
|
134
|
-
* intended for use outside this module.
|
|
135
|
-
*/
|
|
136
|
-
export function expandPath(p: string): string {
|
|
137
|
-
if (p.startsWith('~/')) {
|
|
138
|
-
return path.join(HOME_DIR, p.slice(2));
|
|
139
|
-
}
|
|
140
|
-
if (p === '~') {
|
|
141
|
-
return HOME_DIR;
|
|
142
|
-
}
|
|
143
|
-
return path.resolve(p);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Get the real path, resolving symlinks.
|
|
148
|
-
* Returns null if the path doesn't exist.
|
|
149
|
-
*/
|
|
150
|
-
function getRealPath(p: string): string | null {
|
|
151
|
-
try {
|
|
152
|
-
return fs.realpathSync(p);
|
|
153
|
-
} catch {
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Check if a path matches any blocked pattern
|
|
160
|
-
*/
|
|
161
|
-
function matchesBlockedPattern(realPath: string, blockedPatterns: string[]): string | null {
|
|
162
|
-
const pathParts = realPath.split(path.sep);
|
|
163
|
-
|
|
164
|
-
for (const pattern of blockedPatterns) {
|
|
165
|
-
// Check if any path component matches the pattern
|
|
166
|
-
for (const part of pathParts) {
|
|
167
|
-
if (part === pattern || part.includes(pattern)) {
|
|
168
|
-
return pattern;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Also check if the full path contains the pattern
|
|
173
|
-
if (realPath.includes(pattern)) {
|
|
174
|
-
return pattern;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Check if a real path is under an allowed root
|
|
183
|
-
*/
|
|
184
|
-
function findAllowedRoot(realPath: string, allowedRoots: AllowedRoot[]): AllowedRoot | null {
|
|
185
|
-
for (const root of allowedRoots) {
|
|
186
|
-
const expandedRoot = expandPath(root.path);
|
|
187
|
-
const realRoot = getRealPath(expandedRoot);
|
|
188
|
-
|
|
189
|
-
if (realRoot === null) {
|
|
190
|
-
// Allowed root doesn't exist, skip it
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Check if realPath is under realRoot
|
|
195
|
-
const relative = path.relative(realRoot, realPath);
|
|
196
|
-
if (!relative.startsWith('..') && !path.isAbsolute(relative)) {
|
|
197
|
-
return root;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Validate the container path to prevent escaping /workspace/extra/
|
|
206
|
-
*/
|
|
207
|
-
function isValidContainerPath(containerPath: string): boolean {
|
|
208
|
-
// Must not contain .. to prevent path traversal
|
|
209
|
-
if (containerPath.includes('..')) {
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Must not be absolute (it will be prefixed with /workspace/extra/)
|
|
214
|
-
if (containerPath.startsWith('/')) {
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Must not be empty
|
|
219
|
-
if (!containerPath || containerPath.trim() === '') {
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Must not contain colons — prevents Docker -v option injection (e.g., "repo:rw")
|
|
224
|
-
if (containerPath.includes(':')) {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return true;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export interface MountValidationResult {
|
|
232
|
-
allowed: boolean;
|
|
233
|
-
reason: string;
|
|
234
|
-
realHostPath?: string;
|
|
235
|
-
resolvedContainerPath?: string;
|
|
236
|
-
effectiveReadonly?: boolean;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Validate a single additional mount against the allowlist.
|
|
241
|
-
* Returns validation result with reason.
|
|
242
|
-
*/
|
|
243
|
-
export function validateMount(mount: AdditionalMount): MountValidationResult {
|
|
244
|
-
const allowlist = loadMountAllowlist();
|
|
245
|
-
|
|
246
|
-
// If no allowlist, block all additional mounts
|
|
247
|
-
if (allowlist === null) {
|
|
248
|
-
return {
|
|
249
|
-
allowed: false,
|
|
250
|
-
reason: `No mount allowlist configured at ${MOUNT_ALLOWLIST_PATH}`,
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Derive containerPath from hostPath basename if not specified
|
|
255
|
-
const containerPath = mount.containerPath || path.basename(mount.hostPath);
|
|
256
|
-
|
|
257
|
-
// Validate container path (cheap check)
|
|
258
|
-
if (!isValidContainerPath(containerPath)) {
|
|
259
|
-
return {
|
|
260
|
-
allowed: false,
|
|
261
|
-
reason: `Invalid container path: "${containerPath}" - must be relative, non-empty, and not contain ".."`,
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Expand and resolve the host path
|
|
266
|
-
const expandedPath = expandPath(mount.hostPath);
|
|
267
|
-
const realPath = getRealPath(expandedPath);
|
|
268
|
-
|
|
269
|
-
if (realPath === null) {
|
|
270
|
-
return {
|
|
271
|
-
allowed: false,
|
|
272
|
-
reason: `Host path does not exist: "${mount.hostPath}" (expanded: "${expandedPath}")`,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Check against blocked patterns
|
|
277
|
-
const blockedMatch = matchesBlockedPattern(realPath, allowlist.blockedPatterns);
|
|
278
|
-
if (blockedMatch !== null) {
|
|
279
|
-
return {
|
|
280
|
-
allowed: false,
|
|
281
|
-
reason: `Path matches blocked pattern "${blockedMatch}": "${realPath}"`,
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Check if under an allowed root
|
|
286
|
-
const allowedRoot = findAllowedRoot(realPath, allowlist.allowedRoots);
|
|
287
|
-
if (allowedRoot === null) {
|
|
288
|
-
return {
|
|
289
|
-
allowed: false,
|
|
290
|
-
reason: `Path "${realPath}" is not under any allowed root. Allowed roots: ${allowlist.allowedRoots
|
|
291
|
-
.map((r) => expandPath(r.path))
|
|
292
|
-
.join(', ')}`,
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Determine effective readonly status.
|
|
297
|
-
// RW is only granted if the mount explicitly requests it AND the allowed
|
|
298
|
-
// root permits it. Otherwise it's forced read-only.
|
|
299
|
-
const requestedReadWrite = mount.readonly === false;
|
|
300
|
-
let effectiveReadonly = true;
|
|
301
|
-
|
|
302
|
-
if (requestedReadWrite) {
|
|
303
|
-
if (!allowedRoot.allowReadWrite) {
|
|
304
|
-
log.info('Mount forced to read-only - root does not allow read-write', {
|
|
305
|
-
mount: mount.hostPath,
|
|
306
|
-
root: allowedRoot.path,
|
|
307
|
-
});
|
|
308
|
-
} else {
|
|
309
|
-
effectiveReadonly = false;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return {
|
|
314
|
-
allowed: true,
|
|
315
|
-
reason: `Allowed under root "${allowedRoot.path}"${allowedRoot.description ? ` (${allowedRoot.description})` : ''}`,
|
|
316
|
-
realHostPath: realPath,
|
|
317
|
-
resolvedContainerPath: containerPath,
|
|
318
|
-
effectiveReadonly,
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Validate all additional mounts for a group.
|
|
324
|
-
* Returns array of validated mounts (only those that passed validation).
|
|
325
|
-
* Logs warnings for rejected mounts.
|
|
326
|
-
*/
|
|
327
|
-
export function validateAdditionalMounts(
|
|
328
|
-
mounts: AdditionalMount[],
|
|
329
|
-
groupName: string,
|
|
330
|
-
): Array<{
|
|
331
|
-
hostPath: string;
|
|
332
|
-
containerPath: string;
|
|
333
|
-
readonly: boolean;
|
|
334
|
-
}> {
|
|
335
|
-
const validatedMounts: Array<{
|
|
336
|
-
hostPath: string;
|
|
337
|
-
containerPath: string;
|
|
338
|
-
readonly: boolean;
|
|
339
|
-
}> = [];
|
|
340
|
-
|
|
341
|
-
for (const mount of mounts) {
|
|
342
|
-
const result = validateMount(mount);
|
|
343
|
-
|
|
344
|
-
if (result.allowed) {
|
|
345
|
-
validatedMounts.push({
|
|
346
|
-
hostPath: result.realHostPath!,
|
|
347
|
-
containerPath: `/workspace/extra/${result.resolvedContainerPath}`,
|
|
348
|
-
readonly: result.effectiveReadonly!,
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
log.debug('Mount validated successfully', {
|
|
352
|
-
group: groupName,
|
|
353
|
-
hostPath: result.realHostPath,
|
|
354
|
-
containerPath: result.resolvedContainerPath,
|
|
355
|
-
readonly: result.effectiveReadonly,
|
|
356
|
-
reason: result.reason,
|
|
357
|
-
});
|
|
358
|
-
} else {
|
|
359
|
-
log.warn('Additional mount REJECTED', {
|
|
360
|
-
group: groupName,
|
|
361
|
-
requestedPath: mount.hostPath,
|
|
362
|
-
containerPath: mount.containerPath,
|
|
363
|
-
reason: result.reason,
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return validatedMounts;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* One-shot move of `~/.config/paraclaw/{mount,sender}-allowlist.json` to the
|
|
373
|
-
* `~/.config/parachute-agent/` dir. Called early at host startup so a 0.1.0
|
|
374
|
-
* install picks up an operator's existing allowlist without a manual `mv`.
|
|
375
|
-
*
|
|
376
|
-
* Idempotent — moves a file only when the legacy file exists and the new
|
|
377
|
-
* path is absent. The legacy directory itself is left in place: the operator
|
|
378
|
-
* may have other files there, and a missing-but-empty legacy dir on the next
|
|
379
|
-
* boot is a harmless no-op. Best-effort: rename failures (permission / race)
|
|
380
|
-
* log and continue. Drop in 0.2.0 along with the rest of the paraclaw-era
|
|
381
|
-
* compat sweep.
|
|
382
|
-
*
|
|
383
|
-
* Args default to the canonical `~/.config/{paraclaw,parachute-agent}` pair;
|
|
384
|
-
* the test injects scratch dirs.
|
|
385
|
-
*/
|
|
386
|
-
export function migrateLegacyAllowlistDir(
|
|
387
|
-
legacyDir: string = LEGACY_ALLOWLIST_DIR,
|
|
388
|
-
currentDir: string = ALLOWLIST_DIR,
|
|
389
|
-
): void {
|
|
390
|
-
let legacyDirExists: boolean;
|
|
391
|
-
try {
|
|
392
|
-
legacyDirExists = fs.statSync(legacyDir).isDirectory();
|
|
393
|
-
} catch {
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
if (!legacyDirExists) return;
|
|
397
|
-
|
|
398
|
-
try {
|
|
399
|
-
fs.mkdirSync(currentDir, { recursive: true });
|
|
400
|
-
} catch (err) {
|
|
401
|
-
log.warn('Could not create parachute-agent allowlist dir', { dir: currentDir, err });
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const filenames = ['mount-allowlist.json', 'sender-allowlist.json'];
|
|
406
|
-
for (const name of filenames) {
|
|
407
|
-
const legacy = path.join(legacyDir, name);
|
|
408
|
-
const current = path.join(currentDir, name);
|
|
409
|
-
|
|
410
|
-
let legacyFileExists: boolean;
|
|
411
|
-
try {
|
|
412
|
-
legacyFileExists = fs.statSync(legacy).isFile();
|
|
413
|
-
} catch {
|
|
414
|
-
continue;
|
|
415
|
-
}
|
|
416
|
-
if (!legacyFileExists) continue;
|
|
417
|
-
if (fs.existsSync(current)) continue;
|
|
418
|
-
|
|
419
|
-
try {
|
|
420
|
-
fs.renameSync(legacy, current);
|
|
421
|
-
log.info('Allowlist migrated from legacy dir', { from: legacy, to: current });
|
|
422
|
-
} catch (err) {
|
|
423
|
-
log.warn('Could not migrate legacy allowlist file', { from: legacy, to: current, err });
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* Generate a template allowlist file for users to customize
|
|
430
|
-
*/
|
|
431
|
-
export function generateAllowlistTemplate(): string {
|
|
432
|
-
const template: MountAllowlist = {
|
|
433
|
-
allowedRoots: [
|
|
434
|
-
{
|
|
435
|
-
path: '~/projects',
|
|
436
|
-
allowReadWrite: true,
|
|
437
|
-
description: 'Development projects',
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
path: '~/repos',
|
|
441
|
-
allowReadWrite: true,
|
|
442
|
-
description: 'Git repositories',
|
|
443
|
-
},
|
|
444
|
-
{
|
|
445
|
-
path: '~/Documents/work',
|
|
446
|
-
allowReadWrite: false,
|
|
447
|
-
description: 'Work documents (read-only)',
|
|
448
|
-
},
|
|
449
|
-
],
|
|
450
|
-
blockedPatterns: [
|
|
451
|
-
// Additional patterns beyond defaults
|
|
452
|
-
'password',
|
|
453
|
-
'secret',
|
|
454
|
-
'token',
|
|
455
|
-
],
|
|
456
|
-
};
|
|
457
|
-
|
|
458
|
-
return JSON.stringify(template, null, 2);
|
|
459
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `migrateLegacyAllowlistDir` — idempotent move of
|
|
3
|
-
* `~/.config/paraclaw/{mount,sender}-allowlist.json` to
|
|
4
|
-
* `~/.config/parachute-agent/` at host startup. Tests use injected scratch
|
|
5
|
-
* dirs (the function accepts overrides) so we don't touch the real
|
|
6
|
-
* `$HOME/.config`.
|
|
7
|
-
*/
|
|
8
|
-
import fs from 'node:fs';
|
|
9
|
-
import os from 'node:os';
|
|
10
|
-
import path from 'node:path';
|
|
11
|
-
|
|
12
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
13
|
-
|
|
14
|
-
import { migrateLegacyAllowlistDir } from './index.js';
|
|
15
|
-
|
|
16
|
-
let scratchRoot: string;
|
|
17
|
-
let legacyDir: string;
|
|
18
|
-
let currentDir: string;
|
|
19
|
-
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
scratchRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-allowlist-migrate-'));
|
|
22
|
-
legacyDir = path.join(scratchRoot, 'paraclaw');
|
|
23
|
-
currentDir = path.join(scratchRoot, 'parachute-agent');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
afterEach(() => {
|
|
27
|
-
fs.rmSync(scratchRoot, { recursive: true, force: true });
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe('migrateLegacyAllowlistDir', () => {
|
|
31
|
-
it('moves both legacy allowlist files to the new dir when present', () => {
|
|
32
|
-
fs.mkdirSync(legacyDir, { recursive: true });
|
|
33
|
-
fs.writeFileSync(path.join(legacyDir, 'mount-allowlist.json'), '{"mount":1}\n');
|
|
34
|
-
fs.writeFileSync(path.join(legacyDir, 'sender-allowlist.json'), '{"sender":1}\n');
|
|
35
|
-
|
|
36
|
-
migrateLegacyAllowlistDir(legacyDir, currentDir);
|
|
37
|
-
|
|
38
|
-
expect(fs.readFileSync(path.join(currentDir, 'mount-allowlist.json'), 'utf8')).toBe('{"mount":1}\n');
|
|
39
|
-
expect(fs.readFileSync(path.join(currentDir, 'sender-allowlist.json'), 'utf8')).toBe('{"sender":1}\n');
|
|
40
|
-
expect(fs.existsSync(path.join(legacyDir, 'mount-allowlist.json'))).toBe(false);
|
|
41
|
-
expect(fs.existsSync(path.join(legacyDir, 'sender-allowlist.json'))).toBe(false);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('is a no-op when the legacy dir does not exist (fresh install)', () => {
|
|
45
|
-
fs.mkdirSync(currentDir, { recursive: true });
|
|
46
|
-
fs.writeFileSync(path.join(currentDir, 'mount-allowlist.json'), '{"fresh":1}\n');
|
|
47
|
-
|
|
48
|
-
migrateLegacyAllowlistDir(legacyDir, currentDir);
|
|
49
|
-
|
|
50
|
-
expect(fs.readFileSync(path.join(currentDir, 'mount-allowlist.json'), 'utf8')).toBe('{"fresh":1}\n');
|
|
51
|
-
expect(fs.existsSync(legacyDir)).toBe(false);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('does not clobber existing files in the new dir (post-migration coexistence)', () => {
|
|
55
|
-
// The operator may have populated the new dir before re-running an
|
|
56
|
-
// installer that triggers another migration pass. Treat the new file
|
|
57
|
-
// as canonical and leave the legacy orphan alone — the operator
|
|
58
|
-
// deletes it deliberately.
|
|
59
|
-
fs.mkdirSync(legacyDir, { recursive: true });
|
|
60
|
-
fs.mkdirSync(currentDir, { recursive: true });
|
|
61
|
-
fs.writeFileSync(path.join(legacyDir, 'mount-allowlist.json'), '{"orphan":1}\n');
|
|
62
|
-
fs.writeFileSync(path.join(currentDir, 'mount-allowlist.json'), '{"live":1}\n');
|
|
63
|
-
|
|
64
|
-
migrateLegacyAllowlistDir(legacyDir, currentDir);
|
|
65
|
-
|
|
66
|
-
expect(fs.readFileSync(path.join(currentDir, 'mount-allowlist.json'), 'utf8')).toBe('{"live":1}\n');
|
|
67
|
-
expect(fs.readFileSync(path.join(legacyDir, 'mount-allowlist.json'), 'utf8')).toBe('{"orphan":1}\n');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('handles only one of the two legacy files existing', () => {
|
|
71
|
-
fs.mkdirSync(legacyDir, { recursive: true });
|
|
72
|
-
fs.writeFileSync(path.join(legacyDir, 'sender-allowlist.json'), '{"sender":1}\n');
|
|
73
|
-
|
|
74
|
-
migrateLegacyAllowlistDir(legacyDir, currentDir);
|
|
75
|
-
|
|
76
|
-
expect(fs.existsSync(path.join(currentDir, 'mount-allowlist.json'))).toBe(false);
|
|
77
|
-
expect(fs.readFileSync(path.join(currentDir, 'sender-allowlist.json'), 'utf8')).toBe('{"sender":1}\n');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('creates the new dir when it does not yet exist', () => {
|
|
81
|
-
fs.mkdirSync(legacyDir, { recursive: true });
|
|
82
|
-
fs.writeFileSync(path.join(legacyDir, 'mount-allowlist.json'), '{"m":1}\n');
|
|
83
|
-
|
|
84
|
-
expect(fs.existsSync(currentDir)).toBe(false);
|
|
85
|
-
|
|
86
|
-
migrateLegacyAllowlistDir(legacyDir, currentDir);
|
|
87
|
-
|
|
88
|
-
expect(fs.statSync(currentDir).isDirectory()).toBe(true);
|
|
89
|
-
expect(fs.readFileSync(path.join(currentDir, 'mount-allowlist.json'), 'utf8')).toBe('{"m":1}\n');
|
|
90
|
-
});
|
|
91
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Access control.
|
|
3
|
-
*
|
|
4
|
-
* Privilege is user-level, not group-level. A user holds zero or more roles
|
|
5
|
-
* (owner | admin) via `user_roles`, and is optionally "known" in specific
|
|
6
|
-
* agent groups via `agent_group_members`. Admins are implicitly members of
|
|
7
|
-
* the groups they administer.
|
|
8
|
-
*
|
|
9
|
-
* Approver-picking (`pickApprover`, `pickApprovalDelivery`) lives in the
|
|
10
|
-
* approvals module — see `src/modules/approvals/primitive.ts`.
|
|
11
|
-
*/
|
|
12
|
-
import { isMember } from './db/agent-group-members.js';
|
|
13
|
-
import { isAdminOfAgentGroup, isGlobalAdmin, isOwner } from './db/user-roles.js';
|
|
14
|
-
import { getUser } from './db/users.js';
|
|
15
|
-
|
|
16
|
-
export type AccessDecision =
|
|
17
|
-
| { allowed: true; reason: 'owner' | 'global_admin' | 'admin_of_group' | 'member' }
|
|
18
|
-
| { allowed: false; reason: 'unknown_user' | 'not_member' };
|
|
19
|
-
|
|
20
|
-
/** Can this user interact with this agent group? */
|
|
21
|
-
export function canAccessAgentGroup(userId: string, agentGroupId: string): AccessDecision {
|
|
22
|
-
if (!getUser(userId)) return { allowed: false, reason: 'unknown_user' };
|
|
23
|
-
if (isOwner(userId)) return { allowed: true, reason: 'owner' };
|
|
24
|
-
if (isGlobalAdmin(userId)) return { allowed: true, reason: 'global_admin' };
|
|
25
|
-
if (isAdminOfAgentGroup(userId, agentGroupId)) return { allowed: true, reason: 'admin_of_group' };
|
|
26
|
-
if (isMember(userId, agentGroupId)) return { allowed: true, reason: 'member' };
|
|
27
|
-
return { allowed: false, reason: 'not_member' };
|
|
28
|
-
}
|