Trustable is an explainable machine learning browser extension that classifies webpage content as spam or legitimate and shows the user exactly which words and phrases drove that decision. It combines a TF-IDF + Logistic Regression pipeline with a Flask API backend and a Chrome/Chromium extension frontend. Everything runs locally on your machine — no cloud service, no data leaving your device.
On any webpage, the content script scrapes up to 2,000 characters of visible text and sends it to a local Flask server. The server runs the text through a trained pipeline and returns:
- Prediction —
spamorham - Trust score — a number from 0.0 (very suspicious) to 1.0 (very trustworthy), computed as
1 - P(spam) - Contributing features — the specific words and phrases that most influenced the prediction, labeled as spam-leaning or ham-leaning with numeric contribution scores
Results are shown in a sticky widget: a floating button that stays on every page (like Rakuten / Student Beans). Click it to expand a draggable panel with an animated trust-score ring, an animated network background, and a scrollable "Why this score" dropdown listing every contributing feature.
- Python 3.13 or higher
- uv package manager
- Google Chrome or any Chromium-based browser (Edge, Brave, etc.)
No dataset is required. A pretrained model (src/spam_pipeline.joblib) is
included in the repository and loads automatically on first run.
git clone https://github.com/AN00P-G/Trustable
cd Trustableuv synccd src
uv run server.pyThe server starts at http://127.0.0.1:8000 and loads the pretrained model
instantly. No dataset or training step required.
- Open Chrome and navigate to
chrome://extensions - Enable Developer mode (toggle in the top-right corner)
- Click Load unpacked
- Select the
my_extension/folder from this repository
The Trustable icon will appear in your toolbar. The sticky widget also loads automatically on every page; click the toolbar icon to show/hide it.
Once the extension is loaded and the server is running:
- Sticky widget — a floating Trustable button appears in the bottom-right corner of every page and automatically scrapes and classifies the page. A colored badge on the button shows the verdict at a glance (green for ham, red for spam).
- Click the button to expand the panel with the trust-score ring and the scrollable "Why this score" dropdown. Drag it anywhere; its position and open/closed state are remembered.
- Settings gear — next to the minimize button, choose to hide the widget for 30 minutes, 2 hours, or the rest of the browser session.
- Toolbar icon — click it to toggle the widget, or to bring it back after hiding it.
Example analysis output:
Predicted: spam
Trust score: 0.08
Top contributing features:
- 'free prize' (Spam) contrib: 0.4821
- 'click here' (Spam) contrib: 0.3109
- 'limited time' (Spam) contrib: 0.2744
- 'your account' (Ham) contrib: -0.1203
- 'verify' (Spam) contrib: 0.0987
The server exposes a single endpoint that accepts any text:
curl -X POST http://127.0.0.1:8000/classify \
-H "Content-Type: application/json" \
-d '{"text": "Congratulations! You have won a free iPhone. Click here to claim."}'Response:
{
"prediction": "spam",
"trust_score": 0.04,
"top_features": [
{"feature": "free iphone", "contribution": 0.512, "label": "Spam"},
{"feature": "click here", "contribution": 0.389, "label": "Spam"},
{"feature": "claim", "contribution": 0.271, "label": "Spam"}
]
}cd src
uv run main.pyEnter an SMS message to classify as spam or ham ("quit" to exit): Get a free iPhone now!
SMS: Get a free iPhone now!
Predicted: spam, Trust score: 0.06
Top contributing features:
Feature: 'free iphone', Contribution: 0.5120 --> Spam
Feature: 'free', Contribution: 0.3840 --> Spam
Feature: 'iphone', Contribution: 0.2910 --> Spam
The benchmark script evaluates the pretrained model against src/test.csv,
an unseen balanced dataset of 4,000 messages (2,000 ham, 2,000 spam), running
10,000 inference passes to measure accuracy and timing stability.
cd src
uv run test.pyExpected results:
| Metric | Value |
|---|---|
| Accuracy | 93.47% |
| Ham F1 | 0.9382 |
| Spam F1 | 0.9309 |
| Spam precision | 98.88% |
| Mean inference time | 43.73 ms per 4,000 samples |
| Prediction consistency | True (identical across all 10,000 runs) |
To retrain the model on custom data, create src/Dataset/ and drop labeled
CSV files into it. The preprocessing pipeline normalizes column names and
labels automatically.
Supported column name formats:
| Column names | Example source |
|---|---|
v1, v2 |
UCI SMS Spam Collection |
label, text |
Generic format |
category, message |
Alternative SMS format |
email, label |
Email datasets |
Supported label encodings (all mapped to ham or spam automatically):
| Input value | Mapped to |
|---|---|
spam, junk, phishing, bad, 1 |
spam |
ham, not spam, legit, ok, 0 |
ham |
After adding data, restart the server. It will detect the change via MD5 hash and retrain automatically.
Trustable/
├── src/
│ ├── Dataset/ # place custom training CSVs here (gitignored)
│ ├── main.py # interactive CLI entry point
│ ├── model.py # pipeline training, caching, and explain()
│ ├── preprocess.py # CSV loading, normalization, train/test split
│ ├── server.py # Flask API server
│ ├── test.py # benchmark evaluation script
│ ├── test.csv # unseen benchmark dataset (4,000 samples)
│ ├── spam_pipeline.joblib # pretrained model (loaded automatically)
│ └── spam_pipeline.hash # hash of training data for cache invalidation
├── my_extension/
│ ├── manifest.json # Chrome Manifest V3 config
│ ├── content.js # page scraping + sticky widget UI
│ ├── network.js # animated network background (shared canvas)
│ ├── background.js # service worker: toolbar toggle + session storage
│ └── icon.png # extension icon
├── pyproject.toml # Python project and dependency config
└── uv.lock # locked dependency versions
The repository includes a GitHub Actions workflow (.github/workflows/test.yml)
that runs the CLI automatically on Ubuntu, Windows, and macOS on every push
to main. This verifies that the pretrained model loads and classifies
correctly across all three platforms without any setup beyond uv sync.
| Component | Configuration |
|---|---|
| Feature extraction | TF-IDF, unigrams and bigrams, min_df=3 |
| Classifier | Logistic Regression, class_weight="balanced", max_iter=1000 |
| Train/test split | 80/20, random_state=22 |
| Cache invalidation | MD5 hash of training CSV |
| Serialization | joblib |
Server not reachable from extension
Make sure server.py is running before using the extension. The server must
be started manually.
Extension shows "Content script not ready"
Reload the page and try again. This can happen on pages that restrict script injection (e.g., the Chrome Web Store itself).
Model retrains unexpectedly on startup
This happens when src/Dataset/ contains CSV files and their content has
changed since the last run. Remove or empty src/Dataset/ to always use
the included pretrained model.