Paste a link. Know if it’s safe to buy.
Evidence-first website risk analysis for online stores, listings, checkout pages, and payment links.
ScamShield is a self-hosted consumer security application and Manifest V3 browser extension. It inspects independent technical and content signals, calculates a deterministic 0–100 risk score, and connects every warning to concrete evidence, a source, severity, confidence, and score contribution.
It does not sell subscriptions, require registration, let an LLM decide the score, submit checkout forms, or claim that a website is “definitely a scam.”
Every report leads with a plain recommendation and then shows exactly why:
- domain registration age, TLD and suspicious naming patterns;
- brand impersonation, typo-squatting and homoglyph-style substitutions;
- TLS availability and certificate validity;
- business identity and contact details found on the page;
- missing, conflicting or copied policy information;
- cross-domain redirects, form actions and unknown checkout providers;
- opaque payment links with amount/product data embedded in the URL;
- cryptocurrency or bank-transfer payment warnings;
- suspicious “official” or “unlimited” digital-access resale claims;
- optional phishing/malware reputation matches;
- positive evidence such as an established domain or valid HTTPS.
The screenshot above is a real regression case: a young direct-payment domain selling purported unlimited Claude access is now classified as 72/100 — HIGH RISK, with 94% confidence and seven inspectable observations.
Detectors emit typed observations rather than verdicts:
{
"signal": "opaque_direct_payment_link",
"severity": "high",
"risk_points": 18,
"confidence": 0.95,
"evidence": "The URL opens a direct payment flow with the amount and item supplied in query parameters, without a recognized payment provider.",
"source": "Payment URL structure",
"group": "checkout"
}The risk engine is the only scoring authority. It:
- validates severity, confidence and detector group;
- keeps the strongest duplicate observation;
- applies confidence weighting and severity caps;
- combines correlated signals with diminishing returns;
- caps each detector family to prevent double counting;
- requires evidence from multiple independent groups for scores above 60;
- allows an exceptional score floor only for a high-confidence confirmed phishing, malware, or credential-theft source.
A new domain alone stays low risk when identity and policy evidence are otherwise healthy. A valid HTTPS certificate is shown as a positive transport signal, but never treated as proof that the merchant is legitimate.
flowchart TD
U[Web app or browser extension] -->|URL + anonymous browser ID| A[FastAPI]
A --> V[URL normalization and public-IP preflight]
V --> Q[Redis / Celery scan job]
Q --> C[Isolated safe fetcher]
Q --> D[RDAP + DNS + domain heuristics]
Q --> T[TLS inspection]
Q --> R[Reputation adapters]
C --> X[Deterministic or validated AI extraction]
X --> I[Identity / policy / checkout detectors]
D --> N[Signal normalization]
T --> N
R --> N
I --> N
N --> E[Deterministic risk engine]
E --> P[(PostgreSQL evidence snapshots)]
P --> S[SSE progress + explainable report]
Independent checks run concurrently. Provider failures degrade to a skipped or partial check; one unavailable integration does not fail the entire scan or become a false “safe” result.
There are no accounts, email addresses, passwords, plans, payments, or daily product quotas. The web app creates a random identifier in local browser storage and sends it as X-ScamShield-Visitor. The API stores only an HMAC-SHA256 digest of that value to scope history, saved websites and monitoring records.
Clearing browser storage intentionally breaks the link to that private history. Users can export their data or delete all history from Settings.
Short burst and concurrent-job controls remain as security protections against accidental overload and automated abuse; they are not monetization limits.
ScamShield/
├── apps/
│ ├── web/ # Next.js consumer web app, EN/RU, light/dark
│ ├── api/ # FastAPI routes, detectors, providers and persistence
│ ├── worker/ # Celery scan and monitoring jobs
│ └── extension/ # Chrome/Chromium Manifest V3 extension
├── packages/
│ ├── ai/ # Pydantic-validated extraction + deterministic fallback
│ ├── crawler/ # SSRF-resistant HTTP fetcher
│ └── risk-engine/ # sole deterministic scoring authority
├── migrations/ # Alembic database migrations
├── tests/ # risk, detector, API, AI and crawler regression tests
├── docs/
│ ├── DEPLOYMENT.md
│ ├── PROVIDERS.md
│ └── SECURITY.md
├── docker-compose.yml
├── pyproject.toml
└── pnpm-workspace.yaml
Requirements: Docker Desktop with Compose v2.
git clone https://github.com/rezvvent/ScamShield.git
cd ScamShield
docker compose up --buildCompose waits for PostgreSQL and Redis, applies migrations, seeds detector feature flags, and starts the API, worker, scheduler, object storage and web app.
| Service | URL |
|---|---|
| Web application | http://localhost:3000 |
| FastAPI | http://localhost:8000 |
| OpenAPI | http://localhost:8000/docs |
| MinIO console | http://localhost:9001 |
No external credential is required for deterministic scans. Optional provider checks clearly show when they are not configured.
Stop the stack with:
docker compose downcorepack enable
pnpm install
python3.12 -m venv .venv
.venv/bin/pip install -e '.[dev]'
.venv/bin/python scripts/seed.pyRun the API and web app in separate terminals:
.venv/bin/uvicorn scamshield_api.main:app --reload --app-dir apps/api --port 8000
pnpm --filter @scamshield/web devThe default non-Docker API uses SQLite and inline scan execution. Docker uses PostgreSQL, Redis and Celery for production parity.
Copy .env.example to .env when overriding defaults.
| Variable | Purpose |
|---|---|
SECRET_KEY |
HMAC secret for anonymous owner/IP digests; use 32+ random bytes |
PUBLIC_APP_URL |
exact web origin and shared-report base URL |
NEXT_PUBLIC_API_URL |
API origin embedded in the Next.js build and CSP |
DATABASE_URL |
async SQLAlchemy PostgreSQL or SQLite URL |
REDIS_URL |
Celery broker and result backend |
SCAN_EXECUTION_MODE |
inline or celery |
SCAN_MAX_REDIRECTS |
maximum revalidated redirect hops |
SCAN_TOTAL_TIMEOUT_SECONDS |
whole fetch timeout |
SCAN_MAX_BODY_BYTES |
maximum accepted HTML response size |
SCAN_REQUESTS_PER_MINUTE |
technical burst protection |
MAX_ACTIVE_SCANS_PER_VISITOR |
concurrent-job protection |
RDAP_CACHE_HOURS, TLS_CACHE_HOURS, REPUTATION_CACHE_HOURS |
cache TTLs |
GOOGLE_SAFE_BROWSING_API_KEY |
optional phishing/malware provider |
OPENAI_API_KEY, OPENAI_MODEL |
optional structured extraction; never scoring |
ADMIN_API_KEY |
optional internal feature-flag/stats API; empty disables it |
If Docker cannot resolve public hosts while a VPN is active, set SCAMSHIELD_DNS to a DNS resolver reachable from Docker. Do not use this setting to bypass an organization’s network policy.
The web app and extension create the anonymous visitor ID automatically. Direct API clients should generate and retain a random value of at least 16 characters:
curl -X POST http://localhost:8000/api/v1/scans \
-H 'Content-Type: application/json' \
-H 'X-ScamShield-Visitor: random-client-id-keep-this-private' \
-d '{"url":"https://example.com"}'Core endpoints:
POST /api/v1/scans
GET /api/v1/scans/{id}
GET /api/v1/scans/{id}/events
GET /api/v1/scans
DELETE /api/v1/scans/{id}
POST /api/v1/scans/{id}/share
GET /api/v1/reports/shared/{slug}
GET /api/v1/watch
POST /api/v1/watch
DELETE /api/v1/watch/{id}
GET /api/v1/data/export
DELETE /api/v1/data/history
Build the extension:
pnpm --filter @scamshield/extension buildThen:
- open
chrome://extensions; - enable Developer mode;
- choose Load unpacked;
- select
apps/extension/dist; - open a public website and click Scan this page.
For a hosted deployment:
SCAMSHIELD_API_URL=https://api.example.com \
SCAMSHIELD_APP_URL=https://scamshield.example.com \
pnpm --filter @scamshield/extension buildPermissions are limited to activeTab, scripting, storage, and configured API hosts. The extension never reads input values, cookies, authorization headers, local/session tokens, passwords, card values, or unrelated browsing history.
- RDAP: available without credentials through the included adapter.
- Google Safe Browsing: configure
GOOGLE_SAFE_BROWSING_API_KEY; confirmed matches can activate the exceptional phishing/malware scoring path. - OpenAI: configure
OPENAI_API_KEY; hostile page text is delimited as untrusted data, structured output is schema-validated,store: falseis used, and failures fall back to deterministic extraction. - Additional providers: implement bounded adapters in
providers.py, map observations toRiskSignalin detector code, and add clean/malicious/ambiguous/outage fixtures.
See Provider integration.
.venv/bin/ruff format --check apps packages scripts tests migrations
.venv/bin/ruff check apps packages scripts tests migrations
.venv/bin/pytest -q
pnpm --filter @scamshield/web typecheck
pnpm --filter @scamshield/web build
pnpm --filter @scamshield/extension typecheck
pnpm --filter @scamshield/extension buildThe suite includes:
- established legitimate store → low risk;
- new suspicious store → high risk;
- new legitimate startup → new domain alone stays low risk;
- opaque direct-payment/unlimited-access regression → high risk;
- confirmed phishing exception;
- correlated-signal caps and deduplication;
- loopback, cloud metadata, credentials, redirect and body-size SSRF protections;
- prompt-injection isolation and malformed model-output fallback;
- public mode with no registration or usage-plan endpoints.
Everything from a target website is hostile: URL, DNS response, redirects, headers, HTML, scripts, metadata, extracted text and embedded instructions.
- only HTTP/HTTPS URLs are accepted;
- credentials, internal hostnames and non-standard ports are rejected;
- loopback, private, link-local, reserved, multicast and metadata IPs are denied;
- every redirect is resolved and validated again;
- response MIME type, size, redirect count and time are bounded;
- scraped HTML is never rendered in reports;
- the worker runs read-only with dropped Linux capabilities in Compose;
- production still requires an infrastructure egress firewall and isolated crawler runtime.
Read the complete security model and deployment guide.
- JavaScript-rendered sites are not yet executed; Playwright must run in a separate disposable sandbox before enabling that path publicly.
- Reputation coverage is narrow without optional provider credentials. “No match” never means certified safe.
- Market price comparison, reverse-image search, review manipulation, advanced social analysis and screenshot scanning are not implemented.
- The bundled registrable-domain fallback covers common suffixes; production should use a maintained Public Suffix List.
- Saved-site rechecks work, but outbound notifications and rich identity/reputation diffs are not included.
- High-entropy scan/report IDs are capability links. A public multi-region deployment should add explicit expiring report-access capabilities.
- A risk score is a point-in-time assessment, not insurance, certification, or a guarantee.
- Run Playwright in disposable jobs with enforced DNS pinning and egress policy.
- Add a second independent reputation adapter with health metrics and circuit breakers.
- Introduce a versioned Public Suffix List and brand corpus.
- Add market-price and image-match providers with source timestamps and false-positive fixtures.
- Add retention controls, notification delivery and richer watch-event diffs.
- Run Lighthouse, accessibility, load, dependency, container and external penetration tests before public deployment.
ScamShield reports observed evidence and uncertainty. Prefer payment methods with buyer protection, and never treat a low score as a certification.


