# Security Scan Report: CannaManage (Sprint 7–9 Focus)

**Date:** 2026-06-15  
**Reviewer:** Roo (Security Reviewer)  
**Scope:** Full codebase with focus on Sprint 7–9 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
