mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
fix(security): внутренний гард openAppScheme от опасных схем (CodeQL)
Callers валидируют вход (allowlist в DeepLinkRedirect, конфиг в Connection), но сама утилита доверяла аргументу — забытая валидация в будущем call-site стала бы client-side XSS через location.href='javascript:…'. Теперь схема парсится и javascript/data/ vbscript/file/blob/about/intent/content/filesystem отбрасываются до любого sink'а (зеркало isSafeAppLink из redirect.html).
This commit is contained in:
@@ -88,3 +88,34 @@ describe('openAppScheme', () => {
|
||||
expect(createdIframes).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('openAppScheme guard (defense-in-depth)', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it.each([
|
||||
'javascript:alert(1)',
|
||||
'javascript://%0aalert(1)',
|
||||
'data:text/html,<script>alert(1)</script>',
|
||||
'vbscript://x',
|
||||
'file:///etc/passwd',
|
||||
'intent://scan/#Intent;end',
|
||||
'no-scheme-at-all',
|
||||
])('never navigates for dangerous input (%s)', (url) => {
|
||||
const { location, createdIframes } = setup({
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) Safari/605.1',
|
||||
});
|
||||
openAppScheme(url);
|
||||
expect(location.href).toBe('');
|
||||
expect(createdIframes).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('still opens valid app schemes after the guard', () => {
|
||||
const { location } = setup({
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) Safari/605.1',
|
||||
});
|
||||
openAppScheme('happ://add/https://sp.example.com/abc');
|
||||
expect(location.href).toBe('happ://add/https://sp.example.com/abc');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,7 +31,39 @@ function isIOS(): boolean {
|
||||
return navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1;
|
||||
}
|
||||
|
||||
// Schemes that must never reach a navigation sink even if a caller forgot to
|
||||
// validate: javascript:/data: are XSS vectors, the rest are abuse surfaces.
|
||||
// Mirrors isSafeAppLink in public/miniapp/redirect.html.
|
||||
const BLOCKED_SCHEMES = new Set([
|
||||
'javascript',
|
||||
'data',
|
||||
'vbscript',
|
||||
'file',
|
||||
'blob',
|
||||
'about',
|
||||
'intent',
|
||||
'content',
|
||||
'filesystem',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Defense-in-depth: callers (DeepLinkRedirect allowlist, Connection config)
|
||||
* validate upstream, but the utility itself must not trust its input — a
|
||||
* missed validation at a future call site would otherwise become client-side
|
||||
* XSS via `location.href = 'javascript:…'`.
|
||||
*/
|
||||
function isSafeToOpen(url: string): boolean {
|
||||
const match = url
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.match(/^([a-z][a-z0-9+.-]*):\/\//);
|
||||
if (!match) return false;
|
||||
return !BLOCKED_SCHEMES.has(match[1]);
|
||||
}
|
||||
|
||||
export function openAppScheme(url: string): void {
|
||||
if (!isSafeToOpen(url)) return;
|
||||
|
||||
const isHttp = /^https?:\/\//i.test(url);
|
||||
if (isHttp) {
|
||||
window.location.href = url;
|
||||
|
||||
Reference in New Issue
Block a user