Merge branch 'codex/agent-connection-api-parity' into codex/agent-connection-api-parity-maps

* codex/agent-connection-api-parity:
  Keep GitHub webhook secret rotation in human setup
This commit is contained in:
Dotta committed 2026-10-26 13:38:22 -05:00
commit ede4b768d0
3 files changed
+21

No files matched your search

@@ -647,6 +647,14 @@ describe("connection management with production default agents", () => {
const imported = await f.request("POST", `/chat-endpoints/${created.body.id}/github/app`, { appId: String(appId), privateKey, webhookSecret: "fixture-webhook-secret", clientId: "fixture-client", clientSecret: "fixture-secret" });
expect(imported, JSON.stringify(imported)).toMatchObject({ status: 200 });
expect(JSON.stringify(imported.body)).not.toContain("PRIVATE KEY");
const [beforeRotation] = await server.db.select().from(toolConnections).where(eq(toolConnections.id, created.body.connectionId));
for (const token of [f.key.token, 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 } });
expect(rotation.body).not.toHaveProperty("webhookSecret");
}
const [afterRotation] = await server.db.select().from(toolConnections).where(eq(toolConnections.id, created.body.connectionId));
expect(afterRotation.credentialSecretRefs).toEqual(beforeRotation.credentialSecretRefs);
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");
@@ -668,6 +676,11 @@ describe("connection management with production default agents", () => {
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 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);
} finally { provider = null; }
});
+4
View File
@@ -429,6 +429,10 @@ export function chatChannelRoutes(db: Db, options: ChatChannelRouteOptions) {
router.post("/chat-endpoints/:endpointId/setup-secret", managementGuard("connections:configure", "POST /chat-endpoints/:endpointId/setup-secret"), async (req, res) => {
if (!(await assertEndpointManagementAccess(req, res))) return;
res.set("Cache-Control", "no-store");
if (req.actor.type === "agent") {
res.json(await setupHandoff(req, "The responsible user must rotate and enter the webhook signing secret in GitHub."));
return;
}
res
.status(201)
.json(
@@ -26,6 +26,7 @@ import type {
import {
conflict,
forbidden,
notFound,
unprocessable,
} from "../../../errors.js";
@@ -61,6 +62,9 @@ export function createGitHubSetupSecret(db: Db, options: GitHubSetupSecretOption
managementContext?: ConnectionManagementContext,
) {
await managementContext?.reauthorize();
if (managementContext?.actor.type === "agent") {
throw forbidden("Webhook signing secrets require a human setup handoff");
}
const initial = await endpointRecord(endpointId);
if (!initial) throw notFound("Chat endpoint not found");
if (initial.endpoint.provider !== "github") {