{"id":50753645,"url":"https://github.com/frederico-kluser/anonymous-browser","last_synced_at":"2026-06-11T03:03:49.150Z","repository":{"id":357436797,"uuid":"1236923402","full_name":"frederico-kluser/anonymous-browser","owner":"frederico-kluser","description":"Disposable, fingerprint-rotating browser via Tor (Camoufox) with optional real-time disposable email. One command — new identity. Install: npm i -g anonymous-browser. Linux + macOS.","archived":false,"fork":false,"pushed_at":"2026-05-24T13:46:54.000Z","size":194,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-24T15:24:06.846Z","etag":null,"topics":["anonymity","anti-fingerprinting","anti-tracking","automation","bash","browser-fingerprinting","camoufox","cypherpunk","fingerprint-spoofing","firefox","identity-rotation","linux","macos","playwright","popos","privacy","shell","stealth-browser","tor","web-scraping"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/anonymous-browser","language":"Shell","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/frederico-kluser.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"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}},"created_at":"2026-05-12T17:48:54.000Z","updated_at":"2026-05-24T13:46:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/frederico-kluser/anonymous-browser","commit_stats":null,"previous_names":["frederico-kluser/ghost-browser","frederico-kluser/anonymous-browser"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/frederico-kluser/anonymous-browser","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frederico-kluser%2Fanonymous-browser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frederico-kluser%2Fanonymous-browser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frederico-kluser%2Fanonymous-browser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frederico-kluser%2Fanonymous-browser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/frederico-kluser","download_url":"https://codeload.github.com/frederico-kluser/anonymous-browser/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frederico-kluser%2Fanonymous-browser/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34180147,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"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":["anonymity","anti-fingerprinting","anti-tracking","automation","bash","browser-fingerprinting","camoufox","cypherpunk","fingerprint-spoofing","firefox","identity-rotation","linux","macos","playwright","popos","privacy","shell","stealth-browser","tor","web-scraping"],"created_at":"2026-06-11T03:03:47.819Z","updated_at":"2026-06-11T03:03:49.142Z","avatar_url":"https://github.com/frederico-kluser.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"logo.jpg\" alt=\"anonymous-browser\" width=\"220\"/\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eanonymous-browser\u003c/h1\u003e\n\n\u003e **Sua sessão. Seu IP. Seu fingerprint. Sua escolha.**\n\u003e\n\u003e Um navegador descartável, isolado, com IP rotacionado pelo Tor e fingerprint coerente trocado em nível C++. Linux (Debian/Arch/Fedora + Flatpak fallback) e macOS. Bash. Sem telemetria. Sem conta. Sem rastro.\n\n```bash\nnpm i -g anonymous-browser   # uma vez\nanonymous-browser            # toda vez que quiser uma identidade nova\n```\n\n---\n\n## Por que isto existe\n\nA web de 2026 não te trata como visitante. Te trata como **target**. Cada `fetch()` que seu browser faz num site arbitrário sai com:\n\n- IP público (resolvível pra cidade, ISP, AS, e muitas vezes pra você como pessoa)\n- Canvas hash, WebGL renderer, audio fingerprint, lista de fontes, screen + colorDepth, `Intl.timeZone`, `navigator.platform`, Client Hints, `userAgentData.brands`\n- Cookies, localStorage, IndexedDB, ServiceWorker caches, ETags persistentes\n- TLS JA3/JA4, HTTP/2 SETTINGS frame fingerprint, ordem de headers\n\nCombinando esses sinais, um único site identifica você unicamente em **\u003e99% das visitas** ([Panopticlick](https://amiunique.org), [FingerprintJS](https://fingerprint.com)). Cookie-clearing, modo anônimo e VPN básica resolvem **nenhum** dos vetores acima.\n\n**anonymous-browser** é o oposto político disso: uma stack curta de scripts shell que monta, antes de cada sessão, uma máquina virtual de identidade — IP, fingerprint, locale, geo, timezone — coerente o suficiente pra passar em CreepJS com Trust \u003e70% e descartável o suficiente pra desaparecer no `Ctrl+C`.\n\nIsso não é furtar. Isso é **se recusar a pagar com seus dados** o pedágio que sites cobram pra te deixar entrar. É o mesmo princípio das listas de domínio do uBlock Origin, do Tor Project, do EFF Privacy Badger, do Mullvad VPN: na ausência de uma lei honesta de privacidade, você se defende sozinho.\n\n\u003e \"Privacy is necessary for an open society in the electronic age. Privacy is not secrecy.\"\n\u003e — Eric Hughes, *A Cypherpunk's Manifesto* (1993)\n\n---\n\n## TL;DR\n\n```bash\n# via npm (recomendado): instala um comando 'anonymous-browser' no seu PATH\nnpm i -g anonymous-browser\nanonymous-browser\n```\n\nNa primeira execução o comando dispara `install.sh` automaticamente (instala Tor, libs nativas do Camoufox e o venv Python — pede sudo). Depois disso, cada execução pergunta se você quer um e-mail temporário descartável e abre o browser.\n\nQuer rodar do source sem npm?\n\n```bash\ngit clone https://github.com/frederico-kluser/anonymous-browser\ncd ghost-browser\n./install.sh\n./anonymous.sh\n```\n\n`anonymous-browser` (ou `./anonymous.sh`) te pergunta a URL, sorteia um OS pra spoofar (windows/macos/linux), força um novo circuito Tor, abre um Firefox-patched (Camoufox) com fingerprint coerente, **nega GPS silenciosamente** e apaga tudo (perfil temporário, browser, processo) no momento que você fecha o navegador, dá Ctrl+C ou fecha o terminal.\n\n---\n\n## O que rola debaixo do capô\n\n| Vetor | Defesa |\n|---|---|\n| IP / ASN / geo | Tor SOCKS5 em `127.0.0.1:9050`, novo circuito por sessão via `new-tor-circuit.sh` |\n| Cookies, storage, cache | Perfil em `mktemp -d`, apagado por `trap INT TERM HUP EXIT` |\n| Canvas / WebGL / Audio / Fonts | Camoufox patcha em C++ (Firefox fork da [BrowserForge](https://github.com/daijro/camoufox)) |\n| `navigator.platform`, Client Hints, `userAgentData` | Camoufox `os=\"windows\"\\|\"macos\"\\|\"linux\"` — coerentes entre si |\n| `Intl.timeZone`, `navigator.language` | Camoufox `geoip=True` → bate com cidade do exit Tor (dataset MaxMind) |\n| WebRTC IP leak | Camoufox `block_webrtc=True` (default) |\n| HTML5 Geolocation API | `firefox_user_prefs={\"permissions.default.geo\": 2}` → site recebe `PERMISSION_DENIED` sem prompt |\n| Botão \"X\" do navegador | `BrowserContext.wait_for_event(\"close\")` → encerra script + apaga perfil |\n| Proxy / VPN customizado | env var `PROXY=socks5://...` sobrescreve Tor default; `PROXY=none` desliga proxy |\n| Identidade persistente | env var `KEEP=nome` salva perfil em `~/.anonymous-browser/profiles/\u003cnome\u003e/` com OS fixado |\n\nE o que **não** dá pra resolver com esta stack — sendo honesto:\n\n- **Anti-bot enterprise** (Cloudflare Bot Management, DataDome, PerimeterX, Kasada). Esses caras analisam TLS JA3, HTTP/2 frame ordering, comportamento de mouse com ML. Camoufox melhora mas não esconde. Se você precisa passar por isso, vai pagar GoLogin, Multilogin, AdsPower — não é missão deste repo.\n- **Mobile fingerprint coerente.** Camoufox só suporta desktop (`windows`/`macos`/`linux`). Removemos os perfis iPhone/iPad/Android do projeto para não dar falsa sensação de proteção — qualquer anti-bot detectava como inconsistente.\n- **Identidade externa.** Se o site quer SMS/e-mail único, você precisa de [addy.io](https://addy.io), [SimpleLogin](https://simplelogin.io), número descartável. Fora do escopo.\n\n---\n\n## Anatomia técnica\n\n\u003e Esta seção lista, **sem omissões**, cada técnica usada pelo `anonymous-browser` e como ela é implementada. A intenção é que você possa auditar tudo lendo o código — cada subseção referencia o arquivo e o bloco relevantes. Se algo está aqui, está no código. Se está no código mas não está aqui, é bug de documentação — abra issue.\n\n### 1. Camada de rede\n\n#### 1.1 Tor SOCKS5 como default\n\nSem `PROXY` setado (ou com `PROXY=tor`), o tráfego do navegador sai pelo Tor local em `127.0.0.1:9050`. Tor já vem instalado pelo `install.sh` e habilitado como serviço (`systemctl enable --now tor` no Linux, `brew services start tor` no macOS — ambos persistem entre boots).\n\nArquivo: `anonymous.sh`, bloco `# -------- resolve PROXY --------`. O default está no `case` `\"\"|tor) PROXY_URL=\"socks5://127.0.0.1:9050\"`.\n\n#### 1.2 Proxy custom via `$PROXY`\n\nQuatro modos aceitos: `tor` (default), `none` (sem proxy — IP real), `socks5://host:port`, `http(s)://host:port`. Qualquer outro valor faz o script abortar antes de tocar no navegador. SOCKS4 **não** é suportado — limitação do Playwright (engine do Camoufox), explicitada no comentário do código.\n\nArquivo: `anonymous.sh`, mesmo bloco. Validação por `case` exclusivo.\n\n#### 1.3 DNS resolvido pelo proxy (não pelo resolver do sistema)\n\nQuando o proxy é SOCKS5, todas as chamadas de `curl` que o `anonymous-browser` faz usam `--socks5-hostname` em vez de `--socks5`. A diferença é crítica: `--socks5-hostname` envia o **nome** do host para o proxy resolver; `--socks5` resolve localmente e só manda o IP. Sem `--hostname`, seu resolver DNS do sistema (ISP, Google 8.8.8.8) veria todas as queries — leak de DNS clássico.\n\nArquivo: `lib/platform.sh`, função `anon_curl_proxy_args()`. O comportamento é simétrico ao que `install.sh` usa pra testar Tor (`--socks5-hostname 127.0.0.1:9050`).\n\n#### 1.4 Rotação de circuito Tor (NEWNYM + fallback de reload)\n\nAntes de abrir o navegador (quando proxy é Tor), o `anonymous-browser` força um circuito novo — IP de saída diferente do que você acabou de usar. Dois caminhos:\n\n- **Caminho rápido**: se você habilitou `ControlPort 9051` no `torrc`, mandamos `AUTHENTICATE \"\" / SIGNAL NEWNYM / QUIT` via `nc 127.0.0.1 9051`. Tor renova circuitos sem reiniciar. Latência: ~1s.\n- **Fallback**: se ControlPort estiver fechada, fazemos reload do serviço (`sudo systemctl reload tor` ou `brew services restart tor`). Funciona, mas é mais lento (~5–15s) e fecha qualquer conexão Tor ativa.\n\nArquivo: `new-tor-circuit.sh`. A escolha do caminho é decidida por `anon_port_open 127.0.0.1 9051`.\n\n#### 1.5 Probe de IP de saída via endpoint Tor-friendly\n\nCloudflare hoje bloqueia o conjunto clássico de endpoints de \"qual é meu IP\" (`api.ipify.org`, `ipinfo.io`, `checkip.amazonaws.com`, `icanhazip.com`) quando a origem é exit Tor — devolvem HTTP 403 ou corpo vazio. Se o Camoufox tentasse o auto-probe interno dele, levantaria `InvalidIP` e o navegador **nunca abriria**.\n\nResolução: o `anonymous-browser` resolve o IP de saída **no próprio shell** consultando `https://check.torproject.org/api/ip` (purpose-built pra detectar Tor, nunca bloqueia), parseia `.IP` com `jq`, e passa o IP como string para `geoip=` do Camoufox. Tem fallback para `icanhazip.com`, `ifconfig.co/ip`, `ipecho.net/plain` quando o proxy não é Tor.\n\nSe todos falham (rede caída, todo mundo bloqueado): cai para `geoip=False` com aviso, mas o **navegador ainda abre** — preferimos identidade levemente inconsistente a sessão impossível.\n\nArquivo: `anonymous.sh`, bloco `# -------- resolve IP de saída para geoip --------`.\n\n---\n\n### 2. Camada de fingerprint (Camoufox + BrowserForge)\n\n#### 2.1 Por que Camoufox e não plugin / extensão\n\nA maioria dos anti-detect \"soluções\" baratas spoofa `navigator.platform` via hook JavaScript injetado no documento. Detectar isso é trivial: o site checa `navigator.platform.toString.toString()` ou compara `Object.getOwnPropertyDescriptor(navigator, 'platform').get` com o esperado, ou simplesmente carrega um `iframe` cuja janela ainda tem o platform real.\n\n[Camoufox](https://github.com/daijro/camoufox) é um **fork do Firefox** que patcha os valores **em C++**, antes de o JS ser executado. Não há `Function.prototype.toString` que delate o hook, porque não existe hook — o valor é genuíno no nível do engine. É o mesmo princípio que o Tor Browser usa, exceto que Camoufox spoofa **uma identidade arbitrária** em vez de uma uniforme.\n\n#### 2.2 OS spoof coerente\n\n`anonymous-browser` escolhe (ou recebe via `ANON_OS`) um entre `windows`, `macos`, `linux`. Esse único valor cascateia em **dezenas** de subvetores que Camoufox alinha sozinho:\n\n- `navigator.platform` (`Win32` / `MacIntel` / `Linux x86_64`)\n- `navigator.userAgent` (versão de Firefox compatível com o OS)\n- `navigator.userAgentData.brands` / `.platform` (Client Hints)\n- Headers HTTP `Sec-CH-UA-Platform`, `Sec-CH-UA-Platform-Version`\n- Lista de fontes disponíveis (`Segoe UI` aparece no Windows, não no Linux)\n- Vendor / Renderer de WebGL coerentes (`ANGLE (NVIDIA…)` no Windows, `Apple Inc.` no macOS)\n\nA coerência interna é o que detectores procuram. Spoofar UA pra Windows e deixar `navigator.platform=\"Linux x86_64\"` é detectado em milissegundos por qualquer fingerprinter sério. Camoufox + BrowserForge sincronizam todos os campos antes do primeiro byte ir pro site.\n\nArquivo: `anonymous.sh`, bloco `# -------- resolve ANON_OS --------` + chamada `Camoufox(os=OS_ARG, ...)`.\n\n#### 2.3 Screen, fontes, Canvas, WebGL, Audio context\n\n| Vetor | Como spoofamos |\n|---|---|\n| Screen | `Screen(max_width, max_height)` da `browserforge.fingerprints` — limites por OS (`1920×1080` win/linux, `2560×1600` macos). Camoufox sorteia dentro do limite. |\n| Fontes | Dataset bundled da BrowserForge — Camoufox restringe a `document.fonts` ao set típico do OS sorteado. |\n| Canvas | Patch C++: `HTMLCanvasElement.prototype.toDataURL` retorna pixels com ruído determinístico **por sessão**, mas constante entre frames da mesma sessão (anti-detecção de \"noise random per call\"). |\n| WebGL | Vendor / Renderer / `UNMASKED_VENDOR_WEBGL` / `UNMASKED_RENDERER_WEBGL` reescritos no Firefox patched, casando com o OS. |\n| Audio | `AudioContext.createOscillator` retorna buffer levemente alterado — quebra `audioContext.fingerprint` da FingerprintJS. |\n\nTudo isso é feito pelo Camoufox/BrowserForge — não há código nosso aqui além de **escolher o OS** e passar a `Screen` correta. A \"magia\" toda mora no fork do Firefox.\n\nArquivo: `anonymous.sh`, dict `screens = {...}` + bloco `with Camoufox(...)`.\n\n#### 2.4 Client Hints (`Sec-CH-UA-*`)\n\nChromium e Firefox modernos mandam um set de headers que o site explicitamente requisita (`Accept-CH`). Esses headers **não** estão expostos a `navigator.userAgent` — um spoof clássico de UA não os afeta. Camoufox os patcha em nível de rede no Firefox patched, casando com o OS spoofado.\n\nVerificável: abra `https://browserleaks.com/javascript` dentro do `anonymous.sh` e compare a seção \"User-Agent Client Hints\" com o OS sorteado.\n\n#### 2.5 Humanização de interação\n\n`Camoufox(humanize=True)` injeta micro-delays entre eventos (mouse-move, keypress) seguindo distribuições típicas de humano. **Não** é bot-puro com `page.click()` instantâneo. Útil contra trackers comportamentais simples (`requestAnimationFrame`-based behavioral detection); inútil contra ML-based behavioral (Datadome, Akamai BMP) — esses casos exigem stack diferente.\n\nArquivo: `anonymous.sh`, kwarg `humanize=True` na chamada `with Camoufox(...)`.\n\n#### 2.6 WebRTC bloqueado (anti-leak de IP real)\n\nMesmo com proxy SOCKS5, WebRTC clássico (`RTCPeerConnection`) faz STUN/TURN diretos que ignoram proxy HTTP/SOCKS — vazam IP local e público em segundos. Camoufox traz `block_webrtc=True` como **default** — o stack inteiro de WebRTC fica desligado no Firefox patched. Não é configurável a partir do nosso `anonymous.sh` (intencional: ligar WebRTC anula meio Tor).\n\nVerificável: `https://browserleaks.com/webrtc` deve mostrar \"No leaks\".\n\n#### 2.7 HTML5 Geolocation API negada silenciosamente\n\n`firefox_user_prefs={\"permissions.default.geo\": 2}` — `2` significa **deny implícito** sem mostrar prompt ao usuário. Sites recebem `PERMISSION_DENIED` instantaneamente quando chamam `navigator.geolocation.getCurrentPosition()`. Diferente do default Firefox (`0` = prompt), que daria pop-up — sinal forte de fingerprint atípico.\n\nArquivo: `anonymous.sh`, kwarg `firefox_user_prefs={\"permissions.default.geo\": 2}` no `Camoufox(...)`.\n\n---\n\n### 3. Camada geográfica (geoip)\n\n#### 3.1 IP → locale + timezone + idioma\n\nCamoufox consulta o dataset MaxMind GeoLite2 (baixado pelo `install.sh` via `python -m camoufox fetch`, ~65 MB) para mapear o IP de saída em:\n\n- `Intl.DateTimeFormat().resolvedOptions().timeZone` — timezone coerente com a cidade do exit\n- `navigator.language` / `navigator.languages` — idioma típico da região (pt-BR no Brasil, fr-FR na França, etc.)\n- Locale do Firefox (formato de datas, moeda)\n\nSem isso, você pareceria \"ucraniano com timezone de NY\" para qualquer site que cruza esses sinais.\n\n#### 3.2 Workaround Cloudflare (resolução fora do Camoufox)\n\nComo descrito em §1.5: Camoufox `geoip=True` tentaria descobrir o IP via `api.ipify.org`/`ipinfo.io` — tudo Cloudflare-blocked pra exits Tor. Resolvemos o IP no shell e passamos como string explícita: `geoip=\"185.220.101.42\"`.\n\n#### 3.3 Privacidade com `PROXY=none`\n\nQuando você desliga o proxy, **automaticamente** desabilitamos `geoip` (`geoip_kw = False`). Razão: se o Camoufox fizer auto-probe de IP, ele vai bater em `api.ipify.org` **com seu IP real** — o IP que você acabou de pedir pra esconder. Trade-off: sem `geoip`, locale/timezone podem não bater com sua região real (alguns sites notam), mas seu IP fica em casa.\n\nArquivo: `anonymous.sh`, bloco final no heredoc Python — `if proxy_arg is None: geoip_kw = False`.\n\n---\n\n### 4. Camada de sessão (perfis)\n\n#### 4.1 Perfil descartável\n\nSem `KEEP`, o `user_data_dir` do Firefox é criado via `mktemp -d \"$(anon_tmp_prefix)/anon-XXXXXX\"`. `anon_tmp_prefix()` retorna `$TMPDIR` no macOS (`/var/folders/.../T/`) ou `/tmp` no Linux. Tudo (cookies, localStorage, IndexedDB, ServiceWorker caches, ETags) vive só dentro desse dir e é apagado pelo `trap` no `EXIT`.\n\nPor que perfil temporário e não anônimo do Firefox? Modo anônimo do Firefox **persiste** alguns artefatos entre janelas privadas no mesmo processo, e principalmente **não isola fingerprint** — o objetivo aqui é zero state, não privacidade-de-cookies.\n\nArquivo: `anonymous.sh`, bloco `# -------- perfil descartável OU persistente --------`.\n\n#### 4.2 Perfil persistente (`KEEP=nome`)\n\nSetando `KEEP=trabalho`, o perfil vive em `~/.anonymous-browser/profiles/trabalho/`. Cookies, histórico, logins, extensões — tudo persiste entre execuções. Útil pra ter identidades alternativas estáveis (uma \"pessoa\" pra trabalho, outra pra pessoal, sem cross-contamination).\n\nValidação: `KEEP` precisa casar `^[A-Za-z0-9_-]+$`. Sem `.`, sem `/`, sem espaço — previne path traversal e nomes problemáticos no `mkdir -p`.\n\nArquivo: `anonymous.sh`, bloco `# -------- resolve KEEP --------`.\n\n#### 4.3 OS pinning per perfil persistente\n\nCada perfil persistente memoriza o OS sorteado na primeira vez, num arquivo `.anon-os` dentro do próprio perfil. Sessões subsequentes leem o OS do arquivo (sem sorteio novo), mantendo a identidade coerente entre as visitas — `navigator.platform` não pula de `Win32` pra `MacIntel` entre execuções, o que delataria spoofing.\n\nOverride possível: `ANON_OS=macos KEEP=trabalho ...` reescreve o `.anon-os` na hora.\n\nArquivo: `anonymous.sh`, bloco `# -------- resolve ANON_OS --------` (lógica de leitura/escrita do `OS_FILE`).\n\n#### 4.4 Bloqueio até o usuário fechar o navegador\n\nO Python espera por `browser.wait_for_event(\"close\", timeout=0)` — timeout zero = infinito. O evento `close` é disparado quando o **processo Firefox** termina (todas as janelas fechadas, ou kill no processo). Enquanto o navegador está aberto, o shell fica passivamente bloqueado — zero CPU.\n\nArquivo: `anonymous.sh`, dentro do `with Camoufox(...)`. Note que `KeyboardInterrupt` e `SystemExit` são capturados pra cleanup limpo em Ctrl+C / SIGTERM.\n\n#### 4.5 `page.goto` com timeout não-fatal\n\nTor com exit ruim pode demorar \u003e30s pra responder. Default do Playwright derruba a sessão com `TimeoutError`. Aumentamos pra 60s **e** capturamos a exceção: se `goto` falhar, imprimimos aviso e **a janela continua aberta** — você decide se recarrega, troca circuito (`./new-tor-circuit.sh`), ou desiste com Ctrl+C.\n\nArquivo: `anonymous.sh`, dentro do `with Camoufox(...)` — bloco `try: page.goto(URL, timeout=60_000)`.\n\n---\n\n### 5. Camada de processo (sinais e cleanup)\n\n#### 5.1 Quatro sinais cobertos: INT, TERM, HUP, EXIT\n\n```bash\ntrap cleanup INT TERM HUP EXIT\n```\n\n- `INT` (SIGINT) — Ctrl+C no terminal\n- `TERM` (SIGTERM) — `kill \u003cpid\u003e` ou `systemctl stop`\n- `HUP` (SIGHUP) — terminal fechado (SSH disconnect, fechar janela do gnome-terminal)\n- `EXIT` — qualquer saída do script, incluindo `exit 0` normal e erros via `set -e`\n\nO `cleanup()` é idempotente: mata o watcher de e-mail (se MAIL=1), espera ele encerrar com `wait`, e apaga o `$TMP` se for perfil descartável. Garante que **nenhum** caminho de saída deixa lixo.\n\nArquivo: `anonymous.sh`, função `cleanup()` no topo do script.\n\n#### 5.2 Sinais Python espelham os do bash\n\nDentro do heredoc Python:\n\n```python\nfor s in (signal.SIGHUP, signal.SIGTERM):\n    signal.signal(s, lambda *_: sys.exit(0))\n```\n\nRazão: `SIGINT` o Python já converte em `KeyboardInterrupt` sozinho. `SIGHUP` e `SIGTERM` por default **matam o processo** sem rodar `finally` — perderíamos a chance de fechar o Camoufox educadamente. Convertendo em `sys.exit(0)`, o `with` do context manager dispara `__exit__` e fecha o browser apropriadamente.\n\n#### 5.3 Mail watcher cleanup (kill + wait)\n\nQuando `MAIL=1`, `anonymous-mail.sh` roda em background com seu próprio PID. O `cleanup()` do `anonymous.sh` faz `kill \"$MAIL_PID\"` seguido de `wait \"$MAIL_PID\"`. O `wait` é importante: dá tempo do trap do `anonymous-mail.sh` rodar (que apaga a conta no mail.tm), antes do shell pai sair. Sem `wait`, a conta efêmera vaza no mail.tm.\n\nArquivo: `anonymous.sh`, função `cleanup()` (linhas iniciais).\n\n#### 5.4 Sleep interrompível (`nap` function)\n\n```bash\nnap() {\n    sleep \"$1\" \u0026\n    SLEEP_PID=$!\n    wait \"$SLEEP_PID\" 2\u003e/dev/null || true\n    SLEEP_PID=\"\"\n}\n```\n\n`sleep` puro do bash **não responde a sinais até terminar**. Se o poll do mail é 5s e o usuário Ctrl+C, o watcher esperaria os 5s antes de reagir — visível como lag perceptível ao fechar o navegador. Rodando `sleep \u0026` + `wait`, qualquer sinal dispara o trap imediatamente; o trap mata o `SLEEP_PID` e o script encerra em \u003c100ms.\n\nArquivo: `anonymous-mail.sh`, função `nap()`.\n\n---\n\n### 6. Camada de e-mail descartável (mail.tm)\n\n#### 6.1 Por que mail.tm\n\nCritérios pro serviço: **free, sem API key, sem cadastro, REST público, deletável**. [mail.tm](https://mail.tm) bate todos:\n\n- Gratuito, sem limite de contas (rate limit de 8 req/s, ok pra polling)\n- Sem signup nem API key: `POST /accounts` cria e retorna ID na hora\n- API REST documentada (Hydra-style; aceitamos JSON Accept header pra evitar envelope)\n- `DELETE /accounts/{id}` apaga conta de verdade\n\nAlternativas (Guerrilla Mail, 10minutemail) ou exigem API paga, ou retornam endereços já-spammed, ou bloqueiam Tor agressivamente.\n\nAtribuição obrigatória nos termos: \"Inbox by mail.tm\" — incluída no banner que imprimimos.\n\n#### 6.2 Provisioning: `/domains → /accounts → /token`\n\nTrês chamadas:\n\n1. `GET /domains` — lista de domínios ativos no mail.tm (rotacionam de tempos em tempos). Pegamos o primeiro `isActive` via `jq`.\n2. `POST /accounts {address, password}` — cria a conta. Address é `anon\u003c12-random-chars\u003e@\u003cdomain\u003e`; password é 24 chars `[a-z0-9]`. Retries até 5x em HTTP 422 (colisão de address aleatoriamente).\n3. `POST /token {address, password}` — autentica e retorna JWT. Salvamos no `.anon-mail` dentro do perfil.\n\nArquivo: `anonymous-mail.sh`, função `provision_fresh()`.\n\n#### 6.3 Endereço e senha cripto-OK\n\n```bash\nLC_ALL=C tr -dc 'a-z0-9' \u003c /dev/urandom 2\u003e/dev/null | head -c \"$n\"\n```\n\nTanto address (`\u003c12 chars\u003e`) quanto password (`\u003c24 chars\u003e`) vêm de `/dev/urandom` — entropia de kernel, não `$RANDOM` do bash (que é cíclico e previsível). `LC_ALL=C` evita expansão de classes Unicode no `tr`, garantindo consistência cross-distro.\n\nArquivo: `anonymous-mail.sh`, função `rand_str()`.\n\n#### 6.4 Polling em tempo real\n\nmail.tm **não tem WebSocket público**. A solução é loop de polling de `GET /messages` a cada `ANON_MAIL_POLL` segundos (default 5; mínimo 2 — limite imposto pelo script pra não estourar rate limit). Default escolhido empiricamente: serviços de signup normalmente entregam e-mail em \u003c10s; com poll 5s, latência média do usuário é ~7s entre disparar e ver no terminal.\n\nCada iteração compara IDs vistos (`SEEN`) com IDs retornados; imprime só os novos, em ordem cronológica (mail.tm devolve do mais novo pro mais antigo — invertemos).\n\nArquivo: `anonymous-mail.sh`, loop `while true; do ... done` final.\n\n#### 6.5 Extração automática de código de verificação\n\n99% dos signups mandam um número de 4–8 dígitos como código de OTP. Pra evitar o usuário precisar ler o corpo do e-mail manualmente, regexamos:\n\n```bash\ncode=\"$(printf '%s' \"$body $subj\" | grep -Eo '[0-9]{4,8}' | head -n1)\"\n```\n\nProcuramos no `subject` + `text`. Pega o primeiro match. Falsos positivos existem (ex: ano \"2026\" num footer), mas mostramos ao lado do código também (\"Código provável: 1234\") — usuário valida visualmente.\n\nArquivo: `anonymous-mail.sh`, função `print_message()`.\n\n#### 6.6 Fallback HTML → text\n\nAlguns sites enviam só `text/html`, sem `text/plain`. Quando `.text` vem vazio, fazemos:\n\n```bash\nbody=\"$(jqr '(.html // []) | join(\"\\n\")' \\\n    | sed -e 's/\u003c[^\u003e]*\u003e//g' -e 's/\u0026nbsp;/ /g' -e 's/\u0026amp;/\\\u0026/g')\"\n```\n\nConcatena os fragmentos HTML, remove tags via regex e desescapa entidades comuns. Não é parser HTML — é o suficiente pra extrair texto legível de e-mails transacionais (que tendem a HTML simples), não pra renderizar newsletter complexa.\n\nArquivo: `anonymous-mail.sh`, função `print_message()` (bloco do fallback).\n\n#### 6.7 Refresh de token JWT\n\nmail.tm expira tokens depois de algumas horas. Detectamos via `HTTP 401` no polling, chamamos `get_token()` (que faz `POST /token` com address+password salvos), regravamos as creds, e continuamos o loop **sem perder mensagens** (a próxima iteração re-lista tudo).\n\nArquivo: `anonymous-mail.sh`, dentro do `while true` — `if [[ \"$RESP_CODE\" == \"401\" ]]; then`.\n\n#### 6.8 Rate limit (429) e erros transitórios\n\nmail.tm rate-limit é 8 req/s. Em 429, esperamos extra 5s antes do próximo poll. Em 5xx ou rede caída (HTTP 000), silenciosamente continuamos — o próximo poll resolve. Não rompemos a sessão por blip de rede.\n\nArquivo: `anonymous-mail.sh`, mesmo loop.\n\n#### 6.9 Auto-delete da conta no exit\n\nQuando o perfil é **efêmero** (sem `KEEP`), o `cleanup()` do mail script faz `DELETE /accounts/{id}` antes de morrer. A conta deixa de existir no mail.tm. Casa com a filosofia geral: ao fechar o navegador, **nada** dessa identidade sobrevive.\n\nQuando o perfil é persistente (`KEEP=X` ou caminho explícito via `$1`), **mantemos** a conta — mesma identidade volta na próxima sessão.\n\nArquivo: `anonymous-mail.sh`, função `cleanup()` (condicional `if PERSISTENT -eq 0`).\n\n#### 6.10 Modo persistente: reutiliza endereço\n\nCom `KEEP`, o mail script primeiro tenta `load_creds()` do `.anon-mail` do perfil. Se as creds estão lá e o token ainda valida (`get_token` retorna 200), reutiliza. Se o token expirou e o address ainda existe no mail.tm, renova só o token. Se o address foi deletado pelo mail.tm (acontece após dias de inatividade), cria conta nova com o mesmo padrão.\n\nArquivo: `anonymous-mail.sh`, bloco `# -------- provisiona ou recarrega --------`.\n\n#### 6.11 Credenciais com mode 600\n\n```bash\n( umask 077\n  jq -nc --arg address \"$ADDRESS\" ... \u003e \"$CRED_FILE\" )\n```\n\n`umask 077` dentro de subshell garante que `.anon-mail` é criado com `rw-------` (mode 600). Sem isso, outros usuários no mesmo sistema poderiam ler o token JWT e drenar sua caixa de entrada.\n\nArquivo: `anonymous-mail.sh`, função `save_creds()`.\n\n---\n\n### 7. Camada cross-platform (`lib/platform.sh`)\n\n#### 7.1 Detecção de SO e distro\n\n```bash\ncase \"$(uname -s)\" in\n    Linux)  GHOST_OS=linux ;;\n    Darwin) GHOST_OS=macos ;;\n```\n\nDepois (só no Linux) lemos `/etc/os-release` e classificamos `ID` + `ID_LIKE` em três famílias: `debian`, `arch`, `fedora`. Distros derivadas (Ubuntu, Pop!_OS, Manjaro, Nobara, etc.) são detectadas via `ID_LIKE` — o script funciona em qualquer derivativo sem ajuste manual.\n\nArquivo: `lib/platform.sh`, funções `anon_os()` e `anon_linux_distro()`.\n\n#### 7.2 Dispatch de package manager\n\n```bash\nanon_pkg_install() {\n    case \"$(anon_pkg_manager)\" in\n        apt)    sudo apt install -y \"$@\" ;;\n        pacman) sudo pacman -S --noconfirm --needed \"$@\" ;;\n        dnf)    sudo dnf install -y \"$@\" ;;\n        brew)   brew install \"$@\" ;;\n    esac\n}\n```\n\nTodos os comandos de instalação/remoção/check passam por essas funções. O `install.sh` chama `anon_pkg_install` agnóstico do package manager. Adicionar suporte a uma nova distro = uma linha na tabela.\n\n`brew` no macOS dispensa `sudo` (intencional do Homebrew). `pacman --needed` evita reinstalar pacotes já presentes. `apt -y`/`dnf -y` aceitam confirmações automaticamente.\n\nArquivo: `lib/platform.sh`, funções `anon_pkg_*`.\n\n#### 7.3 Dispatch de serviço (systemctl vs brew services)\n\n```bash\nanon_service_enable() {\n    case \"$(anon_os)\" in\n        linux) sudo systemctl enable --now \"$1\" ;;\n        macos) brew services start \"$1\" ;;\n    esac\n}\n```\n\nNo macOS, `brew services start` já persiste entre boots — não há \"enable\" separado, então `enable` = `start`. No Linux usamos `systemctl enable --now` que faz as duas operações de uma vez. Quem chama (`install.sh`) trata as duas plataformas com a mesma chamada.\n\nArquivo: `lib/platform.sh`, funções `anon_service_*`.\n\n#### 7.4 Port-check portável (`nc -z`)\n\n```bash\nanon_port_open() {\n    nc -z -w 2 \"$1\" \"$2\" \u003e/dev/null 2\u003e\u00261\n}\n```\n\n`nc -z` (zero-I/O port scan) funciona em Linux (`netcat-openbsd`/`openbsd-netcat`/`nmap-ncat`) **e** macOS (BSD `nc`). Não usamos `/dev/tcp` porque o `/bin/bash` do macOS é 3.2.57 e **não tem suporte a `/dev/tcp`** — falharia silenciosamente.\n\n`-w 2` é timeout de 2s. Tor responde em \u003c100ms; se demorar 2s, algo está errado e queremos saber rápido.\n\nArquivo: `lib/platform.sh`, função `anon_port_open()`.\n\n#### 7.5 Bash 3.2 portabilidade\n\n`/bin/bash` no macOS é 3.2.57 (de 2007, último Apple-shipped antes do GPL3). Nada de:\n\n- `mapfile` / `readarray` (bash 4+)\n- `${var,,}` lowercase expansion (bash 4+)\n- Associative arrays `declare -A` (bash 4+)\n- `\u0026\u003e` redirect (bash 4+, embora tolerado)\n\nSubstituições que usamos:\n\n| Em vez de... | Usamos... |\n|---|---|\n| `mapfile -t arr \u003c file` | `arr=(); while IFS= read -r l; do arr+=(\"$l\"); done \u003c file` |\n| `${var,,}` | `printf '%s' \"$var\" \\| tr '[:upper:]' '[:lower:]'` |\n| `[[ -v VAR ]]` | `[[ -n \"${VAR:-}\" ]]` |\n| `read -d ''` | `IFS= read -r` em loop |\n\nArquivo: `lib/platform.sh` (comentários no header explicitam a restrição).\n\n#### 7.6 `$TMPDIR` awareness\n\n`/tmp` no Linux, `/var/folders/.../T/` no macOS (via `$TMPDIR`). Hardcoding `/tmp` quebraria o macOS. Função `anon_tmp_prefix()` retorna `${TMPDIR:-/tmp}` — `mktemp -d` aceita ambos.\n\nArquivo: `lib/platform.sh`, função `anon_tmp_prefix()`.\n\n---\n\n### 8. Camada de instalação (`install.sh`)\n\n#### 8.1 Tracking rastreado de pacotes instalados\n\nTudo que `install.sh` instala é gravado em `~/.cache/anonymous-browser/installed-pkgs`, uma linha por pacote. `uninstall.sh` lê esse arquivo e só remove o que **nós** colocamos — nunca o que o usuário já tinha. Se você já tinha `curl` antes, ele continua depois do `uninstall.sh`.\n\nArquivo: `install.sh`, variável `PKG_TRACK_FILE`.\n\n#### 8.2 Tags por origem (`pkg:`/`cask:`/`flatpak:`/`wrapper:`)\n\n```\npkg:tor\npkg:python3-venv\ncask:firefox        # legado macOS (não usado atualmente)\nflatpak:com.brave...  # legado Pop!_OS\nwrapper:/home/user/.local/bin/anonymous-browser\n```\n\nO prefixo permite o `uninstall.sh` dispatchar pra ferramenta certa: `brew uninstall --cask`, `flatpak uninstall --user`, `rm` direto pro wrapper, etc.\n\nArquivo: `install.sh` (escrita) + `uninstall.sh` (leitura por prefixo).\n\n#### 8.3 Ubuntu Noble t64 transition detection\n\nUbuntu 24.04 (Noble) e Pop!_OS 24.04 fizeram a transição `time_t` para 64-bit em 32-bit ARMv7, renomeando dezenas de libs com sufixo `t64`:\n\n- `libasound2` → `libasound2t64`\n- `libgtk-3-0` → `libgtk-3-0t64`\n\nSe você instala `libasound2` no Noble, o apt aceita (stub virtual), mas a versão real é `libasound2t64`. Detectamos qual é o pacote **real** (não-stub) via `apt-cache show ... | grep '^Filename:'`:\n\n```bash\npick_pkg() {\n    for cand in \"$@\"; do\n        if apt-cache show \"$cand\" 2\u003e/dev/null | grep -q '^Filename:'; then\n            echo \"$cand\"; return 0\n        fi\n    done\n}\nLIBASOUND=$(pick_pkg libasound2t64 libasound2)\n```\n\nPega o primeiro candidato com `Filename:` (pacote real). Funciona em Ubuntu 22.04 (escolhe `libasound2`) e 24.04 (escolhe `libasound2t64`).\n\nArquivo: `install.sh`, bloco do `case` `debian)`.\n\n#### 8.4 Idempotência\n\nCada step do `install.sh` checa antes de agir:\n\n- Pacotes: `anon_pkg_is_installed` antes de instalar\n- Serviço Tor: `anon_service_is_active tor` antes de enable\n- Venv: `[[ -d \"$VENV\" ]]` antes de criar\n- Wrapper: `mkdir -p` e overwrite (sempre regenera, sem corromper estado)\n\nRodar `install.sh` 10 vezes seguidas é seguro. A segunda execução em diante imprime `[=]` (\"já presente\") pra cada step.\n\n#### 8.5 Validação Tor no fim com endpoint Tor-friendly\n\nAntes de declarar sucesso, `install.sh` testa Tor com `https://check.torproject.org/api/ip` (mesmo motivo que §1.5 — `ipinfo.io` foi removido como primário por ficar Cloudflare-blocked). Se a saída do exit responde com IP válido, marca tor como `[+]` no resumo final. Se falha, marca `[!]` mas **não aborta** — você pode estar atrás de DPI que precisa de bridges, e instalação continua.\n\nArquivo: `install.sh`, bloco `# -------- 4. validação final --------`.\n\n#### 8.6 Wrapper local em `~/.local/bin` (skipável)\n\nPor default, `install.sh` cria `~/.local/bin/anonymous-browser` apontando pra `$SCRIPT_DIR/anonymous.sh` (modo clone). Quando rodado via `npm` (que já forneceu o binário global), o launcher Node seta `ANON_SKIP_WRAPPER=1` antes de chamar `install.sh`, e essa step é pulada — evita dois `anonymous-browser` no PATH com mesma função.\n\nArquivo: `install.sh`, bloco `# -------- 4b. wrapper global --------`.\n\n---\n\n### 9. Camada de distribuição (npm shim)\n\n#### 9.1 Por que Node como shim universal\n\n`npm` é a forma mais ubíqua de instalar CLIs globais hoje (mais até que `brew` ou `cargo install` — todo dev tem npm). Mas o `anonymous-browser` é shell + Python. Solução: um shim Node em `bin/anonymous-browser.js` que **apenas** orquestra (`spawn('bash', [anonScript])`). Mantém shell como tecnologia primária; usa Node só pra distribuição.\n\nArquivo: `bin/anonymous-browser.js`.\n\n#### 9.2 Lazy install (postinstall NÃO baixa Tor)\n\n`postinstall.js` **só** corrige execbits dos `.sh` (npm às vezes preserva mode 644 do tarball). **Não** roda `install.sh`. Razão: `npm i -g` frequentemente roda como root (via `sudo`), e `install.sh` cria `~/.camoufox-venv` — que seria criado em `/root` por engano. Pior: `sudo npm i -g` em sistemas onde o usuário não passou `--unsafe-perm` falha em rodar postinstall scripts complexos.\n\nSolução: na **primeira execução** de `anonymous-browser`, se `~/.camoufox-venv` não existe, o shim chama `install.sh` no usuário corrente. Aí sim ele tem `$HOME` certo e roda `sudo` interativamente quando necessário.\n\nArquivo: `bin/anonymous-browser.js`, bloco `if (!fs.existsSync(venv))`.\n\n#### 9.3 TTY detection no prompt\n\n```javascript\nif (process.stdin.isTTY \u0026\u0026 process.stdout.isTTY) {\n    const ans = await ask('[anonymous-browser] Quer e-mail temporário descartável? [y/N] ');\n    ...\n} else {\n    process.env.MAIL = '0';\n}\n```\n\nEm uso humano (terminal), pergunta sobre MAIL. Em scripts (stdin redirecionado de pipe/arquivo), assume `MAIL=0` e segue silenciosamente — não trava esperando input que nunca vem.\n\nArquivo: `bin/anonymous-browser.js`, função `(async () =\u003e ...)`.\n\n#### 9.4 `chmod +x` best-effort\n\n```javascript\nfor (const s of ['anonymous.sh', 'install.sh', 'new-tor-circuit.sh', 'anonymous-mail.sh']) {\n    try { fs.chmodSync(path.join(pkgRoot, s), 0o755); } catch (_) {}\n}\n```\n\nTarball npm às vezes preserva execbits da source, às vezes não (depende da versão do npm e do registry). Em vez de confiar, o shim faz chmod logo no boot. `try/catch` ignora EPERM (sistema readonly, etc.).\n\nArquivo: `bin/anonymous-browser.js` (logo após `require`s).\n\n#### 9.5 Signal propagation\n\nQuando o usuário dá Ctrl+C no `anonymous-browser`, o sinal precisa chegar ao `anonymous.sh` (que tem o trap). Implementamos `spawn(..., { stdio: 'inherit' })` — o shell filho herda stdin/stdout, e Node propaga sinais. Quando o filho morre por sinal, repassamos:\n\n```javascript\nchild.on('exit', (code, signal) =\u003e {\n    if (signal) { process.kill(process.pid, signal); return; }\n    process.exit(code == null ? 0 : code);\n});\n```\n\nSem isso, `anonymous-browser` retornaria exit 0 mesmo o shell tendo sido killed por SIGTERM — mascara o erro pro shell pai.\n\nArquivo: `bin/anonymous-browser.js`, `child.on('exit', ...)`.\n\n---\n\n### 10. Limitações honestas (o que **não** defendemos)\n\nTodas as defesas acima cobrem **fingerprinting passivo** — o que sites veem de você sem cooperar com vendors anti-bot pagos. Existem vetores que esta stack **não** trata:\n\n#### 10.1 TLS JA3 / JA4\n\nA assinatura TLS ClientHello (cipher suites, extensões, ordem) revela \"isso é Firefox real\" vs \"isso é Firefox patched\". Camoufox **não** patcha o stack TLS — a JA3 dele é a de um Firefox normal, mas detectores comparando JA3 entre clientes podem notar. Solução real exige reescrever curl/openssl no nível abaixo do navegador — fora do escopo.\n\n#### 10.2 HTTP/2 SETTINGS frame ordering\n\nMesmo princípio: a ordem de frames HTTP/2 que o browser manda na primeira conexão é fingerprintable. Camoufox segue Firefox upstream — coerente com `os=\"linux\"` e levemente fora se você spoofou `os=\"macos\"` (Firefox no macOS tem ordem ligeiramente diferente).\n\n#### 10.3 Behavioral biometrics\n\nCadência de digitação, padrões de movimento de mouse, scroll velocity — Datadome, Akamai BMP, Kasada usam ML treinado em centenas de milhões de sessões pra distinguir humano de bot. `humanize=True` melhora vs detector ingênuo; não engana ML maduro.\n\n#### 10.4 Enterprise bot management\n\nCloudflare Bot Management ($120k/ano), Datadome, PerimeterX, Shape (F5), Kasada — esses caras combinam TLS+HTTP/2+behavioral+device-graph. Se você precisa passar por isso, alternativas pagas: GoLogin, Multilogin, AdsPower (eles licenciam fingerprints reais via SDK proprietário). Não é o caso de uso desta stack.\n\n#### 10.5 Mobile fingerprint\n\nCamoufox upstream **não** suporta `os=\"ios\"` nem `os=\"android\"`. Tentar spoofar `User-Agent` de iPhone sem o resto (touch events, gyroscope, screen pixel ratio, dpr, sensor APIs) seria fingerprint imediatamente detectado como inconsistente. Removemos quaisquer presets mobile do código pra não dar falsa sensação.\n\n#### 10.6 IP via WebRTC mDNS\n\nMesmo com `block_webrtc=True`, browsers modernos opcionalmente revelam IPs de mDNS local (`.local` addresses) via Network Information API em alguns contextos. Camoufox bloqueia o stack WebRTC inteiro como default, mas se o usuário (ou uma extensão) reativar via about:config, leak volta. **Não toque em `media.peerconnection.enabled`** se você se preocupa com isso.\n\n#### 10.7 Frescor de User-Agent\n\nA versão do Firefox que Camoufox spoofa segue o release do upstream Camoufox. Se Mozilla solta Firefox 142 e Camoufox ainda está em 135, seu UA dirá 135 enquanto humanos reais já estão em 142 — sinal fraco mas crescente. Mitigar: rodar `python -m camoufox fetch` mensalmente (manualmente; o `install.sh` puxa o latest mas não há auto-update entre rodadas).\n\n#### 10.8 Network-level deanonymization\n\nEsta stack rotaciona **identidade de aplicação** (IP, UA, fingerprint). **Não** protege contra adversário que observa o tráfego TLS ao redor do seu PC (ISP, rede corporativa, MITM com cert raiz instalado). Tor protege metadata de rede; se você precisa de privacidade contra adversário com cert root no seu device, problema é maior que browser fingerprint.\n\n---\n\n## Instalação\n\n### Via npm (recomendado)\n\n```bash\nnpm i -g anonymous-browser\n```\n\nIsso instala um comando `anonymous-browser` no seu PATH. Na primeira execução, ele dispara `install.sh` automaticamente — instala Tor + libs nativas via `sudo` (Linux) ou `brew` (macOS), cria o venv Python e baixa o binário Camoufox.\n\nA cada execução depois disso, `anonymous-browser` pergunta se você quer um e-mail temporário descartável (`MAIL=1`) e abre o browser. Para pular o prompt, exporte `MAIL=0` ou `MAIL=1` antes.\n\n### Via clone (modo dev)\n\n`install.sh` detecta S.O. **e distro** automaticamente. Mesmo comando nas três famílias Linux principais e no macOS:\n\n```bash\n./install.sh\n```\n\n### Plataformas suportadas\n\n| Família | Distros confirmadas | Package manager |\n|---|---|---|\n| Debian | Ubuntu, Pop!_OS, Debian, Mint | `apt` |\n| Arch | Arch, Manjaro, EndeavourOS, CachyOS | `pacman` |\n| Fedora | Fedora, Nobara, RHEL, Rocky, AlmaLinux | `dnf` |\n| macOS | macOS 13+ | `brew` (Homebrew obrigatório) |\n\n\u003e Camoufox traz Firefox bundled — não há dependência de navegador do sistema. Em qualquer distro Linux com `tor`, `python3` e libs GTK/X11 básicas, o `anonymous.sh` funciona.\n\n### Requisitos por S.O.\n\n**Linux** — usa `sudo` na primeira execução pro package manager nativo (`apt`/`pacman`/`dnf`) e `systemctl`.\n\n**macOS** — requer [Homebrew](https://brew.sh) **pré-instalado**; o `install.sh` orienta o usuário caso esteja faltando. Não usa `sudo` (brew dispensa root).\n\nO `install.sh` é idempotente e fala muito. Ele instala (só o que falta):\n\n- `tor` (proxy SOCKS5 em `127.0.0.1:9050`) — `systemd` no Linux, `brew services` no macOS\n- Libs runtime do Camoufox (nomes diferem por distro: `libgtk-3-0t64` no Debian, `gtk3` no Arch/Fedora, etc.). macOS dispensa — Camoufox usa Firefox Cocoa nativo.\n- venv Python em `~/.camoufox-venv` com `camoufox[geoip]`\n- binário Camoufox (Firefox patched) + dataset GeoIP (~300 MB)\n- valida saída Tor em endpoints Tor-friendly (`check.torproject.org`, `api.ipify.org`)\n\nNo fim, imprime um **resumo categorizado**: `[+]` instalado agora, `[=]` já presente, `[!]` falhou. Se algo está em `[!]`, [veja FIXES.md](FIXES.md) para diagnóstico.\n\n### Configuração do ControlPort do Tor (opcional, mas recomendado)\n\nSem ControlPort 9051, `new-tor-circuit.sh` cai para um reload do serviço Tor — funciona mas é mais lento e fecha conexões em andamento. Para habilitar a troca rápida de circuito:\n\n- **Linux**: edite `/etc/tor/torrc` adicionando `ControlPort 9051` + `CookieAuthentication 0`, depois `sudo systemctl restart tor`.\n- **macOS**: `brew install tor` deixa apenas `torrc.sample`. Crie o `torrc` primeiro:\n  ```bash\n  cp \"$(brew --prefix)/etc/tor/torrc.sample\" \"$(brew --prefix)/etc/tor/torrc\"\n  printf '\\nControlPort 9051\\nCookieAuthentication 0\\n' \u003e\u003e \"$(brew --prefix)/etc/tor/torrc\"\n  brew services restart tor\n  ```\n\nReverter tudo:\n\n```bash\n./uninstall.sh\n```\n\nRemove venv, cache do Camoufox (XDG no Linux ou `~/Library/Caches/camoufox` no macOS), perfis temporários, e pergunta antes de remover pacotes (só o que foi rastreado em `~/.cache/anonymous-browser/installed-pkgs`). Se houver perfis persistentes em `~/.anonymous-browser/profiles/`, também pergunta interativamente antes de apagá-los.\n\n---\n\n## Uso\n\n### Forma básica\n\n```bash\n./anonymous.sh                            # pergunta URL interativamente\n./anonymous.sh https://site.com/signup    # one-liner\n./anonymous.sh youtube.com                # esquema é opcional, prepende https://\n```\n\nCada execução:\n\n1. Detecta S.O. e inicia o Tor se ele estiver parado (`systemctl start tor` no Linux, `brew services start tor` no macOS).\n2. Força novo circuito Tor (`SIGNAL NEWNYM` se ControlPort estiver aberto, senão reload do serviço).\n3. Sorteia OS spoofado (`windows` | `macos` | `linux`).\n4. Cria perfil descartável em `$TMPDIR/anon-XXXXXX` (`/tmp/...` no Linux, `/var/folders/.../anon-...` no macOS).\n5. Abre Camoufox com fingerprint coerente + Tor + GPS negado silenciosamente.\n6. Bloqueia até você fechar o navegador.\n7. Apaga o perfil no exit (Ctrl+C, X do terminal, X do navegador, kill, crash — tudo).\n\n### Receitas comuns\n\n```bash\n# padrão: Tor + OS aleatório + perfil descartável\n./anonymous.sh https://site.com\n\n# usando VPN própria (Mullvad, ProtonVPN paga, qualquer SOCKS5/HTTP)\nPROXY=socks5://10.2.0.1:1080 ./anonymous.sh\n\n# sem proxy (IP real, mas fingerprint trocado) — útil para sites internos\nPROXY=none ./anonymous.sh\n\n# força um OS específico (sem aleatório)\nANON_OS=macos ./anonymous.sh\n\n# identidade persistente \"trabalho\" (cookies + OS fixos entre sessões)\nKEEP=trabalho ./anonymous.sh https://gmail.com\n\n# cria identidade nova com OS escolhido manualmente\nKEEP=pessoal ANON_OS=windows ./anonymous.sh\n\n# + e-mail descartável: imprime o endereço e mostra os e-mails\n#   recebidos em tempo real NO MESMO terminal (útil pra código de verificação)\nMAIL=1 ./anonymous.sh https://site.com/signup\n\n# e-mail persistente junto da identidade persistente (mesmo endereço sempre)\nMAIL=1 KEEP=trabalho ./anonymous.sh https://gmail.com\n\n# se o exit Tor estiver bloqueado pelo Cloudflare do mail.tm, manda só o\n# e-mail direto (o navegador continua via Tor):\nMAIL=1 ANON_MAIL_PROXY=none ./anonymous.sh https://site.com/signup\n```\n\n### Variáveis de ambiente\n\n| Variável | Valores | Efeito |\n|---|---|---|\n| `PROXY` | `tor` (default) \\| `none` \\| `socks5://host:port` \\| `http://host:port` \\| `https://host:port` | Sobrescreve o proxy Tor padrão. `none` desliga proxy (usa IP real). |\n| `KEEP` | qualquer nome `[A-Za-z0-9_-]+` | Salva o perfil em `~/.anonymous-browser/profiles/\u003cnome\u003e/`. OS é fixado na primeira vez. Sem `KEEP`, o perfil é descartado no fim. |\n| `ANON_OS` | `windows` \\| `macos` \\| `linux` (aceita maiúsculas; é normalizado para lowercase) | Força um OS específico (sem sorteio). Combinado com `KEEP`, fixa o OS persistente. |\n| `USE_TOR` (legado) | `0` | Alias de `PROXY=none`. Mantido por compat com docs antigas. |\n| `MAIL` | `1` | Gera um e-mail descartável (mail.tm) e mostra os recebidos em tempo real no mesmo terminal. Usa o mesmo `PROXY` e o mesmo perfil do navegador. Com `KEEP`, o endereço persiste entre sessões; sem `KEEP`, a conta é apagada no exit. |\n| `ANON_MAIL_POLL` | segundos (default `5`, mínimo `2`) | Intervalo de checagem da caixa de entrada. |\n| `ANON_MAIL_PROXY` | `tor` \\| `none` \\| `socks5://...` \\| `http(s)://...` | Override de proxy **só pro e-mail** (o navegador segue no `PROXY`). Use `none` se o exit Tor estiver bloqueado pelo Cloudflare do mail.tm. |\n\n\u003e **E-mail descartável (`MAIL=1`):** o endereço é criado no [mail.tm](https://mail.tm) — serviço **gratuito, sem API key e sem cadastro** (rate limit 8 req/s). _Inbox by mail.tm._ Como qualquer serviço de e-mail temporário, **não use para nada sensível**: as mensagens são públicas pra quem souber o endereço. Conta efêmera é deletada ao fechar o navegador / Ctrl+C.\n\n\u003e **Tor × Cloudflare no mail.tm:** o `MAIL=1` roteia as chamadas pelo mesmo Tor do navegador (consistência de IP). Exit nodes Tor às vezes levam desafio do Cloudflare e a criação da caixa falha — o `anonymous-mail.sh` avisa e segue **sem derrubar o navegador**. Soluções: `./new-tor-circuit.sh` (troca o exit) ou `MAIL=1 ANON_MAIL_PROXY=none ./anonymous.sh ...` (e-mail direto, navegador ainda via Tor).\n\n\u003e **Schemes de proxy aceitos:** `socks5://`, `http://`, `https://`. O Playwright (engine do Camoufox) não suporta `socks4://` oficialmente — usar `socks4://` resulta em erro do Camoufox.\n\n\u003e **Privacidade com `PROXY=none`:** quando você desliga o proxy, o `anonymous.sh` também desativa `geoip` automaticamente. Sem isso, Camoufox tentaria buscar seu IP real em `api.ipify.org` (ou fallback) para casar locale/timezone — o que vazaria o IP que você quer esconder. Trade-off: sem `geoip`, locale/timezone do Firefox podem não bater com sua região, mas seu IP real fica em casa.\n\n\u003e **Perfil persistente em paralelo:** Firefox usa um arquivo `parent.lock` dentro do `user_data_dir`. Rodar `KEEP=foo ./anonymous.sh` duas vezes simultaneamente faz a segunda instância travar com timeout. Use nomes diferentes (`KEEP=foo` + `KEEP=bar`) para rodar em paralelo.\n\n### Helpers\n\n- **`./new-tor-circuit.sh`** — força IP novo entre execuções. Já é chamado pelo `anonymous.sh` quando o proxy é Tor. Pra rodar standalone, abra `ControlPort 9051` no torrc (caminho depende do S.O. — Linux: `/etc/tor/torrc`; macOS: `$(brew --prefix)/etc/tor/torrc`). Veja a seção \"Configuração do ControlPort do Tor\" acima.\n- **`./anonymous-mail.sh`** — e-mail descartável com leitura em tempo real, standalone (sem abrir navegador). Imprime o endereço e fica mostrando os e-mails recebidos. Aceita `PROXY`, `ANON_MAIL_PROXY`, `ANON_MAIL_POLL` e `KEEP` (endereço persistente, mesmo padrão de perfil do `anonymous.sh`). Já é disparado automaticamente por `MAIL=1 ./anonymous.sh`. _Inbox by mail.tm._\n\n---\n\n## Validar que funcionou\n\nAbre essas URLs **dentro** da janela aberta pelo `anonymous.sh`:\n\n| Site | O que checar |\n|---|---|\n| [check.torproject.org](https://check.torproject.org) | IP é exit Tor (a badge verde \"Congratulations\" só aparece no Tor Browser oficial; aqui o que importa é o IP retornado bater com o de saída) |\n| [ipinfo.io/json](https://ipinfo.io/json) | IP, país, ASN do exit |\n| [browserleaks.com/javascript](https://browserleaks.com/javascript) | `navigator.platform`, screen, UA — devem casar com OS sorteado |\n| [browserleaks.com/webgl](https://browserleaks.com/webgl) | `UNMASKED_VENDOR/RENDERER` — Camoufox spoofa coerente |\n| [browserleaks.com/fonts](https://browserleaks.com/fonts) | Lista de fontes do OS spoofado, não do seu Linux real |\n| [browserleaks.com/geo](https://browserleaks.com/geo) | \"Permission denied\" — GPS negado sem prompt |\n| [abrahamjuliot.github.io/creepjs](https://abrahamjuliot.github.io/creepjs/) | **Trust Score \u003e70% e zero \"lies\"** — métrica de ouro |\n| [amiunique.org/fingerprint](https://amiunique.org/fingerprint) | Entropia / unicidade |\n\nValidar IP no terminal:\n\n```bash\ncurl -s --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip\n# {\"IsTor\":true,\"IP\":\"107.189.5.121\"}\n```\n\n---\n\n## Comparação: anonymous-browser vs Tor Browser\n\n| | `anonymous-browser` (este repo) | Tor Browser oficial |\n|---|---|---|\n| Engine | Camoufox (Firefox patched em C++) | Firefox ESR + patches Tor |\n| Filosofia | Você finge ser outro device | Você se uniformiza com todo mundo |\n| Troca UA | ✅ | n/a (todos têm o mesmo) |\n| Troca `navigator.platform` | ✅ | ✅ (mas todo mundo tem o mesmo) |\n| Troca WebGL/canvas/audio/fonts | ✅ coerente via BrowserForge | ✅ via resistFingerprinting (zeros) |\n| Client Hints (`Sec-CH-UA-*`) | ✅ | ✅ |\n| Geo/timezone casados com IP | ✅ (`geoip=True`) | uniforme |\n| Proxy customizado (VPN, etc.) | ✅ (`PROXY=socks5://...`) | ❌ (só Tor) |\n| Identidade persistente entre sessões | ✅ (`KEEP=nome`) | ❌ (sempre descartável) |\n| Perfis mobile | ❌ (Camoufox não suporta) | ❌ |\n| Passa em CreepJS | ✅ Trust \u003e70% típico | ✅ Trust ~80% (homogêneo) |\n| Dependências | apt + venv (~300 MB) | bundle pronto |\n\n**Use `anonymous.sh`** quando quiser **identidade trocada** (parecer outra pessoa específica, com cookies/sessão controláveis). Use **Tor Browser** quando quiser **anonimato uniforme** (se misturar com a multidão, sem variação entre você e os outros usuários).\n\n---\n\n## Estrutura\n\n```\nanonymous-browser/\n├── README.md              # este arquivo\n├── FIXES.md               # histórico de bugs corrigidos (todos fechados)\n├── LICENSE                # MIT\n├── logo.jpg               # mascote (fantasma minimalista, mono)\n├── .gitignore\n├── install.sh             # auto-detecta S.O. + distro Linux (Debian/Arch/Fedora);\n│                          # instala Tor, libs Camoufox, venv Python; resumo [+]/[=]/[!] no fim\n├── uninstall.sh           # remove venv/cache/perfis; pergunta antes de remover pacotes;\n│                          # também pergunta antes de apagar perfis persistentes em ~/.anonymous-browser/\n├── anonymous.sh               # ★ super-comando: PROXY/KEEP/ANON_OS via env, Camoufox+Tor por default\n├── new-tor-circuit.sh     # força SIGNAL NEWNYM (ControlPort 9051) ou reload do serviço\n├── anonymous-mail.sh          # e-mail descartável (mail.tm) + leitura em tempo real no terminal\n└── lib/platform.sh        # detecção de S.O. + distro + dispatch de package manager\n                           # (apt/pacman/dnf/brew); bash 3.2 portable\n\n# estado (não versionado):\n~/.anonymous-browser/profiles/  # perfis persistentes criados por KEEP=nome\n~/.camoufox-venv/           # venv com Camoufox + BrowserForge + GeoIP\n~/.cache/anonymous-browser/     # track-file de pacotes instalados pelo install.sh\n```\n\n---\n\n## Limitações \u0026 honestidade\n\n1. **Não é silver bullet.** Anti-bot enterprise (Cloudflare BM, DataDome) detecta Camoufox via TLS/HTTP-2 fingerprint. Esta stack mira tracking publicitário e cadastros normais — não nações-estado, não Akamai-fronted login flows.\n2. **Tor é lento.** Em média 5–15s pra primeira requisição. Cloudflare desafia exit nodes. Se um site bloquear, troque por VPN própria: `PROXY=socks5://seu-vpn:1080 ./anonymous.sh`.\n3. **User-Agents envelhecem.** O Camoufox/BrowserForge atualizam UAs automaticamente. Pra puxar o dataset mais recente: `source ~/.camoufox-venv/bin/activate \u0026\u0026 python -m camoufox fetch`.\n4. **Mullvad Browser e Tor Browser homogeneízam, não personificam.** Útil pra ler anonimamente, inútil pra cadastrar como \"outro alguém\".\n5. **WebRTC permanece bloqueado pelo Camoufox** (`block_webrtc=True`) mesmo com `PROXY=none`, mas DNS lookups vão pelo seu resolver local — sua máquina aparece como Linux normal para o ISP nesse modo.\n6. **Camoufox não emula iPhone/Android.** Documentação oficial só aceita `os=\"windows\"|\"macos\"|\"linux\"`. Pra mobile coerente, alternativas pagas: GoLogin, Multilogin, AdsPower.\n\n---\n\n## Troubleshooting\n\n### Perfil persistente não abre depois de um crash / `kill -9`\n\nFirefox deixa um `parent.lock` (e/ou `.parentlock`) dentro do `user_data_dir`. Se você matou o processo no `kill -9` ou o sistema travou, o lock fica órfão e a próxima execução fica esperando.\n\n```bash\n# checar\nls -la ~/.anonymous-browser/profiles/\u003cnome\u003e/ | grep -i lock\n# limpar (com o anonymous.sh fechado)\nrm -f ~/.anonymous-browser/profiles/\u003cnome\u003e/parent.lock \\\n      ~/.anonymous-browser/profiles/\u003cnome\u003e/.parentlock\n```\n\n### Tor não sobe / `[!] Tor não responde em 127.0.0.1:9050`\n\n```bash\n# Linux\nsudo systemctl status tor\nsudo systemctl restart tor\njournalctl -u tor@default | tail -20\n\n# macOS\nbrew services info tor\nbrew services restart tor\n```\n\nSe o ISP está bloqueando Tor, use `PROXY=socks5://seu-vpn:1080 ./anonymous.sh` com uma VPN.\n\n### `[!] PROXY inválido` mas o valor parece correto\n\nConfira os schemes aceitos: `tor`, `none`, `socks5://`, `http://`, `https://`. `socks4://` não é suportado pelo Playwright.\n\n### Camoufox `InvalidIP: Failed to get IP address` com proxy custom\n\nSignifica que o proxy custom (VPN/SOCKS) não está respondendo. Teste manualmente:\n\n```bash\ncurl -s --socks5-hostname \u003cvpn-host\u003e:\u003cport\u003e https://api.ipify.org\n```\n\nSe não retorna IP, o proxy está fora. Volte ao Tor (`PROXY=tor`) ou conserte o VPN.\n\n---\n\n## Bugs conhecidos\n\nNenhum em aberto até 12/05/2026. Os 3 bugs originais (teste de Tor com `ipinfo.io`, ausência de Chromium em Pop!_OS sem snap, `TypeError` do Camoufox 0.4.11) estão **todos fechados** — histórico técnico completo em [FIXES.md](FIXES.md).\n\nSe algo quebrar depois de uma atualização do Camoufox, Firefox ou Tor: abra issue no GitHub com o stacktrace e o resumo do `./install.sh`.\n\n---\n\n## Manifesto curto\n\nPrivacy não é sobre esconder. É sobre **escolher** o que mostrar, pra quem, e quando. Sites que coletam fingerprint sem te perguntar quebraram esse contrato primeiro. Esta ferramenta é uma resposta proporcional.\n\nNão promove fraude. Não burla mecanismos de pagamento. Não invade sistemas. Faz uma coisa só: te devolve o controle de qual identidade seu browser apresenta ao internet.\n\nA legalidade depende de jurisdição e de termos-de-uso do destino. Em quase todos os lugares civilizados, **trocar UA, IP e fingerprint é permitido**. Burlar ToS é cinza. Fraude documental é crime — em qualquer lugar, com ou sem esta ferramenta. **Você é o operador, você assume as consequências.** Esta linha não tem como ser apagada por boa intenção do dev.\n\n---\n\n## Inspirações \u0026 afins\n\n- [Tor Project](https://www.torproject.org/) — o original\n- [Camoufox](https://github.com/daijro/camoufox) — Firefox C++ patched, faz o trabalho pesado\n- [BrowserForge](https://github.com/daijro/browserforge) — fingerprints coerentes\n- [EFF Privacy Badger](https://privacybadger.org/), [uBlock Origin](https://ublockorigin.com/), [Mullvad VPN](https://mullvad.net/)\n- [Cypherpunks Manifesto](https://www.activism.net/cypherpunk/manifesto.html), [Crypto Anarchist Manifesto](https://www.activism.net/cypherpunk/crypto-anarchy.html)\n- [EFF Cover Your Tracks](https://coveryourtracks.eff.org/) — entenda o quanto você vaza\n\n---\n\n## Licença\n\nMIT. Faça fork. Faça merge. Mande PR. Mande issue. Se algo quebrar com uma atualização do Camoufox/Firefox, abra issue com o stacktrace — esta stack vai precisar de manutenção contínua porque o lado adversário também não dorme.\n\n\u003e \"If privacy is outlawed, only outlaws will have privacy.\"\n\u003e — Phil Zimmermann (criador do PGP)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrederico-kluser%2Fanonymous-browser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffrederico-kluser%2Fanonymous-browser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrederico-kluser%2Fanonymous-browser/lists"}