fix(secrets): require explicit responsible users

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-07-01 21:49:34 +00:00
1 parent 7c7f9b1767
commit f3727a6fb0
3 files changed
+21 -5

No files matched your search

@@ -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")
+3 -2
View File
@@ -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<typeof assertBoard>[0], companyId: string) {
assertBoard(req);
@@ -34,7 +34,8 @@ function assertSecretDefinitionAdmin(req: Parameters<typeof assertBoard>[0], com
function currentUserId(req: Parameters<typeof assertBoard>[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 }) {
+1 -3
View File
@@ -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;
}