safesurf / safesurf
A PHP library for checking if a URL is safe to surf.
Fund package maintenance!
Requires
- php: >=8.0
- io-developer/php-whois: ^4.1
- jeremykendall/php-domain-parser: ^6.0
- phpfastcache/phpfastcache: ^9.2
Requires (Dev)
- phpunit/phpunit: ^10.0
- rector/rector: ^2.5
Suggests
- io-developer/php-whois: Adds WHOIS lookup support for checking domain registration data.
- jeremykendall/php-domain-parser: Provides robust domain parsing for URL analysis.
- phpfastcache/phpfastcache: Adds caching support to improve performance of repeated checks.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-07 03:36:56 UTC
README
A PHP 8+ library that analyzes a URL for phishing indicators and returns a transparent, JSON-ready report: every signal inspected, the raw evidence, a risk/trust score breakdown, and a final verdict of Safe, Suspicious, or Risky.
Disclaimer
This project is a PHP-native rewrite of the repository urlvet/urlvet.
SafeSurf for PHP focuses on transparent analysis results (reasons), scores, and verdicts.
Features
- URL signals: known shorteners, raw-IP hosts, punycode, excessive length or depth, subdomain count, phishing keywords
- Domain & DNS signals: global traffic rank (top-1M), IP resolution, NS/MX validity, trusted/risky/hosting-platform TLD classification via the Public Suffix List
- Registration data: RDAP (WHOIS fallback) for domain age, expiry, registry status, and DNSSEC
- Subdomain analysis: sensitive labels (
login.,secure.,verify., ...), brand-name impersonation in subdomains, and root-domain vs subdomain correlation (parked root, off-domain redirect, content similarity, infrastructure split) - Homoglyph / IDN spoofing: punycode decoding, mixed-script (Latin + Cyrillic/Greek/Armenian) and fullwidth lookalike detection, without flagging legitimate single-script IDNs
- Typosquatting: Levenshtein distance 1-2 and combo-squatting against the top-5000 domains
- Domain randomness: entropy and DGA heuristics for the domain label
- TLS/SSL inspection: issuer, certificate age, Certificate Transparency, chain validation, hostname match (probe pinned to a pre-resolved IP)
- HTTP analysis: manual per-hop redirect chain, final status, HSTS, cross-domain jump detection
- Page content analysis: login/payment/personal forms, forms submitting off-domain, hidden iframes, brand mismatch, favicon/meta-refresh/external-asset patterns, obfuscated or injected JavaScript, crypto-wallet hooks
- Threat feeds: PhishTank (optional API key) and external plugin feeds; each feed's PHPDoc is injected into the result as its description and category
- Transparent scoring: risk/trust/final scores (0-100) with plain-English reasons for every signal
- Caching: optional per-check TTL caching via phpfastcache, or any adapter implementing
SafeSurf\Cache\CacheInterface
Documentation
Full documentation lives in docs/:
| Document | Contents |
|---|---|
| Getting Started | Installation, CLI usage, first analysis in code, caching |
| Configuration | Every Config option, cache TTLs, data assets |
| Detection Checks | The full signal catalog: what each check inspects and returns |
| Scoring & Verdicts | How the scores and the Safe/Suspicious/Risky verdict are computed |
| Output Reference | The complete result structure, field by field, with real examples |
| Threat Feeds | Built-in feeds and the plugin system (PHPDoc injection) |
| Security | SSRF-safe fetching, IP pinning, body limits |
| Extending | Adding feeds, data, checks, and tests |
System Requirements
- PHP >= 8.0
- PHP extensions:
curl,openssl,dom,libxml
Installation
Via Composer / Packagist:
composer require safesurf/safesurf
Or from this source repository:
git clone https://github.com/DikaArdnt/safesurf-php.git
cd safesurf-php
composer install
Quick Start
From the CLI (uses the file cache in storage/cache and pretty-prints the JSON):
php examples/analyze.php https://example.com
In your code:
<?php declare(strict_types=1); require __DIR__ . '/vendor/autoload.php'; use SafeSurf\SafeSurf; $result = SafeSurf::analyze('https://example.com'); echo $result['result']['verdict']; // "Safe" | "Suspicious" | "Risky" echo $result['result']['final_score']; // 0-100 print_r($result['result']['reasons']); // plain-English good/neutral/bad reasons
With caching (recommended)
Any implementation of SafeSurf\Cache\CacheInterface works; the bundled adapter wraps phpfastcache (already a dependency):
<?php declare(strict_types=1); require __DIR__ . '/vendor/autoload.php'; use Phpfastcache\CacheManager; use Phpfastcache\Config\ConfigurationOption; use SafeSurf\Cache\PhpFastCacheAdapter; use SafeSurf\Config; use SafeSurf\SafeSurf; $pool = CacheManager::getInstance('Files', new ConfigurationOption([ 'path' => __DIR__ . '/storage/cache', ])); $config = new Config(cache: new PhpFastCacheAdapter($pool)); $result = SafeSurf::analyze('https://example.com', $config);
How the verdict works
The scorer combines weighted signals into risk_score and trust_score, then computes
final_score = 50 + (trust − risk) / 2 (clamped to 0-100):
| Verdict | Final score |
|---|---|
Safe |
>= 65 |
Suspicious |
30-64 |
Risky |
< 30 |
Design principles: one weak signal never produces a Risky verdict on its own; strong indicators (raw IP, punycode, verified PhishTank listing, password form without TLS) carry heavy weights; correlation findings only score as combinations. See Scoring & Verdicts for the complete weight tables.
Example output
Real output of SafeSurf::analyze('https://example.com'), trimmed. The full structure and field docs are in the Output Reference.
Configuration
All options live in Config.php and are passed as named arguments. The most common ones:
cache: cache adapter (see Getting Started)- Threat feeds: configured on the dedicated
ThreatFeedssetup (addThreatFeed(),withPhishTank(), per-feed options); see Threat Feeds enableRootDomainCorrelation(defaulttrue),rootCorrelationMaxHops- HTTP controls:
httpTimeoutMs,httpHeaderTimeoutMs,maxRedirects,userAgent,maxBodyBytes - Cache TTLs:
ttlDomainRankSeconds,ttlWhoisSeconds,ttlContentSeconds, ... (full list in Configuration; feed TTLs live on theThreatFeedssetup)
Threat Feed Plugins
Feeds implement SafeSurf\Service\ThreatFeeds\ThreatFeedInterface. The class PHPDoc of every feed is injected into the result: the summary becomes threat_feeds.results[].description and a @feed-category tag becomes results[].category, so consumers always know what each feed checks.
Severity mapping in the risk score: phishing/malware = 70, blocked = 25, unwanted = 20, info = 0. Full guide: Threat Feeds.
Security & Operational Notes
- SSRF protection: every HTTP fetch resolves DNS first, rejects private/loopback/link-local/metadata IPs (including IPv4-mapped IPv6), pins the connection to the validated IP (
CURLOPT_RESOLVE), never lets curl follow redirects (each hop is re-validated), and caps the download size. Details: Security. - TLS/SSL validation is best effort: some environments fail chain verification due to CA store or proxy configuration.
- Some modules need internet access on first use (PSL download, IANA RDAP bootstrap, PhishTank); the PSL is downloaded automatically to
storage/public_suffix_list.datif missing. - Content analysis performs GET requests and HTML parsing. Enable caching to reduce load when scanning at volume.
Development
Run the test suite:
composer tests
Run Rector to apply automatic code upgrades:
composer rector
Run Rector in dry-run mode to see what changes would be made without actually applying them:
composer rector:dry-run
Extension guide (new feeds, data files, checks): Extending.
License
MIT License. See LICENSE for details.