🧮 fix: Prevent String Corruption of Numeric Agent Model Parameters (#14119)

* 🧮 fix: Prevent String Corruption of Numeric Agent Model Parameters

* 🧮 fix: Support Partial Numeric Input and Cover Parameter Aliases
This commit is contained in:
Danny Avila 2026-07-05 12:03:41 -04:00 committed by GitHub
parent 1f7b38e972
commit 2d4ef52c22
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 379 additions and 13 deletions

View file

@ -9,6 +9,7 @@ const {
refreshListAvatars,
collectEdgeAgentIds,
mergeAgentOcrConversion,
sanitizeModelParameters,
MAX_AVATAR_REFRESH_AGENTS,
collectToolResourceFileIds,
convertOcrToContextInPlace,
@ -348,7 +349,10 @@ const createAgentHandler = async (req, res) => {
const { tools = [], ...agentData } = removeNullishValues(validatedData);
if (agentData.model_parameters && typeof agentData.model_parameters === 'object') {
agentData.model_parameters = removeNullishValues(agentData.model_parameters, true);
agentData.model_parameters = removeNullishValues(
sanitizeModelParameters(agentData.model_parameters),
true,
);
}
const { id: userId, role: userRole } = req.user;
@ -604,7 +608,10 @@ const updateAgentHandler = async (req, res) => {
const updateData = removeNullishValues(rest);
if (updateData.model_parameters && typeof updateData.model_parameters === 'object') {
updateData.model_parameters = removeNullishValues(updateData.model_parameters, true);
updateData.model_parameters = removeNullishValues(
sanitizeModelParameters(updateData.model_parameters),
true,
);
}
if (avatarField === null) {

View file

@ -500,6 +500,38 @@ describe('Agent Controllers - Mass Assignment Protection', () => {
expect(agentInDb.model_parameters.maxContextTokens).toBeUndefined();
});
test('should drop non-numeric strings and coerce numeric strings in model_parameters', async () => {
// Regression test for #12920: a stray placeholder string ("System") persisted
// into max_tokens was forwarded to the provider, causing a 400
const dataWithCorruptModelParams = {
provider: 'openai',
model: 'gpt-4',
name: 'Agent with Corrupt Model Params',
model_parameters: {
max_tokens: 'System',
maxContextTokens: '256000',
fileTokenLimit: 256000,
useResponsesApi: true,
},
};
mockReq.body = dataWithCorruptModelParams;
await createAgentHandler(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(201);
const createdAgent = mockRes.json.mock.calls[0][0];
expect(createdAgent.model_parameters.max_tokens).toBeUndefined();
expect(createdAgent.model_parameters.maxContextTokens).toBe(256000);
expect(createdAgent.model_parameters.fileTokenLimit).toBe(256000);
expect(createdAgent.model_parameters.useResponsesApi).toBe(true);
const agentInDb = await Agent.findOne({ id: createdAgent.id });
expect(agentInDb.model_parameters.max_tokens).toBeUndefined();
expect(agentInDb.model_parameters.maxContextTokens).toBe(256000);
});
test('should handle invalid avatar format', async () => {
const dataWithInvalidAvatar = {
provider: 'openai',
@ -697,6 +729,32 @@ describe('Agent Controllers - Mass Assignment Protection', () => {
expect(agentInDb.name).toBe('Updated Agent');
});
test('should sanitize corrupt numeric model_parameters on update', async () => {
mockReq.user.id = existingAgentAuthorId.toString();
mockReq.params.id = existingAgentId;
mockReq.body = {
name: 'Healed Agent',
model_parameters: {
max_tokens: 'System',
maxContextTokens: 256000,
temperature: '0.7',
},
};
await updateAgentHandler(mockReq, mockRes);
expect(mockRes.json).toHaveBeenCalled();
const updatedAgent = mockRes.json.mock.calls[0][0];
expect(updatedAgent.model_parameters.max_tokens).toBeUndefined();
expect(updatedAgent.model_parameters.maxContextTokens).toBe(256000);
expect(updatedAgent.model_parameters.temperature).toBe(0.7);
const agentInDb = await Agent.findOne({ id: existingAgentId });
expect(agentInDb.model_parameters.max_tokens).toBeUndefined();
expect(agentInDb.model_parameters.maxContextTokens).toBe(256000);
});
test('should reject update with unauthorized fields (mass assignment protection)', async () => {
mockReq.user.id = existingAgentAuthorId.toString();
mockReq.params.id = existingAgentId;