🛂 fix: Gate RUM Proxy Route on the RUM_ENABLED Flag (#13475)

This commit is contained in:
Ravi Kumar L 2026-06-02 20:13:10 +02:00 committed by GitHub
parent 83d8ac0682
commit f27e7d7cad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 81 additions and 1 deletions

View file

@ -0,0 +1,60 @@
const express = require('express');
const request = require('supertest');
const mockRequireJwtAuth = jest.fn((_req, _res, next) => next());
const mockIsRumProxyEnabled = jest.fn();
const mockProxyRumRequest = jest.fn((_req, res) => res.status(202).send());
jest.mock('~/server/middleware', () => ({
requireJwtAuth: (...args) => mockRequireJwtAuth(...args),
}));
jest.mock('@librechat/api', () => ({
getRumProxyBodyLimit: jest.fn(() => '3mb'),
isRumProxyEnabled: (...args) => mockIsRumProxyEnabled(...args),
proxyRumRequest: (...args) => mockProxyRumRequest(...args),
}));
describe('RUM proxy routes', () => {
let app;
beforeAll(() => {
const rumRouter = require('../rum');
app = express();
app.use('/api/rum', rumRouter);
});
beforeEach(() => {
mockRequireJwtAuth.mockClear();
mockIsRumProxyEnabled.mockReset();
mockProxyRumRequest.mockClear();
});
it('returns 404 before auth and proxying when RUM proxy mode is disabled', async () => {
mockIsRumProxyEnabled.mockReturnValue(false);
const response = await request(app)
.post('/api/rum/v1/traces')
.set('Content-Type', 'application/x-protobuf')
.send(Buffer.from('payload'));
expect(response.status).toBe(404);
expect(response.body).toEqual({ message: 'RUM proxy is not configured' });
expect(mockRequireJwtAuth).not.toHaveBeenCalled();
expect(mockProxyRumRequest).not.toHaveBeenCalled();
});
it('authenticates and proxies when RUM proxy mode is enabled', async () => {
mockIsRumProxyEnabled.mockReturnValue(true);
const response = await request(app)
.post('/api/rum/v1/traces')
.set('Content-Type', 'application/x-protobuf')
.send(Buffer.from('payload'));
expect(response.status).toBe(202);
expect(mockRequireJwtAuth).toHaveBeenCalledTimes(1);
expect(mockProxyRumRequest).toHaveBeenCalledTimes(1);
});
});

View file

@ -48,6 +48,7 @@ describe('RUM proxy configuration', () => {
});
it('resolves OTLP paths against the configured collector base URL', () => {
process.env.RUM_ENABLED = 'true';
process.env.RUM_AUTH_MODE = 'proxy';
process.env.RUM_PROXY_TARGET_URL = 'http://otel-collector:4318';
@ -57,6 +58,17 @@ describe('RUM proxy configuration', () => {
expect(resolveRumProxyTarget('/v1/metrics')).toBeUndefined();
});
it('does not enable proxy mode when RUM is disabled', () => {
process.env.RUM_ENABLED = 'false';
process.env.RUM_AUTH_MODE = 'proxy';
process.env.RUM_PROXY_TARGET_URL = 'http://otel-collector:4318';
expect(isRumProxyEnabled()).toBe(false);
delete process.env.RUM_ENABLED;
expect(isRumProxyEnabled()).toBe(false);
});
it('rejects unsafe collector target URLs', () => {
process.env.RUM_PROXY_TARGET_URL = 'https://user:pass@collector.example.com';
expect(getRumProxyTargetBaseUrl()).toBeUndefined();
@ -66,6 +78,7 @@ describe('RUM proxy configuration', () => {
});
it('forwards OTLP requests without forwarding app authorization', async () => {
process.env.RUM_ENABLED = 'true';
process.env.RUM_AUTH_MODE = 'proxy';
process.env.RUM_PROXY_TARGET_URL = 'http://otel-collector:4318';
const fetchMock = jest.spyOn(global, 'fetch').mockResolvedValue(
@ -106,6 +119,7 @@ describe('RUM proxy configuration', () => {
});
it('returns 400 for missing payloads and 404 for unsupported OTLP paths', async () => {
process.env.RUM_ENABLED = 'true';
process.env.RUM_AUTH_MODE = 'proxy';
process.env.RUM_PROXY_TARGET_URL = 'http://otel-collector:4318';
const missingBodyRes = makeResponse();
@ -122,6 +136,7 @@ describe('RUM proxy configuration', () => {
});
it('returns 502 when the collector request fails', async () => {
process.env.RUM_ENABLED = 'true';
process.env.RUM_AUTH_MODE = 'proxy';
process.env.RUM_PROXY_TARGET_URL = 'http://otel-collector:4318';
const fetchMock = jest.spyOn(global, 'fetch').mockRejectedValue(new Error('collector down'));

View file

@ -1,5 +1,6 @@
import type { Request, Response } from 'express';
import { logger } from '@librechat/data-schemas';
import { isEnabled } from '~/utils';
const DEFAULT_PROXY_PATH = '/api/rum';
const DEFAULT_BODY_LIMIT = '3mb';
@ -52,7 +53,11 @@ export function getRumProxyTargetBaseUrl(): URL | undefined {
}
export function isRumProxyEnabled(): boolean {
return process.env.RUM_AUTH_MODE === 'proxy' && getRumProxyTargetBaseUrl() != null;
return (
isEnabled(process.env.RUM_ENABLED) &&
process.env.RUM_AUTH_MODE === 'proxy' &&
getRumProxyTargetBaseUrl() != null
);
}
export function resolveRumProxyTarget(path: string): string | undefined {