Annotask is a development-only tool. It is not designed for production use and should never be deployed to public-facing servers.
Annotask assumes a trusted local environment: the developer's machine, running a local dev server (Vite or Webpack), accessed via localhost.
- Production builds: All Annotask transforms, injected globals, toggle buttons, API endpoints, and WebSocket servers are gated to development mode only. In Vite, this uses
apply: 'serve'. In Webpack, the plugin checkscompiler.options.mode. Runningvite buildor a production Webpack build produces clean output with no Annotask artifacts. - File writes: Annotask only writes to the
.annotask/directory under the project root (design spec, task state). It never modifies source files directly. - HTML injection: Placeholder rendering uses DOM APIs instead of
innerHTMLwith interpolated values. Markdown output (task descriptions, feedback, agent messages) is sanitized with DOMPurify before rendering viav-html.
- Authentication: API endpoints (
/__annotask/api/*) and the WebSocket server (/__annotask/ws) have no authentication. This matches Vite's own HMR WebSocket, which is also unauthenticated. Non-browser clients (CLI, scripts) that omit theOriginheader are trusted, since network-level binding to localhost is the primary access control. - Network exposure: The standalone server binds to
127.0.0.1by default. When using the Vite plugin, the server inherits Vite's host configuration. API and WebSocket endpoints perform origin validation — browser requests from non-localhost origins are rejected. - Input validation: API endpoints validate payload structure, size (max 4 MB request body; rendered-HTML sidecars capped at 200 KB), and field whitelists. POST and PATCH requests only accept known fields. Task status transitions are validated against a state machine. Screenshot filenames are validated with a strict regex. HTTP bodies and MCP tool args parse through shared zod schemas at the boundary.
- WebSocket frames: the WebSocket server enforces a per-frame size cap (
maxPayload). - Screenshot file names: uploads use
crypto.randomBytes(8)filenames (16 hex chars). Theannotask_get_screenshotMCP tool also routestask.screenshotthroughisSafeScreenshotbefore touching the filesystem, closing a path-traversal hole for maliciously constructed task records. server.jsonpermissions: written with mode0o600so other users on shared machines cannot read the live PID and port.
Any code running on the same machine — including the app being developed, its dependencies, and any other page served from localhost — shares the trust boundary with Annotask. In particular, same-origin app code can reach the Annotask API, including the agent spawn endpoint, since it runs on the same dev server. The mitigations are layered, not absolute:
- Spawn routes are same-port-origin-gated.
POST /api/agent/spawnandDELETE /api/agent/spawn/:runIdrequire the requestOriginto match the server's own port (origin_port_mismatchotherwise), so a page on a different localhost port cannot spawn CLIs that have credential access. A page on the same origin still can — that is inherent to running an unauthenticated dev tool inside the app's own server. ANNOTASK_MAX_PERMISSIONis the server-side floor. Spawned CLIs run with a permission mode (plan/default/bypass); the server refuses any spawn whose argv requests more than the configured ceiling, regardless of what the client asked for. The default is no ceiling (bypass) — and the local CLIs apply tasks headless, with claude/opencode defaulting to--dangerously-skip-permissions. So out of the box, any code that can reach the spawn endpoint (i.e. same-origin app code) can drive an agent that writes files and runs shell commands at the project root. In any shared, CI, or otherwise-not-fully-trusted environment, setANNOTASK_MAX_PERMISSION=plan(read-only) ordefaultso a compromised page cannot requestbypass.- All
/__annotask/*requests are Host-gated: the middleware validates theHostheader against local hostnames (andANNOTASK_ALLOWED_HOSTS) before any other handling, narrowing DNS-rebinding-style access. - Spawned binaries are allow-listed (
claude,codex,opencode,copilot); free-form binary names and absolute paths are rejected, processes start withshell: false, cwd is pinned to the project root, andPATH/HOMEoverrides are dropped.
None of this makes the dev server safe to expose beyond the developer's machine. If untrusted code runs in the app you are annotating, assume it can read Annotask state and drive agents up to the configured permission ceiling.
- Do not expose the dev server to untrusted networks.
- Do not include
annotaskin production dependencies — it should be adevDependencyonly. - If you need to share a running dev environment, use a VPN or SSH tunnel rather than binding to
0.0.0.0.