fix(server): validate permission and question ids (#26456)

This commit is contained in:
Kit Langton
2026-05-09 00:20:28 -04:00
committed by GitHub
parent 8cbc43fbb0
commit dc978cb889
3 changed files with 32 additions and 3 deletions

View File

@@ -6,7 +6,7 @@ import { Newtype } from "@/util/schema"
export class PermissionID extends Newtype<PermissionID>()(
"PermissionID",
Schema.String.annotate({ [ZodOverride]: Identifier.schema("permission") }),
Schema.String.check(Schema.isStartsWith("per")).annotate({ [ZodOverride]: Identifier.schema("permission") }),
) {
static ascending(id?: string): PermissionID {
return this.make(Identifier.ascending("permission", id))

View File

@@ -6,7 +6,7 @@ import { Newtype } from "@/util/schema"
export class QuestionID extends Newtype<QuestionID>()(
"QuestionID",
Schema.String.annotate({ [ZodOverride]: Identifier.schema("question") }),
Schema.String.check(Schema.isStartsWith("que")).annotate({ [ZodOverride]: Identifier.schema("question") }),
) {
static ascending(id?: string): QuestionID {
return this.make(Identifier.ascending("question", id))

View File

@@ -1,7 +1,7 @@
import { NodeHttpServer, NodeServices } from "@effect/platform-node"
import { Flag } from "@opencode-ai/core/flag/flag"
import { describe, expect } from "bun:test"
import { Config, Effect, FileSystem, Layer, Path } from "effect"
import { Config, Context, Effect, FileSystem, Layer, Path } from "effect"
import { HttpClient, HttpClientRequest, HttpRouter, HttpServer } from "effect/unstable/http"
import * as Socket from "effect/unstable/socket/Socket"
import { WorkspaceID } from "../../src/control-plane/schema"
@@ -53,6 +53,7 @@ const httpApiServerLayer = servedRoutes.pipe(
)
const it = testEffect(Layer.mergeAll(testStateLayer, httpApiServerLayer))
const handlerContext = Context.empty() as Context.Context<unknown>
const directoryHeader = (dir: string) => HttpClientRequest.setHeader("x-opencode-directory", dir)
@@ -121,6 +122,34 @@ describe("instance HttpApi", () => {
}),
)
it.live("rejects malformed permission and question request ids", () =>
Effect.gen(function* () {
const dir = yield* tmpdirScoped({ git: true })
const request = (path: string, init?: RequestInit) =>
Effect.promise(() =>
ExperimentalHttpApiServer.webHandler().handler(
new Request(`http://localhost${path}`, {
...init,
headers: { "x-opencode-directory": dir, "content-type": "application/json", ...init?.headers },
}),
handlerContext,
),
)
const [permission, questionReply, questionReject] = yield* Effect.all(
[
request("/permission/invalid-permission-id/reply", { method: "POST", body: JSON.stringify({ reply: "once" }) }),
request("/question/invalid-question-id/reply", { method: "POST", body: JSON.stringify({ answers: [["Yes"]] }) }),
request("/question/invalid-question-id/reject", { method: "POST" }),
],
{ concurrency: "unbounded" },
)
expect(permission.status).toBe(400)
expect(questionReply.status).toBe(400)
expect(questionReject.status).toBe(400)
}),
)
it.live("serves path and VCS read endpoints", () =>
Effect.gen(function* () {
const dir = yield* tmpdirScoped({ git: true })