optional-skills/security/unbroker/references/methods.md
How the agent executes each broker optout.method using native Hermes tools. Obey least-disclosure:
submit only the subject's OWN identifiers, and only the fields a broker's official channel requires
(pdd.py plan lists them per broker). Never disclose more than that, and confirm a listing is really
the subject's before acting on any THIRD-PARTY / indirect record (see "Distinguish the subject" and
"Indirect exposure"). See the posture section below for when a confirmed listing is NOT a prerequisite.
Autonomy: pdd.py next <subject> sequences all of this - it decides which method applies, orders
parents first, and routes human-only work to the digest. In autonomy=full (default), execute its
actions without pausing per submission; the consent recorded at intake is the authorization. These
playbooks are the HOW for each action type.
Operator-directed posture: submit an opt-out or deletion on EVERY site that exposes an accessible removal channel, even when a listing was not first confirmed - whichever of opt-out / deletion is optimal per site. Do not hand back a to-do list of "we could not search these."
not_found than any scrape - the broker ran
its own matcher against real identifiers. On guided-flow sites, "run the opt-out" and "search" are one
action (e.g. CheckPeople; see site-playbooks.md).When a removal form is automation-hostile (hard CAPTCHA, a Cloudflare wall that will not clear, a JS
paywall funnel), default to the broker's cited rights-request email rather than recording blocked
and deferring to a human - unless there is an easy in-browser solve. Decision order per site:
blocked.blocked (or human_task_queued with the exact end-state)."Cited" = published by the broker itself (privacy policy / opt-out page / a working deletion alias). Do
not email addresses sourced only from third-party blogs or Reddit. Per-site lanes and gotchas are
pre-recorded in references/site-playbooks.md so future runs execute rather than re-derive.
When cross-checking any external "people OSINT" catalog, separate first-party brokers (removal
targets) from meta-search / link-out aggregators (no first-party data -> no-ops, do not file
opt-outs), cluster front-ends (covered by a parent, e.g. addresses.com -> Intelius), and
non-broker tools / APIs / wrong-jurisdiction (skip). The skip-lists live in site-playbooks.md.
Build the exposure map cheapest first (on a site with an accessible removal channel you may still
blind-opt-out even if the scan is inconclusive - see the posture section above). Run every
search_vectors entry from pdd.py plan (each name x location, phone, email, and address the broker's
search.by supports) - different vectors surface different listings for the same person; dedupe found
URLs.
web_extract on the broker search.url (fast HTML -> markdown). Look for search.match_signal.
Build per-vector URLs from search.url_patterns and heed search.url_format_quirks (see below).
1b. site: search-engine probe (cheap, do it early and in parallel). web_search with
site:<broker-domain> "First Last" (add a city/ZIP or a unique phone/address to cut namesake
noise) often returns the exact profile-slug URL in one shot - which both confirms the listing
exists AND hands you the opaque /find/person/<id> or /p/<slug> URL you'd otherwise have to
derive. Two big wins seen in the field: (a) it disambiguates namesakes fast - the SERP snippet
shows age/city so you can tell the subject from a same-name relative before fetching anything; and
(b) a broad "First Last" <ZIP OR unique-address> search (no site:) surfaces brokers not yet in
your DB (e.g. information.com, peoplefinders.com) - record those as bonus exposures. Note: empty
site: results are INCONCLUSIVE (many broker pages aren't indexed / are noindex), not not_found.browser_navigate + browser_snapshot
(and browser_type/browser_click to run the site's search box).scrapling skill via terminal. If the broker record
has search.antibot set (e.g. datadome), results are behind a device-check CAPTCHA: a
cloud/stealth browser (Browserbase) or scrapling may get through; if none is available, do not
burn attempts - pdd.py record <subject> <broker> blocked and move on (a re-scan with a stealth
backend can pick it up later).
3b. Operator-browser path (the reliable unblock for anti-bot sites). Cloudflare/DataDome key on
datacenter IPs + headless fingerprints, so web_extract, the proxyless agent browser, and even a
cloud browser often fail - but the operator's own everyday browser (residential IP, real
fingerprint) sails straight through. For any blocked site, hand the operator a paste-ready
search URL (built from search.url_patterns), give them the identity anchors to judge by (current
found / not_found / indirect_exposure), citing scanned_via: operator_browser. Same for
opt-out forms the agent's browser can't reach: guide the operator field-by-field (least-disclosure),
pausing before submit. (This is exactly why the same trick clears email-verification links the agent
can't open - see the Verification loop.)browser screenshot into the subject's evidence/ dir,
then pdd.py record <subject> <broker> found --found true --evidence '{"listing_urls":[...]}'.If a listing genuinely does not exist: pdd.py record <subject> <broker> not_found and move on.
A constructed search URL that 404s almost always means the URL pattern is wrong, not that the
person is absent. Never record not_found off a 404. Instead:
search.url_patterns / url_format_quirks and rebuild the URL.browser_navigate to the search page, browser_type
the raw query, browser_click Search, then read the canonical result URL the site lands on.not_found.references/brokers/<id>.json (url_patterns +
url_format_quirks) so the next run is correct - see the rule below.Whenever you discover how a broker's URLs are actually shaped (path layout, hyphen-vs-slash joins,
whether ZIP is required, abbreviation handling, query-param search, anti-bot gating), record it in
that broker's references/brokers/<id>.json under search.url_patterns (the templates) and
search.url_format_quirks (the gotchas, including which forms 404). Bump last_verified. This makes
the deterministic URL path reliable across runs and subjects instead of rediscovered each time. If the
opt-out form's real requirements differ from the record (extra required fields, a CAPTCHA, an account),
fix optout.requires / optout.inputs / optout.tier too - those drive tier selection and
least-disclosure. Log opt-out mechanics gotchas (a broker that needs a profile URL but doesn't expose
one for the subject, an email-only fallback, an authorized-agent toggle) in optout.quirks - the
planner surfaces these as optout_quirks per broker. Example: Radaris sometimes shows the subject only
as a static address-table row with no "View Profile" link, so /control-privacy (which needs a profile
URL) can't be used - fall back to optout.email rather than submitting a namesake's URL.
People-search sites are dense with namesakes and family clusters. Before recording found, confirm the
record is the subject themselves (corroborate via DOB, a known current/prior address, or the
identifier you searched). Two non-removable patterns to record as evidence but NOT as the subject's own
listing:
Two more false-positive traps that a naive scan records as found when it should not:
not_found) from "the subject's personal profile is displayed" (removable,
found). Record found ONLY if a resident name matching the subject is publicly shown; an
address-only match is not_found - there is nothing to opt out of, and public property records are
not removable anyway. See rehold.json search.match_signal_notes.<title>, H1, and intro copy ("FREE public records found for {Name} in {City}", "Over 100+
FREE public records found for {Name}"). That echo is templating, not a result - the actual
result cards are often unrelated namesakes in other states. A match_signal on title/intro text
yields false positives. Require a real result card corroborated by the subject's address or
DOB, and ignore the templated title/intro/H1 entirely. See truepeoplesearch.json /
fastpeoplesearch.json search.match_signal_notes.Both are why the parent re-verifies every found before acting rule is load-bearing (pdd.py show <subject> <broker> reads back a subagent's recorded evidence so the parent can re-verify without
re-deriving the listing URL). If a found turns out to be a false positive, correct it with a fresh
record ... not_found carrying an evidence note explaining the retraction.
browser_navigate to optout.url; browser_snapshot to read the form.disclosure_fields with browser_type/browser_click; for profile_url,
paste the confirmed listing URL from evidence.browser_snapshot to confirm the success state; screenshot to evidence/.pdd.py record <subject> <broker> submitted --disclosed <field> --disclosed <field> --channel web_form.You asked the right question: if a broker lists a relative and names you in their "Family" field, or shows your email/phone on their record, that IS personal information about you - even though the record's primary subject is a third party. Resolve it in two distinct lanes:
render-email with the ccpa/gdpr template, list only the
subject's own identifiers + the URLs where they appear, and record it as a normal submitted →
awaiting_processing email case. Verify by re-scanning those identifier vectors (email/phone) after
the statutory window - confirmed_removed only when the subject's identifier no longer appears.pdd.py send-email <subject> <broker> --listing <url> [--kind ccpa|gdpr|ccpa_indirect] always does
the deterministic parts (recipient locked to an address the broker record declares, refusing anything
else; --listing mandatory; records submitted, logs disclosure, stamps next_recheck_at). How it
actually sends depends on email_mode:
compose
payload (to/subject/body). Compose a NEW message in the operator's logged-in webmail via
browser_* (paste compose.body exactly, disclosing nothing beyond it) and send. No credentials
stored. Requires the inbox signed in in the browser Hermes uses.pdd.py render-email <subject> <broker> --listing <url>; a digest entry
tells the operator to send it, and the agent records submitted --channel email afterward.Then follow the Verification loop if the broker emails a confirmation link.
browser_*), then pdd.py verify-link <subject> <broker> --text '<email body>' returns
the anti-phishing-scored link. browser_navigate it in the same browser (several brokers, e.g.
PeopleConnect, bind the session to the browser that opens the link), finish the flow, record
awaiting_processing.pdd.py poll-verification <subject> polls IMAP for every in-flight
case, extracts the link (anti-phishing scored: only opt-out-looking links on the broker's own
domains), and auto-advances submitted → verification_pending. Then browser_navigate the link in
the agent's own browser, finish the flow, record awaiting_processing.awaiting_processing on their word.pdd.py due) brings the case back after the broker's processing window
for the verifying re-scan; only that re-scan justifies confirmed_removed.Submit the web form, then the site places an automated call with a numeric code. If the operator is available to read the code, capture it and complete the form (T2). Otherwise queue a human task.
Do not attempt to automate. Create a todo task and pdd.py record <subject> <broker> human_task_queued with exact instructions and an explicit withhold list (never SSN; never a
driver's-license number unless the subject chooses to and crosses out the ID number). Capture the
confirmation reference back into the ledger when the operator completes it.
Default: soft/managed CAPTCHAs clear automatically. The recommended baseline backend is the
Browserbase cloud browser (setup --auto selects it when BROWSERBASE_API_KEY is set). Being a
real browser on a residential IP, it passes managed challenges - Cloudflare Turnstile, hCaptcha /
reCAPTCHA checkbox - as normal operation, so those brokers stay T1 and you just proceed. This is
not CAPTCHA solving: no solver service, no fingerprint spoofing.
Only a hard challenge the browser genuinely can't pass (interactive image grids, behavioral
scoring that flags the session) becomes a fallback: record ... blocked and requeue it for the
stealth/operator-browser pass (methods.md → scan ladder 3b - the operator's own residential
browser is the reliable unblock). Without a cloud browser configured, soft-CAPTCHA brokers drop to
T2 and become human tasks. Never use a third-party CAPTCHA-defeating service.
blocked.blocked.Two different jobs need two different browsers. Getting this wrong is the single biggest cause of a run stalling in Phase 2.
scrapling skill is
ideal. On a residential IP with a real fingerprint it passes managed challenges (Cloudflare
Turnstile, hCaptcha checkbox) and reads anti-bot people-search pages that web_extract and the
proxyless agent browser cannot. This is what the skill's browser_backend setting governs
(auto picks Browserbase when BROWSERBASE_API_KEY is present - now also read from
$HERMES_HOME/.env, not just the shell env, so doctor/setup --auto detect the key Hermes
already loads for its own tools).chrome --remote-debugging-port=9222 --user-data-dir="$HOME/.hermes/chrome-debug" and connect the
browser backend to 127.0.0.1:9222. Use a dedicated debug profile (chrome-debug), NOT the
operator's Default Chrome profile, and have the operator sign into their webmail (and any needed
broker accounts) in that profile once. That single browser then carries residential IP + real
fingerprint + logged-in sessions, which is precisely what Phase-2 flows need. (This is a Hermes-side
browser setup, not a pdd config value; browser_backend above only selects the Phase-1 scan
browser.) The skill launches this for you: pdd.py cdp finds a Chrome/Chromium/Brave/Edge
binary, starts it detached on the dedicated profile, waits for the debug port, and prints the CDP
endpoint (webSocketDebuggerUrl). pdd.py cdp --check reports whether a debug browser is already
live (and never launches a second one); pdd.py cdp --print just emits the exact command for the
operator to run themselves. Point the browser tools at the endpoint it returns.Backend precedence, most to least autonomous: operator Chrome over CDP (Phase 2, hands-off once the profile is signed in) > Browserbase cloud stealth (Phase 1 scanning, plus managed-captcha forms that need no login) > proxyless agent browser (only already-unblocked sites) > operator-in-the-loop (paste-ready URLs; the last-resort unblock that always works).
Many brokers are resold shells of a few parents, so one parent removal clears a whole cluster of
children (see owns in each record). In Phase 2 you MUST work the cluster parents first, then
the standalone listings - doing a child before its parent wastes a submission the parent would have
covered. pdd.py plan <subject> --batch orders the found group parents-first and emits a
parent_playbook whose steps come verbatim from each record's optout.playbook - the single
source of truth, field-verified, updated as live runs discover mechanics. What follows is the
operating doctrine; the exact steps are in references/brokers/<id>.json.
Deletion USUALLY beats suppression, email lanes beat forms -- but check the record. Each parent
record carries a structured optout.deletion lane (via: in_flow | email | email_followup, a
privacy address, and prefer). The autopilot routes accordingly, and when deletion.prefer is
false it emits prefer_suppression instead of prefer_deletion:
in_flow (PeopleConnect, prefer: false): the deletion control lives inside the web flow, but
for this cluster it is the WRONG lever for search-visibility (see the exception below). Complete the
suppression flow and maintain it; do not press Delete unless the goal is a data-purge.via: email (Whitepages): the fully-autonomous lane - send-email the request (residency-picked
kind: CCPA for US-CA, GDPR for EU/UK, generic otherwise), then poll-verification for their reply
and answer identity questions with least-disclosure. This is also the rescue lane: any broker
whose form demands a phone-callback/gov-ID/account but that declares a deletion email gets routed
here instead of the human digest.email_followup (BeenVerified, Spokeo): the opt-out form is the fast primary (it clears the
listing), and the playbook then sends a right-to-delete email for full erasure beyond suppression.Verified parent facts (live-checked 2026-07-02; details + steps in the records):
[email protected] is the rights-request address for that
path; published metrics: 33.5k deletion requests, median response < 1 day.[email protected] (or the Zendesk form) handles removal + CCPA
deletion without the phone-callback tool - that phone call is only required by the automated
tool. One removal also drops "all known connected listings". ≤15 days; check 411.com + Premium./svc/optout/search/optouts) + email
verification; one opt-out per email address. Then [email protected] deletion follow-up -
controller is The Lifetime Value Co., so name their sister properties (NeighborWho, Ownerly,
NumberGuru, Bumper) in the same request, and verify each separately.[email protected] for full deletion beyond free-search suppression.After each parent removal is confirmed, re-scan its children before submitting anything for them - usually they drop out and need no separate opt-out.
A parent without a hand-verified optout.playbook gets synthesised steps from its structured record
(URL/email, requires flags, deletion lane, notes/quirks). Follow those, and write what you learn
back into references/brokers/<id>.json (optout.playbook, optout.deletion, quirks,
last_verified) so the next run is exact - that file, not this one, is where per-broker knowledge
accrues.