Skip to content

Unbounded thread creation in ScriptedTool JS callbacks enables DoS #982

@chaliy

Description

@chaliy

Summary

Every JS tool callback invocation during script execution spawns a new OS thread via std::thread::spawn plus a new tokio runtime. A script in a loop can invoke registered tools thousands of times, each spawning a new thread. No thread pool or concurrency limit exists.

Severity: Medium
Category: Resource Exhaustion / DoS (TM-DOS)

Affected Files

  • crates/bashkit-js/src/lib.rs lines 1049-1063

Code

std::thread::spawn(move || {
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build();
    // ...
});

Steps to Reproduce

const bash = new Bash({
  maxLoopIterations: 10000,
  tools: [{ name: "mytool", description: "test", callback: async () => "ok" }]
});
await bash.execute("for i in $(seq 1 10000); do mytool; done");
// Creates 10,000 OS threads, each with its own tokio runtime

Impact

  • OS thread exhaustion
  • File descriptor exhaustion
  • Potential OOM from 10,000+ tokio runtimes

Acceptance Criteria

  • Use a thread pool (e.g., fixed-size tokio::runtime::Runtime with spawn_blocking) instead of unbounded thread::spawn
  • Or: maintain a single dedicated thread for TSFN dispatch
  • Limit concurrent outstanding tool callbacks (e.g., 10)
  • Test: 1000 tool invocations in a loop do not create 1000 threads
  • Test: Thread count stays bounded under heavy tool callback load

Metadata

Metadata

Assignees

No one assigned

    Labels

    securitySecurity vulnerability or hardening

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions