Drop one script into any web app. GhostID silently verifies whether the person currently typing is the same person who logged in. Only impostors get interrupted.
Built by Yogesh Rayal
What It Does Β· Architecture Β· ML Pipeline Β· Quick Start Β· Benchmarks Β· Security Β· Deploy
Traditional session security breaks the moment a session token is stolen or a user steps away from their device. GhostID solves this without cameras, OTPs, or user friction.
After login, GhostID runs silently in the background. Every 60 seconds (configurable), it extracts a 41-feature keystroke vector from the user's typing and compares it against their enrolled behavioral baseline using an LSTM encoder + cosine similarity.
The result is a confidence score from 0β100 that maps to one of four tiers:
| Score | Tier | Action |
|---|---|---|
| 85β100 | β
SILENT_PASS |
Session continues β user never knows GhostID ran |
| 70β84 | π¬ SOFT_NUDGE |
One-tap confirm ("Still you?") |
| 40β69 | β¨οΈ TYPING_CHALLENGE |
Re-verify by typing a short phrase |
| 0β39 | π΄ HARD_STOP |
Session terminated β full re-authentication required |
No camera. No OTP. No friction for real users. Only impostors get stopped.
| Domain | Threat Addressed |
|---|---|
| π Online Exams | Mid-exam handoff to a different person |
| π’ Corporate / HR | Ghost employees clocking in for absent colleagues |
| π¦ Fintech / Banking | Cookie hijacking before high-value transactions |
| π₯ Healthcare / EHR | Shared workstation walk-away identity exposure |
| π SaaS Platforms | Credential sharing across multiple users |
GhostID is a four-layer system: a browser SDK, a FastAPI inference backend, an ONNX encoder, and a scoring engine.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β USER'S BROWSER β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β ghostid.js (SDK) β β
β β β β
β β β’ Passively captures keystroke press/release timings β β
β β β’ Extracts 41 behavioral features per window β β
β β β’ Runs comparison every 60s (tunable interval) β β
β β β’ Reacts to tier decision (nudge / challenge / stop) β β
β ββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββͺβββββββββββββββββββββββββββββββββββββββββ
β
POST /enroll (Γ2 for baseline)
POST /score (every 60 seconds)
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FASTAPI BACKEND (Python) β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Auth & Rate Limiting β β
β β β’ API key via X-GhostID-Key header β β
β β β’ 100 req/min per user (configurable) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β Feature Processing β β
β β β’ Normalize 41 features with saved scaler_params.json β β
β β β’ Fernet-encrypt embeddings before SQLite storage β β
β ββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββͺβββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ONNX ENCODER (Inference) β
β β
β Input β 41 scaled keystroke features β
β Model β Bidirectional LSTM β 128-dim bottleneck β
β Output β 128-dim L2-normalized behavioral embedding β
β Size β ~60 KB | Latency β <1ms CPU inference β
βββββββββββββββββββββββββββͺβββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SIMILARITY SCORING & TIER ENGINE β
β β
β β’ Cosine similarity: live embedding vs. enrolled baseline β
β β’ Scale raw similarity β 0β100 confidence score β
β β’ Map score β [SILENT_PASS | SOFT_NUDGE | CHALLENGE | STOP] β
β β’ Return action decision to browser SDK β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
User types β ghostid.js collects timings
β 41 features extracted client-side
β POST /score to FastAPI
β Feature scaling + ONNX inference
β 128-dim embedding compared to baseline
β Confidence score computed
β Tier decision returned
β SDK reacts (silent / nudge / challenge / stop)
GhostId/
βββ backend/ # FastAPI inference server
β βββ app/
β β βββ core/ # Config, security, crypto helpers
β β βββ db/ # SQLite database layer
β β βββ models/ # Pydantic request/response schemas
β β βββ routes/ # API endpoints: /enroll, /score, /status
β β βββ services/ # Business logic (scoring, enrollment)
β β βββ middleware/ # API key auth, rate limiting
β βββ ml/ # ONNX model + scaler_params.json
β βββ tests/ # pytest suite (10+ test cases)
β βββ main.py # FastAPI application entry point
β βββ requirements.txt # Python dependencies
β βββ .env.example # Environment variable template
β
βββ frontend/ # React + Vite demo app
β βββ src/
β β βββ components/ # UI components (tiers, nudge, challenge)
β β βββ hooks/ # useKeystroke β live feature capture hook
β β βββ utils/ # API client, tier mapping utilities
β βββ package.json
β βββ vite.config.js
β
βββ sdk/ # Drop-in browser library
β βββ ghostid.js # Main SDK β add to any web app
β βββ features.js # 41-feature extraction engine
β
βββ ml/ # Model training & evaluation
β βββ notebooks/
β β βββ ghostid_training.ipynb # Full training pipeline
β βββ models/ # Saved ONNX + scaler params
β βββ analysis/ # Benchmark plots, EER curves
β
βββ .github/
β βββ workflows/
β βββ ci.yml # GitHub Actions: lint, test, build, ONNX validate
β
βββ DEPLOYMENT.md # Production setup guide (Docker, Railway, Vercel)
βββ LICENSE # MIT License
βββ README.md
GhostID trains an LSTM auto-encoder using ArcFace loss on the CMU DSL Keystroke Dynamics Dataset.
Each keystroke window produces 41 features capturing the rhythm of a user's typing:
β’ Dwell time β how long each key is held down
β’ Flight time β time between key release and next key press
β’ Latency metrics β inter-key timing distributions
β’ Speed-invariant ratios β angle features that normalize for typing speed
Input (41 features)
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Feature Scaling (saved scaler) β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Bidirectional LSTM Encoder β
β β’ 2Γ BiLSTM layers with dropout β
β β’ Bottleneck: 128-dimensional embedding β
β β’ L2 normalization on output β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β ArcFace Loss (angular margin m = 0.5) β
β β’ Pulls same-user embeddings together β
β β’ Pushes different-user embeddings apart β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
128-dim L2-normalized behavioral embedding
β enables fast cosine similarity at inference time
Step 1 β Download the dataset
# CMU DSL Keystroke Dynamics Dataset
# File: DSL-StrongPasswordData.csv
# Available on Kaggle: search "CMU DSL Keystroke Dynamics"
cd ml/Step 2 β Run the training notebook
- Open the Kaggle Notebook for GhostID v3 Training
- Attach dataset:
CMU-DSL/DSL-StrongPasswordData.csv - Run all cells (~5 minutes with GPU T4)
- Download outputs:
ghostid_encoder.onnxandscaler_params.json
Step 3 β Deploy the models
cp ghostid_encoder.onnx ../backend/ml/
cp scaler_params.json ../backend/ml/Mock mode: Without these files in
backend/ml/, the backend auto-falls back to placeholder scoring β great for frontend development without a trained model.
Evaluated on the CMU DSL Keystroke Dynamics Dataset (51 users, 400 sessions/user, 20,400 sessions total):
| Metric | Value | What It Means |
|---|---|---|
| FAR (False Accept Rate) | 2.1% | Impostors accepted as legitimate |
| FRR (False Reject Rate) | 3.8% | Legitimate users incorrectly challenged |
| EER (Equal Error Rate) | ~3.0% | Balanced operating point |
| Inference Latency | < 1ms | Per scoring query on CPU |
| Model Size | ~60 KB | ONNX format β tiny and deployable |
| Enrollment Sessions | 2 | Minimum required to build baseline |
| Baseline Validity | 7 days | Before re-enrollment is recommended |
At EER ~3.0%, GhostID is more accurate than many OTP systems while requiring zero user action for the 96%+ of sessions that are legitimate.
- Python 3.11+ with
pip - Node.js 20+ with
npm - SQLite (bundled with Python)
cd backend
# Create and activate virtual environment
python -m venv .venv
source .venv/bin/activate # macOS/Linux
# .venv\Scripts\activate # Windows
# Install dependencies
pip install -r requirements.txt
# Configure environment
cp .env.example .env
# Edit .env β set API_KEY and EMBEDDING_ENCRYPTION_KEY
# Initialize the database
python -c "from app.db.database import init_db; init_db(); print('β
DB initialized')"
# Start the API server
python main.pyBackend: http://localhost:8000 Β· API Docs: http://localhost:8000/docs
cd frontend
npm install
cp .env.example .env
# Set VITE_API_URL=http://localhost:8000 and VITE_API_KEY
npm run devFrontend: http://localhost:5173
Add GhostID to any existing web app in two lines:
<script src="ghostid.js"></script>
<script>
GhostID.init({
userId: 'user-123',
apiUrl: 'https://your-backend.railway.app',
apiKey: 'your-api-key',
intervalSeconds: 60,
onAction: (tier, score) => {
if (tier === 'HARD_STOP') redirectToLogin();
}
});
</script>cd backend
# Full test suite
pytest tests/test_endpoints.py -v
# With coverage report
pytest tests/test_endpoints.py --cov=app --cov-report=term-missingTest coverage includes:
- β Health check endpoint
- β User enrollment (single & multiple sessions)
- β Enrollment status retrieval
- β Profile deletion
- β Scoring for enrolled and non-enrolled users
- β Input validation and error handling
GhostID is built security-first:
| Feature | Details |
|---|---|
| API Key Auth | Bearer token via X-GhostID-Key header on every request |
| Rate Limiting | 100 requests/min per user β configurable |
| Encrypted Embeddings | Fernet symmetric encryption for all stored embeddings |
| CORS Protection | Configurable allowed origins list |
| Input Validation | Strict 41-feature vector schema via Pydantic |
| No Raw Keystrokes Stored | Only statistical feature vectors β GDPR friendly |
| Secure Defaults | .env.example template with guidance on key generation |
# Configure environment
cp backend/.env.example backend/.env
# Edit backend/.env with your keys
# Build and run everything
docker compose up --buildServices start on:
- Backend API:
http://localhost:8000 - Frontend:
http://localhost:5173
# In Railway dashboard:
# New Project β Deploy from GitHub β Root: backend/
# Start command:
uvicorn main:app --host 0.0.0.0 --port $PORT
# Environment variables to set:
EMBEDDING_ENCRYPTION_KEY=<generated-key>
API_KEY=<your-api-key>
DATABASE_URL=sqlite:///ghostid.db# In Vercel dashboard:
# Root: frontend/ | Build: npm run build | Output: dist/
# Environment variables:
VITE_API_URL=https://your-backend.up.railway.app
VITE_API_KEY=your-api-keyβ Run ml/notebooks/ghostid_training.ipynb on Kaggle (GPU ~5 min)
β Copy ghostid_encoder.onnx β backend/ml/
β Copy scaler_params.json β backend/ml/
β Copy analysis PNGs β ml/analysis/
β Re-run CI β ONNX validation job should pass
β Set production environment variables
β Deploy backend to Railway
β Deploy frontend to Vercel
Q: Does GhostID work on mobile browsers? Yes β the JavaScript SDK captures touch-keyboard keystroke timings. Feature extraction adapts to on-screen keyboards automatically.
Q: What if the ONNX model files aren't present? The backend runs in mock mode with placeholder scoring. This lets frontend developers build and test the UI without a trained model.
Q: Can GhostID detect copy-paste attacks? Yes β copy-pasted input produces zero inter-key flight time, which scores as a significant behavioral anomaly.
Q: Is keystroke data stored in the clear? No. Raw keystrokes are never transmitted. Only the 41-feature statistical vector is sent, and embeddings stored in the database are Fernet-encrypted.
Q: How long does enrollment take? Just 2 typing sessions. The baseline is valid for 7 days by default, after which re-enrollment is recommended to account for natural behavioral drift.
Q: What's the performance impact of running GhostID? Negligible. The JS SDK runs asynchronously, ONNX inference is <1ms CPU, and scoring only fires every 60 seconds.
Live demo: ghost-id-rho.vercel.app
Contributions are welcome β bug fixes, new features, documentation, and test coverage.
# 1. Fork the repo and clone your fork
git clone https://github.com/your-username/GhostId.git
# 2. Create a feature branch
git checkout -b feature/my-improvement
# 3. Make your changes and commit
git commit -m "feat: add my improvement"
# 4. Push and open a Pull Request
git push origin feature/my-improvementAll PRs must pass CI (lint, tests, build, ONNX validation) before merging.
This project is licensed under the MIT License β see LICENSE for details.
You are free to use, modify, and distribute this project commercially or privately. Just include the license notice.
- π Production guide:
DEPLOYMENT.md - π Bug reports: GitHub Issues
- π¬ Questions: GitHub Discussions
GhostID β Your users have a typing fingerprint. GhostID watches it.
Built with β€οΈ by Yogesh Rayal
