mirror of https://github.com/garrytan/gstack.git
fix(browse): allow about:blank so a restarted daemon can initialise
The daemon opens its own first tab on about:blank, so blocking it in validateNavigationUrl meant a restarted daemon could never recreate the blank tab it starts from — and `browse newtab about:blank`, which `make-pdf setup` runs as its Chromium smoke test, failed and surfaced as "Chromium failed to launch" against a healthy browser. Allow about:blank ONLY, never the about: scheme: about:blank has no origin, loads nothing and runs nothing, while about:config and friends are real surfaces. Exact href match (lower-cased, since the URL parser normalises the protocol but not the opaque part), so about:blankfoo stays blocked. Contributed by @jwilk-hrep (PR #2537). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
29a717211a
commit
ac05284481
|
|
@ -269,9 +269,24 @@ export async function validateNavigationUrl(url: string): Promise<string> {
|
|||
return pathToFileURL(fsPath).href + parsed.search + parsed.hash;
|
||||
}
|
||||
|
||||
// about:blank ONLY — the canonical empty page, and the one the daemon opens its own
|
||||
// first tab on. Blocking it meant `browse newtab about:blank` failed, which is what
|
||||
// `make-pdf setup` runs as its Chromium smoke test: make-pdf reported "Chromium failed
|
||||
// to launch" against a perfectly healthy Chromium, and any browse session whose daemon
|
||||
// restarted could never recreate the blank tab it starts from.
|
||||
//
|
||||
// Deliberately not the whole `about:` scheme. about:blank has no origin, loads nothing
|
||||
// and runs nothing; about:config, about:net-internals and friends are real surfaces.
|
||||
// Exact href match, not a prefix test, so `about:blankfoo` stays blocked.
|
||||
// Compared lower-cased: the URL parser normalises the PROTOCOL but not the opaque part,
|
||||
// so `ABOUT:BLANK` parses to href `about:BLANK` and an exact === would reject it.
|
||||
if (parsed.protocol === 'about:' && parsed.href.toLowerCase() === 'about:blank') {
|
||||
return 'about:blank';
|
||||
}
|
||||
|
||||
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
||||
throw new Error(
|
||||
`Blocked: scheme "${parsed.protocol}" is not allowed. Only http:, https:, and file: URLs are permitted.`
|
||||
`Blocked: scheme "${parsed.protocol}" is not allowed. Only http:, https:, file:, and about:blank URLs are permitted.`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,28 @@ describe('validateNavigationUrl', () => {
|
|||
await expect(validateNavigationUrl('file://host.example.com/foo.html')).rejects.toThrow(/Unsupported file URL host/i);
|
||||
});
|
||||
|
||||
// The daemon opens its own first tab on about:blank, so blocking it meant a restarted
|
||||
// daemon could never initialise — and `make-pdf setup`, whose Chromium smoke test is
|
||||
// `browse newtab about:blank`, reported "Chromium failed to launch" on a healthy browser.
|
||||
it('allows about:blank — the daemon opens its own first tab there', async () => {
|
||||
await expect(validateNavigationUrl('about:blank')).resolves.toBe('about:blank');
|
||||
});
|
||||
|
||||
it('allows about:blank regardless of case, since URL parsing normalises it', async () => {
|
||||
await expect(validateNavigationUrl('ABOUT:BLANK')).resolves.toBe('about:blank');
|
||||
});
|
||||
|
||||
// The allowance is about:blank EXACTLY, not the about: scheme. about:blank has no
|
||||
// origin and loads nothing; the rest of the scheme is a real surface.
|
||||
it('still blocks other about: URLs', async () => {
|
||||
await expect(validateNavigationUrl('about:config')).rejects.toThrow(/scheme.*not allowed/i);
|
||||
await expect(validateNavigationUrl('about:net-internals')).rejects.toThrow(/scheme.*not allowed/i);
|
||||
});
|
||||
|
||||
it('blocks about:blankfoo — exact match, never a prefix test', async () => {
|
||||
await expect(validateNavigationUrl('about:blankfoo')).rejects.toThrow(/scheme.*not allowed/i);
|
||||
});
|
||||
|
||||
it('blocks javascript: scheme', async () => {
|
||||
await expect(validateNavigationUrl('javascript:alert(1)')).rejects.toThrow(/scheme.*not allowed/i);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue