KnowBe4-style phishing simulation at a fraction of the cost — with AI-personalized emails and adaptive training powered by Claude.
Features · Architecture · Quick Start · Configuration · Security · Deployment
PhishGuard is a self-hosted security awareness training platform that lets IT teams and security professionals:
- Simulate real-world phishing attacks against their own employees — using AI-generated, highly personalized emails across 7 attack scenarios
- Track every interaction — opens, clicks, credential submissions, and phishing reports — in real time
- Automatically deliver adaptive training to anyone who falls for the simulation, with a personalized debrief and quiz generated by Claude based on the specific email they received
- Export campaign results to Excel for compliance reporting and risk analysis
This is the kind of platform enterprise companies pay thousands of dollars per year for. PhishGuard gives you the same capability, self-hosted, for the cost of your API usage.
- Launch phishing campaigns targeting individual employees or entire departments
- Select from 7 built-in attack scenarios spanning easy → medium → hard difficulty
- AI personalizes every email based on the recipient's name, job title, department, and company
- Bulk-import targets from CSV with validation
Every phishing email is uniquely generated by Claude Sonnet for the specific recipient. Difficulty levels are tuned to realistic detection rates:
| Difficulty | Characteristics | Expected Click Rate |
|---|---|---|
| Easy | Typos, generic greetings, obvious domain spoofs | 30–40% |
| Medium | Subtle domain variants, minor inconsistencies | 50–60% |
| Hard | Polished, targeted, executive-style — minimal red flags | 70%+ |
Four distinct tracking events per email:
- 📧 Sent — baseline
- 👁 Opened — 1×1 invisible tracking pixel
⚠️ Clicked — unique HMAC-signed tracking link- 🚨 Credentials Submitted — simulated login page (credentials are immediately discarded — never stored)
- ✅ Reported — footer "Report Phishing" link rewards correct behavior
When an employee clicks, they are immediately redirected to a personalized training module generated by Claude:
- References the exact red flags from the specific email they received
- Friendly, non-shaming tone to encourage engagement
- 5-question quiz with per-question explanations
- Completion tracked and exported in reports
- Dashboard with 30-day rolling stats (click rate, report rate, credential submissions)
- Per-campaign detail view with individual employee statuses
- One-click Excel export for compliance documentation
This platform handles sensitive employee data and simulates attacks — security is not optional. See the Security section for the full audit summary.
┌─────────────────────────────────────────────────────────────────────────┐
│ PhishGuard Platform │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌─────────────────────────────────────────┐ │
│ │ Admin Dashboard │ ──► │ Campaign Manager │ │
│ │ (Flask + Auth) │ │ (target selection, scenario, launch) │ │
│ └──────────────────┘ └────────────────┬────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ AI Email Generator │ │
│ │ (Claude Sonnet API) │ │
│ │ Personalized per target │ │
│ └─────────────┬────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ Resend │ │
│ │ HMAC-signed tracking │ │
│ │ links embedded in HTML │ │
│ └─────────────┬────────────┘ │
│ │ │
│ ▼ (email delivered) │
│ Target's Inbox │
│ │ │
│ ┌─────────────────────────┴────────────────────┐ │
│ │ Tracking Server │ │
│ │ /o/<id> → Open pixel (1×1 GIF) │ │
│ │ /c/<id> → Click → Fake login page │ │
│ │ /c/<id>/submit → Discard creds, redirect │ │
│ │ /r/<id> → Report → "Great catch!" page │ │
│ │ │ │
│ │ All endpoints: HMAC-verified, rate-limited │ │
│ └──────────────────┬────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ AI Training Module │ │
│ │ (Claude Sonnet API) │ │
│ │ Personalized debrief │ │
│ │ + 5-question quiz │ │
│ └────────────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────┐ │
│ │ SQLAlchemy ORM / SQLite │ │
│ │ Organizations · Users · Targets · Campaigns │ │
│ │ CampaignSends · TrainingModules │ │
│ └───────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
| Layer | Technology |
|---|---|
| Web Framework | Flask 3.0 |
| Database ORM | SQLAlchemy 2.0 (SQLite dev / PostgreSQL prod) |
| Authentication | Flask-Login + Flask-Bcrypt |
| CSRF Protection | Flask-WTF |
| Secure Headers | Flask-Talisman (CSP, HSTS, X-Frame-Options) |
| Rate Limiting | Flask-Limiter |
| AI Generation | Anthropic Claude Sonnet (via anthropic SDK) |
| Email Delivery | Resend |
| HTML Sanitization | bleach |
| Data Export | pandas + openpyxl |
| Payments | Stripe (optional) |
| Production Server | Gunicorn |
| Scenario | Title | Difficulty |
|---|---|---|
it_password_reset |
IT Password Reset Required | Easy |
shared_drive |
Shared SharePoint File | Easy |
docusign_invoice |
DocuSign Document Pending | Medium |
ms365_quarantine |
Microsoft 365 Quarantined Messages | Medium |
hr_benefits |
HR Open Enrollment Deadline | Medium |
ceo_gift_card |
CEO Urgent Gift Card Request | Hard |
payroll_update |
Direct Deposit Information Update | Hard |
Each scenario has documented red flags that the post-click training module explicitly calls out, referencing the specific email the employee received.
git clone https://github.com/YOUR_USERNAME/phishguard.git
cd phishguard
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txtcp .env.example .envOpen .env and fill in your values (see Configuration for details):
FLASK_SECRET_KEY=<generate with: python -c "import secrets; print(secrets.token_hex(32))">
RESEND_API_KEY=re_your_key_here
FROM_EMAIL=noreply@yourdomain.com
ANTHROPIC_API_KEY=sk-ant-your_key_here
BASE_URL=http://localhost:5000 # Use https:// in productionflask --app app init-dbpython app.pyOpen http://localhost:5000 and sign up for your first organization account.
All configuration is via environment variables. Copy .env.example to .env to get started.
| Variable | Description |
|---|---|
FLASK_SECRET_KEY |
Long random string for session signing. App refuses to start without this. Generate: python -c "import secrets; print(secrets.token_hex(32))" |
RESEND_API_KEY |
Resend API key for email delivery (starts with re_) |
FROM_EMAIL |
Verified sender address (must be from a domain you've verified in your Resend dashboard) |
ANTHROPIC_API_KEY |
Anthropic API key for Claude email/training generation |
BASE_URL |
Public URL of this deployment (e.g., https://phish.yourcompany.com). Must be https:// in production. |
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
sqlite:///phishing_sim.db |
SQLAlchemy DB URI. Use PostgreSQL for production. |
SEND_ID_HMAC_SECRET |
Falls back to FLASK_SECRET_KEY |
Separate HMAC key for signing tracking tokens. Recommended to set independently. |
RATELIMIT_STORAGE_URI |
memory:// |
Rate limiter backend. Use redis://localhost:6379/0 in production for persistence across workers. |
FLASK_ENV |
(unset) | Set to production to enable HTTPS enforcement, HSTS, and startup checks. |
STRIPE_SECRET_KEY |
(unset) | Stripe key for billing features. Leave unset to disable. |
- Sign up at resend.com and create an API key
- Add and verify your sending domain in the Resend dashboard
- Configure DNS records for your domain as shown in DNS Setup
- Set
FROM_EMAILto an address at your verified domain (e.g.phishguard@yourdomain.com)
PhishGuard has undergone a full security audit. All identified findings have been remediated. Below is a summary — see SECURITY.md for complete details.
| Severity | Finding | Fix |
|---|---|---|
| 🔴 Critical | CSRF on all POST endpoints | Flask-WTF CSRFProtect on all authenticated routes |
| 🔴 Critical | SSTI via render_template_string |
Replaced with static template |
| 🔴 Critical | Stored XSS from unsanitized AI-generated HTML | bleach allowlist sanitization before storage; covers phishing emails, training content, and manager summaries |
| 🔴 Critical | No rate limiting on public tracking endpoints | Flask-Limiter (10–60 req/min per endpoint) |
| 🔴 Critical | Tracking lookup used raw UUID instead of full signed ID | _resolve_send() and training route now look up CampaignSend by the full HMAC-signed ID |
| 🟠 High | SECRET_KEY silently None if env var unset |
Startup RuntimeError — app refuses to run |
| 🟠 High | Unguarded Stripe API key assignment | Conditional on env var presence |
| 🟠 High | CSV upload — no type/size validation | Extension check, 5 MB limit, parse error handling |
| 🟠 High | Tracking IDs guessable / no integrity check | HMAC-SHA256 signed send_id tokens |
| 🟠 High | No brute-force protection on login/signup | Rate limiting: 20/min · 100/hr on login; 10/hr on signup |
| 🟡 Medium | No input validation on signup | Email regex, length limits, duplicate check |
| 🟡 Medium | Email addresses logged in plaintext | Replaced with sha256(email)[:8] hex digest |
| 🟡 Medium | No secure HTTP headers | Flask-Talisman: CSP, HSTS, X-Frame-Options |
| 🟡 Medium | Arbitrary quiz scores accepted | Server-side clamping to 0–100 |
| 🟡 Medium | Oversized send_id inputs not rejected early | Length check (≤ 64 chars) before HMAC verification |
| 🔵 Low | datetime.utcnow() deprecated (Python 3.12+) |
Replaced with datetime.now(timezone.utc) |
| 🔵 Low | Bootstrap CDN without SRI hash | integrity= and crossorigin= attributes added |
The fake login page deliberately discards all submitted credentials. No username or password is ever logged, stored, or transmitted — only the timestamp of the submission event is recorded. This is enforced both in the code comment and server-side in track_credentials_submission().
This platform is designed exclusively for authorized security awareness training. Use it only against employees who have provided implied or explicit consent through their employment agreement, and only within organizations you are authorized to test. Unauthorized phishing simulation is illegal in most jurisdictions.
# Start Gunicorn (4 workers)
gunicorn -w 4 -b 127.0.0.1:8000 app:appNginx configuration (example):
server {
listen 443 ssl http2;
server_name phish.yourcompany.com;
ssl_certificate /etc/letsencrypt/live/phish.yourcompany.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/phish.yourcompany.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name phish.yourcompany.com;
return 301 https://$host$request_uri;
}For simulation emails to reach inboxes (not spam), configure these DNS records for your sending domain. Resend provides exact values for steps 2–4 in its dashboard.
| Record | Type | Purpose |
|---|---|---|
| SPF | TXT | Authorize Resend to send on your domain's behalf |
| DKIM | TXT | Cryptographic sender authentication |
| DMARC | TXT | Policy for failed SPF/DKIM: v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com |
For production, replace SQLite with PostgreSQL:
DATABASE_URL=postgresql://user:password@localhost:5432/phishguardFor multi-worker deployments, use Redis to share rate limit state across Gunicorn workers:
RATELIMIT_STORAGE_URI=redis://localhost:6379/0phishguard/
├── app.py # Main Flask application, all routes
├── models.py # SQLAlchemy ORM models
├── ai_generators.py # Claude API — email & training generation
├── email_engine.py # Resend delivery + HMAC tracking IDs
├── requirements.txt # All Python dependencies
├── .env.example # Environment variable template
├── .gitignore
│
└── templates/
├── base.html # Shared layout (navbar, flash messages)
├── dashboard.html # 30-day stats overview
├── fake_login.html # Simulated credential-harvesting page
├── training.html # Post-click AI training + quiz
└── report_success.html # "Great Catch!" confirmation page
Organization ──< User (multi-admin support)
Organization ──< Target (employees to be phished)
Organization ──< Campaign (a phishing campaign)
Campaign ──< CampaignSend (one email per target)
CampaignSend ──< TrainingModule (one training module per click)
Each CampaignSend records the full lifecycle: sent → opened → clicked → credentials submitted / reported → training started → training completed + quiz score.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/your-feature) - Follow the existing code style
- Ensure no new
datetime.utcnow()orQuery.get()calls (use the patterns already in the codebase) - Do not introduce
render_template_stringorinnerHTMLwith string concatenation - Open a pull request with a clear description
# Install dev dependencies
pip install pytest pytest-flask
# Run tests
pytestMIT License — see LICENSE for details.
This software is provided for authorized security awareness training only. The authors are not responsible for misuse. Always obtain written authorization before conducting phishing simulations against any organization or individuals. Review your local laws and regulations before deployment.
Built with ❤️ for security teams who want enterprise-grade awareness training without enterprise-grade pricing.