test(ap1): demonstrate token storage tradeoff

This commit is contained in:
donghyeon-ka
2026-07-25 14:14:02 +09:00
parent 8961671a1c
commit 6f1ccdd978
4 changed files with 74 additions and 19 deletions
+40 -12
View File
@@ -36,8 +36,32 @@ try {
assert.equal(authorizationUrl?.searchParams.get("code_challenge_method"), "S256");
assert.ok(authorizationUrl?.searchParams.get("code_challenge"));
const accessToken = await page.evaluate(() => window.__pattern1.getAccessToken());
assert.ok(accessToken, "access token must exist in browser memory");
await page.evaluate(() => {
const originalFetch = window.fetch.bind(window);
window.__xssProbe = { authorization: null };
window.fetch = (input, init = {}) => {
const headers = new Headers(
init.headers ?? (input instanceof Request ? input.headers : undefined),
);
const authorization = headers.get("Authorization");
if (authorization) {
window.__xssProbe.authorization = authorization;
}
return originalFetch(input, init);
};
});
await page.locator("#call-api").click();
await page.waitForFunction(() => {
const text = document.querySelector("#result")?.textContent ?? "";
return text.includes('"httpStatus": 200');
});
const capturedAuthorization = await page.evaluate(
() => window.__xssProbe.authorization,
);
assert.match(capturedAuthorization, /^Bearer /u);
const accessToken = capturedAuthorization.slice("Bearer ".length);
const payload = JSON.parse(
Buffer.from(accessToken.split(".")[1], "base64url").toString("utf8"),
);
@@ -56,12 +80,10 @@ try {
false,
"access token must not be persisted in Web Storage",
);
await page.locator("#call-api").click();
await page.waitForFunction(() => {
const text = document.querySelector("#result")?.textContent ?? "";
return text.includes('"httpStatus": 200');
});
assert.ok(
capturedAuthorization,
"runtime XSS-style fetch hooking can still observe a memory-only bearer token",
);
if (process.env.WRONG_AUDIENCE_URL) {
const response = await fetch(process.env.WRONG_AUDIENCE_URL, {
@@ -88,13 +110,19 @@ try {
await page.reload();
await page.locator('[data-authenticated="false"]').waitFor();
assert.equal(
await page.evaluate(() => window.__pattern1.getAccessToken()),
null,
"reload must clear the memory-only token",
await page.evaluate(
(token) => JSON.stringify({
localStorage: Object.values(localStorage),
sessionStorage: Object.values(sessionStorage),
}).includes(token),
accessToken,
),
false,
"reload must clear the memory-only token without persisting it",
);
console.log(
"pattern1 browser verified: code+PKCE S256, audience/issuer negatives 401, Web Storage token 0, reload clears token",
"pattern1 browser verified: PKCE, audience/issuer negatives, persistent token 0, runtime fetch hook observes bearer, reload clears token",
);
} finally {
await browser.close();