mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
Merge commit from fork
- Implemented validation for OpenAPI specifications to ensure the server URL matches the client-provided domain, preventing SSRF attacks. - Added domain extraction and validation functions to improve security checks. - Updated relevant services and routes to utilize the new validation logic, ensuring robust handling of client-provided domains against the OpenAPI spec. - Introduced comprehensive tests to validate the new security features and ensure correct behavior across various scenarios.
This commit is contained in:
parent
4e4c8d0c0e
commit
b6ba2711f9
4 changed files with 497 additions and 2 deletions
|
|
@ -9,6 +9,8 @@ const {
|
|||
PermissionTypes,
|
||||
actionDelimiter,
|
||||
removeNullishValues,
|
||||
validateActionDomain,
|
||||
validateAndParseOpenAPISpec,
|
||||
} = require('librechat-data-provider');
|
||||
const { encryptMetadata, domainParser } = require('~/server/services/ActionService');
|
||||
const { findAccessibleResources } = require('~/server/services/PermissionService');
|
||||
|
|
@ -83,6 +85,32 @@ router.post(
|
|||
|
||||
let metadata = await encryptMetadata(removeNullishValues(_metadata, true));
|
||||
const appConfig = req.config;
|
||||
|
||||
// SECURITY: Validate the OpenAPI spec and extract the server URL
|
||||
if (metadata.raw_spec) {
|
||||
const validationResult = validateAndParseOpenAPISpec(metadata.raw_spec);
|
||||
if (!validationResult.status || !validationResult.serverUrl) {
|
||||
return res.status(400).json({
|
||||
message: validationResult.message || 'Invalid OpenAPI specification',
|
||||
});
|
||||
}
|
||||
|
||||
// SECURITY: Validate the client-provided domain matches the spec's server URL domain
|
||||
// This prevents SSRF attacks where an attacker provides a whitelisted domain
|
||||
// but uses a different (potentially internal) URL in the raw_spec
|
||||
const domainValidation = validateActionDomain(metadata.domain, validationResult.serverUrl);
|
||||
if (!domainValidation.isValid) {
|
||||
logger.warn(`Domain mismatch detected: ${domainValidation.message}`, {
|
||||
userId: req.user.id,
|
||||
agent_id,
|
||||
});
|
||||
return res.status(400).json({
|
||||
message:
|
||||
'Domain mismatch: The domain in the OpenAPI spec does not match the provided domain',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const isDomainAllowed = await isActionDomainAllowed(
|
||||
metadata.domain,
|
||||
appConfig?.actions?.allowedDomains,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const {
|
|||
ImageVisionTool,
|
||||
openapiToFunction,
|
||||
AgentCapabilities,
|
||||
validateActionDomain,
|
||||
defaultAgentCapabilities,
|
||||
validateAndParseOpenAPISpec,
|
||||
} = require('librechat-data-provider');
|
||||
|
|
@ -236,12 +237,26 @@ async function processRequiredActions(client, requiredActions) {
|
|||
|
||||
// Validate and parse OpenAPI spec
|
||||
const validationResult = validateAndParseOpenAPISpec(action.metadata.raw_spec);
|
||||
if (!validationResult.spec) {
|
||||
if (!validationResult.spec || !validationResult.serverUrl) {
|
||||
throw new Error(
|
||||
`Invalid spec: user: ${client.req.user.id} | thread_id: ${requiredActions[0].thread_id} | run_id: ${requiredActions[0].run_id}`,
|
||||
);
|
||||
}
|
||||
|
||||
// SECURITY: Validate the domain from the spec matches the stored domain
|
||||
// This is defense-in-depth to prevent any stored malicious actions
|
||||
const domainValidation = validateActionDomain(
|
||||
action.metadata.domain,
|
||||
validationResult.serverUrl,
|
||||
);
|
||||
if (!domainValidation.isValid) {
|
||||
logger.error(`Domain mismatch in stored action: ${domainValidation.message}`, {
|
||||
userId: client.req.user.id,
|
||||
action_id: action.action_id,
|
||||
});
|
||||
continue; // Skip this action rather than failing the entire request
|
||||
}
|
||||
|
||||
// Process the OpenAPI spec
|
||||
const { requestBuilders } = openapiToFunction(validationResult.spec);
|
||||
|
||||
|
|
@ -525,10 +540,25 @@ async function loadAgentTools({ req, res, agent, signal, tool_resources, openAIA
|
|||
|
||||
// Validate and parse OpenAPI spec once per action set
|
||||
const validationResult = validateAndParseOpenAPISpec(action.metadata.raw_spec);
|
||||
if (!validationResult.spec) {
|
||||
if (!validationResult.spec || !validationResult.serverUrl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// SECURITY: Validate the domain from the spec matches the stored domain
|
||||
// This is defense-in-depth to prevent any stored malicious actions
|
||||
const domainValidation = validateActionDomain(
|
||||
action.metadata.domain,
|
||||
validationResult.serverUrl,
|
||||
);
|
||||
if (!domainValidation.isValid) {
|
||||
logger.error(`Domain mismatch in stored action: ${domainValidation.message}`, {
|
||||
userId: req.user.id,
|
||||
agent_id: agent.id,
|
||||
action_id: action.action_id,
|
||||
});
|
||||
continue; // Skip this action rather than failing the entire request
|
||||
}
|
||||
|
||||
const encrypted = {
|
||||
oauth_client_id: action.metadata.oauth_client_id,
|
||||
oauth_client_secret: action.metadata.oauth_client_secret,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue