fix: add stealth measures to bypass Teams bot detection (webdriver, plugins, chrome.runtime)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ValueOn AG 2026-02-16 16:18:19 +01:00
parent f5be3f886e
commit d3f8457c42

View file

@ -545,6 +545,7 @@ export class BotOrchestrator {
'--disable-web-security', '--disable-web-security',
'--disable-features=IsolateOrigins,site-per-process', '--disable-features=IsolateOrigins,site-per-process',
'--autoplay-policy=no-user-gesture-required', '--autoplay-policy=no-user-gesture-required',
'--disable-blink-features=AutomationControlled', // Prevent navigator.webdriver=true
], ],
}); });
@ -556,6 +557,42 @@ export class BotOrchestrator {
this._page = await this._context.newPage(); this._page = await this._context.newPage();
// Stealth: Override browser properties that reveal automation.
// Teams checks these to detect headless/automated browsers and
// blocks the /v2/ authenticated experience, falling back to light-meetings.
await this._page.addInitScript(() => {
// 1. Remove navigator.webdriver flag (primary detection signal)
Object.defineProperty(navigator, 'webdriver', { get: () => false });
// 2. Add realistic plugins (headless has empty plugins array)
Object.defineProperty(navigator, 'plugins', {
get: () => [
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '' },
],
});
// 3. Add realistic languages
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en', 'de'] });
// 4. Override permissions query to not reveal automation
const originalQuery = window.navigator.permissions.query.bind(window.navigator.permissions);
// @ts-ignore
window.navigator.permissions.query = (parameters: any) => {
if (parameters.name === 'notifications') {
return Promise.resolve({ state: Notification.permission } as PermissionStatus);
}
return originalQuery(parameters);
};
// 5. Add chrome runtime (missing in headless)
// @ts-ignore
if (!window.chrome) { window.chrome = {}; }
// @ts-ignore
if (!window.chrome.runtime) { window.chrome.runtime = {}; }
});
// Initialize procedures // Initialize procedures
const isAuthenticated = !!(this._options.botAccountEmail && this._options.botAccountPassword); const isAuthenticated = !!(this._options.botAccountEmail && this._options.botAccountPassword);
this._joinProcedure = new JoinProcedure(this._page, this._logger, this._botName, isAuthenticated); this._joinProcedure = new JoinProcedure(this._page, this._logger, this._botName, isAuthenticated);