Skip to content

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

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-698assemble_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.

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

No type

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions