Skip to content

fix: refuse control characters in supervisor definitions - #17

Open
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1786151250-security-scan-hardening
Open

fix: refuse control characters in supervisor definitions#17
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1786151250-security-scan-hardening

Conversation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Summary

Two input-validation gaps found while auditing the codebase for hardcoded secrets, SQL injection, unvalidated input, CORS/debug exposure, missing auth, and vulnerable dependencies. Everything else came back clean (details below).

1. Supervisor definition injection (src/supervision/{systemd,launchd}.js). quoteSystemdArgument escaped \, ", and %, and escapeSystemdSpecifiers escaped only % — neither escaped newlines, and systemd units are line-oriented. A --home / PORTREEVE_HOME / PORTREEVE_SOCKET path containing a newline therefore terminated ExecStart= and appended attacker-chosen directives to the generated unit, e.g.

--home '/tmp/x"
ExecStartPre=/bin/sh -c "id'
  →  ExecStart="…" "serve" "--home" "/tmp/x\"
     ExecStartPre=/bin/sh -c "id"

Both renderers now SupervisorDefinitionSchema.parse(...) first, rejecting \u0000-\u001f and \u007f in any of the five definition paths. Same-user scope only (a caller who controls the flags could run the command directly), so this is hardening of the trusted-path boundary rather than a privilege escape — but the generated unit persists and runs at login, which the flag itself does not.

2. Shutdown envelope validated by cast (src/server/server.js). /v1/server/stop parsed client as z.record(z.string(), z.unknown()) and then cast it to the compatibility shape, so negotiateCompatibility read an unvalidated client.protocol and a malformed body produced a 500 internal instead of a validated 400. It now parses ClientCompatibilitySchema, matching every other mutating endpoint.

Audit findings (no change needed)

  • Secrets: none committed; lease tokens are randomBytes(32), stored only as SHA-256 and compared with timingSafeEqual; tokens never reach history payloads or the diagnostic log.
  • SQL injection: every statement in registry.js / migrations.js uses $name bindings. The one interpolated fragment (listStacks's WHERE ${clauses.join(' AND ')}) is built from fixed clause literals with bound values; PRAGMA user_version = ${...} takes an internal migration constant.
  • Input validation: all control-plane requests parse through zod, and every mutating service re-runs assertCompatible independently of the router.
  • CORS / debug endpoints / auth: the control plane is a mode-0600 Unix socket in a 0700 owner-verified directory — no TCP listener, no CORS surface, no unauthenticated network path. /v1/logs and /v1/history are reachable only through that socket.
  • Electron: sandbox + contextIsolation on, nodeIntegration off, permission handlers deny, window-open/navigation/webview blocked, custom app:// scheme is path-canonicalized with a strict CSP, IPC verifies senderFrame.url and schema-parses both directions, and the bundled CLI is sha256-verified against its manifest before spawn (shell: false throughout). No innerHTML/eval anywhere.
  • Dependencies: bun audit reports no vulnerabilities.

Testing

bun run check (typecheck, lint, format, tests). The 23 failing tests are preexisting on main in this environment (no lsof, docker, or ruby available); the two added tests pass.

Link to Devin session: https://app.devin.ai/sessions/5c7ecf1575ac4564be008f8170c5f3cf
Requested by: @TrentBrown

Validate native supervisor definition paths before rendering line-oriented
systemd units and property lists, and validate the shutdown client envelope
against the protocol schema instead of an unchecked cast.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@TrentBrown TrentBrown self-assigned this Aug 8, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant