-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Self-hosted polish: BYOK + expose locally-registered MCP tools + Copilot Keys local fallback #4420
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2c060ae
fdcbfa7
deb9faa
973a85f
d54d375
f504521
04be306
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,7 @@ import { | |
| type RequestId, | ||
| } from '@modelcontextprotocol/sdk/types.js' | ||
| import { db } from '@sim/db' | ||
| import { userStats } from '@sim/db/schema' | ||
| import { apiKey as apiKeyTable, userStats } from '@sim/db/schema' | ||
| import { createLogger } from '@sim/logger' | ||
| import { toError } from '@sim/utils/errors' | ||
| import { generateId } from '@sim/utils/id' | ||
|
|
@@ -22,7 +22,9 @@ import { mcpRequestBodySchema, mcpToolCallParamsSchema } from '@/lib/api/contrac | |
| import { validateOAuthAccessToken } from '@/lib/auth/oauth-token' | ||
| import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' | ||
| import { generateWorkspaceContext } from '@/lib/copilot/chat/workspace-context' | ||
| import { hashApiKey } from '@/lib/api-key/crypto' | ||
| import { ORCHESTRATION_TIMEOUT_MS, SIM_AGENT_API_URL } from '@/lib/copilot/constants' | ||
| import { isHosted } from '@/lib/core/config/feature-flags' | ||
| import { createRequestId } from '@/lib/copilot/request/http' | ||
| import { runHeadlessCopilotLifecycle } from '@/lib/copilot/request/lifecycle/headless' | ||
| import { orchestrateSubagentStream } from '@/lib/copilot/request/subagent' | ||
|
|
@@ -52,8 +54,33 @@ interface CopilotKeyAuthResult { | |
| /** | ||
| * Validates a copilot API key by forwarding it to the Go copilot service's | ||
| * `/api/validate-key` endpoint. Returns the associated userId on success. | ||
| * | ||
| * On self-hosted instances, also accepts local workspace/personal API keys | ||
| * (sk-sim-...) by looking them up directly in the api_key table — the | ||
| * Mothership service does not trust self-hosted INTERNAL_API_SECRETs, so | ||
| * remote validation always fails. The local lookup gives external MCP | ||
| * clients (claude.ai, etc.) a way to authenticate using the same | ||
| * workspace API key they use for /api/v1/workflows execution. | ||
| */ | ||
| async function authenticateCopilotApiKey(apiKey: string): Promise<CopilotKeyAuthResult> { | ||
| if (!isHosted) { | ||
| try { | ||
| const keyHash = hashApiKey(apiKey) | ||
| const [row] = await db | ||
| .select({ userId: apiKeyTable.userId, expiresAt: apiKeyTable.expiresAt }) | ||
| .from(apiKeyTable) | ||
| .where(eq(apiKeyTable.keyHash, keyHash)) | ||
| .limit(1) | ||
| if (row?.userId && (!row.expiresAt || row.expiresAt > new Date())) { | ||
| return { success: true, userId: row.userId } | ||
| } | ||
| } catch (error) { | ||
| logger.warn('Local Copilot API key lookup failed, falling through to remote validation', { | ||
| error: toError(error).message, | ||
| }) | ||
| } | ||
| } | ||
|
Comment on lines
+66
to
+82
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Comment on lines
+74
to
+82
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| try { | ||
| const internalSecret = env.INTERNAL_API_SECRET | ||
| if (!internalSecret) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -142,14 +142,16 @@ export const allNavigationItems: NavigationItem[] = [ | |||||||||
| label: 'BYOK', | ||||||||||
| icon: KeySquare, | ||||||||||
| section: 'system', | ||||||||||
| requiresHosted: true, | ||||||||||
| // BYOK is functional on self-hosted via the byok.ts patch — surface in | ||||||||||
| // sidebar so users can manage workspace credentials from the UI. | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| id: 'copilot', | ||||||||||
| label: 'Copilot Keys', | ||||||||||
| icon: HexSimple, | ||||||||||
| section: 'system', | ||||||||||
| requiresHosted: true, | ||||||||||
| // Copilot keys page works locally for managing keys (proxies to sim.ai | ||||||||||
| // for new key generation but listing/storing existing keys works). | ||||||||||
|
Comment on lines
+153
to
+154
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| }, | ||||||||||
| { | ||||||||||
| id: 'inbox', | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Self-hosted DELETE endpoint missing key type filter
Medium Severity
The self-hosted
GEThandler correctly scopes its query toeq(apiKeyTable.type, 'personal'), but the self-hostedDELETEhandler omits this filter — it deletes any key matching(id, userId)regardless of type. Since workspace keys also carry auserId, a caller who knows (or guesses) a workspace key ID can delete it through the Copilot Keys endpoint, even though Copilot Keys are only meant to managepersonal-type keys.Additional Locations (1)
apps/sim/app/api/copilot/api-keys/route.ts#L36-L37Reviewed by Cursor Bugbot for commit 04be306. Configure here.