import { googleWorkspaceSelectedScopes, defaultGitHubReviewPolicy, SLACK_CHAT_BOT_SCOPES, SLACK_BOT_TOOL_SCOPES } from "@paperclipai/shared"; import { randomUUID, generateKeyPairSync } from "node:crypto"; import { join } from "node:path"; import { createServer } from "node:http"; import { and, eq } from "drizzle-orm"; import { beforeAll, afterAll, describe, expect, it, vi } from "vitest"; import { activityLog, agentApiKeys, companies, chatEndpoints, chatExternalPrincipals, chatIdentityLinks, toolPolicies, connectionGrants, connectionGrantDelegations, toolAccessAuditEvents, toolOauthStates, issueThreadInteractions, connectionIntentDeliveries, agents, companyMemberships, heartbeatRuns, issues, principalPermissionGrants, projects, toolConnections, toolMcpGateways, toolMcpGatewayTokens, toolCatalogEntries, toolInvocations, toolProfileBindings, plugins, adapterAuthSessions, environments, routines, chatGitHubReviews } from "@paperclipai/db"; import { findSatisfiedToolConnection, satisfiedConnectionIntentService } from "../services/satisfied-connection-intents.js"; import { connectionManagementService, continuingConnectionManagement } from "../services/connection-management.js"; import { redactRunnerApiPayloads } from "../services/native-runtime/runner-api-log-redaction.js"; import * as remoteEndpointGuard from "../services/remote-http-endpoint-guard.js"; import * as runIdentity from "../services/run-identity.js"; import { createLocalAgentJwt } from "../agent-auth-jwt.js"; import { startRunnerApiTestServer } from "./helpers/runner-api-server.js"; import { getServerAdapter, registerServerAdapter, unregisterServerAdapter } from "../adapters/index.js"; import { configureAgentLifecycle } from "../services/agent-lifecycle.js"; import { agentService } from "../services/agents.js"; import { accessService } from "../services/access.js"; import { instanceSettingsService } from "../services/instance-settings.js"; import { PaperclipRunnerToolAuthority } from "../services/native-runtime/paperclip-runner-tool-authority.js"; vi.mock("../services/agent-lifecycle-driver.js", () => ({ createLifecycleDriver: () => ({ requiredPluginIds: async () => [], runPlugin: async () => "complete", runHost: async () => "complete" }), })); // All requests traverse the assembled server and production bearer middleware. // No req.actor injection or special permission grants establish access. describe("connection management with production default agents", () => { let server: Awaited>; let lifecycle: ReturnType; let provider: ((url: string, init?: RequestInit) => Promise) | null = null; let providerFetch: ReturnType; beforeAll(async () => { vi.stubEnv("PAPERCLIP_AGENT_JWT_SECRET", randomUUID()); vi.stubEnv("PAPERCLIP_RESPONSIBLE_USER_AUTHZ_MODE", "shadow"); vi.stubEnv("PAPERCLIP_AUTH_RATE_LIMIT_ENABLED", "false"); const realFetch = globalThis.fetch; providerFetch = vi.spyOn(globalThis, "fetch").mockImplementation((input, init) => { const url = String(input); if (new URL(url).hostname === "127.0.0.1") return realFetch(input, init); if (provider) return provider(url, init); throw new Error("Unexpected external request in provider fixture"); }); server = await startRunnerApiTestServer({ authPublicBaseUrl: "https://paperclip.fixture.invalid", humanSessions: true, databaseUrl: process.env.PAPERCLIP_CONNECTION_MANAGEMENT_TEST_DATABASE_URL }); lifecycle = configureAgentLifecycle(server.db, { requiredPluginIds: async () => [], runPlugin: async () => "complete", runHost: async () => "complete" }); vi.stubEnv("PAPERCLIP_SECRETS_MASTER_KEY_FILE", join(server.root, "master.key")); await instanceSettingsService(server.db).updateExperimental({ enableChatConnectors: true, enableGitHubReviewBots: true }); }, 90_000); afterAll(async () => { await lifecycle?.stop(); await server?.close(); providerFetch?.mockRestore(); vi.unstubAllEnvs(); vi.unstubAllGlobals(); }); async function fixture() { const base = await server.fixture({ connectionScenario: "fresh" }); const userId = base.responsibleUserId!; const access = accessService(server.db); await access.ensureMembership(base.companyId, "user", userId, "operator", "active"); await access.ensureRoleDefaultGrants(base.companyId, userId, "operator", userId); await server.db.update(companies).set({ requireBoardApprovalForNewAgents: false }).where(eq(companies.id, base.companyId)); const cookie = await server.humanSession(userId); const created = await fetch(`${server.apiUrl}/api/companies/${base.companyId}/agents`, { method: "POST", headers: { cookie, "Content-Type": "application/json", origin: server.apiUrl }, body: JSON.stringify({ name: "Fresh standard agent", role: "engineer", adapterType: "paperclip_runner", adapterConfig: { provider: "codex" }, runtimeConfig: { heartbeat: { enabled: false, wakeOnDemand: false } }, permissions: {} }), }); const agent = await created.json(); expect(agent, JSON.stringify(agent)).toHaveProperty("id"); expect(created.status).toBe(201); await lifecycle.process(agent.id); await vi.waitFor(async () => expect((await agentService(server.db).getById(agent.id))?.status).toBe("idle"), { timeout: 10_000 }); await server.db.update(heartbeatRuns).set({ agentId: agent.id }).where(eq(heartbeatRuns.id, base.runId)); await server.db.update(issues).set({ assigneeAgentId: agent.id, responsibleUserId: userId }).where(eq(issues.id, base.issueId)); const key = await agentService(server.db).createApiKey(agent.id, "integration", { kind: "standard" }, { responsibleUserId: userId }); const jwt = createLocalAgentJwt(agent.id, base.companyId, "paperclip_runner", base.runId, userId); const request = async (method: string, path: string, body?: unknown, token = key.token) => { const response = await fetch(server.apiUrl + (path.startsWith("/mcp/") ? "" : "/api") + path, { method, headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }) }); return { status: response.status, body: await response.json().catch(() => null) }; }; return { ...base, agentId: agent.id, userId, key, jwt, request, authority: new PaperclipRunnerToolAuthority(server.db, { companyId: base.companyId, agentId: agent.id, issueId: base.issueId, runId: base.runId, apiUrl: server.apiUrl, storage: server.storage }) }; } it("creates and configures tool connections and profiles with fresh grants and actual key/JWT authentication", async () => { const f = await fixture(); const grants = await server.db.select().from(principalPermissionGrants).where(eq(principalPermissionGrants.principalId, f.agentId)); expect(grants.some(g => g.permissionKey === "tools:admin")).toBe(false); expect(await f.request("GET", `/companies/${f.companyId}/tools/stdio-templates`)).toMatchObject({ status: 403 }); const created = await f.request("POST", `/companies/${f.companyId}/tools/connections`, { name: "Fixture tool", transport: "mcp_remote", authKind: "none", transportConfig: { url: "https://1.1.1.1/mcp" } }); expect(created, JSON.stringify(created)).toMatchObject({ status: 201 }); expect(await f.request("PATCH", `/tool-connections/${created.body.id}`, { name: "Updated tool" }, f.jwt)).toMatchObject({ status: 200, body: { name: "Updated tool" } }); const [saved] = await server.db.select().from(toolConnections).where(eq(toolConnections.id, created.body.id)); expect(saved.createdByUserId).toBe(f.userId); const audit = await server.db.select().from(activityLog).where(eq(activityLog.entityId, saved.id)); expect(audit).toEqual(expect.arrayContaining([expect.objectContaining({ actorType: "agent", actorId: f.agentId, responsibleUserId: f.userId, details: expect.objectContaining({ permissionDecision: expect.objectContaining({ action: "connections:configure", reason: expect.stringMatching(/^allow_/) }) }), })])); const profile = await f.request("POST", `/companies/${f.companyId}/tools/profiles`, { profileKey: "fixture-profile", name: "Fixture profile", defaultAction: "deny", entries: [] }); expect(profile, JSON.stringify(profile)).toMatchObject({ status: 201 }); }); it("checks effective-profile connection access once for hundreds of tools on the same account", async () => { const f = await fixture(); const connection = await f.request("POST", `/companies/${f.companyId}/tools/connections`, { name: "Large catalog", transport: "mcp_remote", authKind: "none", config: { url: "https://1.1.1.1/mcp" } }); expect(connection).toMatchObject({ status: 201 }); await server.db.insert(toolCatalogEntries).values(Array.from({ length: 200 }, (_, index) => ({ companyId: f.companyId, applicationId: connection.body.applicationId, connectionId: connection.body.id, name: `tool_${index}`, toolName: `tool_${index}`, versionHash: `fixture_${index}` }))); const profile = await f.request("POST", `/companies/${f.companyId}/tools/profiles`, { profileKey: "large-catalog", name: "Large catalog", entries: [{ selectorType: "connection", connectionId: connection.body.id }] }); expect(profile).toMatchObject({ status: 201 }); expect(await f.request("POST", `/companies/${f.companyId}/tools/profiles/${profile.body.id}/bind`, { targetType: "agent", targetId: f.agentId })).toMatchObject({ status: 201 }); expect(await f.request("PUT", `/tool-connections/${connection.body.id}/installs`, { installs: [{ targetType: "agent", targetId: f.agentId }] })).toMatchObject({ status: 200 }); const module = await import("../services/connection-management.js"); const original = connectionManagementService; let connectionChecks = 0; const factory = vi.spyOn(module, "connectionManagementService").mockImplementation((...args) => { const service = original(...args); return { ...service, capability: async (...input: Parameters) => { if (input[2].connectionId === connection.body.id) connectionChecks++; return service.capability(...input); } }; }); try { const effective = await f.request("GET", `/companies/${f.companyId}/tools/profiles/effective/agents/${f.agentId}`); expect(effective, JSON.stringify(effective)).toMatchObject({ status: 200 }); expect(effective.body.allowedTools).toHaveLength(200); expect(effective.body.installedConnections).toHaveLength(1); expect(connectionChecks).toBe(1); } finally { factory.mockRestore(); } }); it("checks every saved profile selector and effective projection against personal account visibility", async () => { const f = await fixture(); const first = await f.request("POST", `/companies/${f.companyId}/tools/connections`, { name: "Owned profile account", transport: "mcp_remote", authKind: "none", config: { url: "https://1.1.1.1/mcp" } }); expect(first).toMatchObject({ status: 201 }); const second = await f.request("POST", `/companies/${f.companyId}/tools/connections`, { applicationId: first.body.applicationId, name: "Private profile account", transport: "mcp_remote", authKind: "none", config: { url: "https://1.1.1.1/mcp" } }); expect(second, JSON.stringify(second)).toMatchObject({ status: 201, body: { applicationId: first.body.applicationId } }); const [known] = await server.db.insert(toolCatalogEntries).values({ companyId: f.companyId, applicationId: first.body.applicationId, connectionId: first.body.id, name: "known", toolName: "known", versionHash: "known" }).returning(); const profileIds: string[] = []; const selectors = [ { selectorType: "application", applicationId: first.body.applicationId }, { selectorType: "catalog_entry", catalogEntryId: known.id }, { selectorType: "tool_name", toolName: "private_tool" }, { selectorType: "risk_level", riskLevel: "read" }, ]; for (const [index, entry] of selectors.entries()) { const profile = await f.request("POST", `/companies/${f.companyId}/tools/profiles`, { profileKey: `privacy-${index}`, name: `Privacy ${index}`, entries: [entry] }); expect(profile, JSON.stringify(profile)).toMatchObject({ status: 201 }); profileIds.push(profile.body.id); } const all = await f.request("POST", `/companies/${f.companyId}/tools/profiles`, { profileKey: "privacy-all", name: "All tools", defaultAction: "allow", entries: [] }); expect(all).toMatchObject({ status: 201 }); profileIds.push(all.body.id); expect(await f.request("POST", `/companies/${f.companyId}/tools/profiles/${profileIds[0]}/bind`, { targetType: "agent", targetId: f.agentId })).toMatchObject({ status: 201 }); await server.db.insert(toolCatalogEntries).values({ companyId: f.companyId, applicationId: first.body.applicationId, connectionId: second.body.id, name: "private_tool", toolName: "private_tool", description: "Private tool metadata", versionHash: "private", firstSeenAt: new Date(Date.now() + 60_000) }); expect(await f.request("GET", `/tool-profiles/${profileIds[1]}/new-tools`)).toMatchObject({ status: 200, body: { tools: [expect.objectContaining({ connectionName: "Private profile account", description: "Private tool metadata" })] } }); const effectivePath = `/companies/${f.companyId}/tools/profiles/effective/agents/${f.agentId}`; expect((await f.request("GET", effectivePath)).body.allowedTools.some((tool: { toolName: string }) => tool.toolName === "private_tool")).toBe(true); expect(await f.request("PUT", `/tool-connections/${second.body.id}/installs`, { installs: [{ targetType: "agent", targetId: f.agentId }] })).toMatchObject({ status: 200 }); await server.db.update(toolConnections).set({ credentialPolicy: "per_user", createdByUserId: randomUUID() }).where(eq(toolConnections.id, second.body.id)); for (const token of [f.key.token, f.jwt]) { expect(await f.request("GET", `/tool-profiles/${profileIds[1]}/new-tools`, undefined, token)).toMatchObject({ status: 403 }); for (const id of profileIds) expect(await f.request("GET", `/tool-profiles/${id}/new-tools`, undefined, token)).toMatchObject({ status: 403 }); expect((await f.request("GET", `/companies/${f.companyId}/tools/profiles`, undefined, token)).body.profiles).toEqual([]); expect(await f.request("GET", effectivePath, undefined, token)).toMatchObject({ status: 403 }); } for (const [index, entry] of selectors.entries()) expect(await f.request("POST", `/companies/${f.companyId}/tools/profiles`, { profileKey: `private-attempt-${index}`, name: `Private attempt ${index}`, entries: [entry] })).toMatchObject({ status: 403 }); expect(await f.request("POST", `/companies/${f.companyId}/tools/profiles`, { profileKey: "private-attempt-all", name: "Private all", defaultAction: "allow", entries: [] })).toMatchObject({ status: 403 }); await server.db.delete(toolProfileBindings).where(and(eq(toolProfileBindings.companyId, f.companyId), eq(toolProfileBindings.targetType, "agent"), eq(toolProfileBindings.targetId, f.agentId))); expect(await f.request("GET", effectivePath)).toMatchObject({ status: 403 }); await server.db.update(toolConnections).set({ createdByUserId: f.userId }).where(eq(toolConnections.id, second.body.id)); expect(await f.request("GET", effectivePath)).toMatchObject({ status: 200 }); await server.db.update(principalPermissionGrants).set({ scope: { connectionIds: [first.body.id] } }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_profiles"))); for (const id of profileIds) expect(await f.request("GET", `/tool-profiles/${id}/new-tools?connectionId=${first.body.id}`)).toMatchObject({ status: 403 }); await server.db.update(principalPermissionGrants).set({ scope: { connectionIds: [first.body.id, second.body.id] } }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_profiles"))); for (const id of profileIds) expect(await f.request("GET", `/tool-profiles/${id}/new-tools?connectionId=${first.body.id}`)).toMatchObject({ status: 200 }); }); it("rechecks authority after remote endpoint validation before committing a connection update", async () => { const f = await fixture(); const created = await f.request("POST", `/companies/${f.companyId}/tools/connections`, { name: "Before validation", transport: "mcp_remote", authKind: "none", transportConfig: { url: "https://1.1.1.1/mcp" } }); expect(created).toMatchObject({ status: 201 }); const original = remoteEndpointGuard.assertPublicRemoteHttpEndpoint; const validation = vi.spyOn(remoteEndpointGuard, "assertPublicRemoteHttpEndpoint").mockImplementation(async (...args) => { const result = await original(...args); await server.db.delete(principalPermissionGrants).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_connections"))); return result; }); try { expect(await f.request("PATCH", `/tool-connections/${created.body.id}`, { name: "After validation" })).toMatchObject({ status: 403 }); expect(validation).toHaveBeenCalled(); expect((await server.db.select().from(toolConnections).where(eq(toolConnections.id, created.body.id)))[0].name).toBe("Before validation"); } finally { validation.mockRestore(); } }); it("reaches sandbox device-login management as the responsible user and protects another owner's saved session", async () => { const f = await fixture(); registerServerAdapter({ ...getServerAdapter("paperclip_runner"), type: "codex_local", loginCapability: { panelMode: "displayed_code", timeoutPolicy: "caller_bounded", getCommand: () => "codex login --device-auth", parsePrompt: () => null, } }); try { const [environment] = await server.db.insert(environments).values({ name: `Login fixture ${f.agentId}`, driver: "sandbox" }).returning(); const [session] = await server.db.insert(adapterAuthSessions).values({ companyId: f.companyId, environmentId: environment.id, adapterType: "codex_local", startedByUserId: f.userId, publicSessionId: randomUUID(), status: "waiting_for_user", expiresAt: new Date(Date.now() + 60_000), aiConnection: { ownership: "personal", name: "Saved login", allAgents: false, agentIds: [] } }).returning(); const path = `/companies/${f.companyId}/adapters/codex_local/login-sessions`; expect(await f.request("GET", `${path}/active`)).toMatchObject({ status: 200, body: { sessionId: session.publicSessionId } }); expect(await f.request("GET", `${path}/${session.publicSessionId}`, undefined, f.jwt)).toMatchObject({ status: 200, body: { sessionId: session.publicSessionId } }); expect(await f.request("POST", path, {})).toMatchObject({ status: 400 }); expect(await f.request("POST", `${path}/${session.publicSessionId}/cancel`, {})).toMatchObject({ status: 200 }); await server.db.update(adapterAuthSessions).set({ startedByUserId: randomUUID() }).where(eq(adapterAuthSessions.id, session.id)); expect(await f.request("GET", `${path}/${session.publicSessionId}`)).toMatchObject({ status: 404 }); expect(await f.request("POST", `${path}/${session.publicSessionId}/cancel`, {})).toMatchObject({ status: 404 }); } finally { unregisterServerAdapter("codex_local"); } }); it("checks finish access, all-agent sharing, and retained targets against both management grants", async () => { const f = await fixture(); const connection = await f.request("POST", `/companies/${f.companyId}/tools/connections`, { name: "Finish authority", transport: "mcp_remote", authKind: "none", transportConfig: { url: "https://1.1.1.1/mcp" } }); const path = `/companies/${f.companyId}/tools/apps/${connection.body.id}/finish`; const body = { enabledCatalogEntryIds: [], askFirstCatalogEntryIds: [], access: { agentIds: [f.agentId] } }; expect(await f.request("POST", path, body)).toMatchObject({ status: 200 }); await server.db.delete(principalPermissionGrants).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_profiles"))); for (const access of [body.access, { agentIds: [] }, "all_agents"]) expect(await f.request("POST", path, { ...body, access })).toMatchObject({ status: 403 }); await server.db.insert(principalPermissionGrants).values({ companyId: f.companyId, principalType: "agent", principalId: f.agentId, permissionKey: "tools:manage_profiles", scope: { targetAgentIds: [f.agentId] } }); expect(await f.request("POST", path, { ...body, access: "all_agents" })).toMatchObject({ status: 403 }); const [other] = await server.db.select({ id: agents.id }).from(agents).where(and(eq(agents.companyId, f.companyId), eq(agents.name, "Eval agent"))); const targets = await server.db.select({ id: agents.id }).from(agents).where(eq(agents.companyId, f.companyId)); const otherId = other?.id ?? targets.find(agent => agent.id !== f.agentId)!.id; expect(await f.request("POST", path, { ...body, access: { agentIds: [otherId] } })).toMatchObject({ status: 403 }); await server.db.update(principalPermissionGrants).set({ scope: null }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_profiles"))); await server.db.update(principalPermissionGrants).set({ scope: { targetAgentIds: [f.agentId] } }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "agents:configure"))); expect(await f.request("POST", path, { ...body, access: { agentIds: [otherId] } })).toMatchObject({ status: 403 }); const bindings = await server.db.select().from(toolProfileBindings).where(eq(toolProfileBindings.companyId, f.companyId)); expect(bindings.map(binding => binding.targetId)).toEqual([f.agentId]); }); it.each(["none", "other-agent", "agent", "user", "cancelled", "project-block", "rate-limit", "agent-rate-approval", "authorized-policy-edit"] as const)("keeps delegated test policy and approval continuation bounded (%s)", async revocation => { const f = await fixture(); let targetAgentId = f.agentId; if (revocation === "other-agent") { const cookie = await server.humanSession(f.userId); const created = await fetch(`${server.apiUrl}/api/companies/${f.companyId}/agents`, { method: "POST", headers: { cookie, origin: server.apiUrl, "Content-Type": "application/json" }, body: JSON.stringify({ name: "Test target", role: "engineer", adapterType: "paperclip_runner", adapterConfig: { provider: "codex" }, permissions: {}, runtimeConfig: { heartbeat: { enabled: false, wakeOnDemand: false } } }) }); const target = await created.json(); expect(created.status, JSON.stringify(target)).toBe(201); targetAgentId = target.id; await lifecycle.process(targetAgentId); await vi.waitFor(async () => expect((await agentService(server.db).getById(targetAgentId))?.status).toBe("idle")); } const allowed = revocation === "none" || revocation === "other-agent"; let calls = 0; const remote = createServer(async (req, res) => { const chunks: Buffer[] = []; for await (const chunk of req) chunks.push(Buffer.from(chunk)); const request = JSON.parse(Buffer.concat(chunks).toString() || "{}"); let result: unknown; if (request.method === "initialize") result = { protocolVersion: "2025-03-26", capabilities: { tools: {} }, serverInfo: { name: "Fixture", version: "1" } }; else if (request.method === "tools/call") { calls++; result = { content: [{ type: "text", text: "Fixture result" }] }; } else { res.writeHead(req.method === "DELETE" ? 204 : request.method === "notifications/initialized" ? 202 : 405); res.end(); return; } res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ jsonrpc: "2.0", id: request.id, result })); }); await new Promise(resolve => remote.listen(0, "127.0.0.1", resolve)); const address = remote.address(); if (!address || typeof address === "string") throw new Error("Fixture did not listen"); const mcpUrl = `http://127.0.0.1:${address.port}/mcp`; try { const created = await f.request("POST", `/companies/${f.companyId}/tools/connections`, { name: "Governed test", transport: "mcp_remote", authKind: "none", transportConfig: { url: mcpUrl } }); await server.db.update(toolConnections).set({ healthStatus: "ok" }).where(eq(toolConnections.id, created.body.id)); const [entry] = await server.db.insert(toolCatalogEntries).values({ companyId: f.companyId, applicationId: created.body.applicationId, connectionId: created.body.id, entryKind: "tool", name: "Fixture probe", toolName: "fixture_probe", inputSchema: { type: "object", properties: {} }, riskLevel: "read", isReadOnly: true, status: "active", versionHash: "fixture-v1" }).returning(); expect(await f.request("POST", `/companies/${f.companyId}/tools/apps/${created.body.id}/finish`, { enabledCatalogEntryIds: [entry.id], access: { agentIds: [targetAgentId] } })).toMatchObject({ status: 200 }); const path = `/tool-connections/${created.body.id}/test-calls`; const body = { agentId: targetAgentId, runContext: { heartbeatRunId: randomUUID(), issueId: randomUUID(), projectId: randomUUID() }, toolName: "fixture_probe", parameters: {} }; for (const selectors of [{ connectionId: created.body.id, projectId: f.projectId }, { connectionId: created.body.id, issueId: f.issueId }]) { const [block] = await server.db.insert(toolPolicies).values({ companyId: f.companyId, name: "Scoped test block", policyType: "block", selectors, priority: 1 }).returning(); expect(await f.request("POST", path, body, f.jwt)).toMatchObject({ status: 200, body: { decision: "off" } }); expect(calls).toBe(0); await server.db.delete(toolPolicies).where(eq(toolPolicies.id, block.id)); } if (revocation === "rate-limit") { await server.db.insert(toolPolicies).values({ companyId: f.companyId, name: "One test per minute", policyType: "rate_limit", selectors: { connectionId: created.body.id }, config: { limit: 1, windowSeconds: 60, keyBy: ["agent", "tool"] } }); expect(await f.request("POST", path, body, f.jwt)).toMatchObject({ status: 200, body: { decision: "allowed", result: { content: "Fixture result" } } }); expect(calls).toBe(1); expect(await f.request("POST", path, body, f.jwt)).toMatchObject({ status: 200, body: { decision: "off", error: { reasonCode: "rate_limited" } } }); expect(calls).toBe(1); return; } if (revocation === "agent-rate-approval") { await server.db.insert(toolPolicies).values([ { companyId: f.companyId, name: "One admitted call", policyType: "rate_limit", selectors: { connectionId: created.body.id }, priority: 0, config: { limit: 1, windowSeconds: 60, keyBy: ["agent", "tool"] } }, { companyId: f.companyId, name: "Approve admitted call", policyType: "require_approval", selectors: { connectionId: created.body.id }, priority: 1 }, ]); const session = await f.request("POST", "/tool-gateway/sessions", {}, f.jwt); expect(session, JSON.stringify(session)).toMatchObject({ status: 201 }); const gatewayRequest = async (path: string, parameters?: unknown) => { const response = await fetch(server.apiUrl + "/api" + path, { method: parameters === undefined ? "GET" : "POST", headers: { "x-paperclip-tool-gateway-token": session.body.token, "Content-Type": "application/json" }, ...(parameters === undefined ? {} : { body: JSON.stringify(parameters) }) }); return { status: response.status, body: await response.json() }; }; const listed = await gatewayRequest("/tool-gateway/tools"); expect(listed).toMatchObject({ status: 200 }); const tool = listed.body.find((item: { catalogEntryId: string }) => item.catalogEntryId === entry.id); expect(tool, JSON.stringify(listed.body)).toBeTruthy(); const requested = await gatewayRequest("/tool-gateway/tools/call", { tool: tool.name, parameters: {}, idempotencyKey: "rate-approval" }); expect(requested, JSON.stringify(requested)).toMatchObject({ status: 409, body: { reasonCode: "approval_required" } }); expect(calls).toBe(0); expect(await f.request("POST", `/tool-gateway/action-requests/${requested.body.actionRequestId}/approve`, { companyId: f.companyId }, f.jwt)).toMatchObject({ status: 403 }); const cookie = await server.humanSession(f.userId); const response = await fetch(`${server.apiUrl}/api/tool-gateway/action-requests/${requested.body.actionRequestId}/approve`, { method: "POST", headers: { cookie, origin: server.apiUrl, "Content-Type": "application/json" }, body: JSON.stringify({ companyId: f.companyId }) }); expect(response.status, JSON.stringify(await response.json())).toBe(200); expect((await server.db.select().from(toolInvocations).where(eq(toolInvocations.id, requested.body.invocationId)))[0].status).toBe("succeeded"); expect(calls).toBe(1); expect(await gatewayRequest("/tool-gateway/tools/call", { tool: tool.name, parameters: { anotherCall: true }, idempotencyKey: "next-rate-approval" })).toMatchObject({ status: 429, body: { reasonCode: "rate_limited" } }); expect(calls).toBe(1); return; } await server.db.insert(toolPolicies).values({ companyId: f.companyId, name: "Scoped test approval", policyType: "require_approval", selectors: { connectionId: created.body.id, issueId: f.issueId }, priority: 1 }); const pending = await f.request("POST", path, body, f.jwt); expect(pending, JSON.stringify(pending)).toMatchObject({ status: 200, body: { decision: "ask_first" } }); const [saved] = await server.db.select().from(toolInvocations).where(eq(toolInvocations.id, pending.body.invocationId)); expect(saved).toMatchObject({ runId: f.runId, issueId: f.issueId, actorType: "agent", actorId: f.agentId, agentId: targetAgentId }); expect(calls).toBe(0); const approvalPath = `/tool-gateway/action-requests/${pending.body.actionRequestId}/approve`; expect(await f.request("POST", approvalPath, { companyId: f.companyId }, f.jwt)).toMatchObject({ status: 403 }); if (revocation === "authorized-policy-edit") { const policiesPath = `/companies/${f.companyId}/tools/policies`; const policy = await f.request("POST", policiesPath, { name: "Requested self-targeted access", policyType: "allow", priority: 0, selectors: { agentId: f.agentId, connectionId: created.body.id } }, f.jwt); expect(policy, JSON.stringify(policy)).toMatchObject({ status: 201 }); expect(await f.request("POST", path, body, f.jwt)).toMatchObject({ status: 200, body: { decision: "allowed" } }); expect(calls).toBe(1); expect(await f.request("POST", approvalPath, { companyId: f.companyId }, f.jwt)).toMatchObject({ status: 403 }); await server.db.delete(principalPermissionGrants).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_profiles"))); expect(await f.request("PATCH", `${policiesPath}/${policy.body.id}`, { enabled: false }, f.jwt)).toMatchObject({ status: 403 }); return; } await server.db.update(heartbeatRuns).set({ status: "succeeded" }).where(eq(heartbeatRuns.id, f.runId)); if (revocation === "agent") await server.db.delete(principalPermissionGrants).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:use"))); if (revocation === "user") for (const permission of ["tools:use", "tools:manage_connections"]) await server.db.delete(principalPermissionGrants).where(and(eq(principalPermissionGrants.companyId, f.companyId), eq(principalPermissionGrants.principalId, f.userId), eq(principalPermissionGrants.permissionKey, permission))); if (revocation === "cancelled") await server.db.update(issues).set({ status: "cancelled" }).where(eq(issues.id, f.issueId)); if (revocation === "project-block") await server.db.insert(toolPolicies).values({ companyId: f.companyId, name: "New project block", policyType: "block", selectors: { connectionId: created.body.id, projectId: f.projectId }, priority: 0 }); const cookie = await server.humanSession(f.userId); const approve = () => fetch(server.apiUrl + "/api" + approvalPath, { method: "POST", headers: { cookie, origin: server.apiUrl, "Content-Type": "application/json" }, body: JSON.stringify({ companyId: f.companyId }) }); const response = await approve(); expect(response.status, JSON.stringify(await response.json())).toBe(revocation === "cancelled" ? 409 : 200); const [settled] = await server.db.select().from(toolInvocations).where(eq(toolInvocations.id, saved.id)); if (revocation === "cancelled") expect(settled.status).not.toBe("succeeded"); else expect(settled, JSON.stringify(settled)).toMatchObject({ status: allowed ? "succeeded" : "failed" }); expect(calls).toBe(allowed ? 1 : 0); if (allowed) { expect((await approve()).status).toBe(200); expect(calls).toBe(1); } } finally { remote.closeAllConnections(); await new Promise((resolve, reject) => remote.close(error => error ? reject(error) : resolve())); } }); it("completes concurrent run-authenticated profile finishing and policy writes without lock-order failures", async () => { const f = await fixture(); const connection = await f.request("POST", `/companies/${f.companyId}/tools/connections`, { name: "Concurrent management", transport: "mcp_remote", authKind: "none", transportConfig: { url: "https://1.1.1.1/mcp" } }); const policy = await f.request("POST", `/companies/${f.companyId}/tools/policies`, { name: "Concurrent policy", policyType: "allow", selectors: { agentId: f.agentId } }); expect(policy).toMatchObject({ status: 201 }); const finishPath = `/companies/${f.companyId}/tools/apps/${connection.body.id}/finish`; const finish = { enabledCatalogEntryIds: [], access: { agentIds: [f.agentId] } }; expect(await f.request("POST", finishPath, finish, f.jwt)).toMatchObject({ status: 200 }); for (let round = 0; round < 3; round++) { const rows = await server.db.select({ id: toolPolicies.id }).from(toolPolicies).where(eq(toolPolicies.companyId, f.companyId)); const results = await Promise.all([ f.request("POST", `/companies/${f.companyId}/tools/policies/reorder`, { policyIds: rows.map(row => row.id) }, f.jwt), f.request("PATCH", `/companies/${f.companyId}/tools/policies/${policy.body.id}`, { description: `Round ${round}` }, f.jwt), f.request("POST", finishPath, finish, f.jwt), ]); expect([200, 422]).toContain(results[0].status); expect(results.slice(1), JSON.stringify(results)).toEqual([expect.objectContaining({ status: 200 }), expect.objectContaining({ status: 200 })]); } expect((await server.db.select().from(toolPolicies).where(eq(toolPolicies.id, policy.body.id)))[0].description).toBe("Round 2"); }); it("checks issue, routine, and gateway profile targets including retained bindings", async () => { const f = await fixture(); const other = (await server.db.select({ id: agents.id }).from(agents).where(eq(agents.companyId, f.companyId))).find(agent => agent.id !== f.agentId)!; const profile = await f.request("POST", `/companies/${f.companyId}/tools/profiles`, { profileKey: "target-checks", name: "Target checks", entries: [] }); const gatewayProfile = await f.request("POST", `/companies/${f.companyId}/tools/profiles`, { profileKey: "gateway-target", name: "Gateway target", entries: [] }); const gateway = await f.request("POST", `/companies/${f.companyId}/tools/gateways`, { name: "Target gateway", slug: "binding-target", profileId: gatewayProfile.body.id, agentId: other.id }); expect(gateway, JSON.stringify(gateway)).toMatchObject({ status: 201 }); const [issue] = await server.db.insert(issues).values({ companyId: f.companyId, title: "Other agent task", assigneeAgentId: other.id, status: "in_progress", responsibleUserId: f.userId }).returning(); const [routine] = await server.db.insert(routines).values({ companyId: f.companyId, title: "Other agent routine", assigneeAgentId: other.id, responsibleUserId: f.userId }).returning(); const [configurationGrant] = await server.db.select().from(principalPermissionGrants).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "agents:configure"))); await server.db.update(principalPermissionGrants).set({ scope: { ...configurationGrant.scope, targetAgentIds: [f.agentId] } }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "agents:configure"))); const path = `/companies/${f.companyId}/tools/profiles/${profile.body.id}`; const ownBinding = await f.request("POST", `${path}/bind`, { targetType: "issue", targetId: f.issueId }); expect(ownBinding, JSON.stringify(ownBinding)).toMatchObject({ status: 201 }); expect(await f.request("POST", `${path}/unbind`, { targetType: "issue", targetId: f.issueId })).toMatchObject({ status: 200 }); for (const target of [{ targetType: "project", targetId: f.projectId }, { targetType: "issue", targetId: issue.id }, { targetType: "routine", targetId: routine.id }, { targetType: "gateway", targetId: gateway.body.id }]) { expect(await f.request("POST", `${path}/bind`, target), JSON.stringify(target)).toMatchObject({ status: 403 }); const [saved] = await server.db.insert(toolProfileBindings).values({ ...target, companyId: f.companyId, profileId: profile.body.id }).returning(); expect(await f.request("PATCH", `/tool-profiles/${profile.body.id}`, { description: "Cannot edit retained target" })).toMatchObject({ status: 403 }); expect(await f.request("POST", `${path}/unbind`, target)).toMatchObject({ status: 403 }); await server.db.delete(toolProfileBindings).where(eq(toolProfileBindings.id, saved.id)); } await server.db.update(toolMcpGateways).set({ profileId: profile.body.id }).where(eq(toolMcpGateways.id, gateway.body.id)); expect(await f.request("PATCH", `/tool-profiles/${profile.body.id}`, { description: "Cannot edit a gateway's retained profile" })).toMatchObject({ status: 403 }); }); it("filters GitHub review history and detail by the linked task's private visibility", async () => { const f = await fixture(); const endpoint = await f.request("POST", `/companies/${f.companyId}/chat-endpoints`, { provider: "github", assignedAgentId: f.agentId }); const [privateIssue] = await server.db.insert(issues).values({ companyId: f.companyId, title: "Private review task", visibility: "private", status: "in_progress", responsibleUserId: randomUUID() }).returning(); const [visible, hidden] = await server.db.insert(chatGitHubReviews).values([f.issueId, privateIssue.id].map((issueId, index) => ({ companyId: f.companyId, endpointId: endpoint.body.id, issueId, repositoryId: "42", repository: "fixture/repo", pullNumber: index + 1, headSha: "b".repeat(40), deliveryId: randomUUID(), configurationRevision: 1, policySnapshot: defaultGitHubReviewPolicy(), event: { event: "opened" as const, deliveryId: randomUUID(), repositoryId: "42", repository: "fixture/repo", pullNumber: index + 1, title: "Review", body: index ? "Private provider context" : "Public context", baseSha: "a".repeat(40), headSha: "b".repeat(40), baseBranch: "master", author: { id: "1", login: "fixture", isBot: false }, sender: { id: "1", login: "fixture" }, draft: false, labels: [] }, }))).returning(); const path = `/chat-endpoints/${endpoint.body.id}/github/reviews`; expect((await f.request("GET", path)).body.map((review: { id: string }) => review.id)).toEqual([visible.id]); expect(await f.request("GET", `${path}/${visible.id}`)).toMatchObject({ status: 200 }); expect(await f.request("GET", `${path}/${hidden.id}`)).toMatchObject({ status: 404 }); }); it("keeps narrow policy create, update, reorder, and delete within their actual audiences", async () => { const f = await fixture(); const other = (await server.db.select({ id: agents.id }).from(agents).where(eq(agents.companyId, f.companyId))).find(agent => agent.id !== f.agentId)!; await server.db.update(principalPermissionGrants).set({ scope: { targetAgentIds: [f.agentId] } }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "agents:configure"))); const path = `/companies/${f.companyId}/tools/policies`; const created = await f.request("POST", path, { name: "Own target rule", policyType: "allow", selectors: { agentId: f.agentId } }); expect(created, JSON.stringify(created)).toMatchObject({ status: 201 }); expect(await f.request("PATCH", `${path}/${created.body.id}`, { description: "Retain the target" })).toMatchObject({ status: 200 }); expect(await f.request("POST", `${path}/reorder`, { policyIds: [created.body.id] })).toMatchObject({ status: 200 }); expect(await f.request("PATCH", `${path}/${created.body.id}`, { selectors: {} })).toMatchObject({ status: 403 }); expect(await f.request("PATCH", `${path}/${created.body.id}`, { selectors: { agentId: other.id } })).toMatchObject({ status: 403 }); expect(await f.request("POST", path, { name: "Company rule", policyType: "allow", selectors: {} })).toMatchObject({ status: 403 }); expect(await f.request("DELETE", `${path}/${created.body.id}`)).toMatchObject({ status: 200 }); }); it("bounds and deduplicates resource selectors before per-resource database work", async () => { const f = await fixture(); const created = await f.request("POST", `/companies/${f.companyId}/tools/connections`, { name: "Bounded selector", transport: "mcp_remote", authKind: "none", transportConfig: { url: "https://1.1.1.1/mcp" } }); let resourceReads = 0; const originalSelect = server.db.select.bind(server.db); const select = vi.spyOn(server.db, "select").mockImplementation(((selection: never) => { const builder = originalSelect(selection); const originalFrom = builder.from.bind(builder); builder.from = ((table: never) => { if (table === toolConnections || table === toolPolicies || table === toolCatalogEntries) resourceReads++; return originalFrom(table); }) as typeof builder.from; return builder; }) as typeof server.db.select); try { const path = `/companies/${f.companyId}/tools/policies`; const body = { name: "Bounded policy", policyType: "allow", selectors: { agentId: f.agentId, connectionIds: Array(5000).fill(created.body.id) } }; expect(await f.request("POST", path, body)).toMatchObject({ status: 400 }); expect(resourceReads).toBe(0); expect(await f.request("POST", path, { ...body, selectors: { ...body.selectors, connectionIds: Array(100).fill(created.body.id) } })).toMatchObject({ status: 201 }); expect(resourceReads).toBeLessThan(20); resourceReads = 0; await accessService(server.db).ensureMembership(f.companyId, "user", f.userId, "operator", "suspended"); expect(await f.request("POST", path, { ...body, selectors: { ...body.selectors, connectionIds: Array(100).fill(created.body.id) } })).toMatchObject({ status: 403 }); expect(resourceReads).toBe(0); } finally { select.mockRestore(); } }); it("shares one run capture across a management read without caching authority across requests", async () => { const f = await fixture(); for (let index = 0; index < 5; index++) expect(await f.request("POST", `/companies/${f.companyId}/tools/connections`, { name: `Read snapshot ${index}`, transport: "mcp_remote", authKind: "none", transportConfig: { url: "https://1.1.1.1/mcp" } })).toMatchObject({ status: 201 }); const capture = vi.spyOn(runIdentity, "captureRunIdentity"); try { const listed = await f.request("GET", `/companies/${f.companyId}/tools/connections`, undefined, f.jwt); expect(listed).toMatchObject({ status: 200 }); expect(listed.body.connections).toHaveLength(5); expect(capture.mock.calls.length).toBeGreaterThan(0); expect(capture.mock.calls.length).toBeLessThanOrEqual(2); await server.db.delete(principalPermissionGrants).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_connections"))); expect(await f.request("GET", `/companies/${f.companyId}/tools/connections`, undefined, f.jwt)).toMatchObject({ status: 403 }); } finally { capture.mockRestore(); } }); it("returns the same missing-resource response for foreign and absent connection IDs", async () => { const f = await fixture(), other = await fixture(); const foreign = await other.request("POST", `/companies/${other.companyId}/tools/connections`, { name: "Other company", transport: "mcp_remote", authKind: "none", transportConfig: { url: "https://1.1.1.1/mcp" } }); const hidden = await f.request("GET", `/tool-connections/${foreign.body.id}`); const absent = await f.request("GET", `/tool-connections/${randomUUID()}`); expect(hidden).toMatchObject({ status: 404 }); expect(absent).toEqual(hidden); }); it("checks existing and proposed provider scope without treating generic metadata as provider authority", async () => { const f = await fixture(); const payload = { name: "Scoped provider", transport: "mcp_remote", authKind: "none", config: { url: "https://1.1.1.1/mcp", provider: "fixture-one" } }; const created = await f.request("POST", `/companies/${f.companyId}/tools/connections`, payload); expect(created.status).toBe(201); const untagged = await f.request("POST", `/companies/${f.companyId}/tools/connections`, { ...payload, name: "Untagged", config: { url: "https://1.1.1.1/mcp" } }); const application = await f.request("POST", `/companies/${f.companyId}/tools/applications`, { name: "Provider app", type: "rest_api", metadata: { sourceTemplateKey: "fixture-one" } }); expect(application).toMatchObject({ status: 201 }); await server.db.update(principalPermissionGrants).set({ scope: { providers: ["fixture-one"] } }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_connections"))); expect(await f.request("PATCH", `/tool-connections/${untagged.body.id}`, { name: "Spoofed label", provider: "fixture-one" })).toMatchObject({ status: 403 }); expect(await f.request("POST", `/companies/${f.companyId}/tools/applications`, { name: "Spoofed application", type: "rest_api", provider: "fixture-one", metadata: { sourceTemplateKey: "fixture-two" } })).toMatchObject({ status: 403 }); expect(await f.request("PATCH", `/tool-applications/${application.body.id}`, { name: "Same app provider" })).toMatchObject({ status: 200 }); for (const metadata of [{ sourceTemplateKey: "fixture-two" }, {}]) expect(await f.request("PATCH", `/tool-applications/${application.body.id}`, { provider: "fixture-one", metadata })).toMatchObject({ status: 403 }); expect(await f.request("PATCH", `/tool-connections/${created.body.id}`, { name: "Still the same provider" })).toMatchObject({ status: 200 }); for (const config of [{ url: "https://1.1.1.1/mcp", provider: "fixture-two" }, { url: "https://1.1.1.1/mcp" }]) expect(await f.request("PATCH", `/tool-connections/${created.body.id}`, { config })).toMatchObject({ status: 403 }); expect(await f.request("POST", `/companies/${f.companyId}/tools/connections`, { ...payload, name: "Caller-labelled provider" })).toMatchObject({ status: 403 }); await server.db.update(principalPermissionGrants).set({ scope: { purposes: ["tool"] } }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_connections"))); expect(await f.request("POST", `/companies/${f.companyId}/ai-connections`, { provider: "openai", method: "api_key", name: "Wrong purpose", apiKey: "fixture", ownership: "personal", agentIds: [] })).toMatchObject({ status: 403 }); }); it("hands generic app authentication to the saved setup without creating agent-bound OAuth state", async () => { const f = await fixture(); const created = await f.request("POST", `/companies/${f.companyId}/tools/connections`, { name: "OAuth handoff fixture", transport: "mcp_remote", authKind: "oauth", credentialPolicy: "per_user", status: "draft", transportConfig: { url: "https://1.1.1.1/mcp" }, }); expect(created, JSON.stringify(created)).toMatchObject({ status: 201 }); const path = `/tools/oauth/${created.body.id}/start`; const started = await f.request("POST", path, { asCurrentUser: true }); expect(started, JSON.stringify(started)).toMatchObject({ status: 200, body: { expiresAt: null, requiredUserAction: { userId: f.userId, resource: { id: created.body.id } }, } }); expect(new URL(started.body.authorizationUrl).searchParams.get("resume")).toBe(created.body.id); expect(await f.request("POST", path, { asCurrentUser: true })).toMatchObject({ status: 200, body: { authorizationUrl: started.body.authorizationUrl } }); expect(await server.db.select().from(toolOauthStates).where(eq(toolOauthStates.connectionId, created.body.id))).toHaveLength(0); }); it("delegates and revokes an owned credential with actual agent attribution", async () => { const f = await fixture(); const created = await f.request("POST", `/companies/${f.companyId}/tools/connections`, { name: "Personal delegation fixture", transport: "mcp_remote", authKind: "none", credentialPolicy: "per_user", transportConfig: { url: "https://1.1.1.1/mcp" }, }); expect(created.status).toBe(201); // Provider fixture: a completed personal credential, not principal permissions. const [grant] = await server.db.insert(connectionGrants).values({ companyId: f.companyId, connectionId: created.body.id, kind: "user", subjectUserId: f.userId, status: "active", credentialSecretRefs: [], createdByUserId: f.userId, }).returning(); const path = `/tool-connections/${created.body.id}/grants/${grant.id}/delegations`; const delegated = await f.request("POST", path, { agentId: f.agentId }); expect(delegated, JSON.stringify(delegated)).toMatchObject({ status: 201 }); expect(await f.request("POST", path, { agentId: f.agentId })).toMatchObject({ status: 201, body: { id: delegated.body.id } }); expect(await f.request("DELETE", `${path}/${delegated.body.id}`)).toMatchObject({ status: 200 }); expect(await server.db.select().from(connectionGrantDelegations).where(eq(connectionGrantDelegations.grantId, grant.id))).toHaveLength(0); const events = await server.db.select().from(toolAccessAuditEvents).where(eq(toolAccessAuditEvents.connectionId, created.body.id)); expect(events).toEqual(expect.arrayContaining([ expect.objectContaining({ action: "connection_grant.delegated", actorType: "agent", actorId: f.agentId }), expect.objectContaining({ action: "connection_grant.delegation_revoked", actorType: "agent", actorId: f.agentId }), ])); const activity = await server.db.select().from(activityLog).where(eq(activityLog.entityId, grant.id)); expect(activity).toEqual(expect.arrayContaining([expect.objectContaining({ actorType: "agent", actorId: f.agentId, responsibleUserId: f.userId })])); }); it("saves the addressed user's Google service choices before handing off browser consent", async () => { const f = await fixture(); const created = await f.request("POST", `/companies/${f.companyId}/tools/connections`, { name: "Selected Google services", transport: "mcp_remote", authKind: "oauth", credentialPolicy: "per_user", status: "draft", config: { url: "https://1.1.1.1/mcp", sourceTemplateKey: "google-workspace", connectionMethodKey: "paperclip-workspace" } }); expect(created, JSON.stringify(created)).toMatchObject({ status: 201 }); const services = { gmail: "read" as const }; const started = await f.request("POST", `/tools/oauth/${created.body.id}/start`, { asCurrentUser: true, workspaceServices: services }); expect(started, JSON.stringify(started)).toMatchObject({ status: 200, body: { expiresAt: null, requiredUserAction: { userId: f.userId } } }); const [saved] = await server.db.select().from(toolConnections).where(eq(toolConnections.id, created.body.id)); expect((saved.config.oauth as { pendingWorkspaceServiceRequests: Record }).pendingWorkspaceServiceRequests).toEqual({ [f.userId]: { requestedScopes: googleWorkspaceSelectedScopes(services), requestedAt: expect.any(String) }, }); const restored = await f.request("GET", `/tool-connections/${saved.id}`); expect(restored).toMatchObject({ status: 200, body: { config: { oauth: { pendingWorkspaceServiceRequests: { [f.userId]: { requestedScopes: googleWorkspaceSelectedScopes(services) }, } } } } }); expect(await server.db.select().from(toolOauthStates).where(eq(toolOauthStates.connectionId, saved.id))).toHaveLength(0); }); it("discovers and invokes connection APIs through default production native bindings", async () => { const f = await fixture(); const definitions = await f.authority.definitions(); const names = definitions.map(d => d.name); const context = await f.authority.execute({ tool: "get_task_context", callId: "configuration-guidance", arguments: {} }); expect(JSON.stringify(context)).toContain("Standard agents can manage connections under their responsible user's authority"); expect(JSON.stringify(definitions.find(d => d.name === "connections_search"))).toContain("search_api/call_api"); expect(names).toEqual(expect.arrayContaining(["search_api", "call_api"])); const search = await f.authority.execute({ tool: "search_api", callId: "find-profile", arguments: { query: "create tools profiles", limit: 10 } }); expect(JSON.stringify(search)).toContain("POST /api/companies/{companyId}/tools/profiles"); const result = await f.authority.execute({ tool: "call_api", callId: "create-profile", arguments: { operationId: "POST /api/companies/{companyId}/tools/profiles", body: { profileKey: "native-profile", name: "Native profile", entries: [] } } }); expect(result, JSON.stringify(result)).toMatchObject({ status: 201, ok: true }); }); it("persists meaningful Slack and GitHub endpoint configuration for the responsible user", async () => { const f = await fixture(); for (const provider of ["slack", "github"]) { const result = await f.request("POST", `/companies/${f.companyId}/chat-endpoints`, { provider, assignedAgentId: f.agentId, name: `${provider} fixture`, ...(provider === "slack" ? { slackSetupMethod: "manual" } : {}) }); expect(result, JSON.stringify(result)).toMatchObject({ status: 201 }); if (provider === "slack") { const patch = { requireAtMention: true, allowDirectMessages: false }; const updated = await f.request("PATCH", `/chat-endpoints/${result.body.id}`, patch); expect(updated, JSON.stringify(updated)).toMatchObject({ status: 200, body: patch }); } else { const current = await f.request("GET", `/chat-endpoints/${result.body.id}/github/configuration`); expect(current, JSON.stringify(current)).toMatchObject({ status: 200 }); const body = { expectedRevision: current.body.revision, configuration: { ...current.body.configuration, memberAccess: "selected" } }; const updated = await f.request("PUT", `/chat-endpoints/${result.body.id}/github/configuration`, body); expect(updated, JSON.stringify(updated)).toMatchObject({ status: 200, body: { revision: 1, configuration: { memberAccess: "selected" } } }); expect(await f.request("PUT", `/chat-endpoints/${result.body.id}/github/configuration`, body)).toMatchObject({ status: 409 }); const handoff = await f.request("POST", `/chat-endpoints/${result.body.id}/github/identity/start`, {}); expect(handoff).toMatchObject({ status: 200, body: { requiredUserAction: { userId: f.userId, resource: { id: result.body.id } } } }); } expect((await f.request("GET", `/chat-endpoints/${result.body.id}`)).body.sponsorUserId).toBe(f.userId); } }); it("configures Slack against provider fixtures and subjects test messages to the normal action policy", async () => { const f = await fixture(); const botId = `U${f.agentId.replaceAll("-", "").toUpperCase()}`; const scopes = [...SLACK_CHAT_BOT_SCOPES, ...SLACK_BOT_TOOL_SCOPES]; let sent = 0; provider = async (url, init) => { if (url.endsWith("auth.test")) return Response.json({ ok: true, team_id: "TFIXTURE", team: "Fixture workspace", user_id: botId, user: "fixture-bot" }, { headers: { "x-oauth-scopes": scopes.join(",") } }); if (url.includes("conversations.list")) return Response.json({ ok: true, channels: [], response_metadata: { next_cursor: "" } }); if (url.endsWith("users.info")) return Response.json({ ok: true, user: { id: new URLSearchParams(String(init?.body)).get("user") ?? "UHUMAN", team_id: "TFIXTURE", name: "Fixture user", is_bot: false, deleted: false } }); if (url.endsWith("chat.postMessage")) { sent++; return Response.json({ ok: true, channel: "DTEST", ts: "1.000001" }); } if (url.endsWith("agents.sessions.setStatus")) return Response.json({ ok: true }); throw new Error(`Unexpected Slack fixture operation: ${new URL(url).pathname}`); }; try { const created = await f.request("POST", `/companies/${f.companyId}/chat-endpoints`, { provider: "slack", assignedAgentId: f.agentId, slackSetupMethod: "manual" }); const configured = await f.request("POST", `/chat-endpoints/${created.body.id}/setup`, { action: "configure", credentials: { botToken: "xoxb-fixture-token", signingSecret: "fixture-signing-secret" } }); expect(configured, JSON.stringify(configured)).toMatchObject({ status: 200 }); expect(JSON.stringify(configured.body)).not.toContain("xoxb-fixture-token"); // Identity is a provider fixture; production HTTP admission and policy remain real. const [principal] = await server.db.insert(chatExternalPrincipals).values({ companyId: f.companyId, provider: "slack", providerAccountId: "TFIXTURE", externalId: "UHUMAN", isBot: false }).returning(); await server.db.insert(chatIdentityLinks).values({ companyId: f.companyId, endpointId: created.body.id, principalId: principal.id, paperclipUserId: f.userId, status: "linked", confirmedAt: new Date() }); await server.db.update(chatEndpoints).set({ status: "active", allowDirectMessages: true }).where(eq(chatEndpoints.id, created.body.id)); const [deny] = await server.db.insert(toolPolicies).values({ companyId: f.companyId, name: "Test messages off", policyType: "block", selectors: { connectionId: created.body.connectionId }, priority: 1 }).returning(); const testPath = `/chat-endpoints/${created.body.id}/slack/test-message`; const denied = await f.request("POST", testPath, { requestId: randomUUID() }); expect(denied, JSON.stringify(denied)).toMatchObject({ status: 403 }); expect(sent).toBe(0); await server.db.update(toolPolicies).set({ policyType: "allow" }).where(eq(toolPolicies.id, deny.id)); await server.db.insert(toolPolicies).values({ companyId: f.companyId, name: "One Slack setup send", policyType: "rate_limit", selectors: { connectionId: created.body.connectionId }, priority: 0, config: { limit: 1, windowSeconds: 60, keyBy: ["agent", "tool"] } }); const requestId = randomUUID(); const tested = await f.request("POST", testPath, { requestId }); expect(tested, JSON.stringify(tested)).toMatchObject({ status: 200, body: { status: "sent" } }); expect(await f.request("POST", testPath, { requestId })).toMatchObject({ status: 200 }); expect(await f.request("POST", testPath, { requestId: randomUUID() })).toMatchObject({ status: 403, body: { details: { reasonCode: "rate_limited" } } }); expect(await f.request("POST", testPath, { requestId: randomUUID(), slackUserId: "UOTHER" })).toMatchObject({ status: 403 }); expect(sent).toBe(1); } finally { provider = null; } }); it("imports a GitHub App and discovers repositories against provider fixtures", async () => { const f = await fixture(); const permissions = { contents: "read", issues: "write", metadata: "read", pull_requests: "write" }; const appId = 100000 + Number.parseInt(f.agentId.slice(0, 6), 16); const privateKey = generateKeyPairSync("rsa", { modulusLength: 2048 }).privateKey.export({ type: "pkcs8", format: "pem" }).toString(); provider = async (url, init) => { const path = new URL(url).pathname; if (path === "/login/oauth/access_token") return Response.json({ access_token: "fixture-personal-token" }); if (path === "/user") return Response.json({ id: 1357, login: "fixture-person" }); if (path === "/user/installations") return Response.json({ installations: [{ id: 2468, app_id: appId }] }); if (path === "/app") return Response.json({ id: appId, slug: `fixture-${f.agentId.slice(0, 8)}`, name: "Fixture App", owner: { login: "fixture" }, permissions, events: ["pull_request", "github_app_authorization", "installation", "installation_repositories", "issue_comment", "pull_request_review_comment"] }); if (path === "/app/hook/config") return Response.json(init?.body ? JSON.parse(String(init.body)) : { url: "https://paperclip.fixture.invalid", content_type: "json", insecure_ssl: "0" }); if (path === "/app/installations") return Response.json([{ id: 2468, account: { id: 1357, login: "fixture", type: "Organization" }, permissions, suspended_at: null }]); if (path === "/app/installations/2468") return Response.json({ permissions, suspended_at: null }); if (path === "/app/installations/2468/access_tokens") return Response.json({ token: "fixture-installation-token" }, { status: 201 }); if (path === "/installation/repositories") return Response.json({ repositories: [{ id: 42, name: "repo", full_name: "fixture/repo", private: true, owner: { login: "fixture" }, html_url: "https://github.com/fixture/repo" }] }); throw new Error(`Unexpected GitHub fixture operation: ${path}`); }; try { const created = await f.request("POST", `/companies/${f.companyId}/chat-endpoints`, { provider: "github", assignedAgentId: f.agentId }); expect(await f.request("POST", `/chat-endpoints/${created.body.id}/github/registration`, { name: `fixture-${f.agentId.slice(0, 8)}` })).toMatchObject({ status: 200 }); const cookie = await server.humanSession(f.userId); const humanRequest = (path: string, method = "GET", body?: unknown, session = cookie) => fetch(server.apiUrl + path, { method, redirect: "manual", headers: { cookie: session, "Content-Type": "application/json", origin: server.apiUrl }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }); const appCredentials = { appId: String(appId), privateKey, webhookSecret: "fixture-webhook-secret", clientId: "fixture-client", clientSecret: "fixture-secret" }; expect(await f.request("POST", `/chat-endpoints/${created.body.id}/github/app`, appCredentials)).toMatchObject({ status: 200, body: { status: "needs_user_action" } }); const importHandoff = await f.request("POST", `/chat-endpoints/${created.body.id}/github/app`, {}, f.jwt); expect(importHandoff).toMatchObject({ status: 200, body: { requiredUserAction: { kind: "enter_credentials", userId: f.userId } } }); expect((await server.db.select().from(toolConnections).where(eq(toolConnections.id, created.body.connectionId)))[0].credentialSecretRefs).toEqual([]); const imported = await humanRequest(`/api/chat-endpoints/${created.body.id}/github/app`, "POST", appCredentials); expect(imported.status).toBe(200); expect(JSON.stringify(await imported.json())).not.toContain("PRIVATE KEY"); const refreshed = await f.request("POST", `/chat-endpoints/${created.body.id}/github/repositories/refresh`, {}); expect(refreshed, JSON.stringify(refreshed)).toMatchObject({ status: 200 }); expect(JSON.stringify(refreshed.body)).toContain("fixture/repo"); expect(await f.request("POST", `/chat-endpoints/${created.body.id}/github/verify`, {})).toMatchObject({ status: 200 }); const start = await humanRequest(`/api/chat-endpoints/${created.body.id}/github/identity/start`, "POST", {}); const started = await start.json(); expect(started, JSON.stringify(started)).toHaveProperty("authorizationUrl"); const state = new URL(started.authorizationUrl).searchParams.get("state")!; const callback = `/api/chat-github/identity/callback?state=${encodeURIComponent(state)}&code=fixture-code`; const other = await fixture(); const otherCookie = await server.humanSession(other.userId); expect((await humanRequest(callback, "GET", undefined, otherCookie)).status).toBe(403); expect((await humanRequest(callback)).status).toBe(303); expect((await humanRequest(callback)).status).toBe(403); const confirmed = await humanRequest(`/api/chat-endpoints/${created.body.id}/github/identity/confirm`, "POST", { githubUserId: "1357" }); expect(confirmed.status, JSON.stringify(await confirmed.json())).toBe(200); const [endpoint] = await server.db.select().from(chatEndpoints).where(eq(chatEndpoints.id, created.body.id)); await server.db.update(chatEndpoints).set({ status: "active", setup: { ...endpoint.setup, webhookVerifiedAt: new Date().toISOString() } }).where(eq(chatEndpoints.id, endpoint.id)); await server.db.update(toolConnections).set({ status: "active", enabled: true, healthStatus: "healthy" }).where(eq(toolConnections.id, created.body.connectionId)); const [beforeRotation] = await server.db.select().from(toolConnections).where(eq(toolConnections.id, created.body.connectionId)); let rotationIntentId = ""; for (const token of [f.key.token, f.jwt, f.jwt]) { const rotation = await f.request("POST", `/chat-endpoints/${created.body.id}/setup-secret`, {}, token); expect(rotation, JSON.stringify(rotation)).toMatchObject({ status: 200, body: { status: "needs_user_action", endpointId: created.body.id, requiredUserAction: { kind: "enter_credentials", userId: f.userId } } }); expect(rotation.body).not.toHaveProperty("webhookSecret"); expect(rotation.body.requiredUserAction.setupPath).toContain("stage=credentials&reconnect=1&rotateSecret=1"); if (token === f.jwt) { if (rotationIntentId) expect(rotation.body.interactionId).toBe(rotationIntentId); rotationIntentId = rotation.body.interactionId; } } expect(rotationIntentId).not.toBe(importHandoff.body.interactionId); const [afterRotation] = await server.db.select().from(toolConnections).where(eq(toolConnections.id, created.body.connectionId)); expect(afterRotation.credentialSecretRefs).toEqual(beforeRotation.credentialSecretRefs); const [rotationIntent] = await server.db.select().from(issueThreadInteractions).where(eq(issueThreadInteractions.id, rotationIntentId)); const [task] = await server.db.select().from(issues).where(eq(issues.id, f.issueId)); const setupOptions = await humanRequest(`/api/connection-intents/${rotationIntentId}/setup-options`); expect(setupOptions.status).toBe(200); expect((await setupOptions.json()).chatSetup.path).toContain("stage=credentials&reconnect=1&rotateSecret=1"); expect(await findSatisfiedToolConnection(server.db, task, rotationIntent)).toBeNull(); await satisfiedConnectionIntentService(server.db).sweepPending(); expect((await server.db.select().from(issueThreadInteractions).where(eq(issueThreadInteractions.id, rotationIntentId)))[0].status).toBe("pending"); const humanRotation = await humanRequest(`/api/chat-endpoints/${created.body.id}/setup-secret`, "POST", {}); expect(humanRotation.status).toBe(201); expect(await humanRotation.json()).toEqual({ webhookSecret: expect.stringMatching(/^[a-f0-9]{64}$/) }); const [humanRotated] = await server.db.select().from(toolConnections).where(eq(toolConnections.id, created.body.connectionId)); expect(humanRotated.credentialSecretRefs).not.toEqual(beforeRotation.credentialSecretRefs); const rotationState = await f.request("GET", `/chat-endpoints/${endpoint.id}`); expect(rotationState.body.setup).toMatchObject({ credentialRotationId: expect.any(String), webhookVerifiedAt: null }); const nextRotation = await f.request("POST", `/chat-endpoints/${endpoint.id}/setup-secret`, {}); expect(nextRotation.body.requiredUserAction.setupPath).toContain(`afterRotationId=${rotationState.body.setup.credentialRotationId}`); expect(await findSatisfiedToolConnection(server.db, task, rotationIntent)).toBeNull(); // Provider-fixture states separate restored health from new webhook verification. const [rotatedEndpoint] = await server.db.select().from(chatEndpoints).where(eq(chatEndpoints.id, endpoint.id)); await server.db.update(chatEndpoints).set({ status: "active" }).where(eq(chatEndpoints.id, endpoint.id)); await server.db.update(toolConnections).set({ status: "active", enabled: true, healthStatus: "healthy" }).where(eq(toolConnections.id, created.body.connectionId)); expect(await findSatisfiedToolConnection(server.db, task, rotationIntent)).toBeNull(); await server.db.update(chatEndpoints).set({ setup: { ...rotatedEndpoint.setup, webhookVerifiedAt: new Date().toISOString() } }).where(eq(chatEndpoints.id, endpoint.id)); expect((await findSatisfiedToolConnection(server.db, task, rotationIntent))?.id).toBe(created.body.connectionId); } finally { provider = null; } }); it("creates an email account through the API without exposing its write-only credential and reuses retries", async () => { const f = await fixture(); provider = async url => { if (new URL(url).pathname === "/v0/auth/me") return Response.json({ scope_type: "organization", organization_id: "fixture-org" }); throw new Error(`Unexpected AgentMail fixture operation: ${new URL(url).pathname}`); }; try { const body = { apiKey: "fixture-agentmail-secret", grantKind: "user", agentIds: [f.agentId], allAgents: false, idempotencyKey: randomUUID() }; const created = await f.request("POST", `/companies/${f.companyId}/email/connections`, body); expect(created, JSON.stringify(created)).toMatchObject({ status: 201 }); expect(JSON.stringify(created.body)).not.toContain(body.apiKey); expect(await f.request("POST", `/companies/${f.companyId}/email/connections`, body)).toMatchObject({ status: 201, body: { id: created.body.id } }); expect((await f.request("GET", `/companies/${f.companyId}/email/connections`)).body).toEqual(expect.arrayContaining([expect.objectContaining({ id: created.body.id })])); const [saved] = await server.db.select().from(toolConnections).where(eq(toolConnections.id, created.body.id)); expect(saved.createdByUserId).toBe(f.userId); } finally { provider = null; } }); it("keeps durable gateway client tokens bound to the issuer and responsible user", async () => { const f = await fixture(); const profile = await f.request("POST", `/companies/${f.companyId}/tools/profiles`, { profileKey: "gateway-profile", name: "Gateway profile", entries: [] }); expect(profile, JSON.stringify(profile)).toMatchObject({ status: 201 }); const gateway = await f.request("POST", `/companies/${f.companyId}/tools/gateways`, { name: "Delegated gateway", slug: "delegated", profileId: profile.body.id }); expect(gateway, JSON.stringify(gateway)).toMatchObject({ status: 201 }); const call = { tool: "call_api", callId: "gateway-client-secret", arguments: { operationId: "POST /api/tool-gateway/gateways/{gatewayId}/tokens", pathParams: { gatewayId: gateway.body.id }, body: { companyId: f.companyId, name: "Fixture client", clientLabel: "fixture", expiresAt: new Date(Date.now() + 3600000).toISOString() }, } }; const issued = await f.authority.execute(call) as { status: number; data: { id: string; token: string } }; expect(issued, JSON.stringify(issued)).toMatchObject({ status: 201, data: { token: expect.any(String), responsibleUserId: f.userId } }); expect(await f.authority.execute(call)).toEqual(issued); const token = { status: issued.status, body: issued.data }; const [receiptRun] = await server.db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, f.runId)); expect(JSON.stringify(receiptRun.resultJson)).not.toContain(token.body.token); const receipts = (receiptRun.resultJson as { apiToolReceipts: Record }).apiToolReceipts; expect(Object.values(receipts).find(receipt => receipt.operationId === call.arguments.operationId)).toHaveProperty("encryptedResult"); expect(JSON.stringify(redactRunnerApiPayloads(issued))).not.toContain(token.body.token); expect(await server.db.select().from(toolMcpGatewayTokens).where(eq(toolMcpGatewayTokens.gatewayId, gateway.body.id))).toHaveLength(1); const [saved] = await server.db.select().from(toolMcpGatewayTokens).where(eq(toolMcpGatewayTokens.id, token.body.id)); expect(saved).toMatchObject({ createdByAgentId: f.agentId, responsibleUserId: f.userId, subjectType: "gateway_client" }); const use = () => f.request("POST", `/mcp/gateways/${gateway.body.gatewayPublicId}`, { jsonrpc: "2.0", id: 1, method: "tools/list" }, token.body.token); expect(await use()).toMatchObject({ status: 200 }); await server.db.update(principalPermissionGrants).set({ scope: { purpose: "tool" } }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_connections"))); expect(await use()).toMatchObject({ status: 200 }); await server.db.update(heartbeatRuns).set({ status: "succeeded" }).where(eq(heartbeatRuns.id, f.runId)); expect(await use()).toMatchObject({ status: 200 }); await server.db.update(companyMemberships).set({ status: "suspended" }).where(and(eq(companyMemberships.companyId, f.companyId), eq(companyMemberships.principalId, f.userId))); expect((await use()).status).toBeGreaterThanOrEqual(400); await server.db.update(companyMemberships).set({ status: "active" }).where(and(eq(companyMemberships.companyId, f.companyId), eq(companyMemberships.principalId, f.userId))); expect(await use()).toMatchObject({ status: 200 }); await server.db.delete(principalPermissionGrants).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_connections"))); expect((await use()).status).toBeGreaterThanOrEqual(400); // Model the token FK's ON DELETE SET NULL state without deleting the // fixture's protected historical activity references to the agent. await server.db.update(toolMcpGatewayTokens).set({ createdByAgentId: null }).where(eq(toolMcpGatewayTokens.id, token.body.id)); const [orphaned] = await server.db.select().from(toolMcpGatewayTokens).where(eq(toolMcpGatewayTokens.id, token.body.id)); expect(orphaned).toMatchObject({ createdByAgentId: null, responsibleUserId: f.userId }); expect((await use()).status).toBeGreaterThanOrEqual(400); }); it("requires retained profile authority to clear gateway bindings and connection audiences", async () => { const f = await fixture(); const profile = await f.request("POST", `/companies/${f.companyId}/tools/profiles`, { profileKey: "bound-profile", name: "Bound profile", entries: [] }); expect(profile, JSON.stringify(profile)).toMatchObject({ status: 201 }); const gateway = await f.request("POST", `/companies/${f.companyId}/tools/gateways`, { name: "Bound gateway", slug: "bound", profileId: profile.body.id, agentId: f.agentId }); expect(gateway, JSON.stringify(gateway)).toMatchObject({ status: 201, body: { agentId: f.agentId } }); await server.db.delete(principalPermissionGrants).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_profiles"))); expect(await f.request("PATCH", `/tool-gateway/gateways/${gateway.body.id}`, { companyId: f.companyId, agentId: null })).toMatchObject({ status: 403 }); expect(await f.request("GET", `/companies/${f.companyId}/tools/gateways`)).toMatchObject({ status: 200, body: { gateways: expect.arrayContaining([expect.objectContaining({ id: gateway.body.id, agentId: f.agentId })]) } }); for (const audience of [{ allAgents: false }, { agentIds: [] }]) { expect(await f.request("POST", `/companies/${f.companyId}/ai-connections`, { provider: "openai", method: "api_key", name: "No audience authority", apiKey: "unused-fixture-secret", ...audience, })).toMatchObject({ status: 403 }); } }); it("requires access to saved and replacement gateway profiles and project bindings", async () => { const f = await fixture(); const saved = await f.request("POST", `/companies/${f.companyId}/tools/profiles`, { profileKey: "saved-scope", name: "Saved scope", entries: [] }); const replacement = await f.request("POST", `/companies/${f.companyId}/tools/profiles`, { profileKey: "replacement-scope", name: "Replacement scope", entries: [] }); expect(saved.status).toBe(201); expect(replacement.status).toBe(201); expect(await f.request("POST", `/companies/${f.companyId}/tools/profiles/${saved.body.id}/bind`, { targetType: "project", targetId: f.projectId })).toMatchObject({ status: 201 }); const gateway = await f.request("POST", `/companies/${f.companyId}/tools/gateways`, { name: "Scoped gateway", slug: "scoped", profileId: saved.body.id }); expect(gateway.status).toBe(201); await server.db.update(principalPermissionGrants).set({ scope: { profileIds: [replacement.body.id] } }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_connections"))); expect(await f.request("PATCH", `/tool-gateway/gateways/${gateway.body.id}`, { companyId: f.companyId, profileId: replacement.body.id })).toMatchObject({ status: 403 }); // A grant covering both sides permits the same requested change. await server.db.update(principalPermissionGrants).set({ scope: { profileIds: [saved.body.id, replacement.body.id] } }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_connections"))); expect(await f.request("PATCH", `/tool-gateway/gateways/${gateway.body.id}`, { companyId: f.companyId, profileId: replacement.body.id })).toMatchObject({ status: 200, body: { profileId: replacement.body.id } }); expect(await f.request("PATCH", `/tool-gateway/gateways/${gateway.body.id}`, { companyId: f.companyId, projectId: f.projectId })).toMatchObject({ status: 200 }); const nextProjectId = randomUUID(); await server.db.insert(projects).values({ id: nextProjectId, companyId: f.companyId, name: "Replacement project" }); await server.db.update(principalPermissionGrants).set({ scope: { projectIds: [f.projectId] } }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_profiles"))); expect(await f.request("PATCH", `/tool-gateway/gateways/${gateway.body.id}`, { companyId: f.companyId, projectId: nextProjectId })).toMatchObject({ status: 403 }); expect(await f.request("PATCH", `/tool-gateway/gateways/${gateway.body.id}`, { companyId: f.companyId, projectId: null })).toMatchObject({ status: 403 }); await server.db.update(principalPermissionGrants).set({ scope: { projectIds: [f.projectId, nextProjectId] } }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_profiles"))); expect(await f.request("PATCH", `/tool-gateway/gateways/${gateway.body.id}`, { companyId: f.companyId, projectId: nextProjectId })).toMatchObject({ status: 200, body: { projectId: nextProjectId } }); await server.db.update(principalPermissionGrants).set({ scope: null }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_profiles"))); expect(await f.request("PATCH", `/tool-gateway/gateways/${gateway.body.id}`, { companyId: f.companyId, projectId: null })).toMatchObject({ status: 200, body: { projectId: null } }); }); it("saves an AI account with fixture verification under the user's ownership and agent attribution", async () => { const f = await fixture(); provider = async url => { if (url === "https://api.openai.com/v1/models") return Response.json({ data: [] }); throw new Error("Unexpected AI fixture request"); }; registerServerAdapter({ ...getServerAdapter("paperclip_runner"), testEnvironment: async () => ({ adapterType: "paperclip_runner", status: "pass", checks: [], testedAt: new Date().toISOString() }) }); try { const account = await f.request("POST", `/companies/${f.companyId}/ai-connections`, { provider: "openai", method: "api_key", name: "Fixture OpenAI", ownership: "personal", apiKey: "fixture-secret-do-not-return", agentIds: [f.agentId] }); expect(account, JSON.stringify(account)).toMatchObject({ status: 201 }); expect(JSON.stringify(account)).not.toContain("fixture-secret-do-not-return"); const list = await f.request("GET", `/companies/${f.companyId}/ai-connections`); expect(list).toMatchObject({ status: 200, body: { currentUserId: f.userId } }); const [row] = await server.db.select().from(toolConnections).where(eq(toolConnections.id, account.body.connectionId)); expect(row.createdByUserId).toBe(f.userId); const binding = { mode: "delegated", provider: "openai", method: "api_key", ...account.body }; const bound = await f.request("PATCH", `/agents/${f.agentId}`, { runtimeConfig: { heartbeat: { enabled: false, wakeOnDemand: false }, aiConnection: binding } }); expect(bound, JSON.stringify(bound)).toMatchObject({ status: 200 }); await instanceSettingsService(server.db).updateExperimental({ enableAiConnectionRouters: true }); const pluginKey = `fixture.router.${f.agentId}`; await server.db.insert(plugins).values({ pluginKey, packageName: pluginKey, version: "0.1.0", status: "ready", manifestJson: { id: pluginKey, apiVersion: 1, version: "0.1.0", displayName: "Fixture router", description: "Fixture", author: "Tests", categories: ["connector"], capabilities: ["ai.connections.route"], aiConnectionRouter: { name: "Fixture pool", description: "Use saved accounts" }, entrypoints: { worker: "worker.js" }, } }); const config = { name: "Fixture pool", enabled: true, mode: "round_robin", thresholdPercent: 90, members: [{ id: randomUUID(), binding, profile: { provider: "codex", model: "gpt-5.6-sol", effort: "low" } }] }; const pool = await f.request("POST", `/companies/${f.companyId}/ai-connection-pools`, { pluginKey, config }); expect(pool, JSON.stringify(pool)).toMatchObject({ status: 200, body: { revision: 1 } }); const changed = await f.request("POST", `/companies/${f.companyId}/ai-connection-pools`, { pluginKey, id: pool.body.id, expectedRevision: 1, config: { ...config, name: "Changed pool" } }); expect(changed, JSON.stringify(changed)).toMatchObject({ status: 200, body: { revision: 2 } }); expect(await f.request("DELETE", `/companies/${f.companyId}/ai-connection-pools/${pool.body.id}`, { expectedRevision: 1 })).toMatchObject({ status: 409 }); expect(await f.request("DELETE", `/companies/${f.companyId}/ai-connection-pools/${pool.body.id}`, { expectedRevision: 2 })).toMatchObject({ status: 200 }); await server.db.delete(principalPermissionGrants).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_profiles"))); expect(await f.request("POST", `/companies/${f.companyId}/ai-connections`, { provider: "openai", method: "api_key", name: "No sharing authority", ownership: "personal", apiKey: "fixture-secret", agentIds: [f.agentId] })).toMatchObject({ status: 403 }); } finally { provider = null; unregisterServerAdapter("paperclip_runner"); } }); it("filters AI account and pool listings by the agent's resolved connection scope", async () => { const f = await fixture(); provider = async url => { if (url === "https://api.openai.com/v1/models") return Response.json({ data: [] }); throw new Error("Unexpected AI fixture request"); }; try { const accounts: { connectionId: string; grantId: string }[] = []; for (const name of ["Allowed account", "Outside agent scope"]) { const account = await f.request("POST", `/companies/${f.companyId}/ai-connections`, { provider: "openai", method: "api_key", name, ownership: "personal", apiKey: `fixture-${name}`, agentIds: [] }); expect(account, JSON.stringify(account)).toMatchObject({ status: 201 }); accounts.push(account.body); } await instanceSettingsService(server.db).updateExperimental({ enableAiConnectionRouters: true }); const pluginKey = `fixture.scope.${f.agentId}`; await server.db.insert(plugins).values({ pluginKey, packageName: pluginKey, version: "0.1.0", status: "ready", manifestJson: { id: pluginKey, apiVersion: 1, version: "0.1.0", displayName: "Fixture router", description: "Fixture", author: "Tests", categories: ["connector"], capabilities: ["ai.connections.route"], aiConnectionRouter: { name: "Fixture pool", description: "Use saved accounts" }, entrypoints: { worker: "worker.js" }, } }); const savedPools: { id: string }[] = []; for (const account of accounts) { const pool = await f.request("POST", `/companies/${f.companyId}/ai-connection-pools`, { pluginKey, config: { name: `Pool ${account.connectionId}`, enabled: true, mode: "round_robin", thresholdPercent: 90, members: [{ id: randomUUID(), binding: { mode: "delegated", provider: "openai", method: "api_key", ...account }, profile: { provider: "codex", model: "gpt-5.6-sol", effort: "low" } }] } }); expect(pool, JSON.stringify(pool)).toMatchObject({ status: 200 }); savedPools.push(pool.body); } const path = `/companies/${f.companyId}/ai-connections?connectionId=${accounts[0].connectionId}`; expect((await f.request("GET", path)).body.connections).toHaveLength(2); expect((await f.request("GET", path)).body.pools).toHaveLength(2); for (const connectionIds of [[accounts[0].connectionId], [accounts[0].connectionId, ...savedPools.map(pool => pool.id)]]) { await server.db.update(principalPermissionGrants).set({ scope: { connectionIds } }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_connections"))); for (const token of [f.key.token, f.jwt]) { const listed = await f.request("GET", path, undefined, token); expect(listed, JSON.stringify(listed)).toMatchObject({ status: 200 }); expect(listed.body.connections.map((account: { id: string }) => account.id)).toEqual([accounts[0].connectionId]); expect(listed.body.pools.map((pool: { id: string }) => pool.id)).toEqual(connectionIds.length === 1 ? [] : [savedPools[0].id]); } } } finally { provider = null; } }); it("reuses a saved chat authentication request and resumes only while its authority remains valid", async () => { const f = await fixture(); const created = await f.request("POST", `/companies/${f.companyId}/chat-endpoints`, { provider: "github", assignedAgentId: f.agentId }); expect(created.status).toBe(201); const path = `/chat-endpoints/${created.body.id}/github/identity/start`; const first = await f.request("POST", path, {}, f.jwt); expect(first, JSON.stringify(first)).toMatchObject({ status: 200, body: { interactionId: expect.any(String) } }); const retry = await f.request("POST", path, {}, f.jwt); expect(retry.body.interactionId).toBe(first.body.interactionId); const [intent] = await server.db.select().from(issueThreadInteractions).where(eq(issueThreadInteractions.id, first.body.interactionId)); const [task] = await server.db.select().from(issues).where(eq(issues.id, f.issueId)); expect(intent).toMatchObject({ addresseeUserId: f.userId, payload: { savedSetup: { endpointId: created.body.id, connectionId: created.body.connectionId } } }); expect(await findSatisfiedToolConnection(server.db, task, intent)).toBeNull(); // Provider fixture marks the exact saved endpoint authenticated, after the run yields. await server.db.update(heartbeatRuns).set({ status: "succeeded" }).where(eq(heartbeatRuns.id, f.runId)); await server.db.update(chatEndpoints).set({ status: "active" }).where(eq(chatEndpoints.id, created.body.id)); await server.db.update(toolConnections).set({ status: "active", enabled: true, healthStatus: "healthy" }).where(eq(toolConnections.id, created.body.connectionId)); expect((await findSatisfiedToolConnection(server.db, task, intent))?.id).toBe(created.body.connectionId); expect(await findSatisfiedToolConnection(server.db, { ...task, status: "cancelled" }, intent)).toBeNull(); await accessService(server.db).ensureMembership(f.companyId, "user", f.userId, "operator", "suspended"); expect(await findSatisfiedToolConnection(server.db, task, intent)).toBeNull(); await accessService(server.db).ensureMembership(f.companyId, "user", f.userId, "operator", "active"); await satisfiedConnectionIntentService(server.db).sweepPending(); const [delivery] = await server.db.select().from(connectionIntentDeliveries).where(eq(connectionIntentDeliveries.interactionId, intent.id)); expect(delivery).toBeDefined(); }); it("retains a restricted user's personal self-service without company management or spoofed ownership", async () => { const f = await fixture(); await server.db.delete(principalPermissionGrants).where(and(eq(principalPermissionGrants.principalType, "user"), eq(principalPermissionGrants.principalId, f.userId))); provider = async url => { if (url === "https://api.openai.com/v1/models") return Response.json({ data: [] }); throw new Error("Unexpected AI fixture request"); }; const payload = { provider: "openai", method: "api_key", name: "Personal", ownership: "personal", apiKey: "personal-fixture", agentIds: [], allAgents: false }; try { expect(await f.request("POST", `/companies/${f.companyId}/ai-connections`, { ...payload, ownership: "shared" })).toMatchObject({ status: 403 }); expect(await f.request("POST", `/companies/${f.companyId}/ai-connections`, { ...payload, responsibleUserId: randomUUID() })).toMatchObject({ status: 403 }); expect(await f.request("POST", `/companies/${f.companyId}/ai-connections`, payload)).toMatchObject({ status: 201 }); } finally { provider = null; } }); it("rejects foreign personal metadata, missing responsible users and task-bridge keys", async () => { const f = await fixture(); const created = await f.request("POST", `/companies/${f.companyId}/tools/connections`, { name: "Foreign personal fixture", transport: "mcp_remote", authKind: "none", transportConfig: { url: "https://1.1.1.1/mcp" } }); expect(created.status).toBe(201); await server.db.update(toolConnections).set({ credentialPolicy: "per_user", createdByUserId: randomUUID() }).where(eq(toolConnections.id, created.body.id)); expect(await f.request("GET", `/tool-connections/${created.body.id}`)).toMatchObject({ status: 403 }); const listed = await f.request("GET", `/companies/${f.companyId}/tools/connections`); expect(JSON.stringify(listed.body)).not.toContain("Foreign personal fixture"); const [policy] = await server.db.insert(toolPolicies).values({ companyId: f.companyId, name: "Private account rule", policyType: "block", selectors: { connectionIds: [created.body.id] } }).returning(); expect(JSON.stringify((await f.request("GET", `/companies/${f.companyId}/tools/policies`)).body)).not.toContain(policy.id); expect(await f.request("POST", `/companies/${f.companyId}/tools/policies`, { name: "Cannot edit foreign access", policyType: "allow", selectors: { agentId: f.agentId, connectionIds: [created.body.id] } })).toMatchObject({ status: 403 }); await server.db.update(agentApiKeys).set({ responsibleUserId: null }).where(eq(agentApiKeys.id, f.key.id)); expect((await f.request("GET", `/companies/${f.companyId}/tools/gallery`)).status).toBeGreaterThanOrEqual(400); const g = await fixture(); await server.db.update(agentApiKeys).set({ scopeConfig: { kind: "task_bridge", issueId: g.issueId } }).where(eq(agentApiKeys.id, g.key.id)); expect((await g.request("GET", `/companies/${g.companyId}/tools/gallery`)).status).toBeGreaterThanOrEqual(400); }); it("revalidates pending credential promotion after yielding without reviving cancelled work", async () => { const f = await fixture(); const context = await connectionManagementService(server.db).authorize({ type: "agent", companyId: f.companyId, agentId: f.agentId, runId: f.runId, onBehalfOfUserId: f.userId }, "connections:configure", { type: "connection_management", companyId: f.companyId, purpose: "ai", operation: "login" }); const continuation = continuingConnectionManagement(context)!; await server.db.update(heartbeatRuns).set({ status: "succeeded" }).where(eq(heartbeatRuns.id, f.runId)); await expect(context.reauthorize()).rejects.toThrow(); await expect(continuation.reauthorize()).resolves.toBeUndefined(); await server.db.update(issues).set({ status: "cancelled" }).where(eq(issues.id, f.issueId)); await expect(continuation.reauthorize()).rejects.toThrow(); }); it("denies revoked agent grants and user authority, including shadow mode, without backfilling", async () => { const f = await fixture(); await server.db.delete(principalPermissionGrants).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_connections"))); expect(await f.request("GET", `/companies/${f.companyId}/tools/gallery`)).toMatchObject({ status: 403 }); const g = await fixture(); await accessService(server.db).ensureMembership(g.companyId, "user", g.userId, "operator", "suspended"); expect(await g.request("GET", `/companies/${g.companyId}/tools/gallery`)).toMatchObject({ status: 403 }); }); it("enforces grant scopes, company isolation, effective trust, and Ask/Plan work modes", async () => { const f = await fixture(); expect(await f.request("GET", `/companies/${f.foreignCompanyId}/tools/gallery`)).toMatchObject({ status: 403 }); const owned = await f.request("POST", `/companies/${f.companyId}/tools/connections`, { name: "Scoped fixture", transport: "mcp_remote", authKind: "none", transportConfig: { url: "https://1.1.1.1/mcp" } }); expect(owned.status).toBe(201); await server.db.update(principalPermissionGrants).set({ scope: { connectionIds: [owned.body.id], purposes: ["tool"], operations: ["GET /tool-connections/:connectionId"] } }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_connections"))); expect(await f.request("GET", `/tool-connections/${owned.body.id}`)).toMatchObject({ status: 200 }); expect(await f.request("PATCH", `/tool-connections/${owned.body.id}`, { name: "Beyond operation scope" })).toMatchObject({ status: 403 }); await server.db.update(principalPermissionGrants).set({ scope: { provider: "slack" } }).where(and(eq(principalPermissionGrants.principalId, f.agentId), eq(principalPermissionGrants.permissionKey, "tools:manage_connections"))); expect(await f.request("POST", `/companies/${f.companyId}/chat-endpoints`, { provider: "github", assignedAgentId: f.agentId })).toMatchObject({ status: 403 }); const g = await fixture(); for (const workMode of ["ask", "planning", "skill_test"]) { await server.db.update(issues).set({ workMode }).where(eq(issues.id, g.issueId)); expect(await g.request("POST", `/companies/${g.companyId}/tools/profiles`, { name: "Forbidden" }, g.jwt)).toMatchObject({ status: 403 }); } await server.db.update(issues).set({ workMode: "standard" }).where(eq(issues.id, g.issueId)); await server.db.update(projects).set({ executionWorkspacePolicy: { authorizationPolicy: { trustPreset: "low_trust_review", lowTrustBoundary: { projectIds: [g.projectId] } } } }).where(eq(projects.id, g.projectId)); expect(await g.request("POST", `/companies/${g.companyId}/tools/profiles`, { name: "Forbidden trust" }, g.jwt)).toMatchObject({ status: 403 }); await server.db.update(agents).set({ permissions: { trustPreset: "low_trust_review", authorizationPolicy: { trustBoundary: { mode: "low_trust_review", projectIds: [g.projectId], rootIssueId: g.issueId } } } }).where(eq(agents.id, g.agentId)); expect(await g.request("POST", `/companies/${g.companyId}/tools/profiles`, { name: "Forbidden agent trust" })).toMatchObject({ status: 403 }); }); });