Skip to content

sec(tool): ScriptedTool callback errors not sanitized — may leak internal state in tool output #1172

@chaliy

Description

@chaliy

Summary

When a `ScriptedTool` callback returns an error (via `Err(msg)` or panic), the error message is passed through to the bash interpreter's stderr and ultimately to tool output. If callbacks contain internal implementation details (database connection strings, file paths, stack traces), these are exposed in the tool response visible to LLM agents.

Threat category: TM-INF (Information Disclosure) — extends existing category
Severity: Medium
Component: `crates/bashkit/src/scripted_tool/execute.rs`

Root Cause

The scripted tool execution engine passes callback errors directly to the interpreter:

When a callback fails, the error message from the Rust callback (which may include internal details like:

  • Database connection strings
  • Internal API endpoints
  • Stack traces from panics
  • File system paths on the host

These flow through to stderr and end up in the `ToolOutput` response.

Steps to Reproduce

let tool = ScriptedTool::builder("api")
    .tool(
        ToolDef::new("query", "Query database"),
        |args: &ToolArgs| {
            // Error includes internal connection string
            Err(format!("connection failed: postgres://admin:secret@internal-db:5432/prod"))
        },
    )
    .build();

let output = tool.execution(json!({"commands": "query --id 1"}))
    .unwrap().execute().await.unwrap();
// output.result["stderr"] contains: "connection failed: postgres://admin:secret@internal-db:5432/prod"

Impact

  • Credential leakage: Internal credentials in error messages exposed to LLM
  • Architecture disclosure: Internal service endpoints and infrastructure details revealed
  • Attack surface mapping: Stack traces reveal dependency versions and code paths

Acceptance Criteria

  • Sanitize callback error messages before including in tool output
  • Provide a `ScriptedToolBuilder::sanitize_errors(bool)` option (default: true)
  • When sanitization is on, replace detailed errors with generic "callback failed" + error code
  • Log full error details at DEBUG level for operator visibility
  • Add test: callback error with internal details is sanitized in output

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions