Skip to content

Commit cfe95dd

Browse files
committed
doc: [plans] Update enhancement analysis with progress tracker
1 parent 2e33521 commit cfe95dd

File tree

1 file changed

+267
-0
lines changed

1 file changed

+267
-0
lines changed

docs/plans/enhancement-analysis.md

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
# TestingPlatform Enhancement Analysis
2+
3+
Comprehensive audit covering security, feature accuracy, result consistency, and architecture.
4+
Findings are deduplicated and organized by priority.
5+
6+
## Progress Tracker
7+
8+
**FIXED** (16 commits on `dev`):
9+
- [x] #1 — Committed credentials removed, .gitignore updated
10+
- [x] #2 — Auth added to all InfraTesting API endpoints
11+
- [x] #3 + #10 — File size validation fixed (len()), validators use ValidationError
12+
- [x] #4 + #6 — CSRF restored on form endpoint, SameSite=Lax, CORS middleware repositioned
13+
- [x] #5 — SECRET_KEY auto-generated for dev, DEBUG defaults False, ZAP key emptied
14+
- [x] #7 — API views use serializer.validated_data consistently
15+
- [x] #8 — ScopedRateThrottle with per-endpoint scopes (auth/infra/expensive/admin)
16+
- [x] #13 — Email check reports errors instead of silent {} fallback, 60s timeout added
17+
- [x] #17 — IPv6 reachability uses OR (matching IPv4 logic)
18+
- [x] #20 — CSP report cache import added, rate limiter init fixed, error messages sanitized
19+
- [x] #21 — HSTS/SSL/X-Frame-Options/nosniff security headers (production-gated)
20+
- [x] #22 — UniqueConstraint on (user, domain) for UserDomain and MailDomain
21+
- [x] #26 — HSTS max-age parsing handles malformed values
22+
- [x] #27 — CorsMiddleware moved to top of middleware stack
23+
- [x] #28 — Admin URL configurable via ADMIN_URL env var
24+
- [x] #35 — CORS_ALLOWED_ORIGINS configurable via env var
25+
- [x] Bonus: Logout view broken exception handling cleaned up
26+
27+
**REMAINING**:
28+
- [ ] #9 — Nmap execution timeout
29+
- [ ] #11 — Pandora polling (busy-wait, seed_expire=0)
30+
- [ ] #12 — DKIM inconsistent return types
31+
- [ ] #14 — DMARC upload API key in URL
32+
- [ ] #15 — SQLite for production
33+
- [ ] #16 — API always returns HTTP 200
34+
- [ ] #18 — DNS queries without timeout
35+
- [ ] #19 — TLS version check fragile nmap parsing
36+
- [ ] #23 — Cascade delete wipes history
37+
- [ ] #24 — PDF report crashes on missing keys
38+
- [ ] #25 — Vulnerability lookup without caching
39+
- [ ] #29-#34 — Architecture/tech debt (lower priority)
40+
41+
---
42+
43+
## CRITICAL — Fix Before Any Production Use
44+
45+
### 1. Committed Credentials in Git History
46+
- **File**: `compose/staging/.env` — Contains plaintext ONEKEY API email and password
47+
- **Impact**: Anyone with repo access has these credentials; they persist in git history even if deleted
48+
- **Fix**: Remove file from tracking, rotate credentials, add to `.gitignore`
49+
50+
### 2. Unauthenticated Infrastructure Testing API Endpoints
51+
- **File**: `api/views.py:270-380` — All `InfraTestingXxxApiView` classes
52+
- **Impact**: No `authentication_classes` or `permission_classes`. Any anonymous user can trigger nmap scans, Pandora file analysis, DNS queries, TLS checks. Enables reconnaissance-as-a-service and resource exhaustion.
53+
- **Fix**: Add `permission_classes = [IsAuthenticated]` at minimum; add rate limiting
54+
55+
### 3. File Size Validation Uses Wrong Function
56+
- **File**: `testing/validators.py:12``sys.getsizeof(file)` returns Python object overhead (~50-100 bytes), NOT actual file content size
57+
- **Impact**: File size limit (5MB) is never enforced. Arbitrarily large files can be uploaded.
58+
- **Fix**: Use `file.getbuffer().nbytes` or `len(file.getvalue())` for BytesIO objects
59+
60+
### 4. CSRF Protection Disabled on Form Endpoints
61+
- **File**: `testing/views.py:146` (`check_website_security`), `:733` (`dmarc_upload`), `:867` (`receive_csp_report`)
62+
- **Impact**: `@csrf_exempt` on POST endpoints accepting form data. Combined with `SameSite=None` cookies (settings.py:135-136), this enables cross-site request forgery attacks.
63+
- **Fix**: Remove `@csrf_exempt` from form endpoints; use CSRF tokens. For webhooks (CSP reports, DMARC), use token-based auth instead.
64+
65+
### 5. Insecure Default Configuration
66+
- **File**: `testing_platform/settings.py:25,28`
67+
- **Impact**: `SECRET_KEY` defaults to literal `"secret"`, `DEBUG` defaults to `True`. While there's a startup check for production, `ZAP_API_KEY` also defaults to `"123456"` (line 271).
68+
- **Fix**: Remove insecure defaults; require explicit env vars; fail fast on missing critical config
69+
70+
### 6. SameSite=None on All Cookies
71+
- **File**: `testing_platform/settings.py:135-139`
72+
- **Impact**: `CSRF_COOKIE_SAMESITE = "None"` + `SESSION_COOKIE_SAMESITE = "None"` + `CORS_ALLOW_CREDENTIALS = True` effectively disables browser-side CSRF protections. Combined with #4, this is a direct attack vector.
73+
- **Fix**: Change to `"Lax"` (minimum) or `"Strict"`. Only use `"None"` if there's a documented cross-origin requirement.
74+
75+
---
76+
77+
## HIGH — Significant Bugs and Gaps
78+
79+
### 7. API Input Validation Bypassed
80+
- **File**: `api/views.py:280` (`InfraTestingEmailApiView`) — Reads `domain_name` directly from `request.data` without using serializer validation
81+
- **File**: `api/views.py:316` (`InfraTestingIPv6ApiView`) — Validates serializer but then reads from `request.data` anyway (ignoring validated data)
82+
- **File**: `api/views.py:332` (`InfraTestingSOAApiView`) — Creates a *new* serializer instance with raw domain string instead of using validated data
83+
- **Impact**: Unvalidated user input passed directly to DNS queries, nmap, and network operations
84+
- **Fix**: Use `serializer.validated_data['domain_name']` consistently after `is_valid()`
85+
86+
### 8. No Rate Limiting Anywhere
87+
- **File**: `testing_platform/settings.py` — No `DEFAULT_THROTTLE_CLASSES` in `REST_FRAMEWORK` config
88+
- **Impact**: Expensive operations (nmap scans, Pandora submissions, DNS enumeration) can be triggered at unlimited rate. Enables DoS against both the platform and scanned targets.
89+
- **Fix**: Add DRF throttle classes; consider per-endpoint limits for expensive operations
90+
91+
### 9. Nmap Execution Without Timeout
92+
- **File**: `testing/helpers.py:608-664``nmap.nmap_version_detection()` with `--script vulners` has no timeout
93+
- **Impact**: Scans can run indefinitely, exhausting worker threads. With no auth (#2) and no rate limits (#8), this is a DoS vector.
94+
- **Fix**: Add explicit timeouts; run in async task queue; implement per-user concurrency limits
95+
96+
### 10. Validators Raise `Exception` Instead of `ValidationError`
97+
- **File**: `testing/validators.py:13,33,36,51` — All validation failures raise bare `Exception`
98+
- **Impact**: DRF cannot format these as 400 responses; they bubble up as 500 Internal Server Errors. Users see generic error pages instead of helpful validation messages.
99+
- **Fix**: Use `rest_framework.exceptions.ValidationError` or `django.core.exceptions.ValidationError`
100+
101+
### 11. Pandora File Analysis Polling Issues
102+
- **File**: `testing/helpers.py:315-353`
103+
- **Issues**:
104+
- Busy-wait loop with `sleep(0.1)` for up to 500 iterations (~50s) blocks the worker thread
105+
- On exception, returns last (possibly stale) `analysis_result`
106+
- `seed_expire=0` creates permanent public links to analysis results
107+
- **Impact**: Worker thread exhaustion under load; incomplete results served as final; analysis results publicly accessible
108+
- **Fix**: Use async task queue; implement proper status callbacks; set reasonable seed expiration
109+
110+
### 12. DKIM Check Returns Inconsistent Types
111+
- **File**: `testing/helpers.py``check_dkim()` function
112+
- **Impact**: Returns `(record_text, boolean_tuple)` for single selector but `{"dkim": boolean}` for multiple selectors. Callers in `views.py` expect tuple unpacking — type mismatch causes runtime `TypeError`.
113+
- **Fix**: Standardize return type to always be a dict
114+
115+
### 13. Email Check Silent Failures
116+
- **File**: `testing/helpers.py:239-241`
117+
- **Impact**: If `checkdmarc` subprocess output is invalid JSON, result is silently set to `{}`. Users see empty results with no error indication — a "clean bill of health" when the check actually failed.
118+
- **Fix**: Detect and report parsing failures; check stderr; validate subprocess exit code
119+
120+
### 14. Missing DMARC Upload Authentication
121+
- **File**: `testing/views.py:733-750`
122+
- **Impact**: API key passed in URL query string (exposed in logs, referrer headers, browser history). Combined with `@csrf_exempt`, this endpoint is weakly protected.
123+
- **Fix**: Move API key to `Authorization` header; add CSRF protection or proper token auth
124+
125+
### 15. SQLite in Production
126+
- **File**: `testing_platform/settings.py:164-169`
127+
- **Impact**: SQLite cannot handle concurrent writes; "database is locked" errors under load. No connection pooling, no replication, no horizontal scaling.
128+
- **Fix**: Configure PostgreSQL for staging/production via `DATABASE_URL` env var
129+
130+
---
131+
132+
## MEDIUM — Accuracy, Consistency, and Hardening
133+
134+
### 16. API Always Returns HTTP 200
135+
- **File**: `api/views.py:282,318,334,349,365,380`
136+
- **Impact**: All check endpoints return `200 OK` regardless of whether the check succeeded or the input was invalid. API consumers cannot distinguish success from failure via status code.
137+
- **Fix**: Return 400 for validation errors, 422 for processing failures, 200 only for successful checks
138+
139+
### 17. IPv6 Check Logic Inconsistency
140+
- **File**: `testing/helpers.py:428-431,470-473`
141+
- **Impact**: IPv4 reachability requires TCP *or* UDP, but IPv6 reachability requires TCP *and* UDP. This asymmetry means IPv6 is incorrectly marked unreachable when only one protocol responds.
142+
- **Fix**: Use consistent logic (either protocol sufficient) for both
143+
144+
### 18. DNS Queries Without Timeout or Error Handling
145+
- **File**: `testing/helpers.py:392,406,442`
146+
- **Impact**: `dns.query.udp()` calls without timeout parameter can hang indefinitely. Only `Timeout` and `OSError` caught — misses `DNSException`, `NXDOMAIN`, connection refused.
147+
- **Fix**: Add explicit timeouts; catch `dns.exception.DNSException` broadly
148+
149+
### 19. TLS Version Check Fragile Nmap Parsing
150+
- **File**: `testing/helpers.py:858`
151+
- **Impact**: Assumes exact nmap script output structure (`results[k]["ciphers"]["children"]`). Different nmap versions may produce different structures — causes `KeyError` crashes.
152+
- **Fix**: Validate structure before accessing nested keys; handle missing data gracefully
153+
154+
### 20. CSP Report Endpoint Broken
155+
- **File**: `testing/views.py:874`
156+
- **Impact**: Uses `cache.get()` and `cache.incr()` but `cache` is not imported. First CSP report received crashes with `NameError`.
157+
- **Fix**: Add `from django.core.cache import cache` import
158+
159+
### 21. Missing Security Headers in Django Config
160+
- **File**: `testing_platform/settings.py`
161+
- **Missing**:
162+
- `SECURE_HSTS_SECONDS` (no HSTS enforcement)
163+
- `SECURE_SSL_REDIRECT` (no HTTP->HTTPS redirect)
164+
- `X_FRAME_OPTIONS = 'DENY'` (clickjacking, defaults to SAMEORIGIN)
165+
- `SECURE_CONTENT_TYPE_NOSNIFF` not explicit
166+
- **Impact**: Ironic for a security testing platform to not follow its own recommendations
167+
- **Fix**: Add all security headers; make them environment-configurable
168+
169+
### 22. No Database Unique Constraints on Domains
170+
- **File**: `testing/models.py:9-14`
171+
- **Impact**: `Domain` model has no `unique_together` on `(user, domain)`. Users can add the same domain multiple times. Race condition in `authentication/views.py:139-175` (TOCTOU).
172+
- **Fix**: Add `unique_together = [('user', 'domain')]`; handle `IntegrityError`
173+
174+
### 23. Cascade Delete Wipes All History
175+
- **File**: `testing/models.py:10,37,93,113,146` — All FKs use `on_delete=CASCADE`
176+
- **Impact**: Deleting a user cascades to all their domains, DMARC records, CSP endpoints, CSP reports, and test history. No soft delete, no audit trail.
177+
- **Fix**: Use `PROTECT` or `SET_NULL` for forensic data; consider soft deletes
178+
179+
### 24. PDF Report Generation Crashes on Missing Keys
180+
- **File**: `testing/views.py:780-791`
181+
- **Impact**: Email test PDF assumes `spf`, `dmarc`, `dnssec` keys exist in results. If any check failed, keys may be missing — causing `KeyError` during PDF generation.
182+
- **Fix**: Use `.get()` with defaults for all result keys
183+
184+
### 25. Vulnerability Lookup Without Caching or Rate Limiting
185+
- **File**: `testing/helpers.py:678-737`
186+
- **Impact**: Individual CVE lookups to external API with no caching, no batch queries, no rate limiting. API downtime causes silent failures; repeated scans re-fetch the same CVEs.
187+
- **Fix**: Cache CVE results; implement batch lookups; handle API unavailability explicitly
188+
189+
### 26. HSTS Parsing Crashes on Malformed Headers
190+
- **File**: `testing/helpers.py:1830`
191+
- **Impact**: `int()` conversion on `max-age` value without error handling. Malformed header like `max-age=invalid` crashes the check.
192+
- **Fix**: Wrap in try/except; validate before conversion
193+
194+
### 27. CorsMiddleware Position in Middleware Stack
195+
- **File**: `testing_platform/settings.py:127`
196+
- **Impact**: `CorsMiddleware` is last in the stack but [should be placed as high as possible](https://github.com/adamchainz/django-cors-headers#setup), ideally before `CommonMiddleware`.
197+
- **Fix**: Move to top of `MIDDLEWARE` list
198+
199+
### 28. Admin Exposed at Default `/admin/` Path
200+
- **File**: `testing_platform/urls.py:30`
201+
- **Impact**: Django admin at predictable URL with no additional protections (IP allowlist, 2FA)
202+
- **Fix**: Use obscure admin URL; add IP restrictions; require 2FA
203+
204+
---
205+
206+
## LOW — Technical Debt and Improvements
207+
208+
### 29. Monolithic helpers.py (~1900 Lines, ~41 Functions)
209+
- Single file containing all security check logic — DNS, TLS, IPv6, email, web server, CSP, cookies, CORS, HSTS, SRI, etc.
210+
- Hard to test individual functions; changes risk regressions; no separation of concerns
211+
- **Suggestion**: Split into domain-specific modules (`checks/dns.py`, `checks/tls.py`, `checks/headers.py`, etc.)
212+
213+
### 30. No Async Task Queue
214+
- All checks (nmap, Pandora, DNS) run synchronously in request/response cycle
215+
- Long-running scans block gunicorn worker threads
216+
- No task status tracking, no retry mechanism, no progress reporting
217+
- **Suggestion**: Integrate Celery or Django-Q for background processing
218+
219+
### 31. Inconsistent Error Return Formats
220+
- Some functions return `{"error": "msg"}`, others `{"status": False}`, others `{"result": {"error": "msg"}}`
221+
- Views and API must handle multiple formats; no contract between helpers and consumers
222+
- **Suggestion**: Define a standard result envelope (`{success, data, error}`)
223+
224+
### 32. Mixed Authentication Patterns
225+
- Some API views use 3 auth methods (Session + Basic + JWT), some use JWT only, some are fully public
226+
- No consistent pattern; no documentation of which endpoints need what
227+
- **Suggestion**: Define auth tiers in code and docs
228+
229+
### 33. SRI Check Fetches External Resources Without Circuit Breaker
230+
- **File**: `testing/helpers.py:1547-1632`
231+
- Fetches every cross-origin resource; slow CDNs make the check hang
232+
- Uses `print()` instead of `logging`
233+
- **Suggestion**: Add circuit breaker pattern; use proper logging
234+
235+
### 34. checkdmarc Dependency Not Validated at Startup
236+
- **File**: `testing/helpers.py:227-244`
237+
- Calls system binary via subprocess; no check that it's installed or correct version
238+
- **Suggestion**: Validate external tool availability at startup (`AppConfig.ready()`)
239+
240+
### 35. CORS Hardcoded for localhost
241+
- **File**: `testing_platform/settings.py:129-132`
242+
- Only allows `localhost:3000` — works for dev but needs environment-based config for production
243+
- **Suggestion**: Make `CORS_ALLOWED_ORIGINS` configurable via env var
244+
245+
---
246+
247+
## Summary by Category
248+
249+
| Category | Critical | High | Medium | Low |
250+
|----------|----------|------|--------|-----|
251+
| Authentication & Authorization | 2 | 1 | 1 | 1 |
252+
| Input Validation | 1 | 2 | 2 | 0 |
253+
| CSRF / Cookie Security | 2 | 1 | 0 | 0 |
254+
| Feature Accuracy | 0 | 3 | 5 | 2 |
255+
| Configuration & Deployment | 1 | 1 | 2 | 1 |
256+
| API Design | 0 | 1 | 1 | 2 |
257+
| Data Integrity | 0 | 0 | 2 | 1 |
258+
| **Total** | **6** | **9** | **13** | **7** |
259+
260+
## Recommended Fix Order
261+
262+
1. **Immediate**: #1 (credentials), #2 (unauth endpoints), #3 (file size), #5 (defaults)
263+
2. **Week 1**: #4+#6 (CSRF/cookies), #7 (validation bypass), #8 (rate limiting), #10 (validators)
264+
3. **Week 2**: #9 (nmap timeout), #11 (Pandora), #13 (silent failures), #14 (DMARC auth), #15 (database)
265+
4. **Week 3**: #16-#21 (accuracy fixes, headers, broken endpoints)
266+
5. **Month 1**: #22-#28 (hardening, data integrity)
267+
6. **Ongoing**: #29-#35 (architecture, async, tech debt)

0 commit comments

Comments
 (0)