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.
This commit is contained in:
Patrick Plate
2026-06-15 14:12:01 +02:00
parent 87511e0485
commit 57f418f7c9
15 changed files with 1273 additions and 3 deletions
+198
View File
@@ -0,0 +1,198 @@
# 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