dumbtv
dumbtv takes the ads, screen tracking and update nags out of your Fire TV over ADB. No root, none of your apps touched, every step can be undone. Free and open source.
Automated data broker opt-out runner — removes your personal info from 30+ people-search sites on a monthly schedule
SecurityJavaScript · ShellGitHub @stephenlthornSelf-installOwner unconfirmed
Paste into Claude or ChatGPT for installation instructions suited to your environment.
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Installable projects can be verified without a deployment domain. Use GitHub sign-in for a personal repository, or the repository verification file above for an organization repository.
Ask about other verification options
Loading the full README. The saved excerpt is shown below.
Automated data broker opt-out runner for macOS, Linux, and Windows. Removes your personal information from 500+ people-search sites and data broker databases on a monthly schedule - with CAPTCHA solving, persistent state tracking (so completed opt-outs aren't resubmitted every run), and an iMessage notification when done. Privacy & data flow ->Read the full README ↗Automated data broker opt-out runner for macOS, Linux, and Windows. Removes your personal information from 500+ people-search sites and data broker databases on a monthly schedule - with CAPTCHA solving, persistent state tracking (so completed opt-outs aren't resubmitted every run), and an iMessage notification when done. Privacy & data flow ->
Each month, the script:
Beyond the monthly run, it can also:
aidr score), built from verified removals, search-engine visibility, and breach dataaidr verify), watch search engines for new listings (aidr serp-watch), and generate a monthly PDF report (aidr report)aidr know) and auto-generate regulator complaints when a broker blows the legal deadline (aidr complaints)aidr breach) and walk a credit/identity-freeze checklist (aidr freeze)config.json at rest (AES-256-GCM, opt-in via AIDR_PASSPHRASE), and detect success/confirmation text in 6 languagesaidr dashboard)aidr update-brokers), and keep brokers you want to stay listed on via an allowlistnpx playwright install chromium
# 1. Clone the repo
git clone https://github.com/stephenlthorn/auto-identity-remove.git
cd auto-identity-remove
# 2. One-command install (checks Node, installs deps + the Chromium browser)
bash install.sh
# 3. Run interactive setup (creates config.json and schedules the monthly job)
node bin/aidr.js setup
# 4. Preview what it will do - submits nothing
node bin/aidr.js preview
# 5. Run for real anytime
node bin/aidr.js run
Tip: run
npm link(ornpm i -g .) so you can typeaidrdirectly instead ofnode bin/aidr.js. There is no./node_modules/.bin/aidr- npm only creates.binshims for dependencies, never for the package's ownbin.
aidr commandaidr is a friendly wrapper around the underlying scripts. Every subcommand
maps to an existing entrypoint:
| Command | What it does |
|---|---|
aidr setup | Interactive first-run setup (creates config.json, schedules the monthly job) |
aidr preview | Dry-run: fills forms but submits nothing |
aidr run | Runs the opt-out pass for real |
aidr verify | Re-searches brokers and reports whether you still appear |
aidr score | Prints your 0-100 exposure score and its month-over-month trend |
aidr report | Generates the monthly PDF + emails the summary |
aidr pending | Lists brokers awaiting an email-confirmation click |
aidr serp | Scans search engines for where your name still ranks |
aidr serp-watch | Scans and alerts only when your name appears on a NEW domain |
aidr breach | Checks Have I Been Pwned and recommends a credit freeze on high-severity breaches |
aidr freeze | Shows the credit/identity-freeze checklist and its status |
aidr complaints | Generates regulator complaints for brokers past the CCPA/GDPR deadline |
aidr know | Sends CCPA/GDPR right-to-know requests |
aidr update-brokers | Refreshes the broker list from the CA + Vermont registries |
aidr list | Lists configured brokers and their last-known status |
aidr doctor | Self-diagnoses your environment and configuration |
aidr dashboard | Starts the local web dashboard and prints its URL + a one-time login |
Pass extra flags straight through, e.g. aidr run --only Spokeo or
aidr preview --skip BeenVerified. Run aidr --help for the full list.
Every subcommand maps to the equivalent node watcher.js --<flag> invocation.
A native desktop wrapper (Electron/Tauri) is a planned follow-up and is not included here - this release is clean CLI packaging only.
node setup.js guides you through:
| Step | What it does |
|---|---|
| Personal info | Name, city, state, ZIP, email, phone |
| Aliases | Past names or variations (e.g. "Steve Doe") |
| CapSolver key | For CAPTCHA-protected opt-out forms |
| One-time accounts | Creates accounts on sites that require login (stored in config.json, gitignored) |
| iMessage | Phone number to text the results summary to |
| Monthly schedule | Registers a monthly job to run on the 1st at 9am (launchd / systemd / crontab / schtasks - detected automatically) |
Your personal info never leaves your machine. config.json and state.json are both gitignored.
Some opt-out forms have reCAPTCHA. Without CapSolver, those sites go to your manual list instead of being handled automatically.
setup.js asks, or add it to config.json:"capsolver": {
"apiKey": "CAP-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
CapSolver is optional. Without it, CAPTCHA-protected sites are flagged as manual and opened in your browser for completion. Pass
--no-capsolverto skip them entirely rather than opening the browser.
The Dockerfile uses the official Playwright image, so Chromium and all system
dependencies are pre-installed. No Mac required. Builds for linux/amd64 and
linux/arm64; the amd64 image is about 940 MB.
# Build the image (once)
docker build -t auto-identity-remove .
# Dry-run - navigates and checks reachability, submits nothing
docker run --rm --init \
-v "$(pwd)/config.json:/app/config.json" \
-v "$(pwd)/state.json:/app/state.json" \
-v "$(pwd)/logs:/app/logs" \
-e TZ="$(date +%Z)" \
auto-identity-remove node watcher.js --dry-run
# Full run
docker run --rm --init \
-v "$(pwd)/config.json:/app/config.json" \
-v "$(pwd)/state.json:/app/state.json" \
-v "$(pwd)/logs:/app/logs" \
-e TZ="$(date +%Z)" \
auto-identity-remove
Or with compose: docker compose run --rm watcher node watcher.js --dry-run.
Create state.json before the first run - touch state.json is enough. If
the path does not exist, Docker creates a directory there and the run fails on
a confusing EISDIR.
--init matters. Without it node is PID 1, docker stop cannot reap the
Chromium children, and the state lock is left behind, wedging the next run.
TZ matters. Containers are UTC. Without it a "1st of the month, 9am"
schedule and every report timestamp land in the wrong hour.
Prefer mounting a directory over individual files. rename() onto a Docker
single-file bind mount fails with EBUSY, which is how every state write in
Docker used to be silently discarded. There is now a fallback that keeps state
working with file mounts, but it gives up crash-atomicity. To keep the
guarantee, mount a directory and point AIDR_STATE_PATH inside it:
docker run --rm --init \
-v "$(pwd):/app/data" \
-e AIDR_STATE_PATH=/app/data/state.json \
auto-identity-remove
Low-RAM hosts (NAS, Raspberry Pi, small VPS): add -e AIDR_LOW_MEMORY=1. A
full 40-broker run peaks at about 250 MB either way; the flag trades some render
fidelity for less background work on slow cores.
Synology / NAS users: see docs/SYNOLOGY.md for a full DSM Container Manager walkthrough, measured memory numbers, DSM Task Scheduler setup, and the three defects that used to make the container unusable.
When running headless or in Docker you won't have iMessage or a desktop - use
a webhook instead. Set notify.webhook in config.json to any ntfy.sh,
Slack incoming-webhook, or Discord webhook URL:
"notify": {
"textTo": "",
"webhook": "https://ntfy.sh/my-private-channel"
}
The tool POSTs {"text": "<summary>"} after every run. Works on macOS, Linux,
and Windows - the webhook fires in addition to (not instead of) any platform
notification that is available.
auto-identity-remove/
├── setup.js ← Run once: interactive setup + scheduling
├── watcher.js ← Main runner
├── brokers.js ← Broker list with opt-out strategies
├── run.sh ← Manual trigger
├── config.example.json ← Template (copy → config.json)
├── package.json
├── .gitignore
│
├── config.json ← YOUR personal info (gitignored, created by setup.js)
├── state.json ← Opt-out history / skip logic (gitignored)
└── logs/ ← Per-run JSON logs (gitignored)
state.json tracks when each broker was last successfully opted out. The default re-check window is 90 days - brokers typically re-add your data within that window, so the script re-submits when it's time.
{
"optOuts": {
"Spokeo": {
"lastSuccess": "2026-05-01T09:00:00.000Z",
"totalRuns": 3,
"detail": ""
}
}
}
On each run you'll see:
✅ Submitted (form accepted) - opt-out form was submitted this run📧 Awaiting email confirm - broker replied "check your email to confirm"; click the link in your inbox. Auto-retried after 14 days if still pending.⏭ Skipped (fresh) - removed recently, re-check not due yet🔍 Not listed - your name wasn't found on that site📋 Manual needed - opened in your browser for you to handle❌ Error - network/timeout issue, will retry next run💀 Dead (stale URL) - broker URL is gone (DNS/404); not counted as an errorSubmitted ≠ confirmed deleted. Use
node watcher.js --verifyfor spot-check verification. See STATUS.md for a per-broker confidence table.
This tool covers 500+ data brokers in two tiers:
| Tier | Count | Confidence |
|---|---|---|
| Explicit brokers (STATUS.md) | 52 | Hand-mapped with specific selectors. None are currently marked verified - the selectors compile and were correct when written, but none has been re-tested against the live site recently, so any of them may have drifted. |
| Generic runner | ~490 | Best-effort heuristic - tries 4 strategies (Do Not Sell click, OneTrust/TrustArc, generic form, DSAR link). Many succeed; some fail silently. |
Be skeptical of the counts this tool prints. Broker sites change their DOM constantly and a selector that no longer matches fails quietly. That is the central limitation of the whole approach, not a bug that is about to be fixed.
The ✅ Submitted count means the form was accepted by the broker. It does not prove deletion. To check:
node watcher.js --verify - re-searches each broker where a successful opt-out was recorded and reports whether your name still appears.📧 Awaiting email confirm section after each run - these are half-done until you click the link.If you want to know exactly which brokers are hand-verified vs heuristic, see STATUS.md.
| Site | Method |
|---|---|
| Spokeo | Search → find listing → opt-out form |
| WhitePages | Search → find listing → suppression form |
| FastPeopleSearch | Search → opt-out form |
| TruePeopleSearch | Direct opt-out form |
| BeenVerified | Opt-out search form |
| Radaris | Search → privacy form |
| Intelius | Direct opt-out form |
| PeopleFinders | Direct opt-out form |
| PeopleSmart | Direct opt-out form |
| MyLife | Search → opt-out |
| Nuwber | Search → removal form |
| FamilyTreeNow | Direct opt-out form |
| CheckPeople | Direct opt-out form |
| ThatsThem | Direct opt-out form |
| USPhonebook | Direct opt-out form |
| PublicDataUSA | Direct opt-out form |
| SmartBackgroundChecks | Direct opt-out form |
| SearchPeopleFree | Direct opt-out form |
| PeopleSearchNow | Direct opt-out form |
| InfoTracer | Direct opt-out form |
| SocialCatfish | Direct opt-out form |
| NationalPublicData | Direct opt-out form |
| ClustrMaps | Direct opt-out form |
| PrivateRecords | Direct opt-out form |
| Acxiom | Direct form (feeds dozens of downstream brokers) |
| LexisNexis | Direct form (legal/financial data) |
| ZoomInfo | Direct form (B2B professional data) |
| Clearbit | Direct form (B2B enrichment data) |
| Pipl | Email opt-out via Mail.app |
Spam calls are aimed at your number, so these entries are keyed by person.phone, not your name. Caller-ID apps build their directories from uploaded contact books; reverse-lookup sites are where call lists get a name for a number.
| Site | Method |
|---|---|
| Truecaller | Manual - needs an SMS or WhatsApp message sent from your phone to prove it is yours |
| Sync.ME | Removal form (US numbers) |
| CallApp | Unlisting form (needs your number with its country code) |
| NumLookup | Phone opt-out form (US numbers) |
| Hiya | Email to its DPO (Hiya supplies caller ID to Samsung phones and several carriers) |
| Eyecon | Email to its privacy contact |
| National Do Not Call Registry | Manual, one time - stops legitimate telemarketers, not scammers |
| Spy Dialer | Manual - multi-step wizard behind reCAPTCHA Enterprise |
Without a phone number in config.json the phone forms are skipped with a reason in the log. A number outside the US and Canada needs a leading + and country code for CallApp. The generic runner also types your number into any deletion form that has a phone field, but only alongside your name or email, so a lone phone search box is never submitted.
generic-runner.js handles the remaining ~470 brokers from two public datasets:
| Dataset | Source | Count |
|---|---|---|
| The Markup's data broker list | Journalism research, 494 opt-out URLs | ~494 |
| BADBOOL | Community-maintained people-search list | ~27 extra |
For each site it tries four strategies in order:
Sites requiring manual action are opened in your browser automatically.
| Site | Why manual |
|---|---|
| Google - Results About You | Requires Google account interaction |
| Google - Outdated Content | Case-by-case URL submission |
Edit brokers.js and add an entry:
{
name: 'NewBrokerSite',
method: 'direct-form', // or 'search-form', 'email', 'manual'
optOutUrl: 'https://example.com/opt-out',
formFields: {
'input[name*="first" i]': F, // F, L, N, E, ST, Z are from config
'input[name*="last" i]': L,
'input[type="email"]': E,
},
submitSelector: 'button[type="submit"]',
captchaLikely: false,
priority: 2,
}
PRs welcome - especially for brokers with verified working selectors.
./run.sh
Dry-run mode - navigates to each site and fills forms but does NOT submit anything. Good for verifying what the script will do before your first real run:
node watcher.js --dry-run
Or to run in the background and log output:
./run.sh >> logs/manual-run.log 2>&1 &
--verify)Run a read-only spot-check to see whether previous opt-outs are still in effect:
node watcher.js --verify
This opens a browser, searches each broker where you have a recorded successful opt-out, and reports what it finds. No forms are submitted, nothing is written to state.json.
Output is grouped into three sections:
| Section | Meaning |
|---|---|
VERIFIED CLEAR | Your name was not found in the broker's search today |
STILL LISTED | A listing was found - the opt-out may have failed, or your data was re-added |
UNVERIFIABLE | The broker uses a direct-form, email, or manual method - no automated search signal exists to check |
A dated JSON report is saved to logs/verify-YYYY-MM-DD.json.
Important caveats:
search-form brokers (those with a searchUrl and listingPattern) can be checked automatically. Direct-form and email opt-outs are always unverifiable.node watcher.js will attempt removal again.unverifiable (a timeout is not counted as "still listed").--serp-watch)node watcher.js --serp-watch runs a search-engine scan, diffs the broker domains it finds against the previous data/serp-history.json snapshot, and dispatches an alert (via lib/notify.js dispatchNotify: macOS toast/iMessage, Linux notify-send, and/or the notify.webhook URL) only when your name appears on a NEW domain. Because the scan appends to data/serp-history.json, repeated runs diff against the prior run. Add --serp-watch to run.sh to have the existing monthly scheduler watch for new exposures.
WARNING: This feature may violate broker Terms of Service. Submitting fabricated opt-out requests to data broker sites is ethically questionable and could expose you to legal risk. Use at your own discretion. This feature is off by default and is provided only as a research/experimental tool.
The --pollute N flag submits N randomly-generated fake person records to data brokers that are explicitly tagged acceptsBogus: true in brokers.js. The goal (inspired by a suggestion on HN) is to flood broker databases with junk records, degrading the accuracy of their search results.
# Submit 10 bogus records to each acceptsBogus broker
node watcher.js --pollute 10
Each fake record uses:
[email protected] emailOnly brokers tagged acceptsBogus: true in brokers.js will receive noise submissions. Currently tagged: ThatsThem, SearchPeopleFree, PeopleSearchNow, InfoTracer, SocialCatfish. These are direct-form brokers with no SSN/DOB gate.
Regular opt-outs run first - noise submissions happen after the normal run. The --pollute flag has no effect on your real opt-out submissions.
--update-brokers)The bundled Markup dataset is from January 2023 and is increasingly stale. Refresh the broker coverage from the official, auto-updating state data-broker registries:
node watcher.js --update-brokers
This fetches the California (SB-362) and Vermont registries over HTTP (no browser is launched), normalizes each entry, dedups it by hostname against the explicit brokers in brokers.js, and writes data/feeds-brokers.json. The generic runner loads that file alongside the Markup dataset on the next run; the Markup data stays as the fallback, so a failed or skipped refresh never reduces coverage. Override the registry URLs with the CA_REGISTRY_URL / VT_REGISTRY_URL environment variables if the official endpoints move.
The Markup dataset is years old; many of the ~489 generic opt-out URLs now 404 or fail DNS lookup. These are classified as 💀 Dead (stale URL) in run output and do not count as errors.
After several runs have accumulated in logs/, trim permanently-dead hostnames from future runs so they are skipped without any network request:
node scripts/prune-dead.js
The script:
logs/run-*.json filedead in every run they appeared indata/dead-urls.json (deduped, sorted)The script is idempotent - running it twice produces no change. You can add it as a post-run step or run it manually whenever you want to prune the dead list.
data/dead-urls.json is committed to the repo so the dead list is shared with all clones.
| Platform | Command |
|---|---|
| macOS (launchd) | launchctl unload ~/Library/LaunchAgents/com.auto-identity-remove.plist then rm ~/Library/LaunchAgents/com.auto-identity-remove.plist |
| Linux (systemd) | systemctl --user disable --now auto-identity-remove.timer then rm ~/.config/systemd/user/auto-identity-remove.{service,timer} |
| Linux (crontab fallback) | Run crontab -e and delete the auto-identity-remove line |
| Windows (schtasks) | schtasks /Delete /TN auto-identity-remove /F |
This tool supports non-US users with a few important caveats.
setup.js will prompt for Country (2-letter ISO code, e.g. CA, GB, AU) and then replace the US-centric "State" / "ZIP code" prompts with Province/Region and Postal code prompts that accept any format (K1A 0A6, SW1A 1AA, 2000, etc.) with no coercion.(xxx) xxx-xxxx reformatting is applied.lib/forms.js automatically tries province/postal/postcode HTML field variants (e.g. input[name*="province"], input[name*="postcode"]) when filling forms for non-US users, with no change needed in broker definitions.<select> on opt-out forms is targeted and filled with your 2-letter country code when present.The following brokers are flagged usOnly: true and are silently skipped when your configured country is not US. These sites index US public records, voter data, or phone directories - a non-US person definitionally has no record to remove there:
| Broker | Reason |
|---|---|
| Spokeo | US people-search (state-keyed search) |
| WhitePages | US white-pages directory |
| FastPeopleSearch | US people-search |
| TruePeopleSearch | US people-search |
| BeenVerified | US background-check (requires US state) |
| USPhonebook | US phone directory |
| PublicDataUSA | US public records |
| Sync.ME, NumLookup | Country picker defaults to +1 and is not driven yet |
| National Do Not Call Registry, Spy Dialer | US-only services |
All other brokers in the list are attempted regardless of country.
US people-search sites (Spokeo, WhitePages, etc.) hold records sourced from US public records - if you have never lived in the US, your data is very unlikely to appear on these sites. The script skips them for you automatically.
A fair concern raised by some users: aren't you just confirming your data to the brokers by filling out their forms?
A few things worth knowing:
That said: if you're in a situation where even confirming your email address to a broker is a risk, this tool is not the right approach. Consider a paid service that uses a proxy email.
California's Delete Request and Opt-out Platform (DROP), established by SB 362, will eventually let residents submit a single deletion request that all California-registered data brokers must honor. The platform is operated by the California Privacy Protection Agency (CPPA).
Status as of late 2025: not yet live. The broker-side compliance deadline in SB 362 is August 1, 2026. CPPA has missed several preceding milestones and ongoing litigation (Data Brokers Association v. Bonta) may further delay things.
See: https://cppa.ca.gov/data_broker_registry/
Until DROP is live, this tool falls back to per-broker opt-out flows for California-registered brokers.
Paid services like Incogni ($96/yr) or Optery ($39/yr) are excellent and cover more brokers with professionally maintained opt-out flows. This tool is for people who want full control, transparency, and no recurring subscription - or who want to handle the gaps those services miss (Acxiom, LexisNexis, ZoomInfo, Clearbit).
Using both is the strongest approach: a paid service for the bulk of brokers + this script for the gaps.
config.json holds your legal name, home address, phone, date of birth and API
keys. If you find a way for any of that to escape - into a log, a report, the
wrong form field, another local user, or a site chosen by a remote registry -
please report it privately rather than in an issue. See
SECURITY.md.
Most of this codebase was written by an AI model, and the review loop originally used the same model family, which issue #8 correctly called an echo chamber. Files that touch PII, crypto, subprocesses or untrusted network input now require a review from a different model family before merge, and CI builds the Docker image and launches a real browser rather than grepping the Dockerfile. The policy, the tooling, and an honest account of what that does and does not buy you are in docs/CROSS_MODEL_REVIEW.md.
npm run review:cross-model # review the current diff with a non-Anthropic model
MIT - see LICENSE.