{"id":29975488,"url":"https://github.com/soenneker/soenneker.playwrights.extensions.stealth","last_synced_at":"2026-07-28T02:00:17.653Z","repository":{"id":306966520,"uuid":"1027910754","full_name":"soenneker/soenneker.playwrights.extensions.stealth","owner":"soenneker","description":"A collection of Playwright extensions for more stealthy usage","archived":false,"fork":false,"pushed_at":"2026-07-20T00:29:51.000Z","size":557,"stargazers_count":7,"open_issues_count":1,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-07-21T05:57:53.887Z","etag":null,"topics":["browser","csharp","dotnet","extension","extensions","playwright","playwrights","playwrightsstealthextension","stealth"],"latest_commit_sha":null,"homepage":"https://soenneker.com","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/soenneker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"soenneker","thanks_dev":"soenneker"}},"created_at":"2025-07-28T18:08:12.000Z","updated_at":"2026-07-20T00:28:50.000Z","dependencies_parsed_at":"2025-07-28T18:51:51.024Z","dependency_job_id":"a9e82e9e-1f14-49a0-8cda-dcec8a0c4972","html_url":"https://github.com/soenneker/soenneker.playwrights.extensions.stealth","commit_stats":null,"previous_names":["soenneker/soenneker.playwrights.extensions.stealth"],"tags_count":94,"template":false,"template_full_name":null,"purl":"pkg:github/soenneker/soenneker.playwrights.extensions.stealth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soenneker%2Fsoenneker.playwrights.extensions.stealth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soenneker%2Fsoenneker.playwrights.extensions.stealth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soenneker%2Fsoenneker.playwrights.extensions.stealth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soenneker%2Fsoenneker.playwrights.extensions.stealth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/soenneker","download_url":"https://codeload.github.com/soenneker/soenneker.playwrights.extensions.stealth/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soenneker%2Fsoenneker.playwrights.extensions.stealth/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35972064,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-28T02:00:06.341Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["browser","csharp","dotnet","extension","extensions","playwright","playwrights","playwrightsstealthextension","stealth"],"created_at":"2025-08-04T08:01:38.510Z","updated_at":"2026-07-28T02:00:17.626Z","avatar_url":"https://github.com/soenneker.png","language":"C#","funding_links":["https://github.com/sponsors/soenneker","https://thanks.dev/soenneker"],"categories":[],"sub_categories":[],"readme":"[![](https://img.shields.io/nuget/v/soenneker.playwrights.extensions.stealth.svg?style=for-the-badge)](https://www.nuget.org/packages/soenneker.playwrights.extensions.stealth/)\n[![](https://img.shields.io/github/actions/workflow/status/soenneker/soenneker.playwrights.extensions.stealth/publish-package.yml?style=for-the-badge)](https://github.com/soenneker/soenneker.playwrights.extensions.stealth/actions/workflows/publish-package.yml)\n[![](https://img.shields.io/nuget/dt/soenneker.playwrights.extensions.stealth.svg?style=for-the-badge)](https://www.nuget.org/packages/soenneker.playwrights.extensions.stealth/)\n[![](https://img.shields.io/github/actions/workflow/status/soenneker/soenneker.playwrights.extensions.stealth/codeql.yml?label=CodeQL\u0026style=for-the-badge)](https://github.com/soenneker/soenneker.playwrights.extensions.stealth/actions/workflows/codeql.yml)\n\n# Soenneker.Playwrights.Extensions.Stealth\n\nA .NET extension library for [Microsoft Playwright](https://playwright.dev/dotnet/) that makes browser automation harder to detect. It applies launch-argument hardening, context shaping, and init-script evasions so Chromium sessions look more like normal user sessions.\n\n## Related Repos\n\nYou might also be interested in:\n\n- [soenneker.playwrights.crawler](https://github.com/soenneker/soenneker.playwrights.crawler) for crawling and mirroring sites to disk with Playwright.\n- [soenneker.playwrights.installation](https://github.com/soenneker/soenneker.playwrights.installation) for ensuring Playwright browsers are installed before first use.\n\n## Installation\n\n```bash\ndotnet add package Soenneker.Playwrights.Extensions.Stealth\n```\n\n## Quick start\n\n1. Launch Chromium with stealth defaults: `Playwright.LaunchStealthChromium()`.\n2. Create a stealth browser context: `browser.CreateStealthContext()`.\n3. Use the context as usual to create pages and run automation.\n\n```csharp\nusing Microsoft.Playwright;\nusing Soenneker.Playwrights.Extensions.Stealth;\n\nusing var playwright = await Playwright.CreateAsync();\nawait using var browser = await playwright.LaunchStealthChromium(new BrowserTypeLaunchOptions\n{\n    Headless = true,\n    Channel = \"chromium\"\n});\n\nvar browserContext = await browser.CreateStealthContext();\nvar page = await browserContext.NewPageAsync();\n```\n\n### Connecting to a remote Playwright sidecar\n\nIf Chromium is already running remotely, for example in a Docker sidecar, connect with the endpoint overload:\n\n```csharp\nusing Microsoft.Playwright;\nusing Soenneker.Playwrights.Extensions.Stealth;\n\nusing var playwright = await Playwright.CreateAsync();\nawait using var browser = await playwright.LaunchStealthChromium(\n    \"ws://playwright:3000/\",\n    new BrowserTypeConnectOptions());\n\nvar context = await browser.CreateStealthContext();\nvar page = await context.NewPageAsync();\n```\n\nIn endpoint mode, stealth launch arguments are not applied because the remote browser is already running. Stealth still applies at the browser-context level via `CreateStealthContext()`.\n\n### With proxy\n\nPass an optional proxy into `CreateStealthContext`:\n\n```csharp\nvar context = await browser.CreateStealthContext(new Proxy\n{\n    Server = \"http://proxy.example.com:8080\",\n    Username = \"user\",\n    Password = \"secret\"\n});\n```\n\n### Customizing launch and context\n\nThe defaults are recommended, but if you want to tune options further:\n\n```csharp\nusing Microsoft.Playwright;\nusing Soenneker.Playwrights.Extensions.Stealth;\nusing Soenneker.Playwrights.Extensions.Stealth.Options;\n\nusing var playwright = await Playwright.CreateAsync();\nawait using var browser = await playwright.LaunchStealthChromium(\n    new BrowserTypeLaunchOptions { Headless = true },\n    new StealthLaunchOptions\n    {\n        Channel = \"chrome\",\n        IncludeNoSandboxArgument = false,\n        IgnoreDetectableDefaultArguments = true,\n        AdditionalIgnoredDefaultArguments = [\"--disable-features=DialMediaRouteProvider\"],\n        AdditionalArguments = [\"--disable-features=Translate\"]\n    });\n\nvar context = await browser.CreateStealthContext(\n    new BrowserNewContextOptions\n    {\n        // Optionally specify a UserAgent\n        UserAgent = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36\"\n    },\n    new StealthContextOptions\n    {\n        NormalizeDocumentHeaders = true,\n        InjectClientHintHeaders = true,\n        WarmupSpeechVoices = true,\n        PatchFunctionToString = false,\n        EnableCdpDomainHardening = true,\n        DisableConsoleDomain = true,\n        DisableRuntimeDomain = false,\n        Surfaces = new StealthSurfaceOptions\n        {\n            NavigatorProfile = StealthSurfaceMode.Native,\n            NavigatorPlugins = StealthSurfaceMode.Native,\n            Screen = StealthSurfaceMode.Native,\n            Battery = StealthSurfaceMode.Native,\n            UserAgentData = StealthSurfaceMode.Native,\n            PermissionsQuery = StealthSurfaceMode.Native,\n            DocumentFonts = StealthSurfaceMode.Native,\n            Canvas = StealthSurfaceMode.Native,\n            MediaDevices = StealthSurfaceMode.Native\n        },\n        AdditionalHttpHeaders = new Dictionary\u003cstring, string\u003e\n        {\n            [\"DNT\"] = \"1\"\n        }\n    });\n```\n\n### Applying stealth to an existing context\n\nYou can apply the same stealth behavior to a context you already created:\n\n```csharp\nvar context = await browser.NewContextAsync();\nawait context.ApplyStealthAsync();\n```\n\n## Features\n\n| Area | Description |\n|------|-------------|\n| **Launch options** | Normalizes Chromium args with stealth-oriented defaults: ensures `--disable-blink-features=AutomationControlled`, forces `--headless=new` when headless, and can strip detectable Playwright default args via `IgnoreDefaultArgs`. |\n| **Channel selection** | If `BrowserTypeLaunchOptions.Channel` is unset, `StealthLaunchOptions.Channel` is used (default `chromium`). Set `Channel = \"chrome\"` (or pass `BrowserTypeLaunchOptions.Channel`) when you want the installed Google Chrome channel. |\n| **Hardware profile** | Each context gets a random but internally consistent Windows/Chrome profile: CPU cores, memory, viewport, DPR, Chrome version, timezone, locale/languages, and WebGL identity. Randomized geolocation is available as an explicit opt-in. |\n| **Context shaping** | Sets coherent User-Agent, language headers, timezone, viewport, DPR, and color scheme from the same generated profile. Generated Chromium User-Agents follow UA reduction (`Chrome/\u003cmajor\u003e.0.0.0\u003e`), and a caller-supplied `BrowserNewContextOptions.UserAgent` is propagated into the derived Client Hints fields when header injection is enabled. Fingerprint surfaces such as navigator profile getters, plugins/mimeTypes, screen values, battery, `navigator.userAgentData`, `navigator.permissions.query()`, `document.fonts`, canvas, and media devices can each be left native, spoofed, or disabled. |\n| **Speech voices** | The injected init script can warm up native `speechSynthesis` voices before page scripts run, reducing the chance that a site observes an empty or not-yet-populated voice list. |\n| **Request shaping** | Registers early context routing so top-level document navigations get normalized navigation headers before the first page load. |\n| **CDP hardening (optional)** | Can disable selected Chromium CDP domains (e.g. Console, optionally Runtime) per page to reduce protocol surface. |\n| **Init script** | Injected before page load to reduce automation signals: hides `navigator.webdriver`; aligns `navigator`/`screen`/`window` (e.g. `hardwareConcurrency`, `deviceMemory`, `platform`, `vendor`, `languages`, `plugins`, dimensions, DPR); Client Hints and `navigator.userAgentData`; `window.chrome`; permissions/connection/battery/media/geolocation shims; timezone via `Intl`; canvas noise; WebGL vendor/renderer spoofing; WebRTC host-candidate stripping. |\n\n## Options reference\n\n- **StealthLaunchOptions** — Controls how launch arguments are normalized (`Channel`, `RemoveDetectableArguments`, `IncludeNoSandboxArgument`, `IgnoreDetectableDefaultArguments`, `AdditionalArguments`, `AdditionalIgnoredDefaultArguments`).\n- **StealthContextOptions** — Controls context and request behavior (`Proxy`, `AdditionalHttpHeaders`, `InjectClientHintHeaders`, `NormalizeDocumentHeaders`, `AlignColorScheme`, `RandomizeGeolocation`, `WarmupSpeechVoices`, `PatchFunctionToString`, `Surfaces`, `EnableCdpDomainHardening`, `DisableConsoleDomain`, `DisableRuntimeDomain`).\n- **StealthSurfaceOptions** — Controls per-surface behavior with `StealthSurfaceMode` (`Native`, `Spoofed`, `Disabled`) for `NavigatorProfile`, `NavigatorPlugins`, `Screen`, `Battery`, `UserAgentData`, `PermissionsQuery`, `DocumentFonts`, `Canvas`, `MediaDevices`, and `WebGl`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoenneker%2Fsoenneker.playwrights.extensions.stealth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoenneker%2Fsoenneker.playwrights.extensions.stealth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoenneker%2Fsoenneker.playwrights.extensions.stealth/lists"}