Skip to content

Security hardening: bind to localhost by default and validate WebSocket Origin/Host #34

Description

@xytras1

Hi — really like the project, it makes managing skills/agents much nicer.

While reading through the code I noticed a couple of defensive-hardening opportunities for the default deployment posture. Both are small, backwards-compatible changes that meaningfully reduce the attack surface for the common "running locally on a laptop" use case.

1. Default network bind

By default the server binds to 0.0.0.0 in three places:

  • bin/start.mjs:19process.env.HOST = process.env.HOST || '0.0.0.0'
  • Dockerfile:45ENV HOST=0.0.0.0
  • docker-compose.yml:9ports: - "3030:3030" (shorthand for 0.0.0.0:3030:3030)

This exposes the server (and the Claude Code sessions it spawns via node-pty) to anyone on the same LAN/Wi-Fi out of the box. For a tool that can run a PTY and an agent with shell access, that feels stronger-than-needed defaults.

Suggestion: default to 127.0.0.1 and let users opt in to wider exposure via the existing HOST env var. For docker-compose, change the port mapping to "127.0.0.1:3030:3030". No behavior change for the single-user, single-machine case.

2. WebSocket Origin / Host validation

Three WebSocket handlers accept the upgrade with no Origin/Host check:

  • server/api/cli/ws.ts (PTY spawner)
  • server/api/chat-ws/ws.ts
  • server/api/v2/chat/ws.ts

Without an Origin check, any page the user visits in the same browser can open ws://localhost:3030/api/cli/ws and drive the server — WebSockets are exempt from same-origin policy, so binding to localhost alone doesn't prevent this. A Host check additionally guards against DNS rebinding.

Suggestion: in the open hook of each WS handler, reject connections whose Origin isn't in an allowlist (default: http://localhost:<PORT>, http://127.0.0.1:<PORT>, http://[::1]:<PORT>). Same idea for Host. Allow operators to extend the lists via env vars for reverse-proxy setups.

Happy to help

I have working patches for both that I've tested end-to-end against current main. Happy to send them as a PR if you'd like — just say the word.

One more thing

I noticed the repo doesn't have private vulnerability reporting enabled. If you'd consider turning it on (Settings → Security → Private vulnerability reporting), I have a few more findings I'd like to share responsibly rather than through a public issue.

Thanks for the project!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions