SecureVote is a Flask-based secure voting system built to demonstrate security engineering evidence: voter anonymity, encrypted PII, tamper-evident audit logs, access control, WAF enforcement, Vault-backed signing, and race-condition testing.
This started as a Secure Software Systems team project and was later completed, hardened, documented, and tested as a solo portfolio project.
| Security area | Implementation | Evidence in repo |
|---|---|---|
| Anonymous voting | RSA blind signatures, browser-side unblinding, separate anonymous /vote/cast endpoint with no cookies or session data |
app/security/blind_signature.py, app/static/js/blind_vote.js, tests/test_blind_signature.py |
| PII protection | ChaCha20-Poly1305 encryption for voter PII and HMAC-SHA256 blind indexes for driver licence lookup | app/security/encryption.py, app/models.py, tests/test_pii_encryption_and_access.py |
| Audit integrity | HMAC-SHA256 JSONL audit chain with previous-record linkage and manager-visible chain verification | app/logging_service.py, app/routes/audit.py |
| Authentication hardening | Password policy, account lockout, password expiry, signed password reset tokens, email OTP MFA, anti-bot login nonce, honeypot, Origin/Referer checks | app/auth.py, app/security/password_validator.py, tests/test_password_policy.py, tests/integration/test_login_robot_blocking.py |
| Authorization | Voter, delegate, and manager roles with route-level guards and region-limited delegate candidate management | app/utils/auth_decorators.py, app/routes/candidates.py, app/routes/elections.py |
| CSRF protection | Per-session CSRF tokens on state-changing requests with targeted exemptions for public verification and anonymous ballot endpoints | app/security/csrf.py, form templates |
| WAF and rate limiting | nginx fronted by OWASP ModSecurity CRS with endpoint-specific request limits, security headers, and isolated backend service exposure | nginx/conf.d/waf.conf, docker-compose.yml |
| Result integrity | Election result signing and verification through HashiCorp Vault Transit when configured, with local RSA fallback for development | app/security/signing_service.py, app/security/vault_client.py, docs/VAULT_SETUP.md |
| Double-vote prevention | SELECT ... FOR UPDATE, transactional VoteReceipt, and a unique user_id constraint to stop TOCTOU double voting |
app/vote_service.py, tests/test_vote_concurrency.py |
| Production safety | Environment-aware settings, split database binds, local-only developer dashboard controls, and deployment safety documentation | app/environment.py, app/utils/db_utils.py, docs/PRODUCTION_SAFETY_REPORT.md |
- Can translate security requirements into concrete application controls, not just describe them.
- Understands anonymity, integrity, authentication, authorization, auditability, and operational hardening tradeoffs.
- Can write reproducible security tests for password policy, PII access, blind signatures, login automation controls, pagination limits, and race conditions.
- Can document implementation evidence clearly enough for reviewers to verify claims from the code and tests.
- Can reason about deployment boundaries: WAF in front, app isolated behind nginx, MySQL internal only, Vault used for signing, secrets excluded from git.
+-----------------------------+
User browser ---> | nginx + ModSecurity CRS WAF | Rate limits, CRS rules, security headers
+-------------+---------------+
|
v
+-------------+---------------+
| Flask application | Auth, RBAC, CSRF, MFA, blind signatures
+-------------+---------------+
|
+----------------+----------------+
| |
v v
+--------+---------+ +--------+---------+
| MySQL / SQLite | | HashiCorp Vault |
| Split DB binds | | Transit signing |
| Encrypted PII | | KV secret pattern |
+------------------+ +------------------+
Phase 1: Authenticated voter requests authority
Browser creates ballot hash and blinds it.
Server signs the blinded value.
Server records a VoteReceipt so the voter cannot request another ballot.
Server never sees the unblinded ballot.
Phase 2: Anonymous ballot submission
Browser unblinds the signature.
Browser sends ballot plus signature to /vote/cast.
The endpoint does not use cookies or session identity.
Server verifies the signature and records an anonymous Vote.
- PII is encrypted using ChaCha20-Poly1305 before storage.
- Driver licence lookup uses an HMAC-SHA256 blind index with a high-entropy pepper.
- Audit events are appended as HMAC-signed JSON lines with
prev_hmacchaining. - Election results can be signed through Vault Transit and verified through
/results/verify. - Voting concurrency is tested with 10 simultaneous attempts against the same voter.
| Layer | Technology |
|---|---|
| Backend | Flask 2.3, SQLAlchemy, Flask-Login, Flask-Mail, Flask-Migrate |
| Database | MySQL 8.0 for Docker, SQLite for local development and tests |
| Security | RSA blind signatures, ChaCha20-Poly1305, HMAC-SHA256, PyJWT, itsdangerous, HashiCorp Vault |
| Infrastructure | Docker Compose, nginx, OWASP ModSecurity CRS, Gunicorn |
| Testing | pytest, pytest-flask, GitHub Actions |
| Frontend | Jinja2, Bootstrap 5, Inter, browser Web Speech API |
python -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
python scripts/run_demo.pyOpen http://127.0.0.1:5000.
On Windows PowerShell, activate the virtual environment with:
.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt
python scripts\run_demo.pycp .env.example .env
# Fill in strong local values in .env before starting the stack.
docker-compose up --build -dOpen:
- App through WAF:
http://localhost - Vault dev UI:
http://localhost:8200
| Role | Username | Password |
|---|---|---|
| Manager | admin |
Admin@123456! |
| Delegate | delegate1 |
Delegate@123! |
| Voter | voter1 |
Password@123! |
Install test dependencies and run the suite:
python -m pip install -r requirements.txt -r requirements-dev.txt
python -m pytestCurrent collection: 104 pytest tests.
| Test area | File | Count |
|---|---|---|
| Blind signature protocol and HTTP flow | tests/test_blind_signature.py |
8 |
| Password reset, election access, anonymity, audit page | tests/test_new_features.py |
14 |
| Pagination limit enforcement | tests/test_pagination_security.py |
2 |
| Account lockout, expiry, and password change | tests/test_password_policy.py |
19 |
| Password validation and registration integration | tests/test_password_validation.py |
26 |
| PII encryption and access control | tests/test_pii_encryption_and_access.py |
3 |
| Core smoke tests, role access, voting, results | tests/test_smoke.py |
15 |
| Concurrent vote race-condition checks | tests/test_vote_concurrency.py |
2 |
| Login nonce, CLI blocking, honeypot, Origin/Referer checks | tests/integration/test_login_robot_blocking.py |
15 |
The repository is configured to exclude local secrets and generated runtime files:
.envand local.env.*files- SQLite and local database files
instance/, logs, and audit logs- Python caches, pytest cache, coverage output, and virtual environments
- Local package-manager lock files generated during experiments
- Password Policy
- Vault Setup
- Environment Detection
- Production Safety Report
- CI/CD Guide
- Testing Guide
This project is intended to be reviewed as code, tests, and local demo evidence. A public hosted demo is intentionally not required for a voting-system portfolio project.
