Chrome extension with unified Rust server for network monitoring, clickfix detection, YouTube channel whitelisting, and extension tracking.
cd server
cargo run --releaseServer runs on http://127.0.0.1:8080 with no setup required.
- Open Chrome →
chrome://extensions/ - Enable Developer mode
- Click Load unpacked → select the
extensionfolder
- Click the extension icon → Settings
- Server URL:
http://localhost:8080/api/logs(default) - Features: Enable only what you need (Report URLs, JS execution, Clickfix, Extension monitoring)
- YouTube: Optional channel whitelist (only listed channels allowed)
- Save
Done! The extension sends logs and events to the server. Use Client ID (in Settings) to identify this browser across sessions.
Visit http://127.0.0.1:8080/ or http://127.0.0.1:8080/dashboard to view the web dashboard with events, network logs, clients, and blocklist management.
Feature toggles (no slow delay when server is unavailable)
- Report URLs – Send network request logs to the server (off by default)
- JS execution – Report external script loads (script tags with
src) to/api/extensions(on by default) - Clickfix – Detect clipboard/copy-based social engineering (e.g. PowerShell in console) and report to
/api/security(on by default) - Extension monitoring – Report extension install/uninstall/update to
/api/extensions(off by default) - Report file uploads (e.g. ChatGPT) – Report when the user uploads files to monitored hosts (e.g. ChatGPT); hosts configurable (off by default)
Core
- Client ID – Persistent identifier sent in all requests (
/api/logs,/api/extensions,/api/security); visible in Settings, copy/generate. - Compression – Batches sent as gzip when enabled; server decompresses automatically.
- Timeout – Requests abort after 5s so the extension doesn’t hang when the server is down.
- Blocklist – URL patterns and (legacy) YouTube blocklist from server; optional domain whitelist to reduce logging.
YouTube channel whitelist
- Whitelist mode – Only channels in the list are allowed; all others are hidden or blocked.
- Where it applies: Home/search results (video tiles), channel pages (e.g.
/@PirateSoftware/videos), and direct watch links (/watch?v=...). Non-whitelisted watch pages show a full-screen “channel not in whitelist” overlay and no playback. - Matching – Handles and display names are normalized (e.g.
@PirateSoftwarematches “Pirate Software”). Supports/channel/ID,/@handle,/user/name. - Empty whitelist – If whitelist is enabled but empty, all YouTube content is hidden/blocked.
Security
- Clickfix detection – Detects suspicious copy-paste and clipboard writes. Covers PowerShell, CMD (
cmd /c), VBScript (CreateObject WinHttp, Execute), mshta, certutil, wscript, and other Windows abuse patterns. Sends events toPOST /api/security. Deduplicated (15s TTL) to avoid duplicate alerts. - ChatGPT file upload – When enabled, detects file uploads to ChatGPT (
chatgpt.com/backend-api/files) and sendschatgpt_file_uploadtoPOST /api/security(filename and payload captured when possible). - Extension security scan – Optional; results sent to
/api/security.
Modes
- Simple – In-memory, zero config, last 1000 logs; ideal for testing.
- Production – PostgreSQL (and optional Redis), unlimited storage,
client_idstored with logs and extension events.
Endpoints
GET //GET /dashboard– Web dashboard (events, logs, clients, blocklist).GET /logo.png– CanIGoIn logo.GET /health– Health check.POST /api/logs– Batch network logs (optional gzip, optionalclient_id).GET /api/logs– Get logs (simple mode only).GET /api/blocklist/POST /api/blocklist– URL and YouTube blocklist.GET /api/dashboard/events– Events for dashboard (filter: all | security | javascript).GET /api/dashboard/events/{packet_id}– Inspect single event.GET /api/dashboard/clients– Unique client IDs.POST /api/extensions– Extension lifecycle/monitoring events (optional gzip,client_id).POST /api/security– Security events (clickfix, extension security scan); same JSON shape as extensions, optional gzip andclient_id.
Behavior
- Gzip – All POST bodies that send JSON accept
Content-Encoding: gzip; on decompress error the server falls back to plain UTF-8 (no 400). - client_id – Stored in production for logs and extension_events; used for correlation and analytics.
- packet_id – All events receive a unique packet ID; clickfix events include
risk_scorefor dashboard display.
Dashboard
- Tabs: All events, Security, JavaScript, Network logs, Clients, Blocklist.
- Search, date range, filters, pagination, column sorting.
- Packet inspection with JSON syntax highlighting, copy, search.
- Export to CSV, auto-refresh toggle, connection status.
See server/README.md for full API and schema.
┌─────────────────────────────────────────────────────────────────────────┐
│ Chrome Browser Tab │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────┐ ┌─────────────────────────────────┐ │
│ │ PAGE CONTEXT │ │ CONTENT SCRIPT (isolated) │ │
│ │ (page's JS world) │ │ (extension's JS world) │ │
│ ├──────────────────────┤ ├─────────────────────────────────┤ │
│ │ │ │ │ │
│ │ navigator.clipboard │ │ content.js │ │
│ │ window.eval │ │ - Listens to DOM events │ │
│ │ window.Function │ │ - Observes script elements │ │
│ │ │ │ - Runs clickfix detection │ │
│ │ <script> │ │ │ │
│ │ writeText(...) │ │ ┌───────────────────────────┐ │ │
│ │ </script> │ │ │ Injected script │ │ │
│ │ │ │ │ (page-context-clipboard.js)│ │ │
│ │ │ │ │ - Overrides writeText() │ │ │
│ │ │ │ │ - Dispatches DOM event │ │ │
│ │ │ │ └───────────────────────────┘ │ │
│ └──────────────────────┘ └─────────────────────────────────┘ │
│ │ │ │
│ │ │ │
│ └─────────── Shared DOM ─────────────┘ │
│ (document, events) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
│ │
│ │
▼ ▼
┌──────────────────────┐ ┌─────────────────────────────────┐
│ BACKGROUND SCRIPT │ │ Rust Server │
│ (background.js) │ │ │
├──────────────────────┤ ├─────────────────────────────────┤
│ │ │ │
│ - Receives messages │ │ POST /api/security │
│ - Batches events │────────▶│ POST /api/extensions │
│ - Compresses (gzip) │ │ POST /api/logs │
│ - Adds client_id │ │ │
│ │ │ - Decompresses gzip │
│ │ │ - Stores client_id │
│ │ │ - Logs with 🔒 SECURITY │
│ │ │ │
└──────────────────────┘ └─────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ SCENARIO 1: User copies text (Ctrl+C) │
└─────────────────────────────────────────────────────────────────────┘
User selects text → Ctrl+C
│
▼
┌─────────────────┐
│ DOM 'copy' │ ← Shared event (both worlds see it)
│ event fires │
└─────────────────┘
│
├──────────────────────────────┐
│ │
▼ ▼
┌──────────────┐ ┌─────────────────────┐
│ Page context │ │ Content script │
│ (ignored) │ │ (listens & detects) │
└──────────────┘ └─────────────────────┘
│
▼
┌─────────────────────┐
│ detectClickfix() │
│ - Pattern matching │
│ - Risk scoring │
└─────────────────────┘
│
▼
┌─────────────────────┐
│ chrome.runtime │
│ .sendMessage() │
└─────────────────────┘
│
▼
┌─────────────────────┐
│ Background script │
│ → POST /api/security│
└─────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ SCENARIO 2: Page calls writeText() programmatically │
└─────────────────────────────────────────────────────────────────────┘
Page JS: navigator.clipboard.writeText("powershell...")
│
▼
┌─────────────────────────────────────────┐
│ Injected script (page-context-clipboard)│
│ - Overrides writeText() │
│ - Calls original writeText() │
│ - Dispatches custom DOM event │
└─────────────────────────────────────────┘
│
│ CustomEvent('__extensionClipboardWriteText', {text})
│
▼
┌─────────────────┐
│ Shared DOM │ ← Event bubbles to content script
└─────────────────┘
│
▼
┌─────────────────────┐
│ Content script │
│ (listens & detects) │
└─────────────────────┘
│
▼
┌─────────────────────┐
│ detectClickfix() │
│ → Background │
│ → POST /api/security│
└─────────────────────┘
Chrome extensions run content scripts in an isolated world: they share the DOM with the page but have a separate JavaScript context. So:
- Content script (
content.js) can listen to DOM events (e.g.copy) and see the samedocumentas the page. - Content script cannot see when the page’s JavaScript calls
navigator.clipboard.writeText()oreval()— the page uses its ownnavigatorandwindow, so overrides in the content script world are never used by page code.
-
Copy (primary)
The user selects text and copies (Ctrl+C). The copy event fires on the document. The content script listens withdocument.addEventListener('copy', ...), reads the selection, runs clickfix pattern detection, and sends the result to the background →POST /api/security. No injection needed; the DOM event is shared. -
Programmatic clipboard write (secondary)
When page code callsnavigator.clipboard.writeText(text), that runs in the page world, so the content script’s override ofwriteTextis never called. To detect it we inject a script into the page context:- The content script adds a
<script src=".../page-context-clipboard.js">to the document. That script is loaded from the extension (viaweb_accessible_resources) and runs in the page world. - The injected script overrides
navigator.clipboard.writeTextin the page world. When the page calls it, our override runs, calls the realwriteText, then dispatches a custom DOM event (e.g.__extensionClipboardWriteText) withdetail: { text }. - The content script listens for that event on
document. Because the event is on the shared DOM, the content script receives it, runs clickfix detection one.detail.text, and sends to the background →POST /api/security.
- The content script adds a
We use a separate script file (not inline script) so the page’s Content Security Policy does not block execution (inline script would require a nonce/hash).
The content script observes the DOM for <script src="..."> elements (via MutationObserver and document.createElement override). When an external script is loaded, it sends javascript_execution to the background → POST /api/extensions. This stays entirely in the content script world; no page-context injection is used for script loading.
┌─────────────┐
│ Browser │
│ (User) │
└──────┬──────┘
│
│ 1. User action (copy, page writes to clipboard, etc.)
▼
┌─────────────────────────────────────────────────────────────┐
│ Content Script (content.js) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ • Listens to DOM events (copy) │ │
│ │ • Receives custom events from injected script │ │
│ │ • Observes script elements │ │
│ │ • Runs clickfix detection (pattern matching) │ │
│ └───────────────────────────────────────────────────────┘ │
└───────────────────────┬─────────────────────────────────────┘
│
│ 2. chrome.runtime.sendMessage()
▼
┌─────────────────────────────────────────────────────────────┐
│ Background Script (background.js) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ • Receives messages from content script │ │
│ │ • Batches events (reduces server load) │ │
│ │ • Adds client_id (persistent browser ID) │ │
│ │ • Compresses payload (gzip) │ │
│ │ • Routes: │ │
│ │ - clickfix → POST /api/security │ │
│ │ - JS execution → POST /api/extensions │ │
│ │ - Network logs → POST /api/logs │ │
│ └───────────────────────────────────────────────────────┘ │
└───────────────────────┬─────────────────────────────────────┘
│
│ 3. HTTP POST (gzip-compressed)
▼
┌─────────────────────────────────────────────────────────────┐
│ Rust Server (main.rs) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ • Decompresses gzip (or falls back to plain JSON) │ │
│ │ • Parses JSON payload │ │
│ │ • Logs: 🔒 SECURITY for /api/security │ │
│ │ • Stores client_id (production mode) │ │
│ │ • Returns success response │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ YouTube Page Load │
└───────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ youtube-blocker.js (content script) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 1. Loads whitelist from chrome.storage │ │
│ │ 2. Detects page type: │ │
│ │ • Home/search (video tiles) │ │
│ │ • Channel page (/@handle/videos) │ │
│ │ • Watch page (/watch?v=...) │ │
│ │ 3. Extracts channel info: │ │
│ │ • From video links (href="/@handle" or "/channel/")│ │
│ │ • From page URL (if on channel page) │ │
│ │ • From owner renderer (if on watch page) │ │
│ │ 4. Normalizes channel names: │ │
│ │ • "@PirateSoftware" = "Pirate Software" │ │
│ │ • Removes spaces, lowercases, strips @ │ │
│ │ 5. Checks whitelist: │ │
│ │ • If enabled + empty → hide ALL │ │
│ │ • If enabled + channel not in list → hide/block │ │
│ │ • If disabled → show all │ │
│ └───────────────────────────────────────────────────────┘ │
└───────────────────────┬─────────────────────────────────────┘
│
▼
┌───────────────────────────────┐
│ Action taken: │
│ │
│ • Video tiles: display:none │
│ • Watch page: full-screen │
│ overlay + pause video │
│ • Channel page: hide videos │
└───────────────────────────────┘
| Source | Where it runs | How content script sees it | Then |
|---|---|---|---|
| User copies text | DOM copy event |
Content script listens on document |
Detect clickfix → background → /api/security |
Page calls writeText() |
Page world | Injected script overrides, dispatches custom event; content script listens | Detect clickfix → background → /api/security |
| External script load | DOM (script tag) | Content script observes DOM / script elements | background → /api/extensions |
docs/README.md– Full installation and usage.docs/QUICKSTART.md– Short setup.server/README.md– Server API, modes, schema, troubleshooting.examples/README.md– How to run the test page (clickfix, JS execution) and what to expect on the server.
- Personal – Limit YouTube to whitelisted channels, reduce distraction, basic privacy.
- Corporate – Network and extension monitoring, security event collection (clickfix), audit by
client_id. - Security research – Traffic inspection, extension behavior, threat detection.
| Endpoint | Method | Purpose |
|---|---|---|
/ / /dashboard |
GET | Web dashboard |
/logo.png |
GET | CanIGoIn logo |
/health |
GET | Health check |
/api/logs |
POST | Batch network logs (gzip, client_id) |
/api/logs |
GET | Get logs (simple mode) |
/api/blocklist |
GET | Get blocklist |
/api/blocklist |
POST | Update blocklist |
/api/dashboard/events |
GET | Events (filter: all|security|javascript) |
/api/dashboard/events/{id} |
GET | Inspect single event |
/api/dashboard/clients |
GET | Unique client IDs |
/api/extensions |
POST | Extension events (gzip, client_id) |
/api/security |
POST | Security events (gzip, client_id) |
# 1. Start server
cd server
cargo run --release
# 2. Load extension
# chrome://extensions/ → Load unpacked → extension/
# 3. Configure
# Icon → Settings: Server URL, Features, YouTube whitelist, Client ID
# Save
# 4. Test (optional)
# Open examples/index.html (e.g. http://localhost:9000/) to trigger clickfix/JS events