diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/authorization.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/authorization.ts index c21de79a24..db6554590f 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/authorization.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/authorization.ts @@ -63,11 +63,11 @@ function decodeCredential(input: string) { Effect.match({ onFailure: emptyCredential, onSuccess: (header) => { - const parts = header.split(":") - if (parts.length !== 2) return emptyCredential() + const separator = header.indexOf(":") + if (separator === -1) return emptyCredential() return { - username: parts[0], - password: Redacted.make(parts[1]), + username: header.slice(0, separator), + password: Redacted.make(header.slice(separator + 1)), } }, }), diff --git a/packages/opencode/test/server/httpapi-ui.test.ts b/packages/opencode/test/server/httpapi-ui.test.ts index 1ffa0d2005..72e227d115 100644 --- a/packages/opencode/test/server/httpapi-ui.test.ts +++ b/packages/opencode/test/server/httpapi-ui.test.ts @@ -406,6 +406,20 @@ describe("HttpApi UI fallback", () => { }), ) + it.live("accepts basic auth passwords containing colons for the web UI", () => + Effect.gen(function* () { + const response = yield* uiApp({ + password: "sec:ret", + username: "opencode", + disableEmbeddedWebUi: true, + }).request("/", { + headers: { authorization: `Basic ${btoa("opencode:sec:ret")}` }, + }) + + expect(response.status).toBe(200) + }), + ) + // Regression for #25698 (Ope): the browser fetches the PWA manifest and // its icons via flows that don't carry app-managed credentials (the // `` request is not under page-auth control), so the