If you discover a security vulnerability, please report it via GitHub Security Advisories.
Do not open a public issue for security vulnerabilities.
| Version | Supported |
|---|---|
| 0.5.x | Yes |
| 0.4.x | Yes |
| ≤ 0.3.x | No |
agent-dispatch runs claude -p subprocesses in configured directories on
behalf of a calling Claude Code agent. The MCP caller and the agent
configurations are part of the same trust domain as the user running the
server — this is a developer tool, not a multi-tenant service. With that in
mind, the security-relevant areas are:
- Tasks/context strings are passed as argument-list elements to
subprocess.run/Popen(nevershell=True), so there is no shell injection. - Argument injection is guarded. Structured fields placed next to a CLI
flag (
session_id→--resume,model→--model,permission_mode, and tool names) are rejected if they start with-, which theclaudeCLI would otherwise parse as a separate flag. Seerunner._reject_flaglike/runner.ArgInjectionError.
- Setting
permission_mode: bypassPermissions(or a permissivedefault_permission_mode) disables Claude Code's permission prompts for that agent — it can use any tool without confirmation. Only enable it for agents whose project directories you trust. Preferallowed_tools/disallowed_toolsfor least privilege. - A dispatched agent running with broad permissions can, in principle, start
its own
claude/dispatch chain. Recursion depth (AGENT_DISPATCH_DEPTH, bounded bymax_dispatch_depth) is best-effort: it crosses the process boundary via an environment variable, so a deliberately hostile agent that clears its environment can reset the counter. It protects against accidental A→B→A loops, not against an adversarial agent.
- Async/
return_refjob records persist to~/.config/agent-dispatch/jobs/<job_id>.json(override withAGENT_DISPATCH_JOBS_DIR). They contain the full task, context, and result, which may include sensitive output. Files are written0o600and the directory0o700(owner-only). Calldispatch_gc()periodically to purge old results. agents.yamlis written0o600. It records project paths and permission settings.job_ids are unauthenticated 32-char hex UUIDs — anyone who can call the MCP tools and knows ajob_idcan read its result. Don't relayjob_ids over untrusted channels. Caller-suppliedjob_id/refvalues are validated (^[0-9a-f]{32}$) before any filesystem access, blocking path traversal.
- The dispatched subprocess inherits the full parent environment
(
os.environ.copy()) — necessary forclaudeto find its credentials. Keep secrets you don't want dispatched agents to see out of the shell that launches the server. - Agent directories are resolved to absolute paths via
Path.resolve()and must exist at registration time.
max_budget_usd(per agent or as a default) caps spend per dispatch.
Third-party GitHub Actions are pinned to commit SHAs; workflows run with
least-privilege permissions. Releases publish to PyPI via OIDC Trusted
Publishing (no long-lived tokens).