Skip to content

Repository files navigation

PhishShield

A cross-browser phishing detection suite with an active-learning loop — extension catches the URL, local model scores it, user feedback flows back into a labeled corpus, retraining is one CLI away.

A privacy-conscious phishing detector that runs entirely on the user's machine: a Chrome / Firefox MV3 extension talks to a local Python inference service over loopback; flagged URLs and user reports land in a Postgres-backed feedback store; a Next.js dashboard closes the active-learning loop.

What this is for

Phishing-detection products usually fall into two camps: cloud scanners that see every URL you visit (great signal, real privacy cost), and pure block-list extensions (privacy-clean, but one domain-rotation away from useless). PhishShield sits between them — features and inference live on the user's machine, only opt-in user-reported URLs ever leave the device, and the labeled corpus that drives retraining is auditable in the dashboard.

Capability Where it shows up
Cross-browser MV3 extension TypeScript + React; one Vite build pipeline produces both Chrome and Firefox bundles. Background service worker, content-script DOM probe, popup risk card, on-page warning banner.
Lexical + DOM features URL features (entropy, IDN homograph, brand similarity, suspicious TLDs, IP-as-host) computed in the extension; DOM features (password field on non-HTTPS, hidden iframes, login form on a young domain) added by the content script. Same feature schema is recomputed server-side for safety.
Local-only inference FastAPI + scikit-learn GradientBoosting model, bound to 127.0.0.1, bearer-token auth. The extension never sends URLs anywhere except localhost.
Active-learning loop User reports a missed phish (or a false positive) → row lands in feedback table → dashboard shows the queue → reviewer labels → make train rebuilds the model from seed.csv ∪ labeled feedback.
Drift-aware metrics Dashboard plots precision / recall / FPR over the last N labeled rows, score distributions per class, and a per-feature importance bar via SHAP-style permutation.
Model card Honest, machine-readable MODEL_CARD.md: training data, intended use, known failure modes (out-of-distribution TLDs, non-Latin scripts), evaluation slice.
Polyglot monorepo Python for inference + training, TypeScript for both the extension and the dashboard (Next.js + Tailwind).

Architecture at a glance

   ┌─────────────────────┐                    ┌──────────────────────┐
   │ Chrome / Firefox    │  POST /v1/score    │  inference           │
   │ MV3 extension       │ ──────────────────▶  (Python / FastAPI)  |
   │  · URL features     │                    │                      │
   │  · DOM features     │  POST /v1/feedback │                      │
   │  · banner injection │ ──┐                │  · GradientBoost     │
   └─────────────────────┘   │                │  · feature recompute │
              ▲              │                └────┬─────────────────┘
              │ score        │ user reports        │
              │              ▼                     ▼
              │       ┌──────────────────────────────┐
              │       │ Postgres                     │
              │       │   reports, labeled, models   │
              │       └────┬─────────────────────────┘
              │            │
              │            ▼
              │    ┌────────────────────┐
              │    │ dashboard          │
              │    │ (Next.js + TS)     │
              │    │  · /reports queue  │
              │    │  · /labeled        │
              │    │  · /metrics        │
              │    └────────────────────┘
              │
   ┌──────────┴──────────────┐
   │ phishshield-train (CLI) │  retrains model from seed ∪ labeled feedback
   └─────────────────────────┘

See ARCHITECTURE.md for the request lifecycle, feature schema, and the rationale behind each language pick.

Quickstart

make compose-up        # builds + brings inference + dashboard + postgres up
make seed              # loads seed.csv and trains v0 model into postgres
make demo              # scores a benign + a synthesized phish, files a report

After make compose-up, the stack is reachable on localhost:

Port Service What
8000 inference POST /v1/score, POST /v1/feedback, GET /v1/admin/*
3000 dashboard Next.js, the active-learning UI
5432 postgres reports + labeled + model_versions tables

The extension is loaded into the browser as an unpacked extension — see extension/README.md for make ext-build plus the per-browser load step. On first run the extension's options page asks for the inference URL (http://localhost:8000) and the bearer token (printed by make compose-up).

Tear down with make compose-down.

Run locally without Docker

The Compose path is the recommended one — but if you want every moving part on bare metal (no container runtime, SQLite instead of Postgres), the steps below stand up the same stack with about a minute of work.

1. Python venv + inference service

cd services/inference
python -m venv .venv

# Windows PowerShell / Git Bash:
.venv/Scripts/python.exe -m pip install -e ".[dev]"
# macOS / Linux:
.venv/bin/python -m pip install -e ".[dev]"

Generate a bearer token, train v0, then start the API:

cd ../..                          # back to repo root
bash scripts/gen-secret.sh        # writes PHISHSHIELD_API_KEY into .env
TOKEN=$(grep PHISHSHIELD_API_KEY .env | cut -d= -f2)

cd services/inference
PHISHSHIELD_DATABASE_URL=sqlite:///./phishshield.db \
PHISHSHIELD_API_KEY=$TOKEN \
.venv/Scripts/python.exe -m phishshield_inference.train --reseed --note "v0 local"

PHISHSHIELD_DATABASE_URL=sqlite:///./phishshield.db \
PHISHSHIELD_API_KEY=$TOKEN \
PHISHSHIELD_HOST=127.0.0.1 \
PHISHSHIELD_PORT=8000 \
PHISHSHIELD_CORS_ORIGIN=http://localhost:3000 \
.venv/Scripts/phishshield-inference.exe   # use phishshield-inference on macOS/Linux

/healthz should answer 200 on http://127.0.0.1:8000.

2. Dashboard

In a second terminal:

cd dashboard
npm install
PHISHSHIELD_INFERENCE_URL=http://127.0.0.1:8000 \
PHISHSHIELD_API_KEY=$(grep PHISHSHIELD_API_KEY ../.env | cut -d= -f2) \
npm run dev

Open http://localhost:3000. The five routes (/, /reports, /labeled, /metrics, /models) should all render against the running inference service.

3. Extension

In a third terminal:

cd extension
npm install
npm run build         # emits dist-chrome/ and dist-firefox/

Load the unpacked extension:

  • Chrome / Edge / Bravechrome://extensions/ → enable Developer modeLoad unpacked → pick extension/dist-chrome/.
  • Firefoxabout:debuggingThis FirefoxLoad Temporary Add-on… → pick any file inside extension/dist-firefox/ (Firefox loads the directory).

The options page opens automatically on first run. Paste:

  • Inference URL: http://127.0.0.1:8000
  • Bearer token: the value of PHISHSHIELD_API_KEY from .env

Content blockers can mask the extension. uBlock Origin, Privacy Badger, NoScript, and similar extensions can suppress the content script's runtime messaging — the popup will then sit on Loading… indefinitely, with no error logged. If the popup hangs, temporarily disable the blocker on the page you're testing (uBlock: click its icon → power button) and reload. PhishShield coexists fine with content blockers in normal use; this only trips the initial score on a freshly-loaded page.

4. Drive the loop end-to-end

Score a benign + a phish from the shell:

bash examples/curl/score-url.sh "https://www.google.com/"
bash examples/curl/score-url.sh "http://verify-account-update-secure-banking.tk/"

File a report:

bash examples/curl/demo.sh

The /reports page in the dashboard now shows the new row in pending. Click Confirm phish to push it into the labeled set, then retrain:

cd services/inference
PHISHSHIELD_DATABASE_URL=sqlite:///./phishshield.db \
PHISHSHIELD_API_KEY=$TOKEN \
.venv/Scripts/python.exe -m phishshield_inference.train --note "v1 after feedback"

A new row appears in /models. Click Activate — the inference service hot-reloads the new model in-memory; no restart, the extension's next request scores against the new version.

Stopping the local stack

Ctrl-C in each terminal. Removing services/inference/phishshield.db gives you a clean DB on the next run.

What to look at after make demo

  1. The dashboard at http://localhost:3000Reports shows the URL just filed by make demo awaiting review. Click Confirm phish to move it into the labeled set.

  2. Score a URL directly — sanity-check the model from the shell:

    curl -s http://localhost:8000/v1/score \
         -H "Authorization: Bearer $(cat .env | grep PHISHSHIELD_API_KEY | cut -d= -f2)" \
         -H 'Content-Type: application/json' \
         -d '{"url":"http://192.168.0.5/secure-login-verify-account/"}' | jq

    Expect a high score and a top_features array naming the URL shape signals (IP-as-host, suspicious keywords, no HTTPS).

  3. Retrain after labeling — once you have a few labeled feedback rows in the dashboard, regenerate the model:

    make train             # writes a new row into model_versions

    The dashboard's Models tab lists every version with its eval metrics; switch the active model with one click.

  4. Inspect the model cardMODEL_CARD.md is machine-readable (front-matter + body). The dashboard's Metrics tab pulls evaluation numbers from the same source so the card never drifts from reality.

Extension chain (per request)

Innermost first: page navigation → content-script DOM probe → background score request → popup card refresh + (if score ≥ threshold) banner injection. The content script never blocks navigation; the banner is informational. Hard-blocking is opt-in in the options page — the default posture is warn, don't break the web.

CI runs the full TS-extension + TS-dashboard + Python-inference matrix (typecheck, lint, test) in parallel on every push to main and every PR.

Development

Polyglot monorepo. Each component builds and tests independently.

Component Toolchain Build Test
extension Node ≥ 20, TypeScript npm run build npm test
dashboard Node ≥ 20, Next.js 15 npm run build npm test
services/inference Python ≥ 3.11 pip install -e .[dev] pytest

make help lists every target. CI (.github/workflows/ci.yml) runs the same lint + test matrix across TS-extension, TS-dashboard, and Python on every push to main and every PR.

License

MIT — © 2026 Dimitris Sofikitis. See LICENSE.

About

Cross-browser phishing detection suite

Topics

Resources

Contributing

Stars

Watchers

Forks

Contributors

Languages