Skip to content

fix(tools)!: make built-in tools opt-in (default-deny)#289

Merged
JackChen-me merged 2 commits into
mainfrom
security/default-deny-builtin-tools
Jun 7, 2026
Merged

fix(tools)!: make built-in tools opt-in (default-deny)#289
JackChen-me merged 2 commits into
mainfrom
security/default-deny-builtin-tools

Conversation

@JackChen-me

Copy link
Copy Markdown
Member

What this fixes

A no-tools agent received every registered built-in, including an unsandboxed bash, straight from the default path (runAgent, runTeam/runTasks, and the runTeam short-circuit). No tool config required. Every tool result is appended to the model context and sent to the provider, so under prompt injection this default is a remotely triggerable exec + data-exfiltration primitive. #83 added the allowlist/preset mechanism but kept default-allow; #264 sandboxed the filesystem tools but left bash unsandboxed; together they opened the hole.

Built-in tools are now default-deny, the same model #87 already enforces for cross-agent context: an agent gets a built-in only when it grants one via tools or toolPreset.

How

  • resolveTools() is the single chokepoint: no toolPreset and no allowedTools => zero built-in tools. Preset / allowlist / denylist / framework-rail logic otherwise unchanged.
  • The runner gates execution on the resolved grant set, so a registered-but-ungranted call (confused model or injection) returns a clear "not granted" error instead of executing.
  • Custom / runtime tools (customTools / addTool) stay available since registration is the grant, still honoring disallowedTools. delegate_to_agent follows the same rule.
  • OrchestratorConfig.defaultToolPreset restores the old allow-all in one line; per-agent config overrides it.
  • Docs: docs/tool-configuration.md, README.md + README_zh.md, CLAUDE.md invariant.

Non-goals

Not sandboxing bash (a shell escapes per-path checks trivially; process isolation is the right boundary, see #264) and not adding a capability taxonomy. Default-deny is the minimal correct form.

Migration (breaking)

Agents relying on implicit built-in tools get none until granted. Restore with defaultToolPreset: 'full' on the orchestrator, or grant per agent via tools / toolPreset.

Tests

npm run lint && npm test green. New tests/default-deny-tools.test.ts asserts the standalone, short-circuit, and runTasks paths can't run shell or filesystem tools without a grant (a real-bash sentinel proves non-execution), tools/toolPreset still grant, custom tools survive, defaultToolPreset restores plus per-agent override wins, and an ungranted delegate_to_agent is blocked. Rewrote the three tool-filtering tests that encoded default-allow; added the now-required grant to the trace and abort-signal tests whose intent is "the tool runs".

A no-tools agent received every registered built-in, including an unsandboxed
bash, on the default path (runAgent, runTeam/runTasks, and the runTeam
short-circuit). Tool output flows back to the model provider, so under prompt
injection this was a remotely triggerable exec + exfiltration primitive. #83
added the allowlist/preset mechanism but kept default-allow; #264 sandboxed the
filesystem tools but left bash unsandboxed; together they opened this.

Built-in tools now require a positive grant via tools or toolPreset; with
neither, an agent resolves to zero. resolveTools() is the chokepoint and the
runner gates execution on the granted set, so a registered-but-ungranted call
returns a "not granted" error instead of running. Custom/runtime tools
(customTools/addTool) stay available since registration is the grant, and still
honor disallowedTools. delegate_to_agent follows the same rule. Same default-deny
model #87 enforces for cross-agent context.

OrchestratorConfig.defaultToolPreset restores the old allow-all in one line;
per-agent tools/toolPreset override it.

BREAKING CHANGE: agents relying on implicit built-in tools get none until
granted. Restore with defaultToolPreset: 'full', or grant per agent via
tools/toolPreset.
@JackChen-me
JackChen-me force-pushed the security/default-deny-builtin-tools branch from 1796bf0 to 6d136a8 Compare June 7, 2026 15:29
@JackChen-me
JackChen-me merged commit 95b769b into main Jun 7, 2026
5 checks passed
@JackChen-me
JackChen-me deleted the security/default-deny-builtin-tools branch June 8, 2026 02:27
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.

1 participant