fix: preserve Langfuse settings when clearing secrets

This commit is contained in:
Danny Avila 2026-05-12 15:27:25 -04:00
parent bc636c9a2e
commit ddb7090d64
3 changed files with 97 additions and 4 deletions

View file

@ -919,6 +919,45 @@ describe('Agent Controllers - Mass Assignment Protection', () => {
});
});
test('should preserve existing Langfuse settings when update only clears the secret', async () => {
const encryptedOriginal = await encryptStoredSecret('sk-original');
await Agent.updateOne(
{ id: existingAgentId },
{
langfuse: {
enabled: true,
publicKey: 'pk-original',
secretKey: encryptedOriginal,
baseUrl: 'https://cloud.langfuse.com',
},
},
);
mockReq.params.id = existingAgentId;
mockReq.body = {
langfuse: {
secretKey: LANGFUSE_SECRET_CLEAR_VALUE,
},
};
await updateAgentHandler(mockReq, mockRes);
expect(mockRes.json).toHaveBeenCalled();
const updatedAgent = mockRes.json.mock.calls[0][0];
expect(updatedAgent.langfuse).toEqual({
enabled: true,
publicKey: 'pk-original',
baseUrl: 'https://cloud.langfuse.com',
});
const agentInDb = await Agent.findOne({ id: existingAgentId }).lean();
expect(agentInDb.langfuse).toEqual({
enabled: true,
publicKey: 'pk-original',
baseUrl: 'https://cloud.langfuse.com',
});
});
test('uploadAgentAvatarHandler should redact Langfuse secret in response', async () => {
await Agent.updateOne(
{ id: existingAgentId },

View file

@ -38,6 +38,46 @@ describe('normalizeLangfuseConfig', () => {
publicKey: 'pk-agent',
});
});
it('preserves existing non-secret fields when clearing only the secret', async () => {
const result = await normalizeLangfuseConfig(
{
secretKey: LANGFUSE_SECRET_CLEAR_VALUE,
},
{
enabled: true,
publicKey: 'pk-agent',
secretKey: '0123456789abcdef0123456789abcdef:736b2d6167656e74',
baseUrl: 'https://cloud.langfuse.com',
},
);
expect(result).toEqual({
enabled: true,
publicKey: 'pk-agent',
baseUrl: 'https://cloud.langfuse.com',
});
});
it('clears explicit blank non-secret fields while preserving absent fields', async () => {
const result = await normalizeLangfuseConfig(
{
publicKey: '',
secretKey: LANGFUSE_SECRET_CLEAR_VALUE,
},
{
enabled: true,
publicKey: 'pk-agent',
secretKey: '0123456789abcdef0123456789abcdef:736b2d6167656e74',
baseUrl: 'https://cloud.langfuse.com',
},
);
expect(result).toEqual({
enabled: true,
baseUrl: 'https://cloud.langfuse.com',
});
});
});
describe('redactLangfuseSecret', () => {

View file

@ -12,6 +12,10 @@ function isRecord(value: unknown): value is Record<string, unknown> {
return value != null && typeof value === 'object' && !Array.isArray(value);
}
function hasOwn(value: Record<string, unknown>, key: string): boolean {
return Object.prototype.hasOwnProperty.call(value, key);
}
function toPlainObject(value: unknown): unknown {
if (!isRecord(value) || typeof value.toObject !== 'function') {
return value;
@ -57,14 +61,24 @@ export async function normalizeLangfuseConfig(
const existingLangfuse = isRecord(existingConfig) ? existingConfig : {};
const normalized: LangfuseConfig = {};
if (typeof incoming.enabled === 'boolean') {
if (hasOwn(incoming, 'enabled') && typeof incoming.enabled === 'boolean') {
normalized.enabled = incoming.enabled;
} else if (typeof existingLangfuse.enabled === 'boolean') {
normalized.enabled = existingLangfuse.enabled;
}
for (const key of ['publicKey', 'baseUrl'] as const) {
const value = incoming[key];
if (isNonEmptyString(value)) {
normalized[key] = value.trim();
if (hasOwn(incoming, key)) {
const value = incoming[key];
if (isNonEmptyString(value)) {
normalized[key] = value.trim();
}
continue;
}
const existingValue = existingLangfuse[key];
if (isNonEmptyString(existingValue)) {
normalized[key] = existingValue.trim();
}
}