diff --git a/server/src/__tests__/secrets-routes.test.ts b/server/src/__tests__/secrets-routes.test.ts index b054e4588dd..475157964f0 100644 --- a/server/src/__tests__/secrets-routes.test.ts +++ b/server/src/__tests__/secrets-routes.test.ts @@ -169,6 +169,26 @@ describe("secret routes", () => { expect(mockLogActivity).not.toHaveBeenCalled(); }); + it("gives a denied agent safe response and saved-wait options without echoing the submitted value", async () => { + const value = "private-denial-canary-value"; + const res = await request(createApp({ + type: "agent", agentId: "agent-1", companyId: "company-1", source: "agent_jwt", + })).post("/api/companies/company-1/secrets").send({ name: "Deployment key", value }); + + expect(res.status).toBe(403); + expect(res.body.error).toContain("finish the current conversation response"); + expect(res.body.error).toContain("without claiming that credential setup is complete"); + expect(res.body.error).toContain("first create a non-governing secure-save acknowledgment"); + expect(res.body.error).toContain("request_human_input"); + expect(res.body.error).toContain("request_confirmation"); + expect(res.body.error).toContain("never ask for the secret value in that card"); + expect(res.body.error).toContain("Wait only on the actual saved request"); + expect(JSON.stringify(res.body)).not.toContain(value); + expect(res.body).not.toHaveProperty("details"); + expect(mockSecretService.create).not.toHaveBeenCalled(); + expect(mockLogActivity).not.toHaveBeenCalled(); + }); + it.each([undefined, ""])("validates a board caller's missing or empty managed value %j", async (value) => { const res = await request(createApp()).post("/api/companies/company-1/secrets") .send({ name: "Deployment key", value }); diff --git a/server/src/routes/secrets.ts b/server/src/routes/secrets.ts index cf77a56a9a3..ede5d464091 100644 --- a/server/src/routes/secrets.ts +++ b/server/src/routes/secrets.ts @@ -888,7 +888,7 @@ export function secretRoutes(db: Db, deps: SecretRoutesDeps = {}) { const companyId = req.params.companyId as string; if (req.actor.type === "agent") { assertCompanyAccess(req, companyId); - throw forbidden("Company secret creation requires a human board operator. Ask the human to enter the value in Settings → Secrets for this company. Do not send secret values in chat or tool calls."); + throw forbidden("Company secret creation requires a human board operator. Ask the human to enter the value in Settings → Secrets for this company. Do not send secret values in chat or tool calls. You can finish the current conversation response with these secure-entry instructions without claiming that credential setup is complete. If you need a resumable wait, first create a non-governing secure-save acknowledgment with request_human_input using request_confirmation; ask only whether secure entry is complete, never ask for the secret value in that card. Wait only on the actual saved request."); } assertCompanySecretWrite(req, companyId); next();