diff --git a/server/src/__tests__/secrets-service.test.ts b/server/src/__tests__/secrets-service.test.ts index 83ae57cfe01..4aaa54cd017 100644 --- a/server/src/__tests__/secrets-service.test.ts +++ b/server/src/__tests__/secrets-service.test.ts @@ -367,6 +367,40 @@ describeEmbeddedPostgres("secretService", () => { expect(recreated.key).toBe("reusable-key"); }); + it("rejects bindings and env refs to soft-deleted external reference secrets", async () => { + const companyId = await seedCompany(); + const svc = secretService(db); + const awsVault = await svc.createProviderConfig(companyId, { + provider: "aws_secrets_manager", + displayName: "AWS production", + config: { region: "us-east-1", namespace: "prod-use1" }, + }); + const deleted = await svc.create(companyId, { + name: "Deleted external", + key: "deleted-external", + provider: "aws_secrets_manager", + providerConfigId: awsVault.id, + managedMode: "external_reference", + externalRef: "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/deleted", + }); + await svc.update(deleted.id, { status: "deleted" }); + + await expect( + svc.createBinding({ + companyId, + secretId: deleted.id, + targetType: "agent", + targetId: "agent-1", + configPath: "env.API_KEY", + }), + ).rejects.toThrow(/not found/i); + await expect( + svc.normalizeEnvBindingsForPersistence(companyId, { + API_KEY: { type: "secret_ref", secretId: deleted.id, version: "latest" }, + }), + ).rejects.toThrow(/not found/i); + }); + it("allows re-importing a remote secret after the prior external reference is soft-deleted", async () => { const companyId = await seedCompany(); const svc = secretService(db); diff --git a/server/src/services/secrets.ts b/server/src/services/secrets.ts index 5fc2e5715fc..e5efb4919cc 100644 --- a/server/src/services/secrets.ts +++ b/server/src/services/secrets.ts @@ -366,6 +366,7 @@ export function secretService(db: Db) { async function assertSecretInCompany(companyId: string, secretId: string) { const secret = await getById(secretId); if (!secret) throw notFound("Secret not found"); + if (secret.status === "deleted") throw notFound("Secret not found"); if (secret.companyId !== companyId) throw unprocessable("Secret must belong to same company"); return secret; }