Skip to content

🛂 fix: Enforce Actions Capability Gate Across All Event-Driven Tool Loading Paths#12252

Merged
danny-avila merged 6 commits into
devfrom
codex/fix-event-driven-tool-loading-capability-bypass
Mar 16, 2026
Merged

🛂 fix: Enforce Actions Capability Gate Across All Event-Driven Tool Loading Paths#12252
danny-avila merged 6 commits into
devfrom
codex/fix-event-driven-tool-loading-capability-bypass

Conversation

@danny-avila

@danny-avila danny-avila commented Mar 15, 2026

Copy link
Copy Markdown
Owner

Motivation

Event-driven (definitions-only) tool loading previously returned action tool definitions and allowed action execution without checking AgentCapabilities.actions, enabling action tools even when actions were administratively disabled. The bypass created a risk of unintended outbound API calls triggered by agents when actions are disabled.

Description

Core fix (ToolService.js)

  • Extracted resolveAgentCapabilities helper — eliminates the 3x-duplicated capability resolution block (endpoints config lookup + ephemeral fallback). Used by loadToolDefinitionsWrapper, loadAgentTools, and loadToolsForExecution.
  • loadToolDefinitionsWrapper — action tools are now filtered out of filteredTools before reaching loadToolDefinitions when AgentCapabilities.actions is disabled.
  • loadAgentTools (non-definitions path) — replaced the old bypass pattern (!areToolsEnabled && !tool.includes(actionDelimiter)) with explicit action-tool gating: tool.includes(actionDelimiter) → return actionsEnabled. Replaced the late checkCapability(AgentCapabilities.actions) guard with a hasActionTools check to avoid unnecessary loadActionSets DB calls and duplicate warnings.
  • loadToolsForExecution — accepts an actionsEnabled parameter (with cache-based fallback via resolveAgentCapabilities). Gates loadActionToolsForExecution behind actionsEnabled. Warning log includes skipped tool names. Removed orphaned @type JSDoc comment.

Type threading (packages/api)

  • Added actionsEnabled?: boolean to InitializedAgent type, the loadTools callback return type, and the initializeAgent destructuring/return so callers can forward the resolved value.

Caller wiring

  • initialize.js — stores actionsEnabled in agentToolContexts for primary, handoff, and added-convo agents. Backfills agentToolContexts for processAddedConvo parallel agents.
  • openai.js and responses.js — pass primaryConfig.actionsEnabled to loadToolsForExecution.

Tests (ToolService.spec.js)

  • Replaced shadow re-implementations with real-function tests: resolveAgentCapabilities, loadAgentTools, and loadToolsForExecution are called directly with mocked dependencies.
  • Covers: definition filtering, execution gating, actionsEnabled param forwarding, fallback capability resolution, ephemeral agent fallback (using Constants.EPHEMERAL_AGENT_ID).

Testing

  • 30 tests pass across ToolService.spec.js and initialize.spec.js.
  • Existing controller tests (responses.unit.spec.js, openai.spec.js) continue to pass.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR closes a capability-bypass where event-driven (“definitions-only”) action tools could be exposed and executed even when AgentCapabilities.actions was disabled, by gating both definition exposure and runtime action-tool loading on the actions capability.

Changes:

  • Filtered action tool names out of definitions in loadToolDefinitionsWrapper when AgentCapabilities.actions is disabled.
  • Gated loadActionToolsForExecution in loadToolsForExecution behind resolved capabilities, and added a warning when action execution is skipped.
  • Added unit tests covering action-tool definition filtering and execution gating when actions are disabled.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
api/server/services/ToolService.js Adds actions-capability checks for action tool definition exposure and event-driven action tool execution.
api/server/services/__tests__/ToolService.spec.js Adds regression tests to validate action tools are excluded/blocked when actions capability is disabled.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread api/server/services/ToolService.js Outdated
Comment on lines +1201 to +1208
const endpointsConfig = await getEndpointsConfig(req);
let enabledCapabilities = new Set(endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? []);
if (enabledCapabilities.size === 0 && isEphemeralAgentId(agent.id)) {
enabledCapabilities = new Set(
appConfig.endpoints?.[EModelEndpoint.agents]?.capabilities ?? defaultAgentCapabilities,
);
}
const actionsEnabled = enabledCapabilities.has(AgentCapabilities.actions);
Comment thread api/server/services/ToolService.js Outdated
Comment on lines +1203 to +1206
if (enabledCapabilities.size === 0 && isEphemeralAgentId(agent.id)) {
enabledCapabilities = new Set(
appConfig.endpoints?.[EModelEndpoint.agents]?.capabilities ?? defaultAgentCapabilities,
);
Comment thread api/server/services/ToolService.js Outdated
Comment on lines +1252 to +1254
logger.warn(
`[loadToolsForExecution] Capability "${AgentCapabilities.actions}" disabled. ` +
`Skipping action tool execution. User: ${req.user.id} | Agent: ${agent.id}`,
Comment on lines +203 to +205
describe('actions capability gating', () => {
const actionDelimiter = '|';

@danny-avila danny-avila changed the base branch from main to dev March 16, 2026 00:21
Extract resolveAgentCapabilities helper to eliminate 3x-duplicated
capability resolution. Apply early action-tool filtering in both
loadToolDefinitionsWrapper and loadAgentTools non-definitions path.
Gate loadActionToolsForExecution in loadToolsForExecution behind an
actionsEnabled parameter with a cache-based fallback. Replace the
late capability guard in loadAgentTools with a hasActionTools check
to avoid unnecessary loadActionSets DB calls and duplicate warnings.
Add actionsEnabled to the loadTools callback return type,
InitializedAgent, and the initializeAgent destructuring/return
so callers can forward the resolved value to loadToolsForExecution
without redundant getEndpointsConfig cache lookups.
Thread actionsEnabled through the agentToolContexts map in
initialize.js (primary and handoff agents) and through
primaryConfig in the openai.js and responses.js controllers,
avoiding per-tool-call capability re-resolution on the hot path.
Test the real exported functions (resolveAgentCapabilities,
loadAgentTools, loadToolsForExecution) with mocked dependencies
instead of shadow re-implementations. Covers definition filtering,
execution gating, actionsEnabled param forwarding, and fallback
capability resolution.
@danny-avila danny-avila force-pushed the codex/fix-event-driven-tool-loading-capability-bypass branch from 461d26a to a712967 Compare March 16, 2026 00:22
Replaces a string guess with the canonical constant to avoid
fragility if the ephemeral detection heuristic changes.
After processAddedConvo returns, backfill agentToolContexts for
any agents in agentConfigs not already present, so ON_TOOL_EXECUTE
for added-convo agents receives actionsEnabled instead of falling
back to a per-call cache lookup.
@danny-avila danny-avila changed the title fix: enforce actions capability for event-driven action tools 🛂 fix: Enforce Actions Capability Gate Across All Event-Driven Tool Loading Paths Mar 16, 2026
@danny-avila danny-avila merged commit 6f87b49 into dev Mar 16, 2026
9 checks passed
@danny-avila danny-avila deleted the codex/fix-event-driven-tool-loading-capability-bypass branch March 16, 2026 03:01
jcbartle pushed a commit to jcbartle/LibreChat that referenced this pull request May 11, 2026
…oading Paths (danny-avila#12252)

* fix: gate action tools by actions capability in all code paths

Extract resolveAgentCapabilities helper to eliminate 3x-duplicated
capability resolution. Apply early action-tool filtering in both
loadToolDefinitionsWrapper and loadAgentTools non-definitions path.
Gate loadActionToolsForExecution in loadToolsForExecution behind an
actionsEnabled parameter with a cache-based fallback. Replace the
late capability guard in loadAgentTools with a hasActionTools check
to avoid unnecessary loadActionSets DB calls and duplicate warnings.

* fix: thread actionsEnabled through InitializedAgent type

Add actionsEnabled to the loadTools callback return type,
InitializedAgent, and the initializeAgent destructuring/return
so callers can forward the resolved value to loadToolsForExecution
without redundant getEndpointsConfig cache lookups.

* fix: pass actionsEnabled from callers to loadToolsForExecution

Thread actionsEnabled through the agentToolContexts map in
initialize.js (primary and handoff agents) and through
primaryConfig in the openai.js and responses.js controllers,
avoiding per-tool-call capability re-resolution on the hot path.

* test: add regression tests for action capability gating

Test the real exported functions (resolveAgentCapabilities,
loadAgentTools, loadToolsForExecution) with mocked dependencies
instead of shadow re-implementations. Covers definition filtering,
execution gating, actionsEnabled param forwarding, and fallback
capability resolution.

* test: use Constants.EPHEMERAL_AGENT_ID in ephemeral fallback test

Replaces a string guess with the canonical constant to avoid
fragility if the ephemeral detection heuristic changes.

* fix: populate agentToolContexts for addedConvo parallel agents

After processAddedConvo returns, backfill agentToolContexts for
any agents in agentConfigs not already present, so ON_TOOL_EXECUTE
for added-convo agents receives actionsEnabled instead of falling
back to a per-call cache lookup.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants