core: add password authentication and improve server security

- Add OPENCODE_PASSWORD flag for basic auth protection
- Show security warnings when password is not set
- Remove deprecated spawn command
- Improve error handling with HTTPException responses
This commit is contained in:
Dax Raad
2026-01-12 15:23:12 -05:00
parent b4f33485a7
commit 1954c1255e
7 changed files with 23 additions and 55 deletions

View File

@@ -339,13 +339,15 @@ export const RunCommand = cmd({
}
await bootstrap(process.cwd(), async () => {
const server = Server.listen({ port: args.port ?? 0, hostname: "127.0.0.1" })
const sdk = createOpencodeClient({ baseUrl: `http://${server.hostname}:${server.port}` })
const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
const request = new Request(input, init)
return Server.App().fetch(request)
}) as typeof globalThis.fetch
const sdk = createOpencodeClient({ baseUrl: "http://opencode.internal", fetch: fetchFn })
if (args.command) {
const exists = await Command.get(args.command)
if (!exists) {
server.stop()
UI.error(`Command "${args.command}" not found`)
process.exit(1)
}
@@ -370,7 +372,6 @@ export const RunCommand = cmd({
})()
if (!sessionID) {
server.stop()
UI.error("Session not found")
process.exit(1)
}
@@ -389,7 +390,6 @@ export const RunCommand = cmd({
}
await execute(sdk, sessionID)
server.stop()
})
},
})

View File

@@ -1,12 +1,16 @@
import { Server } from "../../server/server"
import { cmd } from "./cmd"
import { withNetworkOptions, resolveNetworkOptions } from "../network"
import { Flag } from "../../flag/flag"
export const ServeCommand = cmd({
command: "serve",
builder: (yargs) => withNetworkOptions(yargs),
describe: "starts a headless opencode server",
handler: async (args) => {
if (!Flag.OPENCODE_PASSWORD) {
console.log("Warning: OPENCODE_PASSWORD is not set; server is unsecured.")
}
const opts = await resolveNetworkOptions(args)
const server = Server.listen(opts)
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)

View File

@@ -1,48 +0,0 @@
import { cmd } from "@/cli/cmd/cmd"
import { Instance } from "@/project/instance"
import path from "path"
import { Server } from "@/server/server"
import { upgrade } from "@/cli/upgrade"
import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
export const TuiSpawnCommand = cmd({
command: "spawn [project]",
builder: (yargs) =>
withNetworkOptions(yargs).positional("project", {
type: "string",
describe: "path to start opencode in",
}),
handler: async (args) => {
upgrade()
const opts = await resolveNetworkOptions(args)
const server = Server.listen(opts)
const bin = process.execPath
const cmd = []
let cwd = process.cwd()
if (bin.endsWith("bun")) {
cmd.push(
process.execPath,
"run",
"--conditions",
"browser",
new URL("../../../index.ts", import.meta.url).pathname,
)
cwd = new URL("../../../../", import.meta.url).pathname
} else cmd.push(process.execPath)
cmd.push("attach", server.url.toString(), "--dir", args.project ? path.resolve(args.project) : process.cwd())
const proc = Bun.spawn({
cmd,
cwd,
stdout: "inherit",
stderr: "inherit",
stdin: "inherit",
env: {
...process.env,
BUN_OPTIONS: "",
},
})
await proc.exited
await Instance.disposeAll()
await server.stop(true)
},
})

View File

@@ -2,6 +2,7 @@ import { Server } from "../../server/server"
import { UI } from "../ui"
import { cmd } from "./cmd"
import { withNetworkOptions, resolveNetworkOptions } from "../network"
import { Flag } from "../../flag/flag"
import open from "open"
import { networkInterfaces } from "os"
@@ -32,6 +33,9 @@ export const WebCommand = cmd({
builder: (yargs) => withNetworkOptions(yargs),
describe: "start opencode server and open web interface",
handler: async (args) => {
if (!Flag.OPENCODE_PASSWORD) {
UI.println(UI.Style.TEXT_WARNING_BOLD + "! " + "OPENCODE_PASSWORD is not set; server is unsecured.")
}
const opts = await resolveNetworkOptions(args)
const server = Server.listen(opts)
UI.empty()