diff --git a/packages/web/vite.config.ts b/packages/web/vite.config.ts index 1da63c8..2fed9ed 100644 --- a/packages/web/vite.config.ts +++ b/packages/web/vite.config.ts @@ -18,6 +18,19 @@ const { version } = JSON.parse( // so req.url is already the upstream path (e.g. /v3/workspaces/list). function honchoApiProxy(): Plugin { const HEADER = "x-honcho-upstream"; + // Mirror nginx's allowlist (spec §D): unset/empty => open; otherwise only + // matching upstream hosts forward. Glob `*` -> any non-slash run, like nginx. + const raw = process.env.OPENCONCHO_UPSTREAM_ALLOWLIST?.trim(); + const allowlist: RegExp[] | null = raw + ? raw + .split(",") + .map((h) => h.trim()) + .filter(Boolean) + .map((host) => { + const esc = host.replace(/[.]/g, "\\.").replace(/[*]/g, "[^/]*"); + return new RegExp(`^https?://${esc}(:[0-9]+)?(/.*)?$`); + }) + : null; return { name: "honcho-api-proxy", configureServer(server) { @@ -29,6 +42,12 @@ function honchoApiProxy(): Plugin { res.end(); return; } + if (allowlist && !allowlist.some((re) => re.test(upstream))) { + res.statusCode = 403; + res.setHeader("X-Honcho-Proxy-Reject", "allowlist"); + res.end(); + return; + } const target = upstream.replace(/\/+$/, "") + (req.url ?? ""); const chunks: Buffer[] = []; for await (const c of req) chunks.push(c as Buffer);