mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
* 🗨️ fix: Safe Validation for Prompt Updates - Added `safeValidatePromptGroupUpdate` function to validate and sanitize prompt group update requests, ensuring only allowed fields are processed and sensitive fields are stripped. - Updated the `patchPromptGroup` route to utilize the new validation function, returning appropriate error messages for invalid requests. - Introduced comprehensive tests for the validation logic, covering various scenarios including allowed and disallowed fields, enhancing overall request integrity and security. - Created a new schema file for prompt group updates, defining validation rules and types for better maintainability. * 🔒 feat: Add JSON parse error handling middleware
222 lines
7 KiB
TypeScript
222 lines
7 KiB
TypeScript
import {
|
|
updatePromptGroupSchema,
|
|
validatePromptGroupUpdate,
|
|
safeValidatePromptGroupUpdate,
|
|
} from './schemas';
|
|
|
|
describe('updatePromptGroupSchema', () => {
|
|
describe('allowed fields', () => {
|
|
it('should accept valid name field', () => {
|
|
const result = updatePromptGroupSchema.safeParse({ name: 'Test Group' });
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.name).toBe('Test Group');
|
|
}
|
|
});
|
|
|
|
it('should accept valid oneliner field', () => {
|
|
const result = updatePromptGroupSchema.safeParse({ oneliner: 'A short description' });
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.oneliner).toBe('A short description');
|
|
}
|
|
});
|
|
|
|
it('should accept valid category field', () => {
|
|
const result = updatePromptGroupSchema.safeParse({ category: 'testing' });
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.category).toBe('testing');
|
|
}
|
|
});
|
|
|
|
it('should accept valid projectIds array', () => {
|
|
const result = updatePromptGroupSchema.safeParse({
|
|
projectIds: ['proj1', 'proj2'],
|
|
});
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.projectIds).toEqual(['proj1', 'proj2']);
|
|
}
|
|
});
|
|
|
|
it('should accept valid removeProjectIds array', () => {
|
|
const result = updatePromptGroupSchema.safeParse({
|
|
removeProjectIds: ['proj1'],
|
|
});
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.removeProjectIds).toEqual(['proj1']);
|
|
}
|
|
});
|
|
|
|
it('should accept valid command field', () => {
|
|
const result = updatePromptGroupSchema.safeParse({ command: 'my-command-123' });
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.command).toBe('my-command-123');
|
|
}
|
|
});
|
|
|
|
it('should accept null command field', () => {
|
|
const result = updatePromptGroupSchema.safeParse({ command: null });
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.command).toBeNull();
|
|
}
|
|
});
|
|
|
|
it('should accept multiple valid fields', () => {
|
|
const input = {
|
|
name: 'Updated Name',
|
|
category: 'new-category',
|
|
oneliner: 'New description',
|
|
};
|
|
const result = updatePromptGroupSchema.safeParse(input);
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data).toEqual(input);
|
|
}
|
|
});
|
|
|
|
it('should accept empty object', () => {
|
|
const result = updatePromptGroupSchema.safeParse({});
|
|
expect(result.success).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('security - strips sensitive fields', () => {
|
|
it('should reject author field', () => {
|
|
const result = updatePromptGroupSchema.safeParse({
|
|
name: 'Test',
|
|
author: '507f1f77bcf86cd799439011',
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject authorName field', () => {
|
|
const result = updatePromptGroupSchema.safeParse({
|
|
name: 'Test',
|
|
authorName: 'Malicious Author',
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject _id field', () => {
|
|
const result = updatePromptGroupSchema.safeParse({
|
|
name: 'Test',
|
|
_id: '507f1f77bcf86cd799439011',
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject productionId field', () => {
|
|
const result = updatePromptGroupSchema.safeParse({
|
|
name: 'Test',
|
|
productionId: '507f1f77bcf86cd799439011',
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject createdAt field', () => {
|
|
const result = updatePromptGroupSchema.safeParse({
|
|
name: 'Test',
|
|
createdAt: new Date().toISOString(),
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject updatedAt field', () => {
|
|
const result = updatePromptGroupSchema.safeParse({
|
|
name: 'Test',
|
|
updatedAt: new Date().toISOString(),
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject __v field', () => {
|
|
const result = updatePromptGroupSchema.safeParse({
|
|
name: 'Test',
|
|
__v: 999,
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject multiple sensitive fields in a single request', () => {
|
|
const result = updatePromptGroupSchema.safeParse({
|
|
name: 'Legit Name',
|
|
author: '507f1f77bcf86cd799439011',
|
|
authorName: 'Hacker',
|
|
_id: 'newid123',
|
|
productionId: 'prodid456',
|
|
createdAt: '2020-01-01T00:00:00.000Z',
|
|
__v: 999,
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('validation rules', () => {
|
|
it('should reject empty name', () => {
|
|
const result = updatePromptGroupSchema.safeParse({ name: '' });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject name exceeding max length', () => {
|
|
const result = updatePromptGroupSchema.safeParse({ name: 'a'.repeat(256) });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject oneliner exceeding max length', () => {
|
|
const result = updatePromptGroupSchema.safeParse({ oneliner: 'a'.repeat(501) });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject category exceeding max length', () => {
|
|
const result = updatePromptGroupSchema.safeParse({ category: 'a'.repeat(101) });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject command with invalid characters (uppercase)', () => {
|
|
const result = updatePromptGroupSchema.safeParse({ command: 'MyCommand' });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject command with invalid characters (spaces)', () => {
|
|
const result = updatePromptGroupSchema.safeParse({ command: 'my command' });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject command with invalid characters (special)', () => {
|
|
const result = updatePromptGroupSchema.safeParse({ command: 'my_command!' });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('validatePromptGroupUpdate', () => {
|
|
it('should return validated data for valid input', () => {
|
|
const input = { name: 'Test', category: 'testing' };
|
|
const result = validatePromptGroupUpdate(input);
|
|
expect(result).toEqual(input);
|
|
});
|
|
|
|
it('should throw ZodError for invalid input', () => {
|
|
expect(() => validatePromptGroupUpdate({ author: 'malicious-id' })).toThrow();
|
|
});
|
|
});
|
|
|
|
describe('safeValidatePromptGroupUpdate', () => {
|
|
it('should return success true for valid input', () => {
|
|
const result = safeValidatePromptGroupUpdate({ name: 'Test' });
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('should return success false for invalid input with errors', () => {
|
|
const result = safeValidatePromptGroupUpdate({ author: 'malicious-id' });
|
|
expect(result.success).toBe(false);
|
|
if (!result.success) {
|
|
expect(result.error.errors.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
});
|