hermes-agent/optional-skills/security/unbroker/scripts/paths.py
SHL0MS c2828f2b9b feat(skills): add security/unbroker (autonomous data-broker removal)
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).
2026-07-02 21:16:10 -04:00

79 lines
2.1 KiB
Python

"""Filesystem paths for the unbroker skill (stdlib only).
All per-subject data lives under PDD_DATA_DIR (default: $HERMES_HOME/unbroker),
which is the same trust boundary Hermes uses for .env and OAuth tokens.
"""
from __future__ import annotations
import os
from pathlib import Path
def hermes_home() -> Path:
return Path(os.environ.get("HERMES_HOME") or (Path.home() / ".hermes"))
def data_dir() -> Path:
override = os.environ.get("PDD_DATA_DIR")
return Path(override) if override else hermes_home() / "unbroker"
def config_path() -> Path:
return data_dir() / "config.json"
def subjects_dir() -> Path:
return data_dir() / "subjects"
def subject_dir(subject_id: str) -> Path:
return subjects_dir() / subject_id
def dossier_path(subject_id: str) -> Path:
return subject_dir(subject_id) / "dossier.json"
def ledger_path(subject_id: str) -> Path:
return subject_dir(subject_id) / "ledger.json"
def audit_path(subject_id: str) -> Path:
return subject_dir(subject_id) / "audit.jsonl"
def evidence_dir(subject_id: str) -> Path:
return subject_dir(subject_id) / "evidence"
def skill_root() -> Path:
"""The skill directory (parent of scripts/)."""
return Path(__file__).resolve().parent.parent
def brokers_dir() -> Path:
return skill_root() / "references" / "brokers"
def brokers_cache_path() -> Path:
"""Live broker snapshot pulled from BADBOOL (merged under the curated DB)."""
return data_dir() / "brokers-cache" / "badbool.json"
def registry_cache_path() -> Path:
"""CA Data Broker Registry snapshot (separate coverage lane; DROP/email, not scanned)."""
return data_dir() / "brokers-cache" / "ca-registry.json"
def age_identity_path() -> Path:
"""age identity (private key) used for at-rest encryption when enabled.
Defaults beside the data; point PDD_AGE_IDENTITY at a separate volume/token
for real key separation from the encrypted data.
"""
override = os.environ.get("PDD_AGE_IDENTITY")
return Path(override) if override else data_dir() / "age-identity.txt"
def templates_dir() -> Path:
return skill_root() / "templates"