🛂 fix: Re-Check execute_code Authorization on Event-Driven Tool Loads (#13912)

This commit is contained in:
Danny Avila 2026-06-23 08:30:39 -04:00 committed by GitHub
parent e807c63d5d
commit edc0aebdb9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 57 additions and 6 deletions

View file

@ -1451,20 +1451,25 @@ async function loadToolsForExecution({
AgentConstants.PROGRAMMATIC_TOOL_CALLING,
].filter((name) => toolNames.includes(name));
const isPTCRequested = ptcToolNames.length > 0;
const isBashToolRequested = toolNames.includes(AgentConstants.BASH_TOOL);
const isLegacyExecuteCodeRequested = toolNames.includes(Tools.execute_code);
const isCodeExecutionToolRequested = isBashToolRequested || isLegacyExecuteCodeRequested;
let enabledCapabilities;
if (actionsEnabled === undefined || isPTCRequested) {
if (actionsEnabled === undefined || isPTCRequested || isCodeExecutionToolRequested) {
enabledCapabilities = await resolveAgentCapabilities(req, appConfig, agent?.id);
}
if (actionsEnabled === undefined) {
actionsEnabled = enabledCapabilities.has(AgentCapabilities.actions);
}
const codeExecutionEnabled =
enabledCapabilities?.has(AgentCapabilities.execute_code) === true &&
agent?.tools?.includes(Tools.execute_code) === true;
const isPTC =
isPTCRequested &&
enabledCapabilities.has(AgentCapabilities.programmatic_tools) &&
enabledCapabilities.has(AgentCapabilities.execute_code) &&
agent?.tools?.includes(Tools.execute_code) === true;
codeExecutionEnabled;
logger.debug(
`[loadToolsForExecution] isToolSearch: ${isToolSearch}, toolRegistry: ${toolRegistry?.size ?? 'undefined'}`,
@ -1498,7 +1503,16 @@ async function loadToolsForExecution({
}
}
const isBashTool = toolNames.includes(AgentConstants.BASH_TOOL);
const isBashTool =
isBashToolRequested &&
codeExecutionEnabled &&
toolRegistry?.has(AgentConstants.BASH_TOOL) === true;
if (isBashToolRequested && !isBashTool) {
logger.warn(
`[loadToolsForExecution] Skipping unregistered or unauthorized ${AgentConstants.BASH_TOOL}. ` +
`User: ${req.user.id} | Agent: ${agent?.id ?? 'unknown'}`,
);
}
if (isBashTool) {
try {
const bashTool = createBashExecutionTool({
@ -1535,9 +1549,22 @@ async function loadToolsForExecution({
}
const requestedNonSpecialToolNames = toolNames.filter((name) => !specialToolNames.has(name));
const allowedNonSpecialToolNames = requestedNonSpecialToolNames.filter((name) => {
if (name !== Tools.execute_code) {
return true;
}
const allowed = codeExecutionEnabled && toolRegistry?.has(Tools.execute_code) === true;
if (!allowed) {
logger.warn(
`[loadToolsForExecution] Skipping unregistered or unauthorized ${Tools.execute_code}. ` +
`User: ${req.user.id} | Agent: ${agent?.id ?? 'unknown'}`,
);
}
return allowed;
});
const allToolNamesToLoad = isPTC
? [...new Set([...requestedNonSpecialToolNames, ...ptcOrchestratedToolNames])]
: requestedNonSpecialToolNames;
? [...new Set([...allowedNonSpecialToolNames, ...ptcOrchestratedToolNames])]
: allowedNonSpecialToolNames;
const actionToolNames = [];
const regularToolNames = [];

View file

@ -1,3 +1,4 @@
const { Constants: AgentConstants } = require('@librechat/agents');
const {
Tools,
Constants,
@ -964,6 +965,29 @@ describe('ToolService - Action Capability Gating', () => {
const actionToolName = `get_weather${actionDelimiter}api_example_com`;
const regularTool = Tools.web_search;
it('does not load code execution tools that were not registered for the agent', async () => {
const capabilities = [
AgentCapabilities.tools,
AgentCapabilities.web_search,
AgentCapabilities.execute_code,
];
const req = createMockReq(capabilities);
const toolRegistry = new Map([[Tools.web_search, { name: Tools.web_search }]]);
mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities));
const result = await loadToolsForExecution({
req,
res: {},
agent: { id: 'agent_without_code', tools: [Tools.web_search] },
toolNames: [AgentConstants.BASH_TOOL, Tools.execute_code],
toolRegistry,
actionsEnabled: false,
});
expect(result.loadedTools.map((tool) => tool.name)).toEqual([]);
expect(mockLoadToolsUtil).not.toHaveBeenCalled();
});
it('loads bash PTC under the legacy programmatic tool name when code capabilities are enabled', async () => {
const capabilities = [
AgentCapabilities.tools,