SprayberryLabs The Audit offer →

Sample deliverable

We audited our own shipping agent. It found a real bug. We shipped the fix the same day.

This is an actual Sprayberry Labs code audit — performed on hands, our open-source computer-use agent (it executes shell commands and drives the mouse, keyboard, and screen — a genuine security surface). It's exactly the deliverable you receive for the $1,500 Audit.

The headline finding was real: a command-injection in the agent's file-editor path that bypassed its own safety guardrail. We disclosed it, fixed it (filesystem calls instead of a shell), and merged the fix — CI-green — the same day. The audit didn't just describe a problem; it drove a fix into a shipping product.
And every finding is mechanically verified against your source. The cited code is re-extracted from the repository and confirmed present before it goes in the report — no hallucinated bugs. During this very audit an early hypothesis ("the safety guardrail is prompt-only") didn't hold against the real code — there's a code-level guardrail — so we discarded it instead of publishing it. That discipline is the difference between a report you can act on and one you have to second-guess.

Subject: github.com/askalf/hands v0.4.1 · TypeScript / Node ≥20 · 5,429 LOC · 7 dependencies · Method: static source analysis (no code executed)

1. Executive Summary

hands lets an LLM drive a real machine — execute shell, control mouse/keyboard, capture the screen. That capability makes it, by definition, a high-trust security surface: the same design that makes it useful lets it do damage if a control fails. The team clearly knows this — there's genuine defense-in-depth (a code-level command guardrail, an append-only audit log, a true dry-run mode, a per-run budget cap, a 0700 check on the secrets directory). That's materially better than most agent code.

Overall risk was MODERATE, concentrated in one place: the safety control for shell execution is a regex denylist, and one code path reached a shell around it with a command-injection weakness. We fixed the injection immediately; the denylist→allowlist hardening is the recommended next investment.

2. Architecture

A dual-mode agent with a small, legible core. SDK mode runs the computer-use loop directly and exposes five tools (computer, bash, file-editor, plus two custom tools — read_page / find_files — that exist specifically to collapse many risky bash turns into one). CLI mode delegates to the installed claude binary. A platform layer isolates OS-native input behind a clean cross-platform seam. The MCP server is stdio-only — no listening port, no network attack surface (the right call). And the safety/observability layer is real: a per-run budget cap enforced each turn, an append-only audit log of every tool call, and a dry-run mode that stubs every side effect while keeping the loop coherent.

3. Security Posture

The realistic adversary isn't a malicious operator — it's the model making a mistake, or prompt injection (hands reads web pages and screens, so attacker-controlled text enters the context and can try to steer tool calls). The control that matters is the gate between "model says" and "machine does."

That gate is a regex denylist (guardrails.ts): it hard-blocks catastrophic literals (root-filesystem deletion, disk format, forced registry deletion) and warns on others. Sound as one layer — but a denylist is the wrong shape for an arbitrary-shell executor: it's bypassable by obfuscation, and it only hard-blocks root-scope destruction, not deletion scoped to the user's home or project. More importantly, one path reached a shell without passing through it at all — see P1.

4. Findings

P1✓ Fixed — same dayCommand injection in the file-editor path (bypassed the guardrail)

The file-editor's view operation built a shell command by interpolating a model-supplied path and running it through the shell — without the command guardrail. A path containing shell metacharacters escaped the intended command. Model-reachable (no malicious user needed); prompt-injected page/screen content is a plausible trigger.

Remediation (shipped, PR #58, CI-green): reimplemented the editor (view / create / str_replace / insert) directly on the filesystem — no shell, nothing to inject into — and completed the edit operations, which had previously been silent no-ops.

P2 · roadmapCommand guardrail is a denylist

Bypassable by obfuscation; only hard-blocks root-scope destruction (not home/project); several dangerous patterns warn-but-allow, which in an autonomous loop means they still run. Recommendation: invert toward an allowlist (or confirm-on-anything-not-allowlisted), treat home/project-scope destructive operations as confirm-required, and make that one gate the single chokepoint for every shell-reaching path.

✓ FixedP3 — incomplete editor tool + unused dependency

The editor's edit operations returned a "handled via bash" stub and did nothing (the model believed it had edited a file). And express was a declared dependency with no import anywhere in the source. Both addressed in PR #58 — the editor is now complete, and the redundant direct dependency is removed.

5. Dependencies & Supply-Chain

Lean and lockfile-pinned, on current majors. The set is defensible for the feature surface (with the now-removed express). cheerio parses untrusted fetched HTML — it correctly feeds the model as text, not as DOM. Recommended standing control: an npm audit gate in CI.

6. What's Working — Keep It

7. Prioritized Remediation Roadmap


Verification: every finding was confirmed by re-reading the cited source; a hypothesis that didn't hold was discarded, not published. Exploit specifics are generalized here (the issue is fixed; this is a public sample) — a client engagement includes exact reproductions. Static analysis only; runtime not exercised. Every engagement includes a 60-minute readout call to walk through findings and the roadmap.

Want another? See our audit that found & fixed a ReDoS — and rejected two findings that didn't hold up →

Want this for the code you're already running?

$1,500 · ~1 week · a written, verified, impact-ranked audit — findings you can act on, not second-guess.

See what the Audit includes →