fix(security): keep EdgeOne cookies and the access code on this site

Two issues from before the provider refactor, found by its review.

The redirect guard dropped Authorization, cookies and the providers' key
headers when a redirect left the origin, but kept x-access-code, this site's
own access code; it is dropped too now.

edgeOneEndpoint took the request's Origin header as the site's address.
Behind a proxy the request URL is the internal one and Origin is the site's,
so Origin still wins, but only when its host matches Host or
X-Forwarded-Host; a request carrying another site's Origin now gets the
request's own origin, so the EdgeOne cookies and the access code never travel
to that site.
This commit is contained in:
dayuan.jiang
2026-10-12 21:17:51 +09:00
parent 75ffc32a49
commit 55a32af76a
4 changed files with 62 additions and 7 deletions
+25
View File
@@ -126,3 +126,28 @@ describe("EdgeOne endpoints", () => {
)
})
})
describe("the EdgeOne endpoint's origin", () => {
it("takes the browser's Origin for the host the request was addressed to", async () => {
vi.stubEnv("AI_PROVIDER", "edgeone")
vi.stubEnv("AI_MODEL", "@tx/deepseek-ai/deepseek-v3-0324")
// Behind a proxy the request URL is internal; Origin names the site
await send({ origin: "https://draw.example", host: "draw.example" })
expect(calls[0]?.url).toBe(
"https://draw.example/api/edgeai/chat/completions",
)
})
it("ignores another site's Origin, so the cookies stay on this site", async () => {
vi.stubEnv("AI_PROVIDER", "edgeone")
vi.stubEnv("AI_MODEL", "@tx/deepseek-ai/deepseek-v3-0324")
await send({
origin: "https://attacker.example",
host: "localhost",
cookie: "eo_token=t",
})
expect(calls[0]?.url).toBe(
"http://localhost/api/edgeai/chat/completions",
)
})
})
+7 -1
View File
@@ -179,6 +179,7 @@ describe("redirectGuardedFetch with the quota on", () => {
"x-api-key": "anthropic-key",
"x-goog-api-key": "google-key",
"api-key": "azure-key",
"x-access-code": "site-code",
"Content-Type": "application/json",
},
})
@@ -194,7 +195,12 @@ describe("redirectGuardedFetch with the quota on", () => {
expect(sent(0).get("authorization")).toBe("Bearer user-key")
expect(sent(1).get("authorization")).toBeNull()
expect(sent(1).get("cookie")).toBeNull()
for (const name of ["x-api-key", "x-goog-api-key", "api-key"]) {
for (const name of [
"x-api-key",
"x-goog-api-key",
"api-key",
"x-access-code",
]) {
expect(sent(1).get(name)).toBeNull()
}
expect(sent(1).get("content-type")).toBe("application/json")