A secure technical framework for the custody, management, preservation, and auditing of cryptocurrency assets in criminal investigations.
Master's thesis proof of concept — 29/29 FR, 20/20 SR, 9/9 MU, 41 tests.
docker compose up --build| Service | URL |
|---|---|
| Frontend | http://localhost:5173 |
| Backend API | http://localhost:8000 |
| Swagger Docs | http://localhost:8000/docs |
Seed demo data (first time):
docker compose exec backend python scripts/seed_demo_data.pyor if it doesnt work as supposed to
docker compose exec backend sh -c "mkdir -p /app/scripts"
docker cp .\scripts\seed_demo_data.py scfca-backend:/app/scripts/seed_demo_data.py
docker compose exec backend python /app/scripts/seed_demo_data.pyTerminal 1 — Backend:
pip3 install -r backend/requirements.txt
python3 -m uvicorn backend.main:app --reload --host 127.0.0.1 --port 8000Terminal 2 — Frontend:
npm --prefix frontend install
npm --prefix frontend run dev -- --host 127.0.0.1 --port 5173Without PostgreSQL, the backend runs in demo mode with in-memory data and 4 pre-seeded users. All features work except DB persistence.
| Role | Username | Password | Permissions |
|---|---|---|---|
| Handler | alice |
alice123 |
View assigned cases, create tickets, upload PDFs |
| Admin | bob |
bob123 |
Create cases/assets, approve tickets, execute actions |
| Admin | eve |
eve123 |
Second approver for dual-control |
| Auditor | carol |
carol123 |
Read-only: audit trail, verify chain, download reports |
pip3 install -r backend/requirements.txt pytest
python3 -m pytest tests/ -vExpected output:
test_cases_and_assets.py 8 passed
test_documents_and_reports.py 7 passed
test_security_phase5.py 7 passed
test_tickets_phase3.py 8 passed
test_user_management.py 5 passed
test_workflows.py 6 passed
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
41 passed in ~70s
python3 -m pytest tests/test_workflows.py -v # Core: CSRF, dual approval, audit chain
python3 -m pytest tests/test_user_management.py -v # Users: CRUD, RBAC
python3 -m pytest tests/test_cases_and_assets.py -v # Cases + assets + immutability
python3 -m pytest tests/test_tickets_phase3.py -v # Tickets: cancel, notes, side-effects
python3 -m pytest tests/test_documents_and_reports.py -v # Documents + PDF reports
python3 -m pytest tests/test_security_phase5.py -v # MFA, re-auth, rate limitingpython3 -m pytest tests/ -k "mfa" -v # All MFA tests
python3 -m pytest tests/ -k "immutability" -v # Asset immutability
python3 -m pytest tests/test_workflows.py::test_audit_hash_chain_verifies -vStart the backend (python3 -m uvicorn backend.main:app --reload), then follow this sequence.
curl -X POST http://localhost:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"bob","password":"bob123"}' \
-c cookies.txt
# Extract CSRF token
CSRF=$(grep scfca_csrf_token cookies.txt | awk '{print $NF}')curl -X POST http://localhost:8000/api/v1/cases/ \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: $CSRF" \
-d '{"title":"Seized BTC wallet","wallet_ref":"WLT-001","handler_username":"alice"}' \
-b cookies.txt
# Note the case ID from the response (e.g., C-7A3F)curl -X POST http://localhost:8000/api/v1/assets/ \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: $CSRF" \
-d '{"caseId":"C-7A3F","symbol":"BTC","assetType":"native","quantity":12.5}' \
-b cookies.txtcurl -X POST http://localhost:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"alice","password":"alice123"}' \
-c alice.txt
ALICE_CSRF=$(grep scfca_csrf_token alice.txt | awk '{print $NF}')
curl -X POST http://localhost:8000/api/v1/tickets/ \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: $ALICE_CSRF" \
-d '{"caseId":"C-7A3F","ticketType":"transfer_request","description":"Transfer to cold storage"}' \
-b alice.txt
# Note the ticket ID (e.g., T-482)# Bob approves (stage 1)
curl -X POST http://localhost:8000/api/v1/tickets/T-482/approve \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: $CSRF" \
-d '{"notes":"Documentation verified."}' \
-b cookies.txt
# Eve approves (stage 2)
curl -X POST http://localhost:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"eve","password":"eve123"}' \
-c eve.txt
EVE_CSRF=$(grep scfca_csrf_token eve.txt | awk '{print $NF}')
curl -X POST http://localhost:8000/api/v1/tickets/T-482/approve \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: $EVE_CSRF" \
-d '{"notes":"Confirmed. Approved for execution."}' \
-b eve.txt# Assign to bob
curl -X PATCH http://localhost:8000/api/v1/tickets/T-482/assign \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: $CSRF" \
-d '{"assignedTo":"bob"}' \
-b cookies.txt
# Get re-auth token (SR-6)
REAUTH=$(curl -s -X POST http://localhost:8000/api/v1/auth/reauth \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: $CSRF" \
-d '{"password":"bob123"}' \
-b cookies.txt | python3 -c "import sys,json; print(json.load(sys.stdin)['reauth_token'])")
# Execute
curl -X POST http://localhost:8000/api/v1/tickets/T-482/execute \
-H "X-CSRF-Token: $CSRF" \
-H "Idempotency-Key: exec-001" \
-H "X-Reauth-Token: $REAUTH" \
-b cookies.txtcurl http://localhost:8000/api/v1/audit/verify -b cookies.txt
# {"ok": true, "count": N}curl http://localhost:8000/api/v1/reports/audit -b cookies.txt -o audit_report.pdf
curl http://localhost:8000/api/v1/reports/case/C-7A3F -b cookies.txt -o case_report.pdf# POST without CSRF token → 403
curl -X POST http://localhost:8000/api/v1/tickets/ \
-H "Content-Type: application/json" \
-d '{"caseId":"C-100","ticketType":"transfer_request","description":"test"}' \
-b cookies.txt
# Expected: 403 CSRF validation failed# 9 rapid failed logins → last ones get 429
for i in $(seq 1 9); do
curl -s -o /dev/null -w "%{http_code} " \
-X POST http://localhost:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"alice","password":"wrong"}'
done
echo
# Expected: 401 401 401 401 401 401 401 401 429# As admin bob (already logged in)
curl -X POST http://localhost:8000/api/v1/auth/mfa/setup \
-H "X-CSRF-Token: $CSRF" \
-b cookies.txt
# Returns: {"secret":"BASE32...","provisioning_uri":"otpauth://..."}
# Scan QR with authenticator app, then verify:
curl -X POST http://localhost:8000/api/v1/auth/mfa/verify \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: $CSRF" \
-d '{"code":"123456"}' \
-b cookies.txt# Execute WITHOUT re-auth token → 403
curl -X POST http://localhost:8000/api/v1/tickets/T-482/execute \
-H "X-CSRF-Token: $CSRF" \
-H "Idempotency-Key: no-reauth" \
-b cookies.txt
# Expected: 403 Re-authentication required# This can only be tested programmatically (ORM listener):
python3 -m pytest tests/test_cases_and_assets.py::test_asset_immutability_enforced -v
# Confirms: modifying quantity raises ValueErrorSuggested live demonstration order:
docker compose up --build— start system- Login as alice → show handler dashboard, restricted view
- Login as bob → show admin dashboard, create case, register assets
- Upload PDF evidence → show SHA-256 hash
- Login as alice → create ticket for assigned case
- bob approves (stage 1) → show status
awaiting_second_approval - eve approves (stage 2) → show status
approved - bob executes → show re-auth requirement (SR-6)
- Verify audit chain →
GET /audit/verify→{"ok": true} - Download reports → audit PDF + case PDF
- Setup MFA for bob → show TOTP enrollment (SR-5)
- Attempt asset modification → show immutability (SR-15)
- Rapid requests → show rate limiting (MU-9)
- POST without CSRF → show 403 (SR-18)
project-scfca-main/
├── backend/
│ ├── api/v1/routes/ 10 route files (~40 endpoints)
│ ├── models/ 10 ORM models + listeners
│ ├── repositories/ 6 data access classes
│ ├── services/ 7 business logic modules
│ ├── middleware/ CSRF + rate limiting
│ ├── auth/ JWT, RBAC, MFA, re-auth
│ ├── alembic/ 5 versioned migrations
│ └── requirements.txt
├── frontend/
│ ├── src/pages/ 9 React pages
│ ├── src/services/ 8 API service modules
│ └── src/components/ 7 reusable components
├── tests/ 6 test files (41 tests)
├── docs/ 10 documentation files
├── docker-compose.yml
├── .gitlab-ci.yml
└── README.md
See docs/README.md for the full index, or jump directly:
- Implementation Record — what was built and why
- Traceability Matrix — every requirement → code → test
- Testing Guide — detailed testing instructions
| Problem | Fix |
|---|---|
docker-compose: command not found |
Use docker compose (with space) or install Docker Desktop |
ModuleNotFoundError: No module named 'backend' |
Run from project root: python3 -m pytest tests/ |
TypeError: unsupported operand type(s) for | |
pip3 install eval_type_backport |
429 Too Many Requests |
Wait 60 seconds (rate limiter resets) |
| Alice gets "Case not assigned" | Use case ID C-100 (pre-assigned) or create a case as bob first |
| No CSRF cookie after login | If MFA is enabled, complete the /mfa/challenge step first |