Skip to content

fix(security): restrict subagent toolsets to parent's enabled set - #3269

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-5ef8201d
Mar 26, 2026
Merged

fix(security): restrict subagent toolsets to parent's enabled set#3269
teknium1 merged 1 commit into
mainfrom
hermes/hermes-5ef8201d

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Salvaged from PR #3188 by @dieutx — cherry-picked onto current main with original authorship preserved.

Root cause

_build_child_agent() passes LLM-provided toolsets through _strip_blocked_tools() which only removes a hardcoded blocklist (delegation, clarify, memory, code_execution). It never intersects with the parent's enabled_toolsets. A model calling delegate_task(toolsets=["terminal", "file", "web", "browser"]) when the parent only has ["terminal", "file"] grants the subagent web and browser — a privilege escalation.

Fix

Intersect LLM-requested toolsets with the parent's enabled set before filtering:

parent_toolsets = set(getattr(parent_agent, "enabled_toolsets", None) or DEFAULT_TOOLSETS)
if toolsets:
    child_toolsets = _strip_blocked_tools([t for t in toolsets if t in parent_toolsets])

3 lines of production code. The no-toolsets path (child inherits parent) and blocked-tools filter are unchanged.

Validation

  • python -m pytest tests/tools/test_delegate_toolset_scope.py tests/tools/test_delegate.py -n0 -q → 54 passed
  • Full suite: 6254 passed, 1 pre-existing failure (unrelated)

Closes #3188

Co-authored-by: dieutx dangtc94@gmail.com

The delegate_task tool accepts a toolsets parameter directly from the
LLM's function call arguments. When provided, these toolsets are passed
through _strip_blocked_tools but never intersected with the parent
agent's enabled_toolsets. A model can request toolsets the parent does
not have (e.g., web, browser, rl), granting the subagent tools that
were explicitly disabled for the parent.

Intersect LLM-requested toolsets with the parent's enabled set before
applying the blocked-tool filter, so subagents can only receive a
subset of the parent's tools.
@teknium1
teknium1 merged commit e5d1444 into main Mar 26, 2026
1 of 2 checks passed
BongSuCHOI added a commit to BongSuCHOI/hermes-agent that referenced this pull request Apr 6, 2026
When parent_agent.enabled_toolsets is None (the default, meaning all tools
are enabled), subagents incorrectly fell back to DEFAULT_TOOLSETS
(['terminal', 'file', 'web']) instead of inheriting the parent's full
toolset.

Root cause:
- Line 188 used 'or' fallback: None or DEFAULT_TOOLSETS evaluates to
  DEFAULT_TOOLSETS
- Line 192 checked truthiness: None is falsy, falling through to else

Fix:
- Use 'is not None' checks instead of truthiness
- When enabled_toolsets is None, derive effective toolsets from
  parent_agent.valid_tool_names via the tool registry

Fixes the bug introduced in f75b1d2 and repeated in e5d1444 (PR NousResearch#3269).
teknium1 pushed a commit that referenced this pull request Apr 6, 2026
When parent_agent.enabled_toolsets is None (the default, meaning all tools
are enabled), subagents incorrectly fell back to DEFAULT_TOOLSETS
(['terminal', 'file', 'web']) instead of inheriting the parent's full
toolset.

Root cause:
- Line 188 used 'or' fallback: None or DEFAULT_TOOLSETS evaluates to
  DEFAULT_TOOLSETS
- Line 192 checked truthiness: None is falsy, falling through to else

Fix:
- Use 'is not None' checks instead of truthiness
- When enabled_toolsets is None, derive effective toolsets from
  parent_agent.valid_tool_names via the tool registry

Fixes the bug introduced in f75b1d2 and repeated in e5d1444 (PR #3269).
teknium1 pushed a commit that referenced this pull request Apr 6, 2026
When parent_agent.enabled_toolsets is None (the default, meaning all tools
are enabled), subagents incorrectly fell back to DEFAULT_TOOLSETS
(['terminal', 'file', 'web']) instead of inheriting the parent's full
toolset.

Root cause:
- Line 188 used 'or' fallback: None or DEFAULT_TOOLSETS evaluates to
  DEFAULT_TOOLSETS
- Line 192 checked truthiness: None is falsy, falling through to else

Fix:
- Use 'is not None' checks instead of truthiness
- When enabled_toolsets is None, derive effective toolsets from
  parent_agent.valid_tool_names via the tool registry

Fixes the bug introduced in f75b1d2 and repeated in e5d1444 (PR #3269).
Tommyeds pushed a commit to Tommyeds/hermes-agent that referenced this pull request Apr 12, 2026
When parent_agent.enabled_toolsets is None (the default, meaning all tools
are enabled), subagents incorrectly fell back to DEFAULT_TOOLSETS
(['terminal', 'file', 'web']) instead of inheriting the parent's full
toolset.

Root cause:
- Line 188 used 'or' fallback: None or DEFAULT_TOOLSETS evaluates to
  DEFAULT_TOOLSETS
- Line 192 checked truthiness: None is falsy, falling through to else

Fix:
- Use 'is not None' checks instead of truthiness
- When enabled_toolsets is None, derive effective toolsets from
  parent_agent.valid_tool_names via the tool registry

Fixes the bug introduced in f75b1d2 and repeated in e5d1444 (PR NousResearch#3269).
angelburgosrosado pushed a commit to angelburgosrosado/hermes-agent that referenced this pull request Apr 27, 2026
…usResearch#3269)

The delegate_task tool accepts a toolsets parameter directly from the
LLM's function call arguments. When provided, these toolsets are passed
through _strip_blocked_tools but never intersected with the parent
agent's enabled_toolsets. A model can request toolsets the parent does
not have (e.g., web, browser, rl), granting the subagent tools that
were explicitly disabled for the parent.

Intersect LLM-requested toolsets with the parent's enabled set before
applying the blocked-tool filter, so subagents can only receive a
subset of the parent's tools.

Co-authored-by: dieutx <dangtc94@gmail.com>
angelburgosrosado pushed a commit to angelburgosrosado/hermes-agent that referenced this pull request Apr 27, 2026
When parent_agent.enabled_toolsets is None (the default, meaning all tools
are enabled), subagents incorrectly fell back to DEFAULT_TOOLSETS
(['terminal', 'file', 'web']) instead of inheriting the parent's full
toolset.

Root cause:
- Line 188 used 'or' fallback: None or DEFAULT_TOOLSETS evaluates to
  DEFAULT_TOOLSETS
- Line 192 checked truthiness: None is falsy, falling through to else

Fix:
- Use 'is not None' checks instead of truthiness
- When enabled_toolsets is None, derive effective toolsets from
  parent_agent.valid_tool_names via the tool registry

Fixes the bug introduced in 9f5702d and repeated in 4fe09e8 (PR NousResearch#3269).
02356abc pushed a commit to 02356abc/hermes-agent that referenced this pull request May 14, 2026
…usResearch#3269)

The delegate_task tool accepts a toolsets parameter directly from the
LLM's function call arguments. When provided, these toolsets are passed
through _strip_blocked_tools but never intersected with the parent
agent's enabled_toolsets. A model can request toolsets the parent does
not have (e.g., web, browser, rl), granting the subagent tools that
were explicitly disabled for the parent.

Intersect LLM-requested toolsets with the parent's enabled set before
applying the blocked-tool filter, so subagents can only receive a
subset of the parent's tools.

Co-authored-by: dieutx <dangtc94@gmail.com>
02356abc pushed a commit to 02356abc/hermes-agent that referenced this pull request May 14, 2026
When parent_agent.enabled_toolsets is None (the default, meaning all tools
are enabled), subagents incorrectly fell back to DEFAULT_TOOLSETS
(['terminal', 'file', 'web']) instead of inheriting the parent's full
toolset.

Root cause:
- Line 188 used 'or' fallback: None or DEFAULT_TOOLSETS evaluates to
  DEFAULT_TOOLSETS
- Line 192 checked truthiness: None is falsy, falling through to else

Fix:
- Use 'is not None' checks instead of truthiness
- When enabled_toolsets is None, derive effective toolsets from
  parent_agent.valid_tool_names via the tool registry

Fixes the bug introduced in f75b1d2 and repeated in e5d1444 (PR NousResearch#3269).
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…usResearch#3269)

The delegate_task tool accepts a toolsets parameter directly from the
LLM's function call arguments. When provided, these toolsets are passed
through _strip_blocked_tools but never intersected with the parent
agent's enabled_toolsets. A model can request toolsets the parent does
not have (e.g., web, browser, rl), granting the subagent tools that
were explicitly disabled for the parent.

Intersect LLM-requested toolsets with the parent's enabled set before
applying the blocked-tool filter, so subagents can only receive a
subset of the parent's tools.

Co-authored-by: dieutx <dangtc94@gmail.com>
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
When parent_agent.enabled_toolsets is None (the default, meaning all tools
are enabled), subagents incorrectly fell back to DEFAULT_TOOLSETS
(['terminal', 'file', 'web']) instead of inheriting the parent's full
toolset.

Root cause:
- Line 188 used 'or' fallback: None or DEFAULT_TOOLSETS evaluates to
  DEFAULT_TOOLSETS
- Line 192 checked truthiness: None is falsy, falling through to else

Fix:
- Use 'is not None' checks instead of truthiness
- When enabled_toolsets is None, derive effective toolsets from
  parent_agent.valid_tool_names via the tool registry

Fixes the bug introduced in f75b1d2 and repeated in e5d1444 (PR NousResearch#3269).
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…usResearch#3269)

The delegate_task tool accepts a toolsets parameter directly from the
LLM's function call arguments. When provided, these toolsets are passed
through _strip_blocked_tools but never intersected with the parent
agent's enabled_toolsets. A model can request toolsets the parent does
not have (e.g., web, browser, rl), granting the subagent tools that
were explicitly disabled for the parent.

Intersect LLM-requested toolsets with the parent's enabled set before
applying the blocked-tool filter, so subagents can only receive a
subset of the parent's tools.

Co-authored-by: dieutx <dangtc94@gmail.com>
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
When parent_agent.enabled_toolsets is None (the default, meaning all tools
are enabled), subagents incorrectly fell back to DEFAULT_TOOLSETS
(['terminal', 'file', 'web']) instead of inheriting the parent's full
toolset.

Root cause:
- Line 188 used 'or' fallback: None or DEFAULT_TOOLSETS evaluates to
  DEFAULT_TOOLSETS
- Line 192 checked truthiness: None is falsy, falling through to else

Fix:
- Use 'is not None' checks instead of truthiness
- When enabled_toolsets is None, derive effective toolsets from
  parent_agent.valid_tool_names via the tool registry

Fixes the bug introduced in ff89ef3 and repeated in eb12e9d (PR NousResearch#3269).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants