Skip to content

Security: internal structured-output sessions can inherit unrestricted tools and loop indefinitely #189

Description

@Hamigua314

Summary

opencode-mem can create internal structured-output sessions that inherit a normal OpenCode agent and its ordinary tools. If the model calls an ordinary tool instead of StructuredOutput, OpenCode continues the agent loop. Because the plugin does not set an agent, tool restrictions, or a finite step limit, this can lead to an unbounded tool loop, extreme token/cache consumption, and unintended local side effects.

This was observed in a real incident. Sensitive prompts, command bodies, local paths, credentials, and session IDs are intentionally omitted.

Environment

  • opencode-mem: 2.20.0
  • OpenCode: 1.18.4
  • OS: Windows
  • OpenCode provider/model: DeepSeek / deepseek-v4-flash
  • Triggering features: auto-capture and user-profile learning through the structured-output path

Observed behavior

The internal opencode-mem capture sessions did not terminate after producing structured output. Instead, the model repeatedly selected ordinary agent tools such as bash, read, and memory. Each completed tool result was appended to the session history and caused another model request.

Aggregate evidence from 16 internal capture sessions:

Metric Value
Input tokens 6,608,198
Output tokens 1,028,838
Cache-read tokens 2,863,690,880
Share of recorded cache-read usage 88.54%
Share of recorded cost 81.98%

The three largest sessions produced 1,570, 984, and 824 effective assistant/tool rounds from a single user message. Across the 16 sessions there were:

  • 5,398 bash tool calls
  • 39 read calls
  • 361 memory searches
  • only one observed StructuredOutput call, which ended in error

Twenty-nine completed shell operations used Out-File against OpenCode state paths. No command bodies or target paths are included here. This means the incident crossed the boundary from resource exhaustion into potential local-state modification.

Relevant implementation behavior

In v2.20.0, the OpenCode structured-output path creates a temporary session and calls session.prompt with a model, system prompt, text part, and format: { type: "json_schema" }, but it does not explicitly set:

  • a dedicated internal agent
  • a restricted tool set
  • a finite step limit
  • a total token/tool budget

Source: src/services/ai/opencode-provider.ts

OpenCode 1.18.4 implements JSON-schema output by adding a StructuredOutput tool and setting tool choice to required, while ordinary agent tools remain available. A tool-calls finish continues the session loop, and the default maximum steps are effectively unlimited when the agent does not define one.

Source: packages/opencode/src/session/prompt.ts

This combination allows an internal summarization/profile task to become a general-purpose autonomous tool session. Network stream errors were present but were not the cause: database records show distinct completed tool rounds, not provider-level retries.

Expected behavior

Internal capture/profile-learning sessions should:

  1. have no access to ordinary tools such as shell, file, task, memory, or question tools;
  2. allow only the framework structured-output mechanism;
  3. have a small finite step/time/token budget;
  4. fail closed if any non-StructuredOutput tool is requested;
  5. terminate immediately when structured output fails validation.

Suggested fix

  • Create a dedicated least-privilege internal agent or explicitly disable all ordinary tools for these sessions.
  • Enforce a finite step limit, timeout, and total tool/token budget at the common generateStructuredOutput boundary.
  • Abort the session if the model requests any tool other than StructuredOutput.
  • Add regression tests where a provider repeatedly requests ordinary tools with changing arguments; verify that execution stops without running them.
  • Identify and exclude internal sessions using session metadata rather than prompt-text marker matching.

Safe reproduction outline

  1. Configure the plugin to use the OpenCode provider path for capture/profile learning.
  2. Use an OpenCode default agent that exposes ordinary tools.
  3. Trigger an internal structured-output capture.
  4. Use a provider response that selects an ordinary tool instead of StructuredOutput.
  5. Observe that the tool executes and the session continues unless an external limit stops it.

Mitigation used

Auto-capture was disabled immediately. The plugin was then removed and OpenCode restarted because no released upstream fix covering tool isolation and bounded execution was available at the time of the incident.

Related reports

Issues #162 and #169 involve auto-capture/provider failures, but they do not describe unrestricted tool inheritance, an unbounded agent loop, or token/cache exhaustion.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions