Skip to content

feat(phishing): add hash-prefix lookup API for Web3 safebrowsing - #311

Open
Douglashdaniel wants to merge 2 commits into
masterfrom
feat/phishing-hash-prefix-lookup
Open

feat(phishing): add hash-prefix lookup API for Web3 safebrowsing#311
Douglashdaniel wants to merge 2 commits into
masterfrom
feat/phishing-hash-prefix-lookup

Conversation

@Douglashdaniel

Copy link
Copy Markdown

Adds a privacy-preserving phishing lookup API so wallets can check connection requests against MetaMask's eth-phishing-detect blocklist without sending plaintext URLs to gate3.

Ingests the blocklist into Redis (normalize → SHA-256 → PSL apex expansion), buckets full hashes by 4-byte prefix, and serves k-anonymity lookups. Follows the TokenManager refresh/staleness pattern, with Prometheus metrics for ingest and lookup latency.

Refresh list:

curl -s 'http://localhost:8000/api/phishing/v1/_admin/refresh'

Response:

{
  "status": "success",
  "message": "Phishing list refreshed successfully",
  "version": "2",
  "entryCount": 107363,
  "hashCount": 108421
}

Lookup by hash prefix:

curl -s 'http://localhost:8000/api/phishing/v1/lookup?prefixes=a5821d84'

Response:

{
  "version": "2",
  "matches": {
    "a5821d84": [
      "a5821d8435392716848b1b570a64af44a33f0fd440570a94311e12c7cdcc2b18"
    ]
  }
}

@Douglashdaniel
Douglashdaniel requested a review from a team July 22, 2026 18:55
@Douglashdaniel Douglashdaniel self-assigned this Jul 22, 2026
Copilot AI review requested due to automatic review settings July 22, 2026 18:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new phishing hash-prefix (k-anonymity) lookup API backed by Redis, with a refresh/reseed workflow and Prometheus instrumentation, enabling wallets to check against the eth-phishing-detect list without sending plaintext URLs.

Changes:

  • Introduces PhishingManager to fetch/normalize/PSL-expand entries, hash them, bucket by 4-byte prefix, and store in Redis with staleness-aware reseeding.
  • Adds FastAPI routes for /api/phishing/v1/lookup and /api/phishing/v1/_admin/refresh, plus Prometheus metrics for lookup/ingest.
  • Adds tests for route validation and manager refresh/lookup behavior; adds publicsuffixlist dependency.

Reviewed changes

Copilot reviewed 10 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
pyproject.toml Adds publicsuffixlist dependency for PSL-aware apex expansion.
poetry.lock Locks publicsuffixlist and updates lock metadata hash.
app/main.py Registers phishing router and adds startup reseed task for the phishing index.
app/api/phishing/routes.py Implements lookup + admin refresh endpoints and request parsing/metrics.
app/api/phishing/manager.py Implements blocklist fetch, normalization/expansion, hashing, Redis ingest + lookup, and staleness reseed logic.
app/api/phishing/constants.py Defines list URL, schema versioning, prefix length, and request limits.
app/api/phishing/metrics.py Adds Prometheus counters/gauges/histograms for ingest and lookup.
app/api/phishing/models.py Adds Pydantic response models with camelCase serialization.
app/api/phishing/test_routes.py Adds route tests (lookup success + validation + admin refresh).
app/api/phishing/test_manager.py Adds manager unit tests (normalize/hash/expand + refresh/lookup + staleness lock).
app/api/phishing/init.py Initializes phishing package (empty module).
app/api/common/models.py Adds PHISHING API tag for routing/docs grouping.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/api/phishing/routes.py
Comment thread app/api/phishing/routes.py
Comment thread app/api/phishing/routes.py
Comment thread app/main.py Outdated
Comment on lines +87 to +90
@router.get("/v1/_admin/refresh", response_model=PhishingRefreshResponse)
async def admin_refresh_phishing_list():
try:
result = await PhishingManager.refresh()

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional — this matches /api/tokens/v1/_admin/refresh. Gate3 doesn’t do in-app auth on admin routes; access is gated by the Brave Services Key at the edge, same as the rest of the public API

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this meant to be the refresh where we pull in the list and compare with our last diff and add the delta to Redis? If so, we could likely get away without even needing an endpoint here and just have it automatically ran by the service.

@onyb onyb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With an 8-char prefix, the number of buckets are significantly more than what we may have in the blocklist. IIUC prefix lookups protect the client only if many entries share a prefix (so gate3 can't tell which one the client meant), which doesn't appear to be the case given the prefix length.

Something like 3 chars makes more sense to me. cc @kdenhartog

@kdenhartog

Copy link
Copy Markdown
Member

++ let's keep this to fewer characters to get proper bucketing here. Might need to reduce to 2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants