const fs = require('fs'); const path = require('path'); const request = require('supertest'); const { MongoMemoryServer } = require('mongodb-memory-server'); const mongoose = require('mongoose'); /** * Mirrors what a production `client/dist/index.html` actually contains: inline * style, inline script, a module entry, and the module preloads Vite emits. */ const INDEX_HTML = 'LibreChat' + '' + '' + '' + '' + '' + '' + '
'; jest.mock('~/server/services/Config', () => ({ syncStaticTools: jest.fn().mockResolvedValue(undefined), mergeAppTools: jest.fn().mockResolvedValue(undefined), loadCustomConfig: jest.fn(() => Promise.resolve({})), getAppConfig: jest.fn().mockResolvedValue({ paths: { uploads: '/tmp', dist: '/tmp/dist-csp', fonts: '/tmp/fonts-csp', assets: '/tmp/assets-csp', }, fileStrategy: 'local', imageOutputType: 'PNG', }), setCachedTools: jest.fn(), })); jest.mock('~/server/services/Agents/triggers', () => ({ initializeAgentTriggerService: jest.fn().mockResolvedValue(undefined), })); jest.mock('~/server/services/Schedules', () => ({ initializeScheduleEngine: jest.fn().mockResolvedValue(undefined), })); jest.mock('~/app/clients/tools', () => ({ createOpenAIImageTools: jest.fn(() => []), createYouTubeTools: jest.fn(() => []), manifestToolMap: {}, toolkits: [], })); jest.mock('~/config', () => ({ createMCPServersRegistry: jest.fn(), createMCPManager: jest.fn().mockResolvedValue({ getAppToolFunctions: jest.fn().mockResolvedValue({}), }), })); jest.mock( '@librechat/api/telemetry', () => ({ initializeTelemetry: jest.fn(() => ({ enabled: false, status: 'disabled', shutdown: jest.fn(), })), telemetryMiddleware: jest.fn((_req, _res, next) => next()), telemetryErrorMiddleware: jest.fn((err, _req, _res, next) => next(err)), }), { virtual: true }, ); describe('Content Security Policy', () => { jest.setTimeout(30_000); let mongoServer; let app; const originalReadFileSync = fs.readFileSync; beforeAll(async () => { fs.readFileSync = function (filepath, options) { if (filepath.includes('index.html')) { return INDEX_HTML; } return originalReadFileSync(filepath, options); }; for (const dir of ['/tmp/dist-csp', '/tmp/fonts-csp', '/tmp/assets-csp']) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } fs.writeFileSync(path.join('/tmp/dist-csp', 'index.html'), INDEX_HTML); mongoServer = await MongoMemoryServer.create(); process.env.MONGO_URI = mongoServer.getUri(); process.env.PORT = '0'; /* Read once at startup, so they must be set before the server module loads. */ process.env.CSP_ENABLED = 'true'; process.env.CSP_REPORT_ONLY = 'false'; process.env.CSP_CONNECT_SRC_EXTRA = 'https://telemetry.example.com'; /* A cacheable override that CSP must refuse for the shell. */ process.env.INDEX_CACHE_CONTROL = 'public, max-age=3600'; app = require('~/server'); await healthCheckPoll(app); }); afterAll(async () => { fs.readFileSync = originalReadFileSync; delete process.env.CSP_ENABLED; delete process.env.CSP_REPORT_ONLY; delete process.env.CSP_CONNECT_SRC_EXTRA; delete process.env.INDEX_CACHE_CONTROL; await mongoServer.stop(); await mongoose.disconnect(); }); it('sends an enforcing policy whose nonce matches the served scripts', async () => { const response = await request(app).get('/'); const csp = response.headers['content-security-policy']; const nonce = csp?.match(/script-src 'nonce-([^']+)'/)?.[1]; expect(response.status).toBe(200); expect(response.headers['content-security-policy-report-only']).toBeUndefined(); expect(nonce).toBeTruthy(); expect(response.text).toContain(``); expect(response.text).toContain(`