From 51d0f0431a121ad179eb5b376fbab539018040dd Mon Sep 17 00:00:00 2001 From: Fringg Date: Sat, 25 Jul 2026 00:32:00 +0300 Subject: [PATCH] =?UTF-8?q?fix(security):=20=D0=B8=D0=BD=D0=BB=D0=B0=D0=B9?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20regex-=D0=B1=D0=B0=D1=80=D1=8C?= =?UTF-8?q?=D0=B5=D1=80=D1=8B=20=D0=B4=D0=BB=D1=8F=20CodeQL=20=D0=B2=20ope?= =?UTF-8?q?nAppScheme=20=D0=B8=20redirect.html?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Хелпер-функции (isSafeToOpen/isSafeAppLink) CodeQL как санитайзеры не распознаёт — js/xss и js/client-side-unvalidated-url-redirection продолжали флаговаться на sink'ах. Проверки перенесены инлайн к sink'ам как префикс-якорные RegExp-гарды (распознаваемый барьер): scheme:// обязателен, javascript/data/vbscript/file/blob/about/intent/content/ filesystem (и http для redirect.html) отбрасываются. Семантика та же. --- public/miniapp/redirect.html | 22 +++++++++++--------- src/utils/openAppScheme.ts | 39 ++++++++---------------------------- 2 files changed, 20 insertions(+), 41 deletions(-) diff --git a/public/miniapp/redirect.html b/public/miniapp/redirect.html index 93fe00a..3382186 100644 --- a/public/miniapp/redirect.html +++ b/public/miniapp/redirect.html @@ -129,19 +129,21 @@ // `file:` URI (DOM-XSS) is not scheme://-shaped, and http(s)/intent // are blocked (open redirect / intent abuse) — so only VPN app // schemes (happ://, vless://, ...) pass. Mirrors the validation in - // src/pages/DeepLinkRedirect.tsx. - function isSafeAppLink(raw) { - var s = String(raw || '').trim().toLowerCase(); - var m = s.match(/^([a-z][a-z0-9+.\-]*):\/\//); - if (!m) return false; - var blocked = ['http', 'https', 'file', 'blob', 'about', 'javascript', 'data', 'vbscript', 'intent', 'content']; - return blocked.indexOf(m[1]) === -1; - } - - if (!url || !isSafeAppLink(url)) { + // src/pages/DeepLinkRedirect.tsx. Both checks are INLINE + // prefix-anchored RegExp tests so CodeQL recognizes them as taint + // barriers (js/xss, js/client-side-unvalidated-url-redirection). + function showNoUrl() { document.getElementById('title').textContent = t.noUrl; document.getElementById('subtitle').textContent = ''; document.querySelector('.spinner').style.display = 'none'; + } + + if (!url || !/^[a-z][a-z0-9+.\-]*:\/\//i.test(url)) { + showNoUrl(); + return; + } + if (/^(https?|file|blob|about|javascript|data|vbscript|intent|content|filesystem):/i.test(url)) { + showNoUrl(); return; } diff --git a/src/utils/openAppScheme.ts b/src/utils/openAppScheme.ts index 9c05d98..8fa3add 100644 --- a/src/utils/openAppScheme.ts +++ b/src/utils/openAppScheme.ts @@ -31,38 +31,15 @@ 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; + // 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:…'`. Both guards are INLINE + // prefix-anchored RegExp tests so CodeQL recognizes them as taint barriers + // (js/xss, js/client-side-unvalidated-url-redirection). + if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(url)) return; + if (/^(javascript|data|vbscript|file|blob|about|intent|content|filesystem):/i.test(url)) return; const isHttp = /^https?:\/\//i.test(url); if (isHttp) {