Skip to content

fix(daemon): add token-based authentication for local connections#397

Open
sorlen008 wants to merge 3 commits intojackwener:mainfrom
sorlen008:fix/daemon-token-auth
Open

fix(daemon): add token-based authentication for local connections#397
sorlen008 wants to merge 3 commits intojackwener:mainfrom
sorlen008:fix/daemon-token-auth

Conversation

@sorlen008
Copy link
Contributor

What this does

Adds a random shared secret (~/.opencli/token) that the daemon requires on all HTTP and WebSocket connections. This closes the local privilege escalation gap where any process on the machine could connect to the daemon by just knowing the port and the static X-OpenCLI: 1 header.

How it works

  • First time the daemon starts, it generates a 32-byte random token and saves it to ~/.opencli/token (mode 0600, owner-only)
  • Every HTTP request must include x-opencli-token header with the correct token, or it gets a 401
  • WebSocket connections pass the token via query param (?token=...) or Sec-WebSocket-Protocol header
  • The CLI reads the token file automatically via readToken() — no config needed from the user
  • The Chrome extension can read the file too (via native messaging or the daemon can expose it on a one-time auth endpoint later)

Files changed

  • src/token.ts (new) — token generation, reading, and constants
  • src/daemon.ts — token validation on HTTP + WebSocket verifyClient
  • src/browser/daemon-client.ts — sends token on all HTTP requests
  • src/browser/discover.ts — sends token on status checks

What stays the same

  • CSRF protections (Origin check, X-OpenCLI header, no CORS) — still in place as defense-in-depth
  • Localhost-only binding — unchanged
  • Body size limit — unchanged
  • All existing tests pass (3 pre-existing failures unrelated to this change)

Testing

  • npx tsc --noEmit — zero type errors
  • npm test — 257 passed, 4 failed (same failures on main, not introduced by this PR)

Closes #395

The existing CSRF protections (Origin check, X-OpenCLI header) block
browser-based attacks but don't protect against local process
impersonation. Any process on the machine that knows the port and
static header value can connect to the daemon and access all browser
sessions.

This adds a random shared secret (stored at ~/.opencli/token) that
the daemon requires on all HTTP and WebSocket connections:

- Token is generated on first daemon start (32 random bytes, hex-encoded)
- File is created with mode 0o600 (owner-only read/write)
- HTTP requests must include x-opencli-token header
- WebSocket connections pass the token via query parameter or
  Sec-WebSocket-Protocol header
- CLI and discover code updated to read and send the token

This closes the local privilege escalation gap identified in jackwener#395.

Closes jackwener#395
…ion, Windows ACLs

Addresses review feedback on the token authentication implementation:

- Use crypto.timingSafeEqual() instead of === for token comparison,
  preventing timing side-channel attacks on the shared secret
- Use O_EXCL flag for atomic token file creation, preventing race
  conditions when multiple daemon processes start simultaneously
- Add icacls enforcement on Windows where Unix mode 0o600 is ignored,
  restricting token file to current user only
- Validate token format with regex (exactly 64 hex chars) to detect
  corrupted files instead of accepting any 32+ char string
- Add rotateToken() for token invalidation if the secret leaks
- Add verifyToken() export for constant-time comparison
- Export authHeaders() from daemon-client and reuse in discover.ts
  to eliminate duplicate auth header construction
- Improve error messages when token directory/file can't be created
…eate

When the token file exists but contains invalid data (corrupted),
the O_EXCL flag on the new file creation fails with EEXIST since
the corrupt file is still on disk. Fix: unlink the corrupt file
before attempting to create a new one.

Found during UAT on Windows.
@sorlen008
Copy link
Contributor Author

UAT Results (Windows 11)

Test Result
Token file creation (64 hex chars) PASS
HTTP with valid token → 200 PASS
HTTP without token → 401 PASS
HTTP with wrong token → 401 PASS
HTTP without X-OpenCLI → 403 PASS
Corrupted token regeneration PASS (bug found and fixed — corrupt file blocked O_EXCL, now unlinks first)
Race condition (two daemons) PASS (O_EXCL ensures only one wins)

All 7 tests pass after the corruption fix. Windows ACLs verified via icacls.

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.

[Bug]: WebSocket verifyClient allows unauthenticated local connections via missing Origin header

1 participant