diff --git a/server/src/__tests__/secrets-routes.test.ts b/server/src/__tests__/secrets-routes.test.ts index fb2be0591a5..33e618af2a6 100644 --- a/server/src/__tests__/secrets-routes.test.ts +++ b/server/src/__tests__/secrets-routes.test.ts @@ -156,6 +156,23 @@ describe("secret routes", () => { expect(JSON.stringify(mockLogActivity.mock.calls)).not.toContain("secret-value"); }); + it("rejects current-user secret values without a concrete user identity", async () => { + const res = await request(createApp({ + type: "board", + source: "local_implicit", + companyIds: ["company-1"], + memberships: [{ companyId: "company-1", status: "active", membershipRole: "admin" }], + })).post("/api/companies/company-1/me/user-secrets").send({ + definitionKey: "github_token", + value: "secret-value", + }); + + expect(res.status).toBe(401); + expect(res.body).toMatchObject({ error: "User identity required for user-specific secrets" }); + expect(mockSecretService.createCurrentUserSecretValue).not.toHaveBeenCalled(); + expect(mockLogActivity).not.toHaveBeenCalled(); + }); + it("rejects empty current-user secret rotation payloads", async () => { const res = await request(createApp()) .post("/api/companies/company-1/me/user-secrets/secret-1/rotate") diff --git a/server/src/routes/secrets.ts b/server/src/routes/secrets.ts index 401a54f57b8..74f42090254 100644 --- a/server/src/routes/secrets.ts +++ b/server/src/routes/secrets.ts @@ -19,7 +19,7 @@ import { validate } from "../middleware/validate.js"; import { assertBoard, assertCompanyAccess } from "./authz.js"; import { logActivity, secretService } from "../services/index.js"; import { getConfiguredSecretProvider } from "../secrets/configured-provider.js"; -import { forbidden } from "../errors.js"; +import { forbidden, unauthorized } from "../errors.js"; function assertSecretDefinitionAdmin(req: Parameters[0], companyId: string) { assertBoard(req); @@ -34,7 +34,8 @@ function assertSecretDefinitionAdmin(req: Parameters[0], com function currentUserId(req: Parameters[0]) { assertBoard(req); - return req.actor.userId ?? "board"; + if (req.actor.userId) return req.actor.userId; + throw unauthorized("User identity required for user-specific secrets"); } function isCompanyScopedSecret(secret: { scope?: string | null }) { diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index ecfcd4971f6..885a659bb8f 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -4614,17 +4614,15 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {}) if (input.run.responsibleUserId) return input.run.responsibleUserId; if (input.routineEnvContext.responsibleUserId) return input.routineEnvContext.responsibleUserId; if (input.issueContext?.responsibleUserId) return input.issueContext.responsibleUserId; - if (input.issueContext?.createdByUserId) return input.issueContext.createdByUserId; if (input.issueContext?.parentId) { const parent = await db .select({ responsibleUserId: issues.responsibleUserId, - createdByUserId: issues.createdByUserId, }) .from(issues) .where(and(eq(issues.companyId, input.run.companyId), eq(issues.id, input.issueContext.parentId))) .then((rows) => rows[0] ?? null); - return parent?.responsibleUserId ?? parent?.createdByUserId ?? null; + return parent?.responsibleUserId ?? null; } return null; }