@agent-native/core 0.131.5 → 0.131.7
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/corpus/README.md +3 -3
- package/corpus/core/CHANGELOG.md +70 -0
- package/corpus/core/docs/content/agent-native-toolkit.mdx +13 -6
- package/corpus/core/docs/content/cloneable-saas.mdx +5 -5
- package/corpus/core/docs/content/creating-templates.mdx +9 -0
- package/corpus/core/docs/content/deployment.mdx +18 -12
- package/corpus/core/docs/content/doctor.mdx +8 -1
- package/corpus/core/docs/content/drop-in-agent.mdx +1 -1
- package/corpus/core/docs/content/extensions.mdx +18 -18
- package/corpus/core/docs/content/faq.mdx +4 -4
- package/corpus/core/docs/content/key-concepts.mdx +30 -21
- package/corpus/core/docs/content/security.mdx +2 -2
- package/corpus/core/docs/content/template-brain-developers.mdx +4 -1
- package/corpus/core/docs/content/template-mail-developers.mdx +4 -1
- package/corpus/core/docs/content/what-is-agent-native.mdx +6 -6
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/client.ts +449 -66
- package/corpus/core/src/a2a/correlation.ts +31 -0
- package/corpus/core/src/a2a/invoke.ts +4 -2
- package/corpus/core/src/a2a/types.ts +4 -0
- package/corpus/core/src/action.ts +3 -0
- package/corpus/core/src/agent/action-continuation-guidance.ts +2 -1
- package/corpus/core/src/agent/engine/registry.ts +98 -6
- package/corpus/core/src/agent/production-agent.ts +151 -18
- package/corpus/core/src/agent/run-loop-with-resume.ts +190 -3
- package/corpus/core/src/agent/run-manager.ts +117 -41
- package/corpus/core/src/agent/thread-debug-history.ts +86 -0
- package/corpus/core/src/agent/types.ts +3 -1
- package/corpus/core/src/cli/create.ts +11 -1
- package/corpus/core/src/cli/templates-meta.ts +2 -2
- package/corpus/core/src/client/agent-chat-adapter.ts +91 -13
- package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +11 -9
- package/corpus/core/src/client/sse-event-processor.ts +17 -3
- package/corpus/core/src/db/client.ts +57 -17
- package/corpus/core/src/guards/no-env-credentials.ts +2 -0
- package/corpus/core/src/integrations/adapters/slack.ts +8 -3
- package/corpus/core/src/localization/default-messages.ts +44 -0
- package/corpus/core/src/observability/traces.ts +78 -5
- package/corpus/core/src/scripts/call-agent.ts +294 -11
- package/corpus/core/src/scripts/describe-workspace-apps.ts +2 -2
- package/corpus/core/src/secrets/crypto.ts +156 -35
- package/corpus/core/src/secrets/storage.ts +64 -26
- package/corpus/core/src/server/agent-capabilities.ts +1 -1
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +113 -7
- package/corpus/core/src/server/agent-chat/context-tools.ts +2 -0
- package/corpus/core/src/server/agent-chat/plugin-options.ts +34 -1
- package/corpus/core/src/server/agent-chat/prompt-resources.ts +1 -1
- package/corpus/core/src/server/agent-chat-plugin.ts +91 -59
- package/corpus/core/src/templates/default/.agents/skills/secrets/SKILL.md +19 -7
- package/corpus/core/src/templates/headless/.agents/skills/secrets/SKILL.md +19 -7
- package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
- package/corpus/core/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
- package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +19 -7
- package/corpus/core/src/vite/client.ts +1 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-ops/SKILL.md +5 -4
- package/corpus/templates/analytics/.agents/skills/gong/SKILL.md +6 -4
- package/corpus/templates/analytics/AGENTS.md +7 -4
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +4 -3
- package/corpus/templates/analytics/changelog/2026-07-30-delegated-analytics-questions-now-return-a-useful-current-me.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-30-scheduled-dashboard-emails-complete-every-panel-without-pool.md +6 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +28 -1
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +18 -11
- package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +4 -4
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -5
- package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +6 -2
- package/corpus/templates/analytics/server/lib/dashboard-panel-source-resolver.ts +11 -4
- package/corpus/templates/analytics/server/lib/dashboard-report-render.ts +49 -31
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +16 -8
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +9 -3
- package/corpus/templates/analytics/server/lib/production-serverless-runtime.ts +11 -0
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +10 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +3 -2
- package/corpus/templates/analytics/server/plugins/analytics-alert-jobs.ts +2 -1
- package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +2 -1
- package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +1 -12
- package/corpus/templates/brain/.agents/skills/brain/RUNBOOK.md +54 -6
- package/corpus/templates/brain/.agents/skills/brain/SKILL.md +20 -9
- package/corpus/templates/brain/.agents/skills/ingestion-and-connectors/SKILL.md +33 -1
- package/corpus/templates/brain/AGENTS.md +3 -1
- package/corpus/templates/brain/README.md +4 -1
- package/corpus/templates/brain/actions/_schemas.ts +30 -8
- package/corpus/templates/brain/actions/ask-brain.ts +124 -14
- package/corpus/templates/brain/actions/create-source.ts +18 -2
- package/corpus/templates/brain/actions/enqueue-captures-distillation.ts +8 -123
- package/corpus/templates/brain/actions/enqueue-distillation.ts +6 -127
- package/corpus/templates/brain/actions/import-capture.ts +17 -5
- package/corpus/templates/brain/actions/import-transcript.ts +17 -5
- package/corpus/templates/brain/actions/update-source.ts +25 -5
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/brain/changelog/2026-07-30-blessed-resources-can-feed-cited-company-answers.md +6 -0
- package/corpus/templates/brain/changelog/2026-07-30-the-left-sidebar-no-longer-shows-a-blank-organization-placeh.md +6 -0
- package/corpus/templates/brain/server/lib/brain.ts +49 -19
- package/corpus/templates/brain/server/lib/distillation-queue.ts +147 -0
- package/corpus/templates/brain/server/lib/source-policy.ts +264 -0
- package/corpus/templates/brain/server/plugins/agent-chat.ts +3 -2
- package/corpus/templates/brain/server/routes/api/_agent-native/brain/ingest.post.ts +116 -19
- package/corpus/templates/clips/app/components/meetings/google-oauth-identity-notice.tsx +51 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +4 -0
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +3 -1
- package/corpus/templates/clips/changelog/2026-07-30-google-calendar-warning.md +6 -0
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +72 -8
- package/corpus/templates/content/changelog/2026-07-30-the-slash-command-hint-now-stays-on-only-the-active-empty-li.md +6 -0
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +506 -11
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +148 -17
- package/corpus/templates/design/app/components/design/canvas-interactions/design-canvas-interactions.ts +357 -0
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +7 -466
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +15 -311
- package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +19 -997
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +8 -1216
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +31 -38
- package/corpus/templates/design/package.json +0 -1
- package/corpus/templates/dispatch/AGENTS.md +8 -0
- package/corpus/templates/dispatch/DEVELOPING.md +29 -0
- package/corpus/templates/dispatch/actions/view-screen.ts +34 -2
- package/corpus/templates/dispatch/app/i18n-data.ts +457 -0
- package/corpus/templates/dispatch/changelog/2026-07-30-delegated-dispatch-work-continues-reliably-in-the-background.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-30-find-failed-runs-across-apps.md +6 -0
- package/corpus/templates/dispatch/netlify.toml +3 -0
- package/corpus/templates/plan/actions/get-visual-plan.ts +10 -6
- package/corpus/templates/plan/actions/patch-visual-plan-source.ts +117 -1
- package/corpus/templates/plan/actions/update-visual-plan.ts +56 -14
- package/corpus/templates/plan/app/pages/PlansPage.tsx +1 -1
- package/corpus/templates/plan/changelog/2026-07-30-plan-agents-recover-with-targeted-writes.md +6 -0
- package/corpus/templates/plan/shared/plan-content.ts +35 -0
- package/corpus/templates/slides/.agents/skills/analytics-data-for-decks/SKILL.md +8 -12
- package/corpus/templates/slides/AGENTS.md +4 -4
- package/corpus/templates/slides/actions/create-deck.ts +1 -1
- package/corpus/templates/slides/actions/duplicate-deck.ts +19 -4
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +2 -10
- package/corpus/templates/slides/app/components/deck/MermaidRenderer.tsx +12 -1
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +30 -13
- package/corpus/templates/slides/app/components/editor/DeckEditorSkeleton.tsx +35 -0
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +63 -56
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +21 -76
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1110 -292
- package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +53 -0
- package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +235 -18
- package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +4 -1
- package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +11 -0
- package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +129 -0
- package/corpus/templates/slides/app/components/editor/selection-overlay-measurement.ts +70 -0
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +519 -65
- package/corpus/templates/slides/app/components/visual-editor/CanvasCommentPins.tsx +36 -426
- package/corpus/templates/slides/app/components/visual-editor/DrawOverlay.tsx +19 -458
- package/corpus/templates/slides/app/context/DeckContext.tsx +179 -72
- package/corpus/templates/slides/app/global.css +105 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +8 -0
- package/corpus/templates/slides/app/lib/deck-editor-loading.ts +26 -0
- package/corpus/templates/slides/app/lib/mermaid-blocks.ts +27 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +71 -38
- package/corpus/templates/slides/app/pages/Index.tsx +98 -41
- package/corpus/templates/slides/changelog/2026-07-30-deck-loading-no-longer-flashes-an-unavailable-message.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-duplicating-a-deck-from-the-deck-list-now-opens-the-copy-imm.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-layout-overflow-warning-is-readable-and-dismissible.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-new-decks-now-start-with-an-empty-slide-list-when-no-slides-.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-shared-canvas-annotations.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-slide-objects-can-be-moved-as-a-group-copied-and-pasted-with.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-slide-text-editing-and-object-controls-now-match-google-slides.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-style-panel-now-scrolls-with-speaker-notes-open-shows-the-sl.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-top-toolbar-controls-now-use-consistent-sizing-with-undo-and.md +6 -0
- package/corpus/templates/slides/netlify.toml +3 -1
- package/corpus/templates/slides/package.json +0 -1
- package/corpus/templates/slides/server/lib/chat-attachments.ts +18 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -0
- package/corpus/templates/slides/vite.config.ts +0 -1
- package/corpus/toolkit/CHANGELOG.md +13 -0
- package/corpus/toolkit/README.md +19 -0
- package/corpus/toolkit/agent-native.eject.json +38 -0
- package/corpus/toolkit/package.json +29 -1
- package/corpus/toolkit/src/canvas-annotations/CanvasCommentPins.tsx +861 -0
- package/corpus/toolkit/src/canvas-annotations/DrawOverlay.tsx +1233 -0
- package/corpus/toolkit/src/canvas-annotations/index.ts +15 -0
- package/corpus/toolkit/src/canvas-annotations/types.ts +14 -0
- package/corpus/toolkit/src/canvas-interactions/canvas-interactions.ts +933 -0
- package/corpus/toolkit/src/canvas-interactions/index.ts +67 -0
- package/corpus/toolkit/src/design-tweaks/scrub-input-utils.ts +311 -0
- package/corpus/toolkit/src/design-tweaks/scrub-input.tsx +491 -0
- package/corpus/toolkit/src/design-tweaks/visual-style-controls.tsx +26 -171
- package/corpus/toolkit/src/index.ts +1 -0
- package/dist/a2a/client.d.ts +23 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +309 -44
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/correlation.d.ts +1 -0
- package/dist/a2a/correlation.d.ts.map +1 -1
- package/dist/a2a/correlation.js +27 -0
- package/dist/a2a/correlation.js.map +1 -1
- package/dist/a2a/invoke.d.ts.map +1 -1
- package/dist/a2a/invoke.js +4 -2
- package/dist/a2a/invoke.js.map +1 -1
- package/dist/a2a/types.d.ts +4 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/action.d.ts +3 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js.map +1 -1
- package/dist/agent/action-continuation-guidance.d.ts.map +1 -1
- package/dist/agent/action-continuation-guidance.js +2 -1
- package/dist/agent/action-continuation-guidance.js.map +1 -1
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +82 -8
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/agent/production-agent.d.ts +40 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +113 -15
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts +21 -0
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +163 -4
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/agent/run-manager.d.ts +16 -9
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +93 -42
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/thread-debug-history.d.ts +10 -0
- package/dist/agent/thread-debug-history.d.ts.map +1 -0
- package/dist/agent/thread-debug-history.js +68 -0
- package/dist/agent/thread-debug-history.js.map +1 -0
- package/dist/agent/types.d.ts +3 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +6 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/templates-meta.js +2 -2
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +76 -13
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.js +1 -1
- package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +12 -3
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +48 -17
- package/dist/db/client.js.map +1 -1
- package/dist/guards/no-env-credentials.d.ts.map +1 -1
- package/dist/guards/no-env-credentials.js +2 -0
- package/dist/guards/no-env-credentials.js.map +1 -1
- package/dist/integrations/adapters/slack.js +7 -3
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/localization/default-messages.d.ts +43 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +43 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/observability/traces.d.ts +3 -1
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +73 -4
- package/dist/observability/traces.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
- package/dist/provider-api/actions/provider-api.d.ts +13 -13
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +237 -12
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/scripts/describe-workspace-apps.js +2 -2
- package/dist/scripts/describe-workspace-apps.js.map +1 -1
- package/dist/secrets/crypto.d.ts +36 -21
- package/dist/secrets/crypto.d.ts.map +1 -1
- package/dist/secrets/crypto.js +134 -33
- package/dist/secrets/crypto.js.map +1 -1
- package/dist/secrets/storage.d.ts +6 -6
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +43 -26
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/agent-capabilities.js +1 -1
- package/dist/server/agent-capabilities.js.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.d.ts +17 -3
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +94 -3
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/agent-chat/context-tools.d.ts.map +1 -1
- package/dist/server/agent-chat/context-tools.js +2 -0
- package/dist/server/agent-chat/context-tools.js.map +1 -1
- package/dist/server/agent-chat/plugin-options.d.ts +23 -0
- package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
- package/dist/server/agent-chat/plugin-options.js +4 -1
- package/dist/server/agent-chat/plugin-options.js.map +1 -1
- package/dist/server/agent-chat/prompt-resources.js +1 -1
- package/dist/server/agent-chat/prompt-resources.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +2 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +79 -56
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/templates/default/.agents/skills/secrets/SKILL.md +19 -7
- package/dist/templates/headless/.agents/skills/secrets/SKILL.md +19 -7
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
- package/dist/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +19 -7
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +1 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/agent-native-toolkit.mdx +13 -6
- package/docs/content/cloneable-saas.mdx +5 -5
- package/docs/content/creating-templates.mdx +9 -0
- package/docs/content/deployment.mdx +18 -12
- package/docs/content/doctor.mdx +8 -1
- package/docs/content/drop-in-agent.mdx +1 -1
- package/docs/content/extensions.mdx +18 -18
- package/docs/content/faq.mdx +4 -4
- package/docs/content/key-concepts.mdx +30 -21
- package/docs/content/security.mdx +2 -2
- package/docs/content/template-brain-developers.mdx +4 -1
- package/docs/content/template-mail-developers.mdx +4 -1
- package/docs/content/what-is-agent-native.mdx +6 -6
- package/package.json +3 -3
- package/src/a2a/client.ts +449 -66
- package/src/a2a/correlation.ts +31 -0
- package/src/a2a/invoke.ts +4 -2
- package/src/a2a/types.ts +4 -0
- package/src/action.ts +3 -0
- package/src/agent/action-continuation-guidance.ts +2 -1
- package/src/agent/engine/registry.ts +98 -6
- package/src/agent/production-agent.ts +151 -18
- package/src/agent/run-loop-with-resume.ts +190 -3
- package/src/agent/run-manager.ts +117 -41
- package/src/agent/thread-debug-history.ts +86 -0
- package/src/agent/types.ts +3 -1
- package/src/cli/create.ts +11 -1
- package/src/cli/templates-meta.ts +2 -2
- package/src/client/agent-chat-adapter.ts +91 -13
- package/src/client/resources/McpIntegrationDialog.tsx +11 -9
- package/src/client/sse-event-processor.ts +17 -3
- package/src/db/client.ts +57 -17
- package/src/guards/no-env-credentials.ts +2 -0
- package/src/integrations/adapters/slack.ts +8 -3
- package/src/localization/default-messages.ts +44 -0
- package/src/observability/traces.ts +78 -5
- package/src/scripts/call-agent.ts +294 -11
- package/src/scripts/describe-workspace-apps.ts +2 -2
- package/src/secrets/crypto.ts +156 -35
- package/src/secrets/storage.ts +64 -26
- package/src/server/agent-capabilities.ts +1 -1
- package/src/server/agent-chat/action-filters-a2a.ts +113 -7
- package/src/server/agent-chat/context-tools.ts +2 -0
- package/src/server/agent-chat/plugin-options.ts +34 -1
- package/src/server/agent-chat/prompt-resources.ts +1 -1
- package/src/server/agent-chat-plugin.ts +91 -59
- package/src/templates/default/.agents/skills/secrets/SKILL.md +19 -7
- package/src/templates/headless/.agents/skills/secrets/SKILL.md +19 -7
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
- package/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +19 -7
- package/src/vite/client.ts +1 -0
package/dist/secrets/crypto.js
CHANGED
|
@@ -10,24 +10,25 @@
|
|
|
10
10
|
* existing single-app deployments can keep reading and writing secrets while
|
|
11
11
|
* they migrate to the shared key.
|
|
12
12
|
*
|
|
13
|
-
* The
|
|
14
|
-
* `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when set, then
|
|
15
|
-
* then `BETTER_AUTH_SECRET`.
|
|
16
|
-
*
|
|
17
|
-
* shared
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* apps
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
13
|
+
* The generic encryption key is derived from
|
|
14
|
+
* `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when set, then
|
|
15
|
+
* `SECRETS_ENCRYPTION_KEY`, then `BETTER_AUTH_SECRET`. Workspace-shared vault
|
|
16
|
+
* rows use a different precedence: `WORKSPACE_SECRETS_ENCRYPTION_KEY`, then
|
|
17
|
+
* the legacy shared `SECRETS_ENCRYPTION_KEY`, then material derived from the
|
|
18
|
+
* workspace-wide `A2A_SECRET`, then the app-local auth/key fallbacks retained
|
|
19
|
+
* for backward-compatible reads. A previous workspace key can be supplied
|
|
20
|
+
* during rotation with `WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS`. This keeps
|
|
21
|
+
* sibling apps on one stable key even when each app correctly has a different
|
|
22
|
+
* auth secret, and prevents A2A trust rotation from stranding vault data.
|
|
23
|
+
*
|
|
24
|
+
* In production we refuse to start without configured key material — a
|
|
25
|
+
* CWD-derived fallback would be effectively static (e.g. `/var/task` on
|
|
26
|
+
* Lambda), so anyone with read access to the DB could decrypt every secret.
|
|
26
27
|
*
|
|
27
28
|
* Encrypted values are tagged `v1:<iv-hex>:<ct-hex>:<tag-hex>`. The `v1:` prefix
|
|
28
29
|
* lets readers distinguish ciphertext from legacy plaintext during migration.
|
|
29
30
|
*/
|
|
30
|
-
import { getWorkspaceA2ADerivedSecret } from "../server/derived-secret.js";
|
|
31
|
+
import { deriveServerSecret, getWorkspaceA2ADerivedSecret, } from "../server/derived-secret.js";
|
|
31
32
|
function getNodeCrypto() {
|
|
32
33
|
if (typeof window !== "undefined" ||
|
|
33
34
|
typeof process === "undefined" ||
|
|
@@ -66,15 +67,81 @@ function appScopedEncryptionKey() {
|
|
|
66
67
|
? process.env[`${appName}_SECRETS_ENCRYPTION_KEY`] // guard:allow-env-credential — deploy-level app encryption material, never a user credential.
|
|
67
68
|
: undefined;
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Preserve the generic/app-local key precedence. Generic encrypted values are
|
|
72
|
+
* not a cross-app boundary, and changing their preferred key would strand
|
|
73
|
+
* existing OAuth tokens and credentials.
|
|
74
|
+
*/
|
|
75
|
+
function genericEncryptionKeyMaterial() {
|
|
70
76
|
if (typeof process === "undefined")
|
|
71
77
|
return undefined;
|
|
72
78
|
return (process.env.SECRETS_ENCRYPTION_KEY ||
|
|
73
79
|
process.env.BETTER_AUTH_SECRET ||
|
|
74
80
|
getWorkspaceA2ADerivedSecret("secrets-encryption"));
|
|
75
81
|
}
|
|
76
|
-
function
|
|
82
|
+
function workspaceSharedEncryptionKeyMaterial() {
|
|
83
|
+
if (typeof process === "undefined")
|
|
84
|
+
return undefined;
|
|
85
|
+
return process.env.WORKSPACE_SECRETS_ENCRYPTION_KEY?.trim() || undefined;
|
|
86
|
+
}
|
|
87
|
+
function previousWorkspaceSharedEncryptionKeyMaterial() {
|
|
88
|
+
if (typeof process === "undefined")
|
|
89
|
+
return undefined;
|
|
90
|
+
return (process.env.WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS?.trim() || undefined);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Vault sharing follows configured A2A trust even on independently deployed
|
|
94
|
+
* sibling apps that are not launched by the workspace wrapper. The wrapper's
|
|
95
|
+
* `AGENT_NATIVE_WORKSPACE` flag remains required for auth/OAuth-derived
|
|
96
|
+
* secrets, but it must not be a hidden prerequisite for decrypting vault rows
|
|
97
|
+
* when every app already has the same explicit `A2A_SECRET`.
|
|
98
|
+
*/
|
|
99
|
+
function a2aSharedEncryptionKeyMaterial() {
|
|
100
|
+
const workspaceDerived = getWorkspaceA2ADerivedSecret("secrets-encryption");
|
|
101
|
+
if (workspaceDerived)
|
|
102
|
+
return workspaceDerived;
|
|
103
|
+
if (typeof process === "undefined")
|
|
104
|
+
return undefined;
|
|
105
|
+
const rootSecret = process.env.A2A_SECRET?.trim();
|
|
106
|
+
return rootSecret
|
|
107
|
+
? deriveServerSecret(rootSecret, "secrets-encryption")
|
|
108
|
+
: undefined;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Stable materials that may be used for workspace-shared vault rows, ordered
|
|
112
|
+
* from current preference to legacy compatibility fallbacks.
|
|
113
|
+
*
|
|
114
|
+
* `BETTER_AUTH_SECRET` deliberately comes after the workspace and A2A-derived
|
|
115
|
+
* materials: sibling apps normally have different auth secrets but share
|
|
116
|
+
* workspace trust.
|
|
117
|
+
*/
|
|
118
|
+
function sharedEncryptionKeyMaterials() {
|
|
119
|
+
if (typeof process === "undefined")
|
|
120
|
+
return [];
|
|
121
|
+
const candidates = [
|
|
122
|
+
workspaceSharedEncryptionKeyMaterial(),
|
|
123
|
+
process.env.SECRETS_ENCRYPTION_KEY,
|
|
124
|
+
a2aSharedEncryptionKeyMaterial(),
|
|
125
|
+
previousWorkspaceSharedEncryptionKeyMaterial(),
|
|
126
|
+
process.env.BETTER_AUTH_SECRET,
|
|
127
|
+
appScopedEncryptionKey(),
|
|
128
|
+
].filter((value) => Boolean(value));
|
|
129
|
+
return [...new Set(candidates)];
|
|
130
|
+
}
|
|
131
|
+
function preferredSharedEncryptionKeyMaterial() {
|
|
132
|
+
if (typeof process === "undefined")
|
|
133
|
+
return undefined;
|
|
134
|
+
return (workspaceSharedEncryptionKeyMaterial() ||
|
|
135
|
+
process.env.SECRETS_ENCRYPTION_KEY ||
|
|
136
|
+
a2aSharedEncryptionKeyMaterial() ||
|
|
137
|
+
process.env.BETTER_AUTH_SECRET ||
|
|
138
|
+
appScopedEncryptionKey());
|
|
139
|
+
}
|
|
140
|
+
function hashSecretEncryptionKey(material) {
|
|
77
141
|
const { createHash } = requireNodeCrypto();
|
|
142
|
+
return createHash("sha256").update(material).digest();
|
|
143
|
+
}
|
|
144
|
+
function deriveSecretEncryptionKey(explicit, errorMessage, warningMessage) {
|
|
78
145
|
if (!explicit) {
|
|
79
146
|
if (processNodeEnv() === "production") {
|
|
80
147
|
throw new Error(errorMessage);
|
|
@@ -86,7 +153,7 @@ function deriveSecretEncryptionKey(explicit, errorMessage, warningMessage) {
|
|
|
86
153
|
}
|
|
87
154
|
}
|
|
88
155
|
const material = explicit || `agent-native-secrets:${processCwd()}`;
|
|
89
|
-
return
|
|
156
|
+
return hashSecretEncryptionKey(material);
|
|
90
157
|
}
|
|
91
158
|
/**
|
|
92
159
|
* Derive the key used by generic encrypted values such as credentials and
|
|
@@ -100,7 +167,7 @@ export function getSecretEncryptionKey() {
|
|
|
100
167
|
.toUpperCase()
|
|
101
168
|
.replace(/[^A-Z0-9]+/g, "_")
|
|
102
169
|
.replace(/^_+|_+$/g, "");
|
|
103
|
-
return deriveSecretEncryptionKey(appScopedEncryptionKey() ||
|
|
170
|
+
return deriveSecretEncryptionKey(appScopedEncryptionKey() || genericEncryptionKeyMaterial(), "[agent-native/secrets] Refusing to start in production without an encryption key. " +
|
|
104
171
|
`Set ${appName ? `${appName}_SECRETS_ENCRYPTION_KEY, ` : ""}SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET in the deploy environment. ` +
|
|
105
172
|
"The previous CWD-derived fallback was effectively static (e.g. `/var/task` on Lambda), " +
|
|
106
173
|
"which means anyone with read access to the DB could decrypt every secret.", "[agent-native/secrets] SECRETS_ENCRYPTION_KEY not set — using a machine-local fallback. " +
|
|
@@ -109,25 +176,30 @@ export function getSecretEncryptionKey() {
|
|
|
109
176
|
}
|
|
110
177
|
/**
|
|
111
178
|
* Derive the preferred workspace-shared key used by `app_secrets` rows.
|
|
112
|
-
* Unlike generic column-level encryption, workspace vault data
|
|
113
|
-
* in sibling apps
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
179
|
+
* Unlike generic column-level encryption, workspace vault data must decrypt
|
|
180
|
+
* in sibling apps. An explicit `WORKSPACE_SECRETS_ENCRYPTION_KEY` wins without
|
|
181
|
+
* changing the key for app-local OAuth and credential ciphertext. The legacy
|
|
182
|
+
* shared `SECRETS_ENCRYPTION_KEY` remains supported; hosted workspaces
|
|
183
|
+
* otherwise derive material from their shared `A2A_SECRET` before considering
|
|
184
|
+
* app-local `BETTER_AUTH_SECRET` or app-scoped key fallbacks. Reads try every
|
|
185
|
+
* configured legacy/previous candidate and report when the ciphertext should
|
|
186
|
+
* be refreshed under the preferred key.
|
|
120
187
|
*/
|
|
121
188
|
export function getSharedSecretEncryptionKey() {
|
|
122
|
-
return deriveSecretEncryptionKey(
|
|
123
|
-
"Set
|
|
124
|
-
"
|
|
125
|
-
"
|
|
126
|
-
"or rely on A2A_SECRET-derived material on hosted workspace deploys.");
|
|
189
|
+
return deriveSecretEncryptionKey(preferredSharedEncryptionKeyMaterial(), "[agent-native/secrets] Refusing to start in production without encryption key material for workspace secrets. " +
|
|
190
|
+
"Set WORKSPACE_SECRETS_ENCRYPTION_KEY (preferred), SECRETS_ENCRYPTION_KEY, ensure a hosted workspace has A2A_SECRET, " +
|
|
191
|
+
"or set BETTER_AUTH_SECRET / an app-scoped " +
|
|
192
|
+
"*_SECRETS_ENCRYPTION_KEY compatibility fallback in the deploy environment.", "[agent-native/secrets] Workspace encryption key not set — using app-scoped or machine-local fallback for workspace secrets. " +
|
|
193
|
+
"Set WORKSPACE_SECRETS_ENCRYPTION_KEY or rely on A2A_SECRET-derived material on hosted workspace deploys so sibling apps share vault rows.");
|
|
127
194
|
}
|
|
128
195
|
/** Whether this deployment has stable workspace-shared key material. */
|
|
129
196
|
export function hasSharedSecretEncryptionKeyMaterial() {
|
|
130
|
-
|
|
197
|
+
if (typeof process === "undefined")
|
|
198
|
+
return false;
|
|
199
|
+
return Boolean(workspaceSharedEncryptionKeyMaterial() ||
|
|
200
|
+
process.env.SECRETS_ENCRYPTION_KEY ||
|
|
201
|
+
a2aSharedEncryptionKeyMaterial() ||
|
|
202
|
+
process.env.BETTER_AUTH_SECRET);
|
|
131
203
|
}
|
|
132
204
|
function encryptWithKey(plaintext, key) {
|
|
133
205
|
const { createCipheriv, randomBytes } = requireNodeCrypto();
|
|
@@ -168,7 +240,36 @@ export function encryptSharedSecretValue(plaintext) {
|
|
|
168
240
|
}
|
|
169
241
|
/** Decrypt a workspace-shared `app_secrets` value. */
|
|
170
242
|
export function decryptSharedSecretValue(encrypted) {
|
|
171
|
-
return
|
|
243
|
+
return decryptSharedSecretValueDetailed(encrypted).value;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Decrypt workspace ciphertext with the preferred key first, then configured
|
|
247
|
+
* legacy candidates. The result never exposes which secret material matched.
|
|
248
|
+
*/
|
|
249
|
+
export function decryptSharedSecretValueDetailed(encrypted) {
|
|
250
|
+
const preferredKey = getSharedSecretEncryptionKey();
|
|
251
|
+
const candidateKeys = [preferredKey];
|
|
252
|
+
for (const material of sharedEncryptionKeyMaterials()) {
|
|
253
|
+
const candidate = hashSecretEncryptionKey(material);
|
|
254
|
+
if (!candidateKeys.some((existing) => existing.equals(candidate))) {
|
|
255
|
+
candidateKeys.push(candidate);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
let lastError;
|
|
259
|
+
for (const candidate of candidateKeys) {
|
|
260
|
+
try {
|
|
261
|
+
return {
|
|
262
|
+
value: decryptWithKey(encrypted, candidate),
|
|
263
|
+
needsReencrypt: !candidate.equals(preferredKey),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
lastError = error;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
throw lastError instanceof Error
|
|
271
|
+
? lastError
|
|
272
|
+
: new Error("Unable to decrypt workspace secret");
|
|
172
273
|
}
|
|
173
274
|
/**
|
|
174
275
|
* Strict check for a value produced by `encryptSecretValue`: `v1:` followed by
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/secrets/crypto.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared AES-256-GCM encryption for secret values at rest.\n *\n * Used by the framework secrets vault, per-user/per-org credentials\n * (`resolveCredential` / `saveCredential`, stored in `settings`), and other\n * column-level encrypted values. The `app_secrets` storage layer uses the\n * shared-key variant below because workspace-scoped vault rows are readable by\n * sibling apps in the same workspace. When a deployment has not been given\n * shared key material yet, that variant falls back to the app-scoped key so\n * existing single-app deployments can keep reading and writing secrets while\n * they migrate to the shared key.\n *\n * The default encryption key is derived from\n * `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when set, then `SECRETS_ENCRYPTION_KEY`,\n * then `BETTER_AUTH_SECRET`. The app-scoped key lets a local multi-app\n * workspace read one app's encrypted production data without replacing the\n * shared local auth secret. Hosted workspace deploys (`AGENT_NATIVE_WORKSPACE`)\n * typically set none of those literal env vars per app; in that case the\n * shared-key variant falls back further to material derived from the\n * workspace-wide `A2A_SECRET` (see `getWorkspaceA2ADerivedSecret`), so sibling\n * apps in the same workspace still land on the same shared key without any\n * extra configuration. In production we refuse to start without configured\n * key material — a CWD-derived fallback would be effectively static (e.g.\n * `/var/task` on Lambda), so anyone with read access to the DB could decrypt\n * every secret.\n *\n * Encrypted values are tagged `v1:<iv-hex>:<ct-hex>:<tag-hex>`. The `v1:` prefix\n * lets readers distinguish ciphertext from legacy plaintext during migration.\n */\n\nimport { getWorkspaceA2ADerivedSecret } from \"../server/derived-secret.js\";\n\ntype NodeCryptoModule = typeof import(\"node:crypto\");\n\nfunction getNodeCrypto(): NodeCryptoModule | undefined {\n if (\n typeof window !== \"undefined\" ||\n typeof process === \"undefined\" ||\n !process.versions?.node ||\n typeof process.getBuiltinModule !== \"function\"\n ) {\n return undefined;\n }\n return process.getBuiltinModule(\"node:crypto\") as\n | NodeCryptoModule\n | undefined;\n}\n\nconst nodeCrypto = getNodeCrypto();\n\nlet _warnedFallback = false;\n\nfunction requireNodeCrypto(): NonNullable<typeof nodeCrypto> {\n if (!nodeCrypto) {\n throw new Error(\n \"[agent-native/secrets] Secret encryption is only available in server/runtime code.\",\n );\n }\n return nodeCrypto;\n}\n\nfunction processNodeEnv(): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return process.env.NODE_ENV;\n}\n\nfunction processCwd(): string {\n if (typeof process === \"undefined\") return \".\";\n return process.cwd();\n}\n\nfunction appScopedEncryptionKey(): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n const appName = process.env.APP_NAME?.trim() // guard:allow-env-credential — deploy-level app configuration selects the scoped encryption key.\n .toUpperCase()\n .replace(/[^A-Z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n return appName\n ? process.env[`${appName}_SECRETS_ENCRYPTION_KEY`] // guard:allow-env-credential — deploy-level app encryption material, never a user credential.\n : undefined;\n}\n\nfunction sharedEncryptionKeyMaterial(): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return (\n process.env.SECRETS_ENCRYPTION_KEY ||\n process.env.BETTER_AUTH_SECRET ||\n getWorkspaceA2ADerivedSecret(\"secrets-encryption\")\n );\n}\n\nfunction deriveSecretEncryptionKey(\n explicit: string | undefined,\n errorMessage: string,\n warningMessage: string,\n): Buffer {\n const { createHash } = requireNodeCrypto();\n\n if (!explicit) {\n if (processNodeEnv() === \"production\") {\n throw new Error(errorMessage);\n }\n if (!_warnedFallback) {\n _warnedFallback = true;\n // eslint-disable-next-line no-console\n console.warn(warningMessage);\n }\n }\n\n const material = explicit || `agent-native-secrets:${processCwd()}`;\n return createHash(\"sha256\").update(material).digest();\n}\n\n/**\n * Derive the key used by generic encrypted values such as credentials and\n * OAuth tokens. App-specific key material is allowed for these app-local\n * values.\n */\nexport function getSecretEncryptionKey(): Buffer {\n const appName =\n typeof process === \"undefined\"\n ? undefined\n : process.env.APP_NAME?.trim() // guard:allow-env-credential — deploy-level app configuration selects the scoped encryption key.\n .toUpperCase()\n .replace(/[^A-Z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n return deriveSecretEncryptionKey(\n appScopedEncryptionKey() || sharedEncryptionKeyMaterial(),\n \"[agent-native/secrets] Refusing to start in production without an encryption key. \" +\n `Set ${appName ? `${appName}_SECRETS_ENCRYPTION_KEY, ` : \"\"}SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET in the deploy environment. ` +\n \"The previous CWD-derived fallback was effectively static (e.g. `/var/task` on Lambda), \" +\n \"which means anyone with read access to the DB could decrypt every secret.\",\n \"[agent-native/secrets] SECRETS_ENCRYPTION_KEY not set — using a machine-local fallback. \" +\n \"Set an app-scoped *_SECRETS_ENCRYPTION_KEY, SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET for production. \" +\n \"Production deploys without one of these env vars now hard-fail.\",\n );\n}\n\n/**\n * Derive the preferred workspace-shared key used by `app_secrets` rows.\n * Unlike generic column-level encryption, workspace vault data should decrypt\n * in sibling apps, so `SECRETS_ENCRYPTION_KEY` / `BETTER_AUTH_SECRET` take\n * precedence. Hosted workspace deploys (`AGENT_NATIVE_WORKSPACE`) that set\n * neither literal var still get stable shared material derived from the\n * workspace-wide `A2A_SECRET` before falling further back. The app-scoped key\n * remains a compatibility fallback for deployments that have not configured\n * shared material yet; once the shared key is configured, writes use it and\n * reads still fall back to old rows.\n */\nexport function getSharedSecretEncryptionKey(): Buffer {\n return deriveSecretEncryptionKey(\n sharedEncryptionKeyMaterial() || appScopedEncryptionKey(),\n \"[agent-native/secrets] Refusing to start in production without encryption key material for workspace secrets. \" +\n \"Set SECRETS_ENCRYPTION_KEY, BETTER_AUTH_SECRET, or the app-scoped *_SECRETS_ENCRYPTION_KEY in the deploy environment \" +\n \"— or, on a hosted workspace deploy, ensure A2A_SECRET is set so shared material can be derived from it.\",\n \"[agent-native/secrets] SECRETS_ENCRYPTION_KEY not set — using app-scoped or machine-local fallback for workspace secrets. \" +\n \"Set SECRETS_ENCRYPTION_KEY or BETTER_AUTH_SECRET in every workspace app so sibling apps share vault rows, \" +\n \"or rely on A2A_SECRET-derived material on hosted workspace deploys.\",\n );\n}\n\n/** Whether this deployment has stable workspace-shared key material. */\nexport function hasSharedSecretEncryptionKeyMaterial(): boolean {\n return Boolean(sharedEncryptionKeyMaterial());\n}\n\nfunction encryptWithKey(plaintext: string, key: Buffer): string {\n const { createCipheriv, randomBytes } = requireNodeCrypto();\n const iv = randomBytes(12);\n const cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n const ct = Buffer.concat([cipher.update(plaintext, \"utf8\"), cipher.final()]);\n const tag = cipher.getAuthTag();\n return `v1:${iv.toString(\"hex\")}:${ct.toString(\"hex\")}:${tag.toString(\"hex\")}`;\n}\n\nfunction decryptWithKey(encrypted: string, key: Buffer): string {\n const { createDecipheriv } = requireNodeCrypto();\n if (!encrypted.startsWith(\"v1:\")) {\n throw new Error(\"Unrecognised secret encoding\");\n }\n const [, ivHex, ctHex, tagHex] = encrypted.split(\":\");\n if (!ivHex || !ctHex || !tagHex) {\n throw new Error(\"Corrupt secret payload\");\n }\n const decipher = createDecipheriv(\n \"aes-256-gcm\",\n key,\n Buffer.from(ivHex, \"hex\"),\n );\n decipher.setAuthTag(Buffer.from(tagHex, \"hex\"));\n const pt = Buffer.concat([\n decipher.update(Buffer.from(ctHex, \"hex\")),\n decipher.final(),\n ]);\n return pt.toString(\"utf8\");\n}\n\n/** Encrypt a plain-text value with the generic app-local key. */\nexport function encryptSecretValue(plaintext: string): string {\n return encryptWithKey(plaintext, getSecretEncryptionKey());\n}\n\n/** Decrypt a value produced by `encryptSecretValue`. Throws on tampering. */\nexport function decryptSecretValue(encrypted: string): string {\n return decryptWithKey(encrypted, getSecretEncryptionKey());\n}\n\n/** Encrypt a workspace-shared `app_secrets` value. */\nexport function encryptSharedSecretValue(plaintext: string): string {\n return encryptWithKey(plaintext, getSharedSecretEncryptionKey());\n}\n\n/** Decrypt a workspace-shared `app_secrets` value. */\nexport function decryptSharedSecretValue(encrypted: string): string {\n return decryptWithKey(encrypted, getSharedSecretEncryptionKey());\n}\n\n/**\n * Strict check for a value produced by `encryptSecretValue`: `v1:` followed by\n * three hex segments. Intentionally strict so a legacy plaintext credential\n * that merely happens to start with `v1:` is treated as plaintext (and read via\n * the legacy fallback) rather than mis-decrypted.\n */\nconst ENCRYPTED_VALUE_RE = /^v1:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+$/;\n\nexport function isEncryptedSecretValue(value: unknown): value is string {\n return typeof value === \"string\" && ENCRYPTED_VALUE_RE.test(value);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/secrets/crypto.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared AES-256-GCM encryption for secret values at rest.\n *\n * Used by the framework secrets vault, per-user/per-org credentials\n * (`resolveCredential` / `saveCredential`, stored in `settings`), and other\n * column-level encrypted values. The `app_secrets` storage layer uses the\n * shared-key variant below because workspace-scoped vault rows are readable by\n * sibling apps in the same workspace. When a deployment has not been given\n * shared key material yet, that variant falls back to the app-scoped key so\n * existing single-app deployments can keep reading and writing secrets while\n * they migrate to the shared key.\n *\n * The generic encryption key is derived from\n * `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when set, then\n * `SECRETS_ENCRYPTION_KEY`, then `BETTER_AUTH_SECRET`. Workspace-shared vault\n * rows use a different precedence: `WORKSPACE_SECRETS_ENCRYPTION_KEY`, then\n * the legacy shared `SECRETS_ENCRYPTION_KEY`, then material derived from the\n * workspace-wide `A2A_SECRET`, then the app-local auth/key fallbacks retained\n * for backward-compatible reads. A previous workspace key can be supplied\n * during rotation with `WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS`. This keeps\n * sibling apps on one stable key even when each app correctly has a different\n * auth secret, and prevents A2A trust rotation from stranding vault data.\n *\n * In production we refuse to start without configured key material — a\n * CWD-derived fallback would be effectively static (e.g. `/var/task` on\n * Lambda), so anyone with read access to the DB could decrypt every secret.\n *\n * Encrypted values are tagged `v1:<iv-hex>:<ct-hex>:<tag-hex>`. The `v1:` prefix\n * lets readers distinguish ciphertext from legacy plaintext during migration.\n */\n\nimport {\n deriveServerSecret,\n getWorkspaceA2ADerivedSecret,\n} from \"../server/derived-secret.js\";\n\ntype NodeCryptoModule = typeof import(\"node:crypto\");\n\nfunction getNodeCrypto(): NodeCryptoModule | undefined {\n if (\n typeof window !== \"undefined\" ||\n typeof process === \"undefined\" ||\n !process.versions?.node ||\n typeof process.getBuiltinModule !== \"function\"\n ) {\n return undefined;\n }\n return process.getBuiltinModule(\"node:crypto\") as\n | NodeCryptoModule\n | undefined;\n}\n\nconst nodeCrypto = getNodeCrypto();\n\nlet _warnedFallback = false;\n\nfunction requireNodeCrypto(): NonNullable<typeof nodeCrypto> {\n if (!nodeCrypto) {\n throw new Error(\n \"[agent-native/secrets] Secret encryption is only available in server/runtime code.\",\n );\n }\n return nodeCrypto;\n}\n\nfunction processNodeEnv(): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return process.env.NODE_ENV;\n}\n\nfunction processCwd(): string {\n if (typeof process === \"undefined\") return \".\";\n return process.cwd();\n}\n\nfunction appScopedEncryptionKey(): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n const appName = process.env.APP_NAME?.trim() // guard:allow-env-credential — deploy-level app configuration selects the scoped encryption key.\n .toUpperCase()\n .replace(/[^A-Z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n return appName\n ? process.env[`${appName}_SECRETS_ENCRYPTION_KEY`] // guard:allow-env-credential — deploy-level app encryption material, never a user credential.\n : undefined;\n}\n\n/**\n * Preserve the generic/app-local key precedence. Generic encrypted values are\n * not a cross-app boundary, and changing their preferred key would strand\n * existing OAuth tokens and credentials.\n */\nfunction genericEncryptionKeyMaterial(): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return (\n process.env.SECRETS_ENCRYPTION_KEY ||\n process.env.BETTER_AUTH_SECRET ||\n getWorkspaceA2ADerivedSecret(\"secrets-encryption\")\n );\n}\n\nfunction workspaceSharedEncryptionKeyMaterial(): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return process.env.WORKSPACE_SECRETS_ENCRYPTION_KEY?.trim() || undefined;\n}\n\nfunction previousWorkspaceSharedEncryptionKeyMaterial(): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return (\n process.env.WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS?.trim() || undefined\n );\n}\n\n/**\n * Vault sharing follows configured A2A trust even on independently deployed\n * sibling apps that are not launched by the workspace wrapper. The wrapper's\n * `AGENT_NATIVE_WORKSPACE` flag remains required for auth/OAuth-derived\n * secrets, but it must not be a hidden prerequisite for decrypting vault rows\n * when every app already has the same explicit `A2A_SECRET`.\n */\nfunction a2aSharedEncryptionKeyMaterial(): string | undefined {\n const workspaceDerived = getWorkspaceA2ADerivedSecret(\"secrets-encryption\");\n if (workspaceDerived) return workspaceDerived;\n if (typeof process === \"undefined\") return undefined;\n const rootSecret = process.env.A2A_SECRET?.trim();\n return rootSecret\n ? deriveServerSecret(rootSecret, \"secrets-encryption\")\n : undefined;\n}\n\n/**\n * Stable materials that may be used for workspace-shared vault rows, ordered\n * from current preference to legacy compatibility fallbacks.\n *\n * `BETTER_AUTH_SECRET` deliberately comes after the workspace and A2A-derived\n * materials: sibling apps normally have different auth secrets but share\n * workspace trust.\n */\nfunction sharedEncryptionKeyMaterials(): string[] {\n if (typeof process === \"undefined\") return [];\n const candidates = [\n workspaceSharedEncryptionKeyMaterial(),\n process.env.SECRETS_ENCRYPTION_KEY,\n a2aSharedEncryptionKeyMaterial(),\n previousWorkspaceSharedEncryptionKeyMaterial(),\n process.env.BETTER_AUTH_SECRET,\n appScopedEncryptionKey(),\n ].filter((value): value is string => Boolean(value));\n return [...new Set(candidates)];\n}\n\nfunction preferredSharedEncryptionKeyMaterial(): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return (\n workspaceSharedEncryptionKeyMaterial() ||\n process.env.SECRETS_ENCRYPTION_KEY ||\n a2aSharedEncryptionKeyMaterial() ||\n process.env.BETTER_AUTH_SECRET ||\n appScopedEncryptionKey()\n );\n}\n\nfunction hashSecretEncryptionKey(material: string): Buffer {\n const { createHash } = requireNodeCrypto();\n return createHash(\"sha256\").update(material).digest();\n}\n\nfunction deriveSecretEncryptionKey(\n explicit: string | undefined,\n errorMessage: string,\n warningMessage: string,\n): Buffer {\n if (!explicit) {\n if (processNodeEnv() === \"production\") {\n throw new Error(errorMessage);\n }\n if (!_warnedFallback) {\n _warnedFallback = true;\n // eslint-disable-next-line no-console\n console.warn(warningMessage);\n }\n }\n\n const material = explicit || `agent-native-secrets:${processCwd()}`;\n return hashSecretEncryptionKey(material);\n}\n\n/**\n * Derive the key used by generic encrypted values such as credentials and\n * OAuth tokens. App-specific key material is allowed for these app-local\n * values.\n */\nexport function getSecretEncryptionKey(): Buffer {\n const appName =\n typeof process === \"undefined\"\n ? undefined\n : process.env.APP_NAME?.trim() // guard:allow-env-credential — deploy-level app configuration selects the scoped encryption key.\n .toUpperCase()\n .replace(/[^A-Z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n return deriveSecretEncryptionKey(\n appScopedEncryptionKey() || genericEncryptionKeyMaterial(),\n \"[agent-native/secrets] Refusing to start in production without an encryption key. \" +\n `Set ${appName ? `${appName}_SECRETS_ENCRYPTION_KEY, ` : \"\"}SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET in the deploy environment. ` +\n \"The previous CWD-derived fallback was effectively static (e.g. `/var/task` on Lambda), \" +\n \"which means anyone with read access to the DB could decrypt every secret.\",\n \"[agent-native/secrets] SECRETS_ENCRYPTION_KEY not set — using a machine-local fallback. \" +\n \"Set an app-scoped *_SECRETS_ENCRYPTION_KEY, SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET for production. \" +\n \"Production deploys without one of these env vars now hard-fail.\",\n );\n}\n\n/**\n * Derive the preferred workspace-shared key used by `app_secrets` rows.\n * Unlike generic column-level encryption, workspace vault data must decrypt\n * in sibling apps. An explicit `WORKSPACE_SECRETS_ENCRYPTION_KEY` wins without\n * changing the key for app-local OAuth and credential ciphertext. The legacy\n * shared `SECRETS_ENCRYPTION_KEY` remains supported; hosted workspaces\n * otherwise derive material from their shared `A2A_SECRET` before considering\n * app-local `BETTER_AUTH_SECRET` or app-scoped key fallbacks. Reads try every\n * configured legacy/previous candidate and report when the ciphertext should\n * be refreshed under the preferred key.\n */\nexport function getSharedSecretEncryptionKey(): Buffer {\n return deriveSecretEncryptionKey(\n preferredSharedEncryptionKeyMaterial(),\n \"[agent-native/secrets] Refusing to start in production without encryption key material for workspace secrets. \" +\n \"Set WORKSPACE_SECRETS_ENCRYPTION_KEY (preferred), SECRETS_ENCRYPTION_KEY, ensure a hosted workspace has A2A_SECRET, \" +\n \"or set BETTER_AUTH_SECRET / an app-scoped \" +\n \"*_SECRETS_ENCRYPTION_KEY compatibility fallback in the deploy environment.\",\n \"[agent-native/secrets] Workspace encryption key not set — using app-scoped or machine-local fallback for workspace secrets. \" +\n \"Set WORKSPACE_SECRETS_ENCRYPTION_KEY or rely on A2A_SECRET-derived material on hosted workspace deploys so sibling apps share vault rows.\",\n );\n}\n\n/** Whether this deployment has stable workspace-shared key material. */\nexport function hasSharedSecretEncryptionKeyMaterial(): boolean {\n if (typeof process === \"undefined\") return false;\n return Boolean(\n workspaceSharedEncryptionKeyMaterial() ||\n process.env.SECRETS_ENCRYPTION_KEY ||\n a2aSharedEncryptionKeyMaterial() ||\n process.env.BETTER_AUTH_SECRET,\n );\n}\n\nfunction encryptWithKey(plaintext: string, key: Buffer): string {\n const { createCipheriv, randomBytes } = requireNodeCrypto();\n const iv = randomBytes(12);\n const cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n const ct = Buffer.concat([cipher.update(plaintext, \"utf8\"), cipher.final()]);\n const tag = cipher.getAuthTag();\n return `v1:${iv.toString(\"hex\")}:${ct.toString(\"hex\")}:${tag.toString(\"hex\")}`;\n}\n\nfunction decryptWithKey(encrypted: string, key: Buffer): string {\n const { createDecipheriv } = requireNodeCrypto();\n if (!encrypted.startsWith(\"v1:\")) {\n throw new Error(\"Unrecognised secret encoding\");\n }\n const [, ivHex, ctHex, tagHex] = encrypted.split(\":\");\n if (!ivHex || !ctHex || !tagHex) {\n throw new Error(\"Corrupt secret payload\");\n }\n const decipher = createDecipheriv(\n \"aes-256-gcm\",\n key,\n Buffer.from(ivHex, \"hex\"),\n );\n decipher.setAuthTag(Buffer.from(tagHex, \"hex\"));\n const pt = Buffer.concat([\n decipher.update(Buffer.from(ctHex, \"hex\")),\n decipher.final(),\n ]);\n return pt.toString(\"utf8\");\n}\n\n/** Encrypt a plain-text value with the generic app-local key. */\nexport function encryptSecretValue(plaintext: string): string {\n return encryptWithKey(plaintext, getSecretEncryptionKey());\n}\n\n/** Decrypt a value produced by `encryptSecretValue`. Throws on tampering. */\nexport function decryptSecretValue(encrypted: string): string {\n return decryptWithKey(encrypted, getSecretEncryptionKey());\n}\n\n/** Encrypt a workspace-shared `app_secrets` value. */\nexport function encryptSharedSecretValue(plaintext: string): string {\n return encryptWithKey(plaintext, getSharedSecretEncryptionKey());\n}\n\n/** Decrypt a workspace-shared `app_secrets` value. */\nexport function decryptSharedSecretValue(encrypted: string): string {\n return decryptSharedSecretValueDetailed(encrypted).value;\n}\n\nexport interface DecryptedSharedSecretValue {\n value: string;\n /**\n * True when a legacy app-local candidate decrypted the value. Callers that\n * own the row should opportunistically re-encrypt it with the preferred\n * workspace key.\n */\n needsReencrypt: boolean;\n}\n\n/**\n * Decrypt workspace ciphertext with the preferred key first, then configured\n * legacy candidates. The result never exposes which secret material matched.\n */\nexport function decryptSharedSecretValueDetailed(\n encrypted: string,\n): DecryptedSharedSecretValue {\n const preferredKey = getSharedSecretEncryptionKey();\n const candidateKeys = [preferredKey];\n for (const material of sharedEncryptionKeyMaterials()) {\n const candidate = hashSecretEncryptionKey(material);\n if (!candidateKeys.some((existing) => existing.equals(candidate))) {\n candidateKeys.push(candidate);\n }\n }\n\n let lastError: unknown;\n for (const candidate of candidateKeys) {\n try {\n return {\n value: decryptWithKey(encrypted, candidate),\n needsReencrypt: !candidate.equals(preferredKey),\n };\n } catch (error) {\n lastError = error;\n }\n }\n throw lastError instanceof Error\n ? lastError\n : new Error(\"Unable to decrypt workspace secret\");\n}\n\n/**\n * Strict check for a value produced by `encryptSecretValue`: `v1:` followed by\n * three hex segments. Intentionally strict so a legacy plaintext credential\n * that merely happens to start with `v1:` is treated as plaintext (and read via\n * the legacy fallback) rather than mis-decrypted.\n */\nconst ENCRYPTED_VALUE_RE = /^v1:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+$/;\n\nexport function isEncryptedSecretValue(value: unknown): value is string {\n return typeof value === \"string\" && ENCRYPTED_VALUE_RE.test(value);\n}\n"]}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Storage layer for the framework secrets registry.
|
|
3
3
|
*
|
|
4
4
|
* Values are encrypted at rest with AES-256-GCM. The workspace-shared
|
|
5
|
-
* encryption key
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* encryption key prefers `WORKSPACE_SECRETS_ENCRYPTION_KEY`, then the legacy
|
|
6
|
+
* combined `SECRETS_ENCRYPTION_KEY`, then the workspace-wide `A2A_SECRET`.
|
|
7
|
+
* A configured previous workspace key, `BETTER_AUTH_SECRET`, and app-scoped
|
|
8
|
+
* keys remain read fallbacks for rows written before sibling apps converged on
|
|
9
|
+
* the shared key. Successful fallback reads race-safely refresh the shared
|
|
10
|
+
* ciphertext.
|
|
11
11
|
*
|
|
12
12
|
* Secret values are NEVER logged and NEVER returned from any route handler.
|
|
13
13
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/secrets/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AA+FjD;;;GAGG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI3C;AAMD,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/secrets/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AA+FjD;;;GAGG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI3C;AAMD,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAqF3E;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAmID;;;GAGG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,SAAS,GACb,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA8BlC;AAED,wEAAwE;AACxE,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAoCzC;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,SAAS,GACb,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAItD;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,SAAS,GACb,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAsC5B;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,UAAU,EAAE,CAAC,CAuCvB;AAeD,wBAAsB,eAAe,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAStE"}
|
package/dist/secrets/storage.js
CHANGED
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
* Storage layer for the framework secrets registry.
|
|
3
3
|
*
|
|
4
4
|
* Values are encrypted at rest with AES-256-GCM. The workspace-shared
|
|
5
|
-
* encryption key
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* encryption key prefers `WORKSPACE_SECRETS_ENCRYPTION_KEY`, then the legacy
|
|
6
|
+
* combined `SECRETS_ENCRYPTION_KEY`, then the workspace-wide `A2A_SECRET`.
|
|
7
|
+
* A configured previous workspace key, `BETTER_AUTH_SECRET`, and app-scoped
|
|
8
|
+
* keys remain read fallbacks for rows written before sibling apps converged on
|
|
9
|
+
* the shared key. Successful fallback reads race-safely refresh the shared
|
|
10
|
+
* ciphertext.
|
|
11
11
|
*
|
|
12
12
|
* Secret values are NEVER logged and NEVER returned from any route handler.
|
|
13
13
|
*/
|
|
14
14
|
import { randomUUID } from "node:crypto";
|
|
15
15
|
import { getDbExec, isPostgres } from "../db/client.js";
|
|
16
16
|
import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
17
|
-
import { encryptSecretValue as encryptLegacyValue, encryptSharedSecretValue as encryptValue,
|
|
17
|
+
import { encryptSecretValue as encryptLegacyValue, encryptSharedSecretValue as encryptValue, decryptSharedSecretValueDetailed as decryptValue, decryptSecretValue as decryptLegacyValue, hasSharedSecretEncryptionKeyMaterial, } from "./crypto.js";
|
|
18
18
|
import { APP_SECRETS_CREATE_SQL } from "./schema.js";
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
20
|
// Table bootstrap
|
|
@@ -139,8 +139,9 @@ export async function writeAppSecret(args) {
|
|
|
139
139
|
// pointing at data that's no longer current. Siblings then get an honest
|
|
140
140
|
// cache miss (falling back to the legacy column or reporting missing) until
|
|
141
141
|
// the owning app's next read repopulates shared_encrypted_value via
|
|
142
|
-
// `populateSharedAppSecret`, which
|
|
143
|
-
//
|
|
142
|
+
// `populateSharedAppSecret`, which fills a NULL column or compare-and-swap
|
|
143
|
+
// replaces the exact legacy ciphertext it just decrypted. A temporary miss
|
|
144
|
+
// is safer than serving rotated-away plaintext.
|
|
144
145
|
const upsertSql = `INSERT INTO app_secrets (id, scope, scope_id, key, encrypted_value, shared_encrypted_value, description, url_allowlist, created_at, updated_at)
|
|
145
146
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
146
147
|
ON CONFLICT (scope, scope_id, key) DO UPDATE SET
|
|
@@ -183,10 +184,14 @@ export async function writeAppSecret(args) {
|
|
|
183
184
|
function decryptAppSecretValue(encrypted, sharedEncrypted) {
|
|
184
185
|
if (sharedEncrypted) {
|
|
185
186
|
try {
|
|
187
|
+
const decrypted = decryptValue(sharedEncrypted);
|
|
186
188
|
return {
|
|
187
|
-
value:
|
|
189
|
+
value: decrypted.value,
|
|
188
190
|
usedLegacyKey: false,
|
|
189
|
-
needsSharedCiphertext:
|
|
191
|
+
needsSharedCiphertext: decrypted.needsReencrypt,
|
|
192
|
+
sharedCiphertextToReplace: decrypted.needsReencrypt
|
|
193
|
+
? sharedEncrypted
|
|
194
|
+
: undefined,
|
|
190
195
|
};
|
|
191
196
|
}
|
|
192
197
|
catch {
|
|
@@ -195,10 +200,12 @@ function decryptAppSecretValue(encrypted, sharedEncrypted) {
|
|
|
195
200
|
}
|
|
196
201
|
}
|
|
197
202
|
try {
|
|
203
|
+
const decrypted = decryptValue(encrypted);
|
|
198
204
|
return {
|
|
199
|
-
value:
|
|
205
|
+
value: decrypted.value,
|
|
200
206
|
usedLegacyKey: false,
|
|
201
207
|
needsSharedCiphertext: true,
|
|
208
|
+
sharedCiphertextToReplace: sharedEncrypted ?? null,
|
|
202
209
|
};
|
|
203
210
|
}
|
|
204
211
|
catch {
|
|
@@ -206,27 +213,37 @@ function decryptAppSecretValue(encrypted, sharedEncrypted) {
|
|
|
206
213
|
value: decryptLegacyValue(encrypted),
|
|
207
214
|
usedLegacyKey: true,
|
|
208
215
|
needsSharedCiphertext: true,
|
|
216
|
+
sharedCiphertextToReplace: sharedEncrypted ?? null,
|
|
209
217
|
};
|
|
210
218
|
}
|
|
211
219
|
}
|
|
212
220
|
/**
|
|
213
|
-
*
|
|
214
|
-
* intentionally best-effort: a read must still succeed if a deployment's
|
|
215
|
-
* role cannot update the row. The
|
|
216
|
-
*
|
|
217
|
-
*
|
|
221
|
+
* Create or refresh the shared column without touching encrypted_value. This
|
|
222
|
+
* is intentionally best-effort: a read must still succeed if a deployment's
|
|
223
|
+
* DB role cannot update the row. The compare-and-swap predicate prevents a
|
|
224
|
+
* concurrent writer from being overwritten, and leaving updated_at untouched
|
|
225
|
+
* preserves the row's user-visible ordering/metadata.
|
|
218
226
|
*/
|
|
219
|
-
async function populateSharedAppSecret(id, value) {
|
|
227
|
+
async function populateSharedAppSecret(id, value, sharedCiphertextToReplace) {
|
|
220
228
|
if (id === undefined ||
|
|
221
229
|
id === null ||
|
|
222
230
|
!hasSharedSecretEncryptionKeyMaterial()) {
|
|
223
231
|
return;
|
|
224
232
|
}
|
|
225
233
|
try {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
const encrypted = encryptValue(value);
|
|
235
|
+
if (sharedCiphertextToReplace === null) {
|
|
236
|
+
await getDbExec().execute({
|
|
237
|
+
sql: `UPDATE app_secrets SET shared_encrypted_value = ? WHERE id = ? AND shared_encrypted_value IS NULL`,
|
|
238
|
+
args: [encrypted, id],
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
await getDbExec().execute({
|
|
243
|
+
sql: `UPDATE app_secrets SET shared_encrypted_value = ? WHERE id = ? AND shared_encrypted_value = ?`,
|
|
244
|
+
args: [encrypted, id, sharedCiphertextToReplace],
|
|
245
|
+
});
|
|
246
|
+
}
|
|
230
247
|
}
|
|
231
248
|
catch {
|
|
232
249
|
// Migration is opportunistic. Preserve the successful read if the update
|
|
@@ -280,7 +297,7 @@ export async function readAppSecret(ref) {
|
|
|
280
297
|
const encrypted = rows[0].encrypted_value;
|
|
281
298
|
const decrypted = decryptAppSecretValue(encrypted, rows[0].shared_encrypted_value);
|
|
282
299
|
if (decrypted.needsSharedCiphertext) {
|
|
283
|
-
await populateSharedAppSecret(rows[0].id, decrypted.value);
|
|
300
|
+
await populateSharedAppSecret(rows[0].id, decrypted.value, decrypted.sharedCiphertextToReplace ?? null);
|
|
284
301
|
}
|
|
285
302
|
return {
|
|
286
303
|
value: decrypted.value,
|
|
@@ -313,7 +330,7 @@ export async function readAppSecrets(args) {
|
|
|
313
330
|
const encrypted = row.encrypted_value;
|
|
314
331
|
const decrypted = decryptAppSecretValue(encrypted, row.shared_encrypted_value);
|
|
315
332
|
if (decrypted.needsSharedCiphertext) {
|
|
316
|
-
await populateSharedAppSecret(row.id, decrypted.value);
|
|
333
|
+
await populateSharedAppSecret(row.id, decrypted.value, decrypted.sharedCiphertextToReplace ?? null);
|
|
317
334
|
}
|
|
318
335
|
results.set(key, {
|
|
319
336
|
value: decrypted.value,
|
|
@@ -359,7 +376,7 @@ export async function readAppSecretMeta(ref) {
|
|
|
359
376
|
const encrypted = row.encrypted_value;
|
|
360
377
|
const decrypted = decryptAppSecretValue(encrypted, row.shared_encrypted_value);
|
|
361
378
|
if (decrypted.needsSharedCiphertext) {
|
|
362
|
-
await populateSharedAppSecret(row.id, decrypted.value);
|
|
379
|
+
await populateSharedAppSecret(row.id, decrypted.value, decrypted.sharedCiphertextToReplace ?? null);
|
|
363
380
|
}
|
|
364
381
|
last4Value = last4(decrypted.value);
|
|
365
382
|
}
|
|
@@ -396,7 +413,7 @@ export async function listAppSecretsForScope(scope, scopeId) {
|
|
|
396
413
|
const encrypted = row.encrypted_value;
|
|
397
414
|
const decrypted = decryptAppSecretValue(encrypted, row.shared_encrypted_value);
|
|
398
415
|
if (decrypted.needsSharedCiphertext) {
|
|
399
|
-
await populateSharedAppSecret(row.id, decrypted.value);
|
|
416
|
+
await populateSharedAppSecret(row.id, decrypted.value, decrypted.sharedCiphertextToReplace ?? null);
|
|
400
417
|
}
|
|
401
418
|
last4Value = last4(decrypted.value);
|
|
402
419
|
}
|