8.4 KiB
Security Audit Report — GuardDog Nexus
Date: 2026-05-10
Auditor: Automated security audit
Last updated: 2026-05-11
Scope: Full codebase review — security vulnerabilities, logic errors, missing controls
Summary
| Severity | Count | Fixed | Rejected | Remaining |
|---|---|---|---|---|
| CRITICAL | 5 | 2 | 2 | 1 |
| HIGH | 7 | 2 | 3 | 2 |
| MEDIUM | 8 | 3 | 0 | 5 |
| LOW | 6 | 2 | 0 | 4 |
| Total | 26 | 9 | 5 | 12 |
CRITICAL (5)
C1. SSRF via webhook downloadUrl ✅ FIXED
Severity: CRITICAL
Fix: NEXUS_ALLOWED_HOSTS env var + _validate_download_url() in core/nexus.py.
Problem: downloadUrl из webhook-пэйлода передаётся напрямую в httpx.AsyncClient.get() без валидации.
download_url = asset.get("downloadUrl") or _build_download_url(repository, asset_path)
# ...
response = await client.get(download_url) # no validation
Real-world impact: Атакующий отправляет webhook с downloadUrl: "http://169.254.169.254/latest/meta-data/iam/security-credentials/" → сервер скачивает IAM-учётные данные облака.
Fix: Validate URL scheme (http/https only), block private IP ranges (10.x, 172.16.x, 192.168.x, 127.x, 169.254.x, ::1), optionally whitelist domain against config.nexus_url.
C2. Webhook secret not enforced by default ❌ ACCEPTED RISK
Severity: CRITICAL
Decision: Внутренний сервис, секрет опционален.
C3. Default admin credentials ✅ FIXED
Severity: CRITICAL
Fix: Убран BasicAuth из всех запросов к Nexus (анонимный доступ).
C4. XSS via LLM report verdict ❌ NOT DANGEROUS
Severity: CRITICAL — downgraded to INFO
Decision: Jinja2 autoescape блокирует инъекцию в атрибутах.
C5. LLM Prompt Injection ⚠️ PARTIALLY MITIGATED
Severity: CRITICAL
Mitigation: System prompt gives priority to system instructions. Raw finding data still in user message.
HIGH (7)
H1. No rate limiting ❌ REJECTED
H2. Path traversal ⚠️ LOW RISK
Severity: HIGH — downgraded
Analysis: os.path.basename("../../../etc/passwd") → "passwd", traversal невозможен.
H3. Sensitive data in API ❌ REJECTED (source_ip is a feature)
H4. No authentication ❌ REJECTED (internal service)
H5. Memory leak in locks ✅ FIXED (bg cleanup every 30min)
H6. Race condition in URL locking ✅ FIXED (DB re-check inside lock)
H7. CSV export bounded ❌ REJECTED (acceptable for internal tool)
MEDIUM (8)
M1. No LLM response schema validation
Severity: MEDIUM
File: core/llm.py:80-82
Problem: LLM response parsed as JSON but not validated against schema. Missing report.verdict → Jinja2 renders empty string → CSS broken.
Fix: Pydantic model для валидации LLM response.
M2. No CSRF protection
Severity: MEDIUM
File: routes/web.py:205-274
Problem: POST /api/v1/findings/{id}/analyze без CSRF token.
Fix: Добавить CSRF token для всех POST endpoints.
M3. No security headers
Severity: MEDIUM
File: main.py
Problem: Отсутствие CSP, X-Content-Type-Options, X-Frame-Options, X-XSS-Protection.
Fix: Middleware для security headers.
M4. SQLite without WAL mode
Severity: MEDIUM
File: db/engine.py:12
Problem: Concurrent readers block writers → poor performance under load.
Fix: PRAGMA journal_mode=WAL in connection setup.
M5. Scoped npm packages not supported
Severity: MEDIUM
File: core/nexus.py:54-70
Problem: extract_npm_info returns None для @scope/package → пропускаются сканирования.
Fix: Обновить extractor для scoped packages.
M6. Dashboard stats — potential IndexError
Severity: MEDIUM
File: routes/api_scans.py:145-147
Problem: dashboard["latest_flagged"][0] — IndexError если latest_flagged пустой.
"latest_scan_at": dashboard["latest_flagged"][0].started_at.isoformat()
Fix: Guard с if dashboard.get("latest_flagged").
M7. Error message HTML escaping
Severity: MEDIUM
File: web/templates/scan_detail.html:30
Problem: scan.error_message rendered в template — если содержит HTML/JS, может сломать UI.
Fix: Jinja2 autoescape handles this, но стоит добавить explicit escaping для code fields.
M8. Unknown ecosystem defaults to pypi ✅ FIXED
Severity: MEDIUM
Fix: _detect_ecosystem() возвращает None → webhook reject с "unknown_ecosystem".
Duplicate: L6.
LOW (6)
L1. Dockerfile grep hack ✅ FIXED (uv pip install . --system)
L2. Health check without DB ✅ FIXED (/health/dependencies)
L3. No backup strategy for SQLite
Severity: LOW
Risk: Crash → corrupted database → data loss.
Fix: Регулярные backups через cron или switch to PostgreSQL for production.
L4. Dead code — parse_package_path unused in harvester
Severity: LOW
File: core/nexus.py:93-99
Problem: Функция определена но не используется в harvester pipeline.
Fix: Убрать или интегрировать.
L5. Hardcoded LLM API base URL
Severity: LOW
File: constants.py:139
Problem: Default https://api.openai.com/v1 — unexpected API calls для пользователей локальных моделей.
Fix: Better default или warning at startup.
L6. Unknown ecosystem defaults to pypi (webhook)
Severity: LOW
File: routes/webhooks.py:62
Problem: Неизвестный format → fallback к pypi. Maven/NuGet webhooks будут сканироваться как PyPI пакеты.
Fix: Явно reject неизвестные ecosystems.
Implementation Plan
Phase 1 — P0 (Critical)
| # | Task | Status |
|---|---|---|
| 1 | SSRF protection | ✅ FIXED |
| 2 | Mandatory WEBHOOK_SECRET | ❌ ACCEPTED |
| 3 | Remove default Nexus credentials | ✅ FIXED |
| 4 | LLM verdict whitelist + prompt injection | ⚠️ PARTIAL |
| 5 | Path traversal fix | ⚠️ LOW RISK |
Phase 2 — P1 (High)
| # | Task | Status |
|---|---|---|
| 6 | Rate limiting | ❌ REJECTED |
| 7 | API authentication | ❌ REJECTED |
| 8 | Memory leak fix for locks | ✅ FIXED |
| 9 | Race condition fix | ✅ FIXED |
| 10 | Remove source_ip from public API | ❌ REJECTED |
| 11 | CSV export auth + limit | ❌ REJECTED |
Phase 3 — P2 (Medium)
| # | Task | Status |
|---|---|---|
| 12 | LLM response validation (Pydantic) | ⬜ |
| 13 | CSRF protection | ⬜ |
| 14 | Security headers middleware | ⬜ |
| 15 | SQLite WAL mode | ⬜ |
| 16 | Scoped npm support | ⬜ |
| 17 | Dashboard None guard | ⬜ |
| 18 | serialize_finding вместо **f.data |
✅ FIXED |
| 19 | _scan_component try/except |
✅ FIXED |
| 20 | Reject unknown ecosystem | ✅ FIXED |
Phase 4 — P3 (Low)
| # | Task | Status |
|---|---|---|
| 21 | Dockerfile deps | ✅ FIXED |
| 22 | Health check DB ping | ✅ FIXED |
| 23 | Backup strategy docs | ⬜ |
| 24 | Reject unknown ecosystems | ✅ FIXED (duplicate) |
Test Coverage Gaps
The existing 85 tests do NOT cover:
- SSRF prevention (malicious downloadUrl)
- Webhook signature validation with empty secret
- Path traversal in download URLs
- Rate limiting on webhook endpoint
- Authentication on API endpoints
- LLM prompt injection
- LLM response schema validation
- CSRF protection
- Security headers presence
- Memory leak in lock dictionaries
- Race condition in URL locking
- Scoped npm package extraction
- Dashboard IndexError on empty data
Recommendations
- Immediate: Implement C1-C5 before any production deployment
- Short-term: Implement H1-H7 within first sprint
- Medium-term: Implement M1-M8 within first month
- Long-term: Implement L1-L6 during routine maintenance
- Ongoing: Add security-focused tests for all findings above