Authenticate plugin-owned OpenCode servers; support secured external servers (#27)#35
Merged
TheRealDinghyDog merged 1 commit intoJul 10, 2026
Conversation
…servers (#27) Plugin-owned `opencode serve` processes ran unsecured: any local process that could reach the loopback port had full API access (file content, sessions, lifecycle). Conversely, a user who exported OPENCODE_SERVER_PASSWORD got a server the plugin's header-less client could no longer talk to. Every owned server now gets a per-server random password, passed only via the child environment (never argv) with the username pinned, and recorded in server.json written owner-only (0600). The client sends HTTP Basic auth — matching OpenCode's own convention, username defaulting to "opencode" — on all four paths: normal requests, fresh-connection requests, health checks, and the SSE event stream. On 1.17.15 every route including /global/health and /event requires the header, verified live. External servers configured via OPENCODE_COMPANION_SERVER_URL read the same OPENCODE_SERVER_PASSWORD/OPENCODE_SERVER_USERNAME variables the user already exports for a secured server; an unauthenticated 401 now produces a precise diagnostic instead of a generic health failure. Cancel processes re-derive credentials from the persisted server session or the ambient variables, and teardown authenticates its dispose call (dispose only cleans instance state — the PID termination remains the actual shutdown). The fake fixture now enforces Basic auth on every route whenever a password is present in its environment, so the whole existing suite exercises authenticated paths; new tests cover unauthenticated HTTP/SSE rejection, owner-only server.json, credential redaction in status output, and password-protected external servers (missing, wrong, and correct credentials). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TheRealDinghyDog
merged commit Jul 10, 2026
2940cde
into
fix/issue-26-write-permissions
1 check passed
TheRealDinghyDog
added a commit
that referenced
this pull request
Jul 10, 2026
PR #35 was accidentally merged into its stacked base branch fix/issue-26-write-permissions instead of opencode-conversion; this brings the already-reviewed content onto the integration branch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #27 (review finding H-02).
Note
Stacked on #34 (base branch
fix/issue-26-write-permissions) because both touch the same files; GitHub will retarget this PR toopencode-conversionautomatically when #34 merges.Problem
opencode servewas spawned withoutOPENCODE_SERVER_PASSWORD, so any local process that could reach the loopback port had full API access (file content, sessions, lifecycle) — the server itself warns about this. And the client sent noAuthorizationheader anywhere, so a user who had secured their server got a plugin that could no longer connect.Fix
crypto.randomBytes(24), base64url), passed only via the child environment (never argv, which is visible in the process list), withOPENCODE_SERVER_USERNAMEpinned toopencodeso an ambient value cannot desynchronize server and client. The credential is recorded inserver.json, now written owner-only (0600).OpencodeServerClientsends HTTP Basic auth — matching OpenCode's own convention (username defaults toopencode) — on all four paths: normal requests, fresh-connection requests, health checks, and the SSE event stream. Verified live on 1.17.15 that every route, including/global/healthand/event, requires the header once a password is set.OPENCODE_COMPANION_SERVER_URL): credentials come from the sameOPENCODE_SERVER_PASSWORD/OPENCODE_SERVER_USERNAMEvariables the user already exports for a secured server. A 401 now yields a precise diagnostic (missing vs. rejected credentials) instead of a generic "not healthy".server.json; status output and logs never contain it (asserted in tests).Tests
server.jsonis 0600 and holds the generated credential;status --jsonoutput never contains the password; external-server matrix (no credentials → precise error, wrong password → precise error, correct password → connects, health check carries the header);buildBasicAuthHeaderunit tests.npm test: 85 passed, 0 failed, 0 skipped.server.jsonmode 600, password absent from logs, teardown actually stops the listener.🤖 Generated with Claude Code