authorgravatar for 71498452+StiensWout@users.noreply.github.comWout Stiens <71498452+StiensWout@users.noreply.github.com> 2026-06-23 01:21:06+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-22 16:21:06-07:00
log92e54fb96c5e24c45fc0fa7066a3a4d38294dfc7
tree5581d1ebf6e353907c44806656f82500ebee7888
parent8cc9a6fa6b0e3b6d6528793e942982d7de040833
signature Signed by PGP key B5690EEEBB952194

[codex] fix: guard DPoP fallback URL construction (#3503)

Co-authored-by: Codex <codex@openai.com> Co-authored-by: Julius Marminge <jmarminge@gmail.com>

1 files changed, 9 insertions(+), 18 deletions(-)

apps/server/src/auth/dpop.ts+9-18
......@@ -3,7 +3,8 @@ import * as Crypto from "effect/Crypto";
33import * as DateTime from "effect/DateTime";
44import * as Effect from "effect/Effect";
55import * as Encoding from "effect/Encoding";
6import type * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
6import * as Option from "effect/Option";
7import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
78
89import {
910 ServerAuthDpopReplayKeyCalculationError,
......@@ -13,22 +14,6 @@ import {
1314} from "./EnvironmentAuth.ts";
1415import * as ServerSecretStore from "./ServerSecretStore.ts";
1516
16function firstHeaderValue(value: string | undefined): string | undefined {
17 const first = value?.split(",")[0]?.trim();
18 return first && first.length > 0 ? first : undefined;
19}
20
21export function requestAbsoluteUrl(request: HttpServerRequest.HttpServerRequest): string {
22 try {
23 return new URL(request.originalUrl).href;
24 } catch {
25 const host = firstHeaderValue(request.headers.host) ?? "127.0.0.1";
26 const forwardedProto = firstHeaderValue(request.headers["x-forwarded-proto"]);
27 const proto = forwardedProto === "https" || forwardedProto === "http" ? forwardedProto : "http";
28 return new URL(request.originalUrl, `${proto}://${host}`).href;
29 }
30}
31
3217export const mapDpopReplayStoreError = (
3318 error: ServerSecretStore.SecretStoreError,
3419): ServerAuthInvalidCredentialError | ServerAuthInternalError =>
......@@ -48,11 +33,17 @@ export const verifyRequestDpopProof = (input: {
4833}) =>
4934 Effect.gen(function* () {
5035 const proof = input.request.headers.dpop;
36 const url = HttpServerRequest.toURL(input.request);
37 if (Option.isNone(url)) {
38 return yield* new ServerAuthInvalidCredentialError({
39 diagnostic: "Invalid DPoP request URL.",
40 });
41 }
5142 const now = yield* DateTime.now;
5243 const result = verifyDpopProof({
5344 proof,
5445 method: input.request.method,
55 url: requestAbsoluteUrl(input.request),
46 url: url.value.href,
5647 nowEpochSeconds: Math.floor(now.epochMilliseconds / 1_000),
5748 ...(input.expectedThumbprint ? { expectedThumbprint: input.expectedThumbprint } : {}),
5849 ...(input.expectedAccessToken ? { expectedAccessToken: input.expectedAccessToken } : {}),