From 01cc247bc8c11da7c3fec0696b004aa12efd9650 Mon Sep 17 00:00:00 2001 From: "Ma.Jinkai" Date: Mon, 23 Mar 2026 17:42:27 +0800 Subject: [PATCH] feat: support stealth skip hosts configurable #27 --- shell/settings.html | 23 +++++++++++++++++++++++ src/config/manager.ts | 2 ++ src/config/tests/config.test.ts | 1 + src/main.ts | 2 +- src/utils/security.ts | 16 ++-------------- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/shell/settings.html b/shell/settings.html index ef05f08..e5fc06b 100644 --- a/shell/settings.html +++ b/shell/settings.html @@ -819,6 +819,14 @@

🛡️ Stealth

+ +
+
+
Stealth skip hosts
+
Sites that actively detect stealth patches and break when injected.
These sites get no stealth injection — they run as a normal browser.
+
+ +
@@ -1132,6 +1140,11 @@ toggleField('field-customUserAgent', st.userAgent === 'custom'); toggleField('field-customAcceptLanguage', st.acceptLanguage === 'custom'); + const skipHotsEl = document.getElementById('cfg-skipHosts'); + if (skipHotsEl && st.skipHosts) { + skipHotsEl.value = st.skipHosts.join('\n'); + } + // Sync const sy = config.sync || {}; setChecked('cfg-chromeBookmarks', !!sy.chromeBookmarks); @@ -1368,6 +1381,16 @@ wire('cfg-chromeBookmarks', 'sync.chromeBookmarks'); wire('cfg-chromeProfile', 'sync.chromeProfile'); wire('cfg-trackingEnabled', 'behavior.trackingEnabled'); + + // Stealth skip hosts + const skipHotsEl = document.getElementById('cfg-skipHosts'); + if (skipHotsEl) { + skipHotsEl.addEventListener('change', () => { + const hosts = skipHotsEl.value.split('\n').map(s => s.trim()).filter(Boolean); + saveConfig({stealth: { skipHosts: hosts }}); + }); + } + // Folder picker for screenshot storage path document.getElementById('btn-chooseFolder').addEventListener('click', async () => { try { diff --git a/src/config/manager.ts b/src/config/manager.ts index 0df8ed7..7484bf6 100644 --- a/src/config/manager.ts +++ b/src/config/manager.ts @@ -56,6 +56,7 @@ export interface TandemConfig { stealthLevel: 'low' | 'medium' | 'high'; acceptLanguage: 'auto' | 'custom'; customAcceptLanguage: string; + skipHosts: string[]; }; // Sync (Chrome bookmarks import) @@ -152,6 +153,7 @@ const DEFAULT_CONFIG: TandemConfig = { stealthLevel: 'medium', acceptLanguage: 'auto', customAcceptLanguage: '', + skipHosts: ['x.com', 'twitter.com', 'abs.twimg.com'], }, sync: { chromeBookmarks: false, diff --git a/src/config/tests/config.test.ts b/src/config/tests/config.test.ts index f2a4736..d8f45e9 100644 --- a/src/config/tests/config.test.ts +++ b/src/config/tests/config.test.ts @@ -81,6 +81,7 @@ describe('ConfigManager', () => { expect(config.stealth.stealthLevel).toBe('medium'); expect(config.stealth.acceptLanguage).toBe('auto'); expect(config.stealth.customAcceptLanguage).toBe(''); + expect(config.stealth.skipHosts).toEqual(['x.com', 'twitter.com', 'abs.twimg.com']); }); it('loads with correct autonomy defaults', () => { diff --git a/src/main.ts b/src/main.ts index 4af5207..13a0e4f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -269,7 +269,7 @@ async function createWindow(): Promise { contents.on('dom-ready', () => { // Skip stealth injection on sites that detect and block stealth patches const url = contents.getURL(); - if (isGoogleAuthUrl(url) || shouldSkipStealth(url)) { + if (isGoogleAuthUrl(url) || shouldSkipStealth(url, runtime?.configManager.getConfig().stealth.skipHosts)) { log.info('🔑 Skipping stealth for:', url.substring(0, 60)); return; } diff --git a/src/utils/security.ts b/src/utils/security.ts index 9022718..00dc2fa 100644 --- a/src/utils/security.ts +++ b/src/utils/security.ts @@ -76,23 +76,11 @@ export function isGoogleAuthUrl(rawValue: string): boolean { ); } -/** - * Sites that actively detect stealth patches and break when injected. - * These sites get no stealth injection — they run as a normal browser. - */ -const STEALTH_SKIP_HOSTS = new Set([ - 'x.com', - 'twitter.com', - 'abs.twimg.com', - 'zhipin.com', - 'login.zhipin.com', -]); - -export function shouldSkipStealth(rawValue: string): boolean { +export function shouldSkipStealth(rawValue: string, skipHosts: Array = []): boolean { const parsed = tryParseUrl(rawValue); if (!parsed || !urlHasProtocol(parsed, 'http:', 'https:')) return false; const host = parsed.hostname.replace(/^www\./, ''); - return STEALTH_SKIP_HOSTS.has(host); + return new Set(skipHosts).has(host); } export function isSearchEngineResultsUrl(rawValue: string): boolean {