Skip to content

Subagent empty tool allowlist is fail-open — child inherits parent's full tool surface (shell/spawn) #4452

Closed

Description

@senamakel

Part of the v0.58.7 → HEAD TinyAgents-migration audit (see parent issue).

Problem

Sub-agent tool exposure inverted from fail-closed to fail-open. Old subagent_runner/ops/tool_source.rs:53-107 (v0.58.7) denied any tool not in the allowlist — an empty allowlist meant deny-all. The new registration predicate at src/openhuman/tinyagents/mod.rs:1126 is allowed.is_empty() || allowed.contains(name) — an empty allowlist now registers every tool across tool_sets, which includes parent.all_tools: shell, file-write, spawn/delegate tools.

Reachable by:

  • a custom agent TOML with tools = [] (ToolScope::Named is user-deserializable, src/openhuman/agent/harness/definition.rs:621-629);
  • a skill_filter that matches zero tools;
  • a named list whose entries are absent from the parent surface (silently resolves to empty).

Compounding factors: the spawn-tool strip at subagent_runner/ops/runner.rs:415 operates on allowed_indices only, so the "sub-agents must never spawn" invariant is bypassed too; and subagent turns pass tool_policy: None (ops/graph.rs:267), so registration is the only gate.

Failure scenario

A deliberately tool-less summarizer agent processing untrusted content (web page, email) silently gains shell + file-write + spawn — a prompt-injection privilege escalation.

Fix plan

  1. In src/openhuman/tinyagents/mod.rs:1126, distinguish "no filter supplied" (None → all tools) from "empty allowlist" (Some(empty) → deny all). Change the plumbed type from Vec<String>/HashSet<String> to Option<HashSet<String>> end-to-end (subagent_runner/ops/runner.rs:690-698 → assemble_turn_harness).
  2. Make ToolScope::Named([]) and a zero-match skill_filter produce Some(empty) (deny-all), matching v0.58.7 semantics. Log a warn ([subagent] tool allowlist resolved empty — registering no tools) so misconfigured agents are diagnosable.
  3. Re-assert the spawn-tool invariant at registration time (strip spawn/delegate names from whatever set is registered, not just from allowed_indices).
  4. Consider also passing a real tool_policy on the subagent path instead of None (defense in depth).

Acceptance criteria

  • tools = [] / zero-match filters register zero tools; the subagent can still complete text-only turns.
  • No subagent configuration can implicitly inherit the parent's full tool surface.
  • Spawn/delegate tools are never registered to a subagent regardless of allowlist content.
  • Unit tests over the registration predicate for: None, Some(empty), Some({named}), named-but-unresolvable; plus an e2e asserting a tools = [] agent has no shell.

Activity

  1. added
    subtaskSubtask of a larger tracked effort.
    agentBuilt-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/.
    securitySecurity, encryption, approvals, credentials, and trust boundaries.
    on Jul 3, 2026
  2. self-assigned this
    on Jul 7, 2026
  3. M3gA-Mind commented on Jul 7, 2026

    @M3gA-Mind
    Collaborator

    Already resolved by merged PR #4495 ("TinyAgents migration parity & bug fixes (#4451–#4469)") plus follow-up #4504 — the fix is present in main. Closing as completed.

  4. added theissue type on Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

agentBuilt-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/.bugpriority: criticalBlocks boot or core functionalitysecuritySecurity, encryption, approvals, credentials, and trust boundaries.subtaskSubtask of a larger tracked effort.

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions