fix(sse): exact-domain cookie match and origin-equality URL assertions

Clears CodeQL js/incomplete-url-substring-sanitization alerts #860-#865:

- volcengineConsoleAutoLogin: cookie domain filter now uses an exact/
  dot-suffix helper (isVolcengineCookieDomain) instead of substring
  includes(), rejecting look-alike hosts like volcengine.com.evil.test
- security-s1-s2-s4 tests: agent-card/agent.json URL assertions compare
  parsed origin equality instead of startsWith prefix
This commit is contained in:
Markus Hartung
2026-08-24 23:35:45 -03:00
parent 943b9aaa84
commit 7b13a70a71
2 changed files with 19 additions and 8 deletions

View File

@@ -96,6 +96,13 @@ const ARK_CONSOLE_URL =
/** Cookie names required for a valid console session (mirrors tokenExtractionConfig) */
const REQUIRED_COOKIES = ["digest", "AccountID", "csrfToken", "userInfo"] as const;
/** Exact-domain match for session cookies — substring checks would also accept
* look-alike hosts (e.g. `volcengine.com.evil.test`). Playwright may report the
* domain with or without a leading dot. */
function isVolcengineCookieDomain(domain: string): boolean {
return domain === "volcengine.com" || domain.endsWith(".volcengine.com");
}
const DEFAULT_SESSION_TIMEOUT = 300_000;
const SUBMIT_COOKIE_TIMEOUT = 90_000;
const CAPTURE_POLL_INTERVAL = 1_000;
@@ -618,7 +625,7 @@ export class VolcengineConsoleAutoLoginService {
for (const cookie of cookies as Array<{ name: string; domain: string; value: string }>) {
if (
REQUIRED_COOKIES.includes(cookie.name as (typeof REQUIRED_COOKIES)[number]) &&
cookie.domain.includes("volcengine.com")
isVolcengineCookieDomain(cookie.domain)
) {
credentials[cookie.name] = cookie.value;
}
@@ -763,7 +770,7 @@ export class VolcengineConsoleAutoLoginService {
domain: string;
}>;
const present = REQUIRED_COOKIES.filter((name) =>
cookies.some((c) => c.name === name && c.domain.includes("volcengine.com"))
cookies.some((c) => c.name === name && isVolcengineCookieDomain(c.domain))
);
parts.push(
`cookies=[${present.join(",") || "none of digest/AccountID/csrfToken/userInfo"}]`

View File

@@ -41,11 +41,13 @@ describe("S2 — agent-card topology sanitisation", () => {
assert.equal(res.status, 200);
const card = (await res.json()) as { url?: string; supportedInterfaces?: { url?: string }[] };
assert.ok(card.url, "card must have a url");
assert.ok(card.url.startsWith("https://gateway.example.com"), `expected gateway.example.com, got ${card.url}`);
assert.equal(new URL(card.url).origin, "https://gateway.example.com", `expected gateway.example.com origin, got ${card.url}`);
if (card.supportedInterfaces && card.supportedInterfaces.length > 0) {
assert.ok(
card.supportedInterfaces[0].url?.startsWith("https://gateway.example.com"),
`interface URL should use dynamic origin, got ${card.supportedInterfaces[0].url}`
const ifaceUrl = card.supportedInterfaces[0].url;
assert.equal(
ifaceUrl ? new URL(ifaceUrl).origin : undefined,
"https://gateway.example.com",
`interface URL should use dynamic origin, got ${ifaceUrl}`
);
}
});
@@ -62,7 +64,8 @@ describe("S2 — agent-card topology sanitisation", () => {
const res = await mod.GET(request);
assert.equal(res.status, 200);
const card = (await res.json()) as { url?: string };
assert.ok(card.url?.startsWith("https://custom.example.com"), `expected custom.example.com, got ${card.url}`);
assert.ok(card.url, "card must have a url");
assert.equal(new URL(card.url).origin, "https://custom.example.com", `expected custom.example.com origin, got ${card.url}`);
});
it("agent.json derives URL from request.nextUrl.origin when OMNIROUTE_BASE_URL is unset", async () => {
@@ -76,7 +79,8 @@ describe("S2 — agent-card topology sanitisation", () => {
const res = await mod.GET(request);
assert.equal(res.status, 200);
const card = (await res.json()) as { url?: string };
assert.ok(card.url?.startsWith("https://gateway.example.com"), `expected gateway.example.com, got ${card.url}`);
assert.ok(card.url, "card must have a url");
assert.equal(new URL(card.url).origin, "https://gateway.example.com", `expected gateway.example.com origin, got ${card.url}`);
});
});