fix(browse): reject NaN/Infinity expires and case-fold internal domains

filterPersistableCookies trusted expires as a finite number and matched
internal-network domains case-sensitively. A cookie with expires=NaN or
Infinity slipped past the `=== -1` session check (NaN <= nowSec is false),
and an uppercase `LOCALHOST` / `Foo.INTERNAL` bypassed the internal-domain
block. Add a `Number.isFinite` guard and a shared `normalizeCookieDomain`
that lower-cases before the internal-domain comparison. Allowlist matching
already lower-cases internally, so behavior there is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
현성 2026-07-07 20:59:22 +09:00
parent 1404106293
commit c0d85f7c0e
2 changed files with 18 additions and 2 deletions

View File

@ -133,6 +133,11 @@ function lockPath(config: BrowseConfig): string {
return path.join(autoDir(config), `${computeProfileId(config)}.lock`);
}
function normalizeCookieDomain(domain: string): string {
const d = domain.startsWith('.') ? domain.slice(1) : domain;
return d.toLowerCase();
}
/**
* Keep only cookies that are safe and sensible to persist:
* - persistent (expires !== -1) and not already expired,
@ -150,9 +155,9 @@ export function filterPersistableCookies(
if (!c || typeof c.name !== 'string' || typeof c.value !== 'string') return false;
if (typeof c.domain !== 'string' || !c.domain) return false;
// Session cookie — Playwright reports expires === -1. Never persist.
if (typeof c.expires !== 'number' || c.expires === -1) return false;
if (typeof c.expires !== 'number' || c.expires === -1 || !Number.isFinite(c.expires)) return false;
if (c.expires <= nowSec) return false; // already expired
const d = c.domain.startsWith('.') ? c.domain.slice(1) : c.domain;
const d = normalizeCookieDomain(c.domain);
if (d === 'localhost' || d.endsWith('.internal') || d === '169.254.169.254') return false;
if (allowlist && !hostMatchesAllowlist(c.domain, allowlist)) return false;
return true;

View File

@ -100,13 +100,24 @@ describe('cookie filter', () => {
test('rejects internal-network domains', () => {
const kept = filterPersistableCookies([
cookie({ name: 'lh', domain: 'localhost' }),
cookie({ name: 'lh2', domain: 'LOCALHOST' }),
cookie({ name: 'meta', domain: '169.254.169.254' }),
cookie({ name: 'int', domain: 'foo.internal' }),
cookie({ name: 'int2', domain: 'Foo.INTERNAL' }),
cookie({ name: 'ok', domain: 'example.com' }),
], null);
expect(kept.map(c => c.name)).toEqual(['ok']);
});
test('rejects malformed expires values', () => {
const kept = filterPersistableCookies([
cookie({ name: 'nan', expires: Number.NaN }),
cookie({ name: 'inf', expires: Number.POSITIVE_INFINITY }),
cookie({ name: 'ok' }),
], null);
expect(kept.map(c => c.name)).toEqual(['ok']);
});
test('applies host allowlist incl. leading-wildcard + apex', () => {
const cookies = [
cookie({ name: 'a', domain: 'example.com' }),