Files
cannamanage/docs/sprint-9/security-scan-results.md
Patrick Plate 57f418f7c9 feat(sprint9): Phase 6 — Compliance dashboard, RetentionService, testing
Backend:
- ComplianceDashboardService: traffic-light status per ComplianceArea
  (KCANG/FINANCE/DSGVO/VEREIN) based on deadlines, payments, board positions
- RetentionService: scheduled anonymization of expired member data (KCanG §24,
  5 years), with dry-run preview and retention report endpoints
- ComplianceDeadlineSeeder: seeds 5 standard recurring deadlines on club creation
- ComplianceDashboardController: GET /api/v1/compliance/dashboard,
  GET /retention, POST /retention/preview
- Repository additions: countOverdue, countActive board positions/members

Frontend:
- /compliance page with traffic-light status cards per area
- Overdue deadlines section (highlighted red) with 'days overdue' badges
- Upcoming deadlines with 'days until due' badges and 'Complete' buttons
- Retention info cards (KCanG §24: 5y, AO §147: 10y, DSGVO: 2y)
- Navigation: added 'Compliance-Status' to sidebar under Compliance group
- compliance-dashboard.ts service with mock data for dev mode

Build verified: pnpm build passes clean.
2026-06-15 14:12:01 +02:00

199 lines
9.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Security Scan Report: CannaManage (Sprint 79 Focus)
**Date:** 2026-06-15
**Reviewer:** Roo (Security Reviewer)
**Scope:** Full codebase with focus on Sprint 79 additions
**Tools Used:** SonarQube SAST (MCP), Snyk SCA, Manual Checklist
---
## Verdict: ✅ PASS (with Medium advisories)
No Critical or High severity security vulnerabilities found.
2 Medium findings require attention in the next sprint.
---
## 1. Automated Scan Results
### 1.1 SonarQube SAST (Static Application Security Testing)
| File | Issues | Security Impact |
|------|--------|-----------------|
| `SecurityConfig.java` | 4× string duplication (S1192), 1× package FP | None — maintainability only |
| `JwtAuthFilter.java` | 1× package FP | None |
| `AuthController.java` | 1× package FP | None |
| `DocumentService.java` | 1× integer overflow in constant (S2184), 1× generic exception (S112), 2× hardcoded path delimiter (S1075) | **Low** — see findings |
**Summary:** 0 security vulnerabilities detected. All SAST findings are maintainability/reliability issues, not exploitable security weaknesses.
### 1.2 Snyk SCA (Software Composition Analysis)
| Component | Severity | Vulnerability | Fix Available |
|-----------|----------|---------------|---------------|
| `spring-boot-autoconfigure@4.0.6` | **Medium** | Insecure Temporary File (SNYK-JAVA-ORGSPRINGFRAMEWORKBOOT-17308346) | Upgrade to `4.0.7` |
| `openpdf@2.0.4` | **Medium** | Dual License (LGPL-2.1/MPL-2.0) — license compliance risk | Accept or replace |
**Frontend (npm):** ✅ 0 vulnerabilities across 18 dependencies
**Backend (Maven):** 2 medium issues (1 vulnerability, 1 license)
**Total projects tested:** 7
---
## 2. Manual Security Checklist
| # | Check | Result | Evidence |
|---|-------|--------|----------|
| 1 | No hardcoded secrets in source | ⚠️ Medium | JWT dev secret in `application.properties` line 8 — acceptable for dev profile, production uses `${CANNAMANAGE_SECURITY_JWT_SECRET}` env var |
| 2 | JWT secret from environment variable only | ✅ | Production profile (`application-production.properties:22`) uses `${CANNAMANAGE_SECURITY_JWT_SECRET}` |
| 3 | CSRF protection configured properly | ✅ | API (stateless JWT) correctly disables CSRF; Portal (session-based) uses `CookieCsrfTokenRepository` |
| 4 | CORS not overly permissive | ✅ | Restricted to `localhost:3000` and `frontend:3000` (Docker internal). Production should add production domain. |
| 5 | File upload size/type restrictions | ✅ | `DocumentService.java:26-33` — 10MB max, allowlist: PDF/DOCX/XLSX/PNG/JPG |
| 6 | SQL injection prevention | ✅ | All queries use JPQL with named parameters (`:param`). No native queries with string concatenation. 12 `@Query` annotations reviewed — all parameterized. |
| 7 | Path traversal prevention in DocumentService | ⚠️ Medium | `DocumentService.java:62` — filename from `file.getOriginalFilename()` is used in path construction without sanitization. UUID prefix mitigates exploitation but the original filename is concatenated directly. |
| 8 | Rate limiting on sensitive endpoints | ✅ | Authority export: 1/hour per tenant (`AuthorityExportService.java:76-79`). Email: rate-limited to 50/min. Login: no explicit rate limit but mitigated by BCrypt cost factor. |
| 9 | Password hashing with BCrypt | ✅ | `SecurityConfig.java:124``BCryptPasswordEncoder`. Password validation with complexity regex in `SetPasswordRequest.java`. |
| 10 | Tenant isolation (club_id filtering) | ✅ | All service methods accept `clubId` from `TenantContext.getCurrentTenant()` (JWT claim, not user input). Portal endpoints derive `memberId` from authenticated `userId`. |
---
## 3. Detailed Findings
### 3.1 ⚠️ Medium: Path Traversal Risk in DocumentService
**File:** [`DocumentService.java`](cannamanage-service/src/main/java/de/cannamanage/service/DocumentService.java:62)
**Rule:** OWASP A01:2021 — Broken Access Control
```java
String filename = file.getOriginalFilename() != null ? file.getOriginalFilename() : "document";
String storagePath = clubId + "/" + documentId + "_" + filename;
Path fullPath = Paths.get(UPLOAD_BASE, storagePath);
```
**Risk:** A malicious filename like `../../../etc/passwd` could theoretically escape the upload directory. The UUID prefix (`documentId + "_"`) and the fact that `clubId` is server-controlled reduce exploitability, but the original filename is not sanitized for path separators.
**Recommendation:** Add filename sanitization:
```java
String sanitized = Paths.get(filename).getFileName().toString();
// or: filename.replaceAll("[^a-zA-Z0-9.\\-_]", "_");
```
**Exploitability:** Low (UUID prefix + server-controlled clubId make it very hard to construct a useful path traversal), but defense-in-depth principle applies.
---
### 3.2 ⚠️ Medium: JWT Dev Secret in Default Properties
**File:** [`application.properties`](cannamanage-api/src/main/resources/application.properties:8)
```properties
cannamanage.security.jwt.secret=Y2FubmFtYW5hZ2Utand0LXNlY3JldC1rZXktZm9yLWRldmVsb3BtZW50LW9ubHktMzI=
```
**Risk:** This is a base64-encoded development-only secret. Production correctly overrides via `${CANNAMANAGE_SECURITY_JWT_SECRET}`. However, if the production environment variable is ever missing, Spring Boot falls back to this known value.
**Recommendation:**
- Add a startup check that fails if running with `production` profile and the default secret is detected
- Or remove the default and require the env var in all profiles
---
### 3.3 ⚠️ Medium: Spring Boot Insecure Temporary File (CVE)
**Component:** `spring-boot-autoconfigure@4.0.6`
**Fix:** Upgrade to Spring Boot `4.0.7`
**Impact:** Temporary file creation may use insecure permissions on some OS configurations.
---
### 3.4 ️ Low: No Login Rate Limiting
**File:** [`AuthController.java`](cannamanage-api/src/main/java/de/cannamanage/api/controller/AuthController.java:30)
The `/api/v1/auth/login` endpoint has no explicit rate limiting. BCrypt's computational cost provides some natural brute-force resistance (~100ms per attempt), but a dedicated rate limiter (e.g., Bucket4j or Spring Security's `AuthenticationFailureHandler` with exponential backoff) would strengthen defense.
**Recommendation:** Add rate limiting: max 5 failed attempts per IP per 15 minutes.
---
### 3.5 ️ Low: CORS Missing Production Domain
**File:** [`SecurityConfig.java`](cannamanage-api/src/main/java/de/cannamanage/api/security/SecurityConfig.java:131)
CORS `allowedOrigins` only includes `localhost:3000` and `frontend:3000`. The production domain (`cannamanage.plate-software.de`) is not listed. This is likely handled by the reverse proxy (nginx), but if the API is ever accessed directly, CORS will block legitimate requests.
**Recommendation:** Make CORS origins configurable via `@Value` from application properties.
---
### 3.6 ️ Low: AuthorityExportService — JSON Injection in Audit Log
**File:** [`AuthorityExportService.java`](cannamanage-service/src/main/java/de/cannamanage/service/report/AuthorityExportService.java:104)
```java
"{\"year\":" + year + ",\"reason\":\"" + escapeJson(reason) + "\"}"
```
The `reason` field is escaped via `escapeJson()`, which is good. However, manual JSON construction is fragile. Consider using a proper JSON library (Jackson `ObjectMapper`) for audit metadata serialization.
---
## 4. Security Architecture Assessment
### Strengths ✅
1. **Multi-layer authentication:** JWT for API, session-based for portal, re-authentication for sensitive exports
2. **RBAC with granular permissions:** 23+ `StaffPermission` enum values, checked via `StaffPermissionChecker`
3. **Tenant isolation:** `TenantContext` from JWT claims, not user-controllable input
4. **Token revocation:** JTI-based blacklist checked on every request
5. **Append-only financial data:** `LedgerEntry` per §147 AO — cannot delete or modify
6. **Audit trail:** Comprehensive `AuditService.log()` calls on all sensitive operations
7. **File upload validation:** Size limit + content-type allowlist + UUID-based storage paths
8. **Production hardening:** Error details hidden (`server.error.include-message=never`), Swagger disabled, minimal actuator exposure
9. **Session security:** `httpOnly=true`, `sameSite=strict`, 30min timeout, max 1 concurrent session
10. **Secure error messages:** `GlobalExceptionHandler` returns generic messages, no stack traces
### Areas for Improvement 📋
1. Add explicit login rate limiting (Bucket4j or similar)
2. Sanitize original filename in `DocumentService`
3. Upgrade Spring Boot to 4.0.7
4. Make CORS origins environment-configurable
5. Add Content-Security-Policy headers
6. Consider adding request signing for webhook endpoints (`/api/v1/webhooks/**`)
---
## 5. Compliance Notes
| Standard | Status | Notes |
|----------|--------|-------|
| OWASP Top 10 (2021) | ✅ Good | No A01-A10 critical findings |
| DSGVO/GDPR | ✅ Good | PII minimization in authority export (anonymized member list), audit trail |
| §147 AO (Aufbewahrung) | ✅ Good | Append-only ledger, no deletion of financial records |
| KCanG (Cannabis law) | ✅ Good | Compliance deadlines, quantity tracking, authority reporting |
---
## 6. Summary Table
| Severity | Count | Action Required |
|----------|-------|-----------------|
| Critical | 0 | — |
| High | 0 | — |
| Medium | 3 | Fix in next sprint (path traversal sanitization, JWT fallback guard, Spring Boot upgrade) |
| Low | 3 | Advisory — address when convenient |
---
## Verdict
### ✅ PASS
No Critical or High severity findings. The application demonstrates strong security architecture with proper authentication, authorization, tenant isolation, and audit logging. The 3 Medium findings are defense-in-depth improvements, not actively exploitable vulnerabilities.
**Recommended next actions (priority order):**
1. `mvn versions:set -DnewVersion=...` — upgrade Spring Boot to 4.0.7
2. Add `Paths.get(filename).getFileName().toString()` sanitization in DocumentService
3. Add startup validation that rejects the default JWT secret in production profile