0
stars
15
commits
Python
primary language
Sep 6, 2026
updated
A full-stack web application that analyzes URLs for phishing indicators using a combination of machine learning (Random Forest) and security rules. Built with React.js, Bootstrap 5, FastAPI, and SQLite.
| Layer | Technology |
|---|---|
| Frontend | React.js, Vite, Bootstrap 5, Bootstrap Icons |
| HTTP Client | Axios |
| Charts | Chart.js, react-chartjs-2 |
| Backend | Python, FastAPI, Uvicorn |
| Database | SQLite + SQLAlchemy |
| ML | scikit-learn Random Forest (SivakumarP/PhishingURLDetection), joblib, scipy, tldextract |
React + Bootstrap
↓
Axios
↓
FastAPI
↓
URL Validation
↓
URL Feature Extraction
↓
┌──────────────────────┐
│ │
│ Security Rules │
│ + │
│ RandomForest ML Model│
│ │
└──────────┬───────────┘
↓
Risk Engine
↓
Final Classification
↓
Response
↓
React Result UI
phishguard/
├── .gitignore
├── README.md
├── frontend/
│ ├── .env # API base URL config
│ ├── .env.example
│ ├── package.json
│ ├── vite.config.js
│ └── src/
│ ├── main.jsx # App entry point
│ ├── App.jsx # React Router setup
│ ├── index.css # Global theme styles
│ ├── dashboard.css # Dashboard styles
│ ├── scanhistory.css # Scan History styles
│ ├── about.css # About page styles
│ ├── components/
│ │ ├── Navbar.jsx
│ │ ├── Footer.jsx
│ │ ├── Layout.jsx
│ │ ├── BrandLogo.jsx
│ │ ├── UrlScanner.jsx # URL input + validation
│ │ ├── HeroSection.jsx
│ │ ├── FeaturesSection.jsx
│ │ ├── HowItWorksSection.jsx
│ │ ├── EmptyState.jsx
│ │ ├── DashboardStats.jsx
│ │ ├── RecentScansTable.jsx
│ │ ├── RiskDistributionChart.jsx
│ │ ├── RiskScoreTrendChart.jsx
│ │ ├── ScanHistoryFilters.jsx
│ │ └── ScanHistoryTable.jsx
│ ├── pages/
│ │ ├── HomePage.jsx
│ │ ├── AnalysisResultPage.jsx
│ │ ├── DashboardPage.jsx
│ │ ├── ScanHistoryPage.jsx
│ │ └── AboutPage.jsx
│ ├── services/
│ │ └── api.js # Centralized API client
│ ├── data/
│ │ ├── siteContent.js
│ │ └── aboutContent.js
│ └── utils/
│ └── historyFilters.js
└── backend/
├── .env.example
├── main.py # FastAPI app setup
├── requirements.txt
├── phishguard.db # SQLite database (auto-created)
├── database/
│ ├── __init__.py
│ ├── database.py # SQLAlchemy config
│ ├── models.py # ScanRecord model
│ └── schemas.py # Pydantic schemas
├── routes/
│ ├── __init__.py
│ ├── scanner.py # POST /analyze
│ ├── history.py # GET /history, DELETE /history/{id}
│ └── dashboard.py # GET /dashboard
├── services/
│ ├── __init__.py
│ ├── feature_extractor.py # URL feature extraction (rules engine)
│ ├── security_rules.py # Security rules engine
│ ├── risk_engine.py # Risk scoring + classification
│ └── ml_predictor.py # Cached ML predictor (SivakumarP RF, legacy ONNX fallback)
└── ml/
├── sivakumar/ # SivakumarP Random Forest artifacts (production)
│ ├── model.pkl # RandomForestClassifier (100 trees, gini, depth 32)
│ ├── dataencoder_url.pkl # char TF-IDF of full URL (96 features)
│ ├── dataencoder_dom.pkl # char TF-IDF of registered domain (57 features)
│ ├── dataencoder_tld.pkl # char TF-IDF of public suffix / TLD (32 features)
│ └── datascaler.pkl # StandardScaler(digit_cnt, is_https) (2 features)
└── model.onnx # Legacy pirocheto ONNX model (rollback only)
cd frontend
npm install
npm run dev
The frontend runs at http://localhost:5173.
cd backend
# Create virtual environment
python -m venv venv
# Activate virtual environment
# Windows:
venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Start the server
uvicorn main:app --reload --port 8000
The backend runs at http://127.0.0.1:8000.
The production ML model is SivakumarP/PhishingURLDetection, a
scikit-learn Random Forest (100 trees, gini, depth 32) trained on a
feature-engineered URL dataset. It does not accept a raw URL string alone;
ml_predictor.py builds its exact 187-feature input internally:
TF-IDF(char, full URL) # dataencoder_url.pkl -> 96 features
+ TF-IDF(char, registered domain) # dataencoder_dom.pkl -> 57 features
+ TF-IDF(char, public suffix) # dataencoder_tld.pkl -> 32 features
+ scaled(digit_cnt, is_https) # datascaler.pkl -> 2 features
= 187 features
Registered domain and public suffix are extracted with tldextract (IP hosts
yield dom = IP string and an empty TLD, matching the training dataset). The
model loads the five pickle artifacts once per Python process (module-level
cache) and returns the phishing probability as predict_proba()[1]
(class 0 = benign, class 1 = phishing). No probability post-processing is
applied.
The legacy pirocheto/phishing-url-detection ONNX model is preserved at
backend/ml/model.onnx for rollback; set MODEL_BACKEND=pirocheto to
restore it. URL features for the explainable rules engine are computed
independently and never substituted for the model's TF-IDF input.
The final risk score is a documented heuristic, not a calibrated probability:
final phishing risk = ML phishing probability * 70% (heuristic weight)
+ URL phishing-rule risk * 30% (heuristic weight)
TLS connection security and HTTP hardening are separate dimensions and never
enter this phishing-risk formula. Unavailable network checks do not become
phishing evidence. Missing HTTPS remains visible as a transport warning but
does not inflate the URL phishing-rule score. The API returns each phishing
component, weight, and weighted contribution so the final integer is
reproducible, along with separate connection_security and http_security
objects.
Both ML models are loaded once per Python process from trusted repository
files under backend/ml/; there is no uploaded-model endpoint and no model
download on each request. The SivakumarP artifacts total ~29.8 MB; with
scikit-learn/scipy/joblib/tldextract the full deployment bundle is ~247 MB,
which fits Vercel's 500 MB uncompressed Python function limit (verified live,
see backend/benchmark/vercel_live_report.md). Cold start is ~1.8 s with
model load cached per warm instance.
| Variable | Default | Description |
|---|---|---|
VITE_API_BASE_URL | http://127.0.0.1:8000 | Backend API URL |
| Variable | Default | Description |
|---|---|---|
CORS_ORIGINS | (empty) | Comma-separated allowed origins |
MODEL_BACKEND | sivakumar | ML backend: sivakumar (Random Forest, default) or pirocheto (legacy ONNX, rollback) |
GET /
Response:
{
"status": "ok",
"service": "PhishGuard API"
}
POST /analyze
Request:
{
"url": "https://example.com"
}
Response:
{
"url": "https://example.com",
"classification": "SAFE",
"risk_score": 12,
"confidence": 82,
"message": "URL analysis completed. No significant phishing indicators detected.",
"detected_indicators": [...],
"summary": "No major phishing indicators were detected...",
"features": {...},
"rules": [...],
"ml_analysis": {...},
"rule_analysis": {"score": 0, "findings": [...]},
"connection_security": {"https": true, "tls_available": true, "headers_available": true},
"risk_breakdown": {...},
"top_factors": [...],
"model_info": {...}
}
GET /history?search=&classification=All&risk=All&date=All%20Time&page=1&page_size=10
Response:
{
"records": [...],
"total": 25,
"page": 1,
"page_size": 10,
"total_pages": 3
}
GET /history/{id}
Returns the exact stored scan record (the ML model is NOT re-run). The
detected_indicators field is a JSON string of the indicators that were
flagged at scan time.
Response:
{
"id": 1,
"url": "https://example.com",
"classification": "SAFE",
"risk_score": 0,
"confidence": 1,
"message": "URL analysis completed. ...",
"detected_indicators": "[]",
"summary": "No major phishing indicators were detected. ...",
"scanned_at": "2026-09-05T11:28:14.718806"
}
DELETE /history/{id}
GET /dashboard
Response:
{
"total_scans": 1250,
"safe_count": 820,
"suspicious_count": 275,
"phishing_count": 155,
"average_risk_score": 46.2,
"recent_scans": [...]
}
GET /dashboard/trends
Returns the real average risk score per calendar day for the last 7 days,
derived from SQLite scan records. Days with no scans are reported as null
(rendered as a gap in the chart) — no values are fabricated.
Response:
{
"labels": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
"average_risk_scores": [null, null, null, null, null, null, 54.3],
"total_scans": 19
}
Status: Active
Model: SivakumarP/PhishingURLDetection
Inference: scikit-learn (predict_proba)
Model type: Random Forest classifier (100 trees, gini, max depth 32)
Purpose: Binary classification of URLs as phishing or safe
Artifacts (in backend/ml/sivakumar/): model.pkl + three TF-IDF
vectorizers (dataencoder_url.pkl, dataencoder_dom.pkl,
dataencoder_tld.pkl) + datascaler.pkl
How it works:
ml_predictor.py preprocesses each URL into the exact 187-feature vector: char TF-IDF of the full URL, the registered domain, and the public suffix, concatenated with scaled digit count and HTTPS flag (see "Detection and Risk Model" above)Input/Output (verified by inspection and live testing):
predict_proba() shape [n, 2] — column 0 = benign (class 0), column 1 = phishing (class 1)phishing_probability = predict_proba(features)[1]; the mapping is not reversedBenchmark results (internal 210-URL benchmark only — not universal
real-world accuracy): accuracy 85.71%, precision 93.42%, recall 73.96%, F1
0.8256, ROC-AUC 0.9471, FPR 4.39%, FNR 26.04%. Full methodology and
per-URL predictions in backend/benchmark/.
Verified live predictions (local vs Vercel, bit-identical):
learnova-ai-8.vercel.app → 54.0% phishingwww.google.com → 13.0% phishingexample.com → 30.0% phishinggithub.com → 32.0% phishingpaypal.com → 45.0% phishingLimitations:
Fallback / rollback: If the SivakumarP artifacts fail to load, the
predictor falls back to rule-based analysis only. Set MODEL_BACKEND=pirocheto
to restore the legacy ONNX model (kept at backend/ml/model.onnx).
Engine: SQLite (file: backend/phishguard.db)
Table: scan_records
| Column | Type | Description |
|---|---|---|
| id | INTEGER | Primary key |
| url | VARCHAR(2048) | Analyzed URL |
| classification | VARCHAR(20) | SAFE / SUSPICIOUS / PHISHING |
| risk_score | INTEGER | 0-100 risk score |
| confidence | INTEGER | 0-100 confidence |
| message | TEXT | Analysis message |
| detected_indicators | TEXT | JSON string of indicators |
| summary | TEXT | Analysis summary |
| scanned_at | DATETIME | Timestamp (UTC) |
The database is auto-created on first startup.
# Frontend
cd frontend
npm install
npm run dev
# Backend
cd backend
python -m venv venv
venv\Scripts\activate # Windows
# source venv/bin/activate # macOS/Linux
pip install -r requirements.txt
uvicorn main:app --reload --port 8000
Educational project for cybersecurity and machine learning learning.
15 commits
Python
53.0%
JavaScript
36.1%
CSS
10.7%
0
stars
15
commits
Python
primary language
Sep 6, 2026
updated
A full-stack web application that analyzes URLs for phishing indicators using a combination of machine learning (Random Forest) and security rules. Built with React.js, Bootstrap 5, FastAPI, and SQLite.
| Layer | Technology |
|---|---|
| Frontend | React.js, Vite, Bootstrap 5, Bootstrap Icons |
| HTTP Client | Axios |
| Charts | Chart.js, react-chartjs-2 |
| Backend | Python, FastAPI, Uvicorn |
| Database | SQLite + SQLAlchemy |
| ML | scikit-learn Random Forest (SivakumarP/PhishingURLDetection), joblib, scipy, tldextract |
React + Bootstrap
↓
Axios
↓
FastAPI
↓
URL Validation
↓
URL Feature Extraction
↓
┌──────────────────────┐
│ │
│ Security Rules │
│ + │
│ RandomForest ML Model│
│ │
└──────────┬───────────┘
↓
Risk Engine
↓
Final Classification
↓
Response
↓
React Result UI
phishguard/
├── .gitignore
├── README.md
├── frontend/
│ ├── .env # API base URL config
│ ├── .env.example
│ ├── package.json
│ ├── vite.config.js
│ └── src/
│ ├── main.jsx # App entry point
│ ├── App.jsx # React Router setup
│ ├── index.css # Global theme styles
│ ├── dashboard.css # Dashboard styles
│ ├── scanhistory.css # Scan History styles
│ ├── about.css # About page styles
│ ├── components/
│ │ ├── Navbar.jsx
│ │ ├── Footer.jsx
│ │ ├── Layout.jsx
│ │ ├── BrandLogo.jsx
│ │ ├── UrlScanner.jsx # URL input + validation
│ │ ├── HeroSection.jsx
│ │ ├── FeaturesSection.jsx
│ │ ├── HowItWorksSection.jsx
│ │ ├── EmptyState.jsx
│ │ ├── DashboardStats.jsx
│ │ ├── RecentScansTable.jsx
│ │ ├── RiskDistributionChart.jsx
│ │ ├── RiskScoreTrendChart.jsx
│ │ ├── ScanHistoryFilters.jsx
│ │ └── ScanHistoryTable.jsx
│ ├── pages/
│ │ ├── HomePage.jsx
│ │ ├── AnalysisResultPage.jsx
│ │ ├── DashboardPage.jsx
│ │ ├── ScanHistoryPage.jsx
│ │ └── AboutPage.jsx
│ ├── services/
│ │ └── api.js # Centralized API client
│ ├── data/
│ │ ├── siteContent.js
│ │ └── aboutContent.js
│ └── utils/
│ └── historyFilters.js
└── backend/
├── .env.example
├── main.py # FastAPI app setup
├── requirements.txt
├── phishguard.db # SQLite database (auto-created)
├── database/
│ ├── __init__.py
│ ├── database.py # SQLAlchemy config
│ ├── models.py # ScanRecord model
│ └── schemas.py # Pydantic schemas
├── routes/
│ ├── __init__.py
│ ├── scanner.py # POST /analyze
│ ├── history.py # GET /history, DELETE /history/{id}
│ └── dashboard.py # GET /dashboard
├── services/
│ ├── __init__.py
│ ├── feature_extractor.py # URL feature extraction (rules engine)
│ ├── security_rules.py # Security rules engine
│ ├── risk_engine.py # Risk scoring + classification
│ └── ml_predictor.py # Cached ML predictor (SivakumarP RF, legacy ONNX fallback)
└── ml/
├── sivakumar/ # SivakumarP Random Forest artifacts (production)
│ ├── model.pkl # RandomForestClassifier (100 trees, gini, depth 32)
│ ├── dataencoder_url.pkl # char TF-IDF of full URL (96 features)
│ ├── dataencoder_dom.pkl # char TF-IDF of registered domain (57 features)
│ ├── dataencoder_tld.pkl # char TF-IDF of public suffix / TLD (32 features)
│ └── datascaler.pkl # StandardScaler(digit_cnt, is_https) (2 features)
└── model.onnx # Legacy pirocheto ONNX model (rollback only)
cd frontend
npm install
npm run dev
The frontend runs at http://localhost:5173.
cd backend
# Create virtual environment
python -m venv venv
# Activate virtual environment
# Windows:
venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Start the server
uvicorn main:app --reload --port 8000
The backend runs at http://127.0.0.1:8000.
The production ML model is SivakumarP/PhishingURLDetection, a
scikit-learn Random Forest (100 trees, gini, depth 32) trained on a
feature-engineered URL dataset. It does not accept a raw URL string alone;
ml_predictor.py builds its exact 187-feature input internally:
TF-IDF(char, full URL) # dataencoder_url.pkl -> 96 features
+ TF-IDF(char, registered domain) # dataencoder_dom.pkl -> 57 features
+ TF-IDF(char, public suffix) # dataencoder_tld.pkl -> 32 features
+ scaled(digit_cnt, is_https) # datascaler.pkl -> 2 features
= 187 features
Registered domain and public suffix are extracted with tldextract (IP hosts
yield dom = IP string and an empty TLD, matching the training dataset). The
model loads the five pickle artifacts once per Python process (module-level
cache) and returns the phishing probability as predict_proba()[1]
(class 0 = benign, class 1 = phishing). No probability post-processing is
applied.
The legacy pirocheto/phishing-url-detection ONNX model is preserved at
backend/ml/model.onnx for rollback; set MODEL_BACKEND=pirocheto to
restore it. URL features for the explainable rules engine are computed
independently and never substituted for the model's TF-IDF input.
The final risk score is a documented heuristic, not a calibrated probability:
final phishing risk = ML phishing probability * 70% (heuristic weight)
+ URL phishing-rule risk * 30% (heuristic weight)
TLS connection security and HTTP hardening are separate dimensions and never
enter this phishing-risk formula. Unavailable network checks do not become
phishing evidence. Missing HTTPS remains visible as a transport warning but
does not inflate the URL phishing-rule score. The API returns each phishing
component, weight, and weighted contribution so the final integer is
reproducible, along with separate connection_security and http_security
objects.
Both ML models are loaded once per Python process from trusted repository
files under backend/ml/; there is no uploaded-model endpoint and no model
download on each request. The SivakumarP artifacts total ~29.8 MB; with
scikit-learn/scipy/joblib/tldextract the full deployment bundle is ~247 MB,
which fits Vercel's 500 MB uncompressed Python function limit (verified live,
see backend/benchmark/vercel_live_report.md). Cold start is ~1.8 s with
model load cached per warm instance.
| Variable | Default | Description |
|---|---|---|
VITE_API_BASE_URL | http://127.0.0.1:8000 | Backend API URL |
| Variable | Default | Description |
|---|---|---|
CORS_ORIGINS | (empty) | Comma-separated allowed origins |
MODEL_BACKEND | sivakumar | ML backend: sivakumar (Random Forest, default) or pirocheto (legacy ONNX, rollback) |
GET /
Response:
{
"status": "ok",
"service": "PhishGuard API"
}
POST /analyze
Request:
{
"url": "https://example.com"
}
Response:
{
"url": "https://example.com",
"classification": "SAFE",
"risk_score": 12,
"confidence": 82,
"message": "URL analysis completed. No significant phishing indicators detected.",
"detected_indicators": [...],
"summary": "No major phishing indicators were detected...",
"features": {...},
"rules": [...],
"ml_analysis": {...},
"rule_analysis": {"score": 0, "findings": [...]},
"connection_security": {"https": true, "tls_available": true, "headers_available": true},
"risk_breakdown": {...},
"top_factors": [...],
"model_info": {...}
}
GET /history?search=&classification=All&risk=All&date=All%20Time&page=1&page_size=10
Response:
{
"records": [...],
"total": 25,
"page": 1,
"page_size": 10,
"total_pages": 3
}
GET /history/{id}
Returns the exact stored scan record (the ML model is NOT re-run). The
detected_indicators field is a JSON string of the indicators that were
flagged at scan time.
Response:
{
"id": 1,
"url": "https://example.com",
"classification": "SAFE",
"risk_score": 0,
"confidence": 1,
"message": "URL analysis completed. ...",
"detected_indicators": "[]",
"summary": "No major phishing indicators were detected. ...",
"scanned_at": "2026-09-05T11:28:14.718806"
}
DELETE /history/{id}
GET /dashboard
Response:
{
"total_scans": 1250,
"safe_count": 820,
"suspicious_count": 275,
"phishing_count": 155,
"average_risk_score": 46.2,
"recent_scans": [...]
}
GET /dashboard/trends
Returns the real average risk score per calendar day for the last 7 days,
derived from SQLite scan records. Days with no scans are reported as null
(rendered as a gap in the chart) — no values are fabricated.
Response:
{
"labels": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
"average_risk_scores": [null, null, null, null, null, null, 54.3],
"total_scans": 19
}
Status: Active
Model: SivakumarP/PhishingURLDetection
Inference: scikit-learn (predict_proba)
Model type: Random Forest classifier (100 trees, gini, max depth 32)
Purpose: Binary classification of URLs as phishing or safe
Artifacts (in backend/ml/sivakumar/): model.pkl + three TF-IDF
vectorizers (dataencoder_url.pkl, dataencoder_dom.pkl,
dataencoder_tld.pkl) + datascaler.pkl
How it works:
ml_predictor.py preprocesses each URL into the exact 187-feature vector: char TF-IDF of the full URL, the registered domain, and the public suffix, concatenated with scaled digit count and HTTPS flag (see "Detection and Risk Model" above)Input/Output (verified by inspection and live testing):
predict_proba() shape [n, 2] — column 0 = benign (class 0), column 1 = phishing (class 1)phishing_probability = predict_proba(features)[1]; the mapping is not reversedBenchmark results (internal 210-URL benchmark only — not universal
real-world accuracy): accuracy 85.71%, precision 93.42%, recall 73.96%, F1
0.8256, ROC-AUC 0.9471, FPR 4.39%, FNR 26.04%. Full methodology and
per-URL predictions in backend/benchmark/.
Verified live predictions (local vs Vercel, bit-identical):
learnova-ai-8.vercel.app → 54.0% phishingwww.google.com → 13.0% phishingexample.com → 30.0% phishinggithub.com → 32.0% phishingpaypal.com → 45.0% phishingLimitations:
Fallback / rollback: If the SivakumarP artifacts fail to load, the
predictor falls back to rule-based analysis only. Set MODEL_BACKEND=pirocheto
to restore the legacy ONNX model (kept at backend/ml/model.onnx).
Engine: SQLite (file: backend/phishguard.db)
Table: scan_records
| Column | Type | Description |
|---|---|---|
| id | INTEGER | Primary key |
| url | VARCHAR(2048) | Analyzed URL |
| classification | VARCHAR(20) | SAFE / SUSPICIOUS / PHISHING |
| risk_score | INTEGER | 0-100 risk score |
| confidence | INTEGER | 0-100 confidence |
| message | TEXT | Analysis message |
| detected_indicators | TEXT | JSON string of indicators |
| summary | TEXT | Analysis summary |
| scanned_at | DATETIME | Timestamp (UTC) |
The database is auto-created on first startup.
# Frontend
cd frontend
npm install
npm run dev
# Backend
cd backend
python -m venv venv
venv\Scripts\activate # Windows
# source venv/bin/activate # macOS/Linux
pip install -r requirements.txt
uvicorn main:app --reload --port 8000
Educational project for cybersecurity and machine learning learning.
15 commits
Python
53.0%
JavaScript
36.1%
CSS
10.7%