mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-16 14:32:34 +00:00
unbroker finds where a consenting person's info is exposed across data brokers and people-search sites and files the removals, running as far as each site allows and handing only genuinely human-only steps (hard CAPTCHA, gov-ID, phone, fax) back as an end-of-run digest. - Deterministic stdlib CLI (scripts/pdd.py) owns config, dossiers+consent, the broker DB, tier planning, the ledger, email, and the autonomous action queue; the agent scans/submits with native tools (web_extract, browser_*, delegate_task, cronjob, terminal). - Verify-before-disclose, least-disclosure (never volunteers SSN), consent gate, opaque ids, optional age-at-rest encryption, file-locked ledger. - Jurisdiction-aware (CCPA/CPRA, GDPR, generic); CA DROP one-shot covers the state registry (~545) in a single request; BADBOOL + curated people-search coverage; scheduled re-scan for re-listing. - No CAPTCHA-solving services or anti-bot bypass; browser email mode needs no stored password. - 85 hermetic tests (tests/skills/test_unbroker_skill.py; SMTP/IMAP via injected fakes, registry via CSV fixtures). Ships placeholder data only. Broker dataset adapted from BADBOOL (Yael Grauer, CC BY-NC-SA 4.0).
53 lines
2.1 KiB
Python
53 lines
2.1 KiB
Python
"""Enumerate the search queries to run per broker, across ALL of a subject's identifiers.
|
|
|
|
People-search sites index a person under every name, phone, email, and address they
|
|
have. A subject with two names (maiden/married) and three past cities can have many
|
|
distinct listings on one broker, each found via a different search. `search_vectors`
|
|
expands the dossier into the concrete searches to run, filtered by what each broker
|
|
supports (`broker.search.by`, default ["name"]).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import dossier as dossier_mod
|
|
|
|
# What a broker can be searched by; default if a record doesn't declare it.
|
|
DEFAULT_BY = ["name"]
|
|
|
|
|
|
def supported_by(broker: dict) -> list[str]:
|
|
return list((broker.get("search") or {}).get("by") or DEFAULT_BY)
|
|
|
|
|
|
def search_vectors(subject_dossier: dict, broker: dict) -> list[dict]:
|
|
"""List of {by, query} searches to run for this subject on this broker."""
|
|
by = set(supported_by(broker))
|
|
ident = subject_dossier.get("identity", {})
|
|
vectors: list[dict] = []
|
|
|
|
if "name" in by:
|
|
names = dossier_mod.all_names(subject_dossier)
|
|
locations = dossier_mod.all_locations(subject_dossier)
|
|
if locations:
|
|
for name in names:
|
|
for loc in locations:
|
|
vectors.append({"by": "name",
|
|
"query": {"full_name": name, "city": loc.get("city"), "state": loc.get("state")}})
|
|
else:
|
|
for name in names:
|
|
vectors.append({"by": "name", "query": {"full_name": name}})
|
|
|
|
if "phone" in by:
|
|
for phone in ident.get("phones") or []:
|
|
vectors.append({"by": "phone", "query": {"phone": phone}})
|
|
|
|
if "email" in by:
|
|
for email in ident.get("emails") or []:
|
|
vectors.append({"by": "email", "query": {"email": email}})
|
|
|
|
if "address" in by:
|
|
for a in dossier_mod.all_addresses(subject_dossier):
|
|
if a.get("line1"):
|
|
vectors.append({"by": "address",
|
|
"query": {k: a.get(k) for k in ("line1", "city", "state", "postal")}})
|
|
|
|
return vectors
|