From 98056157205da7cbaac3397b758d713e6bc682fe Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 20:53:07 +0000 Subject: [PATCH] feat: shared injection template registry for platform credential proxy support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create a centralized injection template registry that maps well-known credential service/field pairs to their default injection templates. This enables platform-hosted assistants to use the Slack skill with MITM proxy credential injection — previously only personal assistants could use this because the daemon handler was the sole source of injection template metadata. Changes: - New injection-registry.ts with Slack bot token injection templates - handleAddSecret now applies registry templates when storing credentials - config-slack-channel.ts delegates to shared registry instead of hardcoding - Updated Slack skill compatibility to include platform-hosted assistants Co-Authored-By: emmie@vellum.ai --- .../src/config/bundled-skills/slack/SKILL.md | 2 +- .../daemon/handlers/config-slack-channel.ts | 18 ++--- assistant/src/runtime/routes/secret-routes.ts | 4 +- .../tools/credentials/injection-registry.ts | 71 +++++++++++++++++++ 4 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 assistant/src/tools/credentials/injection-registry.ts diff --git a/assistant/src/config/bundled-skills/slack/SKILL.md b/assistant/src/config/bundled-skills/slack/SKILL.md index 9888d9b77c7..e2c85a01d45 100644 --- a/assistant/src/config/bundled-skills/slack/SKILL.md +++ b/assistant/src/config/bundled-skills/slack/SKILL.md @@ -1,7 +1,7 @@ --- name: slack description: Read, send, and manage Slack messages via the Web API -compatibility: "Designed for Vellum personal assistants" +compatibility: "Works with Vellum personal and platform-hosted assistants" metadata: emoji: "💬" vellum: diff --git a/assistant/src/daemon/handlers/config-slack-channel.ts b/assistant/src/daemon/handlers/config-slack-channel.ts index 34982dce5f1..36f847d5efe 100644 --- a/assistant/src/daemon/handlers/config-slack-channel.ts +++ b/assistant/src/daemon/handlers/config-slack-channel.ts @@ -17,6 +17,7 @@ import { getSecureKeyAsync, setSecureKeyAsync, } from "../../security/secure-keys.js"; +import { getInjectionRegistryEntry } from "../../tools/credentials/injection-registry.js"; import { deleteCredentialMetadata, getCredentialMetadata, @@ -41,21 +42,12 @@ export interface SlackChannelConfigResult { // -- Helpers -- -const SLACK_INJECTION_TEMPLATES = [ - { - hostPattern: "slack.com" as const, - injectionType: "header" as const, - headerName: "Authorization", - valuePrefix: "Bearer ", - }, -]; - /** Ensure the bot token credential has injection templates for the proxy. */ function ensureBotTokenInjectionTemplates(): void { - upsertCredentialMetadata("slack_channel", "bot_token", { - allowedDomains: ["slack.com"], - injectionTemplates: SLACK_INJECTION_TEMPLATES, - }); + const entry = getInjectionRegistryEntry("slack_channel", "bot_token"); + if (entry) { + upsertCredentialMetadata("slack_channel", "bot_token", entry); + } } /** diff --git a/assistant/src/runtime/routes/secret-routes.ts b/assistant/src/runtime/routes/secret-routes.ts index 930dfbee753..21638ff9cd9 100644 --- a/assistant/src/runtime/routes/secret-routes.ts +++ b/assistant/src/runtime/routes/secret-routes.ts @@ -29,6 +29,7 @@ import { listSecureKeysAsync, setSecureKeyAsync, } from "../../security/secure-keys.js"; +import { getInjectionRegistryEntry } from "../../tools/credentials/injection-registry.js"; import { assertMetadataWritable, deleteCredentialMetadata, @@ -259,7 +260,8 @@ export async function handleAddSecret( 500, ); } - upsertCredentialMetadata(service, field, {}); + const registryEntry = getInjectionRegistryEntry(service, field); + upsertCredentialMetadata(service, field, registryEntry ?? {}); await syncManualTokenConnection(service); if (service === "vellum" && field === "platform_base_url") { setPlatformBaseUrl(effectiveValue); diff --git a/assistant/src/tools/credentials/injection-registry.ts b/assistant/src/tools/credentials/injection-registry.ts new file mode 100644 index 00000000000..ac12b0f4dd0 --- /dev/null +++ b/assistant/src/tools/credentials/injection-registry.ts @@ -0,0 +1,71 @@ +/** + * Credential injection template registry. + * + * Maps well-known credential service/field pairs to their default injection + * templates and allowed domains. When a credential is stored (via the daemon + * config handler *or* via the HTTP secret route), the registry is consulted + * so that the proxy injection metadata is written regardless of which code + * path provisioned the credential. + * + * To add a new credential type with proxy injection support, add an entry + * to {@link INJECTION_REGISTRY} below. + */ + +import type { CredentialInjectionTemplate } from "./policy-types.js"; + +// --------------------------------------------------------------------------- +// Registry entry type +// --------------------------------------------------------------------------- + +export interface InjectionRegistryEntry { + /** Domains the credential is scoped to. */ + allowedDomains: string[]; + /** Templates describing how to inject the credential into proxied requests. */ + injectionTemplates: CredentialInjectionTemplate[]; +} + +// --------------------------------------------------------------------------- +// Registry +// --------------------------------------------------------------------------- + +/** + * Static registry of well-known credential types that require proxy injection. + * + * Key format: `{service}/{field}` (matches the credential ref format used by + * skill definitions, e.g. `credential_ids: ["slack_channel/bot_token"]`). + */ +const INJECTION_REGISTRY: ReadonlyMap = new Map( + [ + [ + "slack_channel/bot_token", + { + allowedDomains: ["slack.com"], + injectionTemplates: [ + { + hostPattern: "slack.com", + injectionType: "header" as const, + headerName: "Authorization", + valuePrefix: "Bearer ", + }, + ], + }, + ], + ], +); + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +/** + * Look up the default injection metadata for a credential type. + * + * @returns The registry entry if a well-known injection configuration exists, + * or `undefined` if the credential type has no registered injection. + */ +export function getInjectionRegistryEntry( + service: string, + field: string, +): InjectionRegistryEntry | undefined { + return INJECTION_REGISTRY.get(`${service}/${field}`); +}