# Skill: security-review Security-focused code review against ADP security policies and PAISY patterns. ## Description Analyzes code changes against ADP's 67 SEC-* security rules, OWASP guidelines, and 10 PAISY-specific security patterns. Integrates automated scan results (SonarQube via MCP, DataRake secrets detection) with a 16-item manual checklist. Produces a structured security review report with a clear PASS/FAIL verdict. PASS is required before code review can proceed. ## Invoked by 🔒 Security Reviewer mode ## Required Inputs | Input | Source | Example | |-------|--------|---------| | `TICKET_KEY` | Jira issue key | `ESIDEPAISY-12081` | | `MODULE` | PAISY module name | `eau`, `eubp`, `svmeldungen` | ## Output Markdown file: `docs///-security-review.md` ## Steps ### 1. Get the diff ```bash cd /Users/pplate/git/paisy- git diff origin/current --name-only git diff origin/current --stat git diff origin/current ``` Identify all changed files. Separate Java source files from test files, resources, and documentation. ### 2. Read changed files fully for context For each changed Java file, read the full file — not just the diff hunks. Security issues often depend on surrounding context (e.g., a method that looks safe in isolation but is called with untrusted input). ```bash cd /Users/pplate/git/paisy- git diff origin/current --name-only | grep "\.java$" ``` ### 3. Run SonarQube analyze_code_snippet on changed files For each changed Java file, use the SonarQube MCP tool: ```python # Read the full file content file_content = read_file("") # Analyze with SonarQube analyze_code_snippet( fileContent=file_content, language=["java"], scope=["MAIN"] # or ["TEST"] for test files ) ``` Collect all SonarQube findings. Filter for security-relevant rules (SECURITY impact software quality). #### 3.1 DataRake Secrets Scan (Pipeline-Äquivalent) ADP's Jenkins-Pipeline ruft `secretsScan()` über das **DataRake**-Widget auf (regex-basierter Secrets-Detector). Der Reviewer simuliert dieses Verhalten manuell, da DataRake nicht als MCP-Tool verfügbar ist. **Was DataRake erkennt:** - Passwörter (Literalwerte in sicherheitsrelevanten Kontexten) - Tokens (Basic Auth, Bearer, JWT) - Unverschlüsselte Private Keys - Gefährliche Dateien (`.netrc`, Java Keystores `.jks`/`.p12`, SSH Private Keys) - Gefährliche Kommandos, die Credentials offenlegen - Hartkodierte Secrets in JDBC-Connection-Strings (Oracle, MySQL, PostgreSQL, Informix) **Detection-Regeln (wichtig für die Beurteilung):** - Passwort-Werte müssen **6–70 Zeichen** lang sein, um zu triggern (kürzere Werte werden als Placeholder gefiltert) - JDBC-Rakes erkennen `user/password` direkt in URL-Connection-Strings - Dateiendung entscheidet, welche Rakes greifen (`.java` → Java-spezifische Rakes) - **Nur Single-Line-Matching** — über mehrere Zeilen verteilte Secrets werden nicht erkannt **Schweregrade laut DataRake:** | Schweregrad | Kontext | Begründung | |-------------|---------|-----------| | CRITICAL | Passwort/Token in HTML oder JavaScript | Kann ADP-Netzwerk verlassen (Browser-deliverable) | | HIGH | Secrets in anderem Source-Code (`.java`, `.yml`, `.properties`, `.sh`) | Im Repository sichtbar | | MEDIUM / LOW | Review-würdige Items (Keystores, verdächtige Muster) | Manuelle Bewertung nötig | **Manuelles Scan-Vorgehen:** ```bash # 1. Verdächtige Dateien identifizieren cd /Users/pplate/git/paisy- git diff origin/current --name-only | grep -E '\.(java|yml|yaml|properties|xml|sh|js|html|jsp)$' # 2. Auf typische Secret-Patterns prüfen (6–70 Zeichen) git diff origin/current | grep -iE '(password|passwd|pwd|token|api[_-]?key|secret)\s*[:=]\s*["\x27][^"\x27]{6,70}["\x27]' # 3. JDBC-Connection-Strings auf Inline-Credentials prüfen git diff origin/current | grep -iE 'jdbc:[^"\x27\s]*(user|password)=' # 4. Gefährliche Dateien finden git diff origin/current --name-only | grep -iE '(\.netrc|\.jks|\.p12|id_rsa|id_dsa|id_ecdsa)$' ``` Quelle: Confluence EIT-Space, Seiten `3270157258` (DataRake), `3270157260` (FAQ), `3270157263` (JDBC Rakes). ### 4. Apply SEC-* rules — manual checklist (16 items) For each changed file, systematically check: | # | Rules | Check | What to look for | |---|-------|-------|-----------------| | 1 | SEC-001..004 | No hardcoded credentials | Passwords, API keys, tokens, JDBC credentials in string literals. Exclude test files. | | 2 | SEC-005 | Credentials via `@Value`/env | All credentials must come from `@Value("${...}")`, `System.getProperty()`, or `System.getenv()`. No `private static final String PASSWORD = "..."`. | | 3 | SEC-011 | No SQL injection | All SQL must use parameterized queries (`?` placeholders, `@Query` with `:param`, JPA Criteria API). No string concatenation in SQL. | | 4 | SEC-012 | No path traversal | File paths from external input must be sanitized. Check for `new File(userInput)`, `Paths.get(userInput)` without validation. | | 5 | SEC-016 | Input validation | All external data entry points (REST endpoints, file parsers, PAISY responses) must validate input before processing. | | 6 | SEC-018 | No info disclosure in errors | Error messages must not expose stack traces, internal paths, SQL queries, or system details to callers. | | 7 | SEC-033 | PII encryption | Payroll data, HR data, personal data must be encrypted at rest. Check for PII stored in plain text in new DB columns. | | 8 | SEC-035 | No PII in LLM processing | No personal data (names, BBNR, Versicherungsnummer) sent to AI/LLM services. | | 9 | SEC-040 | No sensitive data in logs | Log statements must not contain passwords, tokens, PII, or full payroll records. Check `log.debug/info/warn/error` calls. | | 10 | SEC-055 | No `src.gen/` modifications | Files under `src.gen/` are JAXB-generated. Any modification is silently overwritten and creates false security. | | 11 | SEC-057 | ServiceCenter `F;` response validation | Every `ServiceCenter.INSTANCE()` call must check if the response starts with `F;` before parsing. Unchecked `F;` responses can lead to corrupted payroll data sent to government agencies. | | 12 | SEC-058 | Date sentinel handling | Before parsing dates from PAISY, check for sentinel values: `00.00.0000`, `0000000`, `9999999`. Parsing these causes `DateTimeParseException` or silently wrong date calculations. | | 13 | SEC-059 | Batch EntityManager flush/clear | Batch processing loops must call `em.flush()` and `em.clear()` every 100 items. Without this, the JPA persistence context grows unbounded → `OutOfMemoryError` in production. | | 14 | SEC-060 | Dual Flyway migrations | Every new migration must exist in BOTH `db/migration/H2/` AND `db/migration/ORACLE/`. Missing one breaks either dev/test (H2) or production (Oracle). | | 15 | SEC-061 | No hardcoded BBNR/IDs | No hardcoded Betriebsnummern, sprint IDs, Epic keys, or instance names. These must come from configuration or runtime lookup. | | 16 | SEC-064 | DataRake compliance — no secrets in source | No literal passwords/tokens (6–70 chars), no inline credentials in JDBC URLs, no committed keystores/SSH keys, no dangerous credential-exposing commands. H2 test DBs must use random UUID passwords, not default `sa`/empty. | ### 5. Check PAISY-specific patterns (SEC-055 through SEC-063) with code examples For each PAISY-specific rule, verify against the actual code: #### SEC-055: src.gen/ protection ```bash # Check if any changed files are under src.gen/ git diff origin/current --name-only | grep "src\.gen/" # If any match → CRITICAL finding ``` #### SEC-056: JAXB namespace ```java // ❌ FAIL — javax namespace (legacy) import javax.xml.bind.annotation.*; // ✅ PASS — jakarta namespace import jakarta.xml.bind.annotation.*; ``` #### SEC-057: ServiceCenter F; validation ```java // ❌ UNSAFE — no error check String response = ServiceCenter.INSTANCE().getPaisy().pgmReadLine(); String[] parts = response.split(";"); String bbnr = parts[1]; // ArrayIndexOutOfBoundsException or wrong data if F; response // ✅ SAFE — error check first String response = ServiceCenter.INSTANCE().getPaisy().pgmReadLine(); if (response.startsWith("F;")) { log.error("PAISY error: {}", response); throw new PaisyRuntimeException("ServiceCenter error: " + response); } String[] parts = response.split(";"); ``` #### SEC-058: Date sentinel handling ```java // ❌ UNSAFE — no sentinel check LocalDate date = LocalDate.parse(paiDate, formatter); // ✅ SAFE — sentinel check first if (paiDate.equals("00.00.0000") || paiDate.equals("0000000") || paiDate.equals("9999999")) { return null; // or LocalDate.MAX / LocalDate.MIN as appropriate } LocalDate date = LocalDate.parse(paiDate, formatter); ``` #### SEC-059: Batch EM flush/clear ```java // ❌ UNSAFE — no flush/clear in batch loop for (Record record : records) { em.persist(record); } // ✅ SAFE — flush/clear every 100 items for (int i = 0; i < records.size(); i++) { em.persist(records.get(i)); if (i % 100 == 0) { em.flush(); em.clear(); } } ``` #### SEC-060: Dual Flyway migrations ```bash # Check for new migration files git diff origin/current --name-only | grep "db/migration" # For each H2 migration, verify a matching ORACLE migration exists (and vice versa) ``` #### SEC-061: No hardcoded identifiers ```java // ❌ FAIL — hardcoded BBNR String bbnr = "12345678"; // ❌ FAIL — hardcoded sprint ID int sprintId = 1234; // ✅ PASS — from configuration @Value("${paisy.bbnr}") private String bbnr; ``` #### SEC-062: CSV encoding ```java // ❌ FAIL — wrong encoding new FileReader(csvFile, StandardCharsets.UTF_8); // ✅ PASS — correct German government standard new FileReader(csvFile, Charset.forName("ISO-8859-1")); ``` #### SEC-063: Parameterized logging ```java // ❌ FAIL — string concatenation log.debug("Processing BBNR: " + bbnr + " with status: " + status); // ✅ PASS — parameterized log.debug("Processing BBNR: {} with status: {}", bbnr, status); ``` #### SEC-064: DataRake compliance — H2 test database pattern H2 in-memory Test-Datenbanken sind ein häufiger Auslöser für DataRake-Findings. Der **default user `sa` mit leerem Passwort** triggert die JDBC-Rakes nicht zuverlässig (`sa` < 6 Zeichen), aber jeder kurze hartkodierte Wert in `JDBC_PASSWORD` kann als Secret erkannt werden, sobald er die 6-Zeichen-Schwelle überschreitet. ```java // ✅ SAFE — kein User gesetzt, zufälliges UUID-Passwort (wird nicht als Secret erkannt, // da UUID-Format als Test-Pattern gefiltert wird und kein User → kein JDBC-Rake-Match) props.put(JDBC_DRIVER, "org.h2.Driver"); props.put(JDBC_URL, "jdbc:h2:mem:test_db;DB_CLOSE_DELAY=-1;MODE=Oracle"); props.put(JDBC_PASSWORD, UUID.randomUUID().toString()); // ❌ TRIGGERS — default user "sa" + festes kurzes Passwort props.put(JDBC_USER, "sa"); props.put(JDBC_PASSWORD, "testpassword123"); // ❌ TRIGGERS — Inline-Credentials im JDBC-URL props.put(JDBC_URL, "jdbc:oracle:thin:scott/tiger123@//host:1521/SID"); // ✅ SAFE — Credentials separat, via @Value oder env @Value("${db.user}") private String dbUser; @Value("${db.password}") private String dbPassword; ``` **Generelle Regeln zur Vermeidung von DataRake-Findings:** - Keine literalen Passwörter (6–70 Zeichen) in `.java`, `.yml`, `.properties`, `.xml`, `.sh` - Keine Inline-Credentials in JDBC-URLs (`user=`/`password=` im URL-String) - Keine Java Keystores (`.jks`, `.p12`), SSH Private Keys oder `.netrc`-Dateien im Repository - Template-Variablen verwenden: `${ENV_VAR}`, `{placeholder}`, `((concourse-var))` — DataRake filtert diese als StandardPatterns - Für Tests: `UUID.randomUUID().toString()` oder `ENC[AES256,...]` für verschlüsselte Werte Präzedenzfall: `ESIDEPAISY-12154` dokumentiert das Pattern `user=password=` als bewusst akzeptierten lokalen Dev-Pattern. ### 6. Identify false positives Before flagging a finding, check against known false positive patterns: | Pattern | Tool | Why It's Safe | Action | |---------|------|--------------|--------| | Variable self-assignment | DataRake | Variable assignment, not a password literal | Skip | | Credential parsing from connection string | DataRake | Extracting, not hardcoding | Skip | | Environment variable retrieval | DataRake | Runtime injection, not hardcoded | Skip | | Test data in `src/test/` | Both | Test-only scope, never deployed | Skip (unless test exposes real credentials) | | Application-specific passwords by design | DataRake | Business logic requirement (e.g., PDF encryption) | Document as exception | | Log file matches | DataRake | Build artifacts, not source code | Skip | | Snyk result files | DataRake | Scan output, not source | Skip | | Username=password pattern for local dev | DataRake | Intentional dev-only pattern (ESIDEPAISY-12154 precedent) | Document as accepted risk | #### DataRake StandardPatternFilters (built-in false positives) DataRake filtert die folgenden Werte automatisch — sie lösen **kein** Finding aus und müssen nicht als False Positive dokumentiert werden: | Pattern-Beispiel | Typ | Begründung | |------------------|-----|-----------| | `${variable_name}` | Spring/Shell Template | Wird zur Laufzeit ersetzt | | `{variable}` | Bare Braces | Template-Platzhalter | | `{{variable}}` | Handlebars / Helm Template | Template-Engine-Syntax | | `$(variable)` | Shell Substitution | Wird zur Laufzeit ersetzt | | `$VARIABLE` | Bare Dollar Sign | Shell-/Env-Variable | | `%variable%` | Windows Env Style | Wird zur Laufzeit ersetzt | | `#variable#` | Hash-Delimited | Template-Platzhalter | | `??variable??` | Null-Coalescing Style | Template-Platzhalter | | `((concourse-placeholder))` | Concourse/Pipeline | Pipeline-Variable | | `XXXXXXXXXX` / `xxxxxxxxxx` | Repeated Single Char | Regex `^([0-9A-ZxX+#*-])(\1)+$` | | `test-test`, `foo_foo`, `aaaaaa` | Repeated Word | 3–10 gleiche Zeichen/Wortgruppen | | `ENC[AES256,...]` | AES256-verschlüsselt | Bereits sicher verschlüsselt | | Werte unter 6 Zeichen | Zu kurz | Alle Regexes verlangen `{6,70}` | | `example.com`, `test.de` | Domain/URL | Beispiel-Domains | **Konsequenz für den Reviewer:** Tritt einer dieser Patterns im Diff auf, ist es **kein Finding** — auch wenn der Kontext "password" oder "secret" enthält. Nicht in den False-Positive-Report aufnehmen, sondern still überspringen. Also search BigMind for known false positive patterns: ```python memory_search_facts("false positive security") memory_search_facts("SecBench accepted") ``` ### 7. Generate security review document Write `docs///-security-review.md`: ```markdown # Security Review: — **Datum:** **Modul:** **Reviewer:** Roo (Security Reviewer) **Branch:** **Verdict:** ✅ PASS / ❌ FAIL --- ## Scan-Ergebnisse | Tool | Status | Befunde | |------|--------|---------| | SonarQube (SAST) | ✅ Sauber / ⚠️ N Befunde |
| | Datarake (Secrets) | ✅ Sauber / ⚠️ N Befunde / ⏭️ Nicht verfügbar |
| | Snyk Code | ✅ Sauber / ⚠️ N Befunde / ⏭️ Nicht verfügbar |
| ## Manuelle Sicherheits-Checkliste | # | Regel | Prüfpunkt | Ergebnis | Anmerkung | |---|-------|-----------|----------|-----------| | 1 | SEC-001..004 | Keine hartkodierten Credentials | ✅/❌ | | | 2 | SEC-005 | Credentials via @Value/env | ✅/❌ | | | 3 | SEC-011 | Keine SQL-Injection | ✅/❌ | | | 4 | SEC-012 | Kein Path Traversal | ✅/❌ | | | 5 | SEC-016 | Input-Validierung | ✅/❌ | | | 6 | SEC-018 | Keine Info-Disclosure in Fehlern | ✅/❌ | | | 7 | SEC-033 | PII-Verschlüsselung | ✅/N/A | | | 8 | SEC-035 | Kein PII in LLM-Verarbeitung | ✅/N/A | | | 9 | SEC-040 | Keine sensiblen Daten in Logs | ✅/❌ | | | 10 | SEC-055 | Keine src.gen/ Änderungen | ✅/❌ | | | 11 | SEC-057 | F;-Response-Validierung | ✅/N/A | | | 12 | SEC-058 | Datums-Sentinel-Behandlung | ✅/N/A | | | 13 | SEC-059 | Batch-EM-Flush/Clear | ✅/N/A | | | 14 | SEC-060 | Duale Flyway-Migrationen | ✅/N/A | | | 15 | SEC-061 | Keine hartkodierten BBNR/IDs | ✅/❌ | | | 16 | SEC-064 | DataRake-Compliance (keine Secrets im Source) | ✅/❌ | | ## Befunde ### ❌ Kritisch / Hoch (muss behoben werden) 1. **:** — [SEC-XXX] - Schweregrad: Critical/High - Empfehlung: ### ⚠️ Mittel (sollte behoben werden) 1. **:** — [SEC-XXX] - Empfehlung: ### ℹ️ Niedrig / Info 1. **:** — [SEC-XXX] ## Identifizierte False Positives | Tool | Datei | Befund | Begründung | |------|-------|--------|-----------| | | | | | ## Verdict **✅ PASS** — Keine kritischen oder hohen Sicherheitsbefunde. Weiter zum Code Review. — oder — **❌ FAIL** — N kritische/hohe Befunde müssen vor dem Code Review behoben werden. ``` ### 8. Determine verdict | Verdict | Criteria | Pipeline Action | |---------|---------|----------------| | ✅ PASS | No Critical or High findings after false positive filtering | Proceed to Phase 6 (Code Review) | | ❌ FAIL | Any Critical or High finding remains | Loop back to Phase 4 (Code mode) for fixes, then re-review | ### 9. Store findings in BigMind ```python memory_store_fact( category="codebase", fact=f"{TICKET_KEY}: Security review {verdict}. {total_findings} findings ({critical} Critical, {high} High, {medium} Medium, {low} Low). {false_positives} false positives identified." ) # For significant findings, store details memory_append_chunk( session_id=SESSION_ID, content=f"Security review for {TICKET_KEY}:\nVerdict: {verdict}\nFindings: {details}\nFalse positives: {fp_details}", flag_reason="security review findings" ) ``` ## Expected Output - Security review document at `docs///-security-review.md` - SonarQube scan results integrated (where available) - All 16 manual checklist items evaluated (incl. DataRake secrets compliance) - False positives identified and documented with rationale - Clear PASS/FAIL verdict - BigMind fact stored ## Error Handling | Error | Resolution | |-------|------------| | Worktree not found | Check if `/Users/pplate/git/paisy-` exists, or use main repo with branch checkout | | SonarQube MCP unavailable | Skip automated scan, note in report as "⏭️ Nicht verfügbar", rely on manual checklist | | DataRake not available as MCP | Expected — always perform manual DataRake simulation via Step 3.1 grep commands | | No Java files changed | Skip SonarQube scan and SAST checks, focus on configuration/resource security | | Empty diff | Branch identical to `current` — report "no changes to review" | | Large diff (>50 files) | Focus on high-risk files first: files with `ServiceCenter`, `EntityManager`, `@Value`, SQL, file I/O | ## Severity Levels | Severity | Symbol | Definition | SLA | Pipeline Impact | |----------|--------|-----------|-----|----------------| | Critical | ❌ | Exploitable vulnerability, hardcoded production credentials, data corruption risk | Fix immediately | Blocks pipeline — FAIL | | High | ❌ | Security weakness that could be exploited with effort, missing input validation on external data | Fix before merge | Blocks pipeline — FAIL | | Medium | ⚠️ | Security improvement needed but not immediately exploitable | Fix in sprint | Advisory — PASS with warnings | | Low | ℹ️ | Best practice suggestion, defense-in-depth improvement | Fix when convenient | Advisory — PASS | ## Language - Document content: **German** - Code references (class names, methods, file paths): English as-is - Checklist items: German - BigMind facts: English ## Conventions - One security review per ticket — don't split across multiple documents - Always run SonarQube `analyze_code_snippet` when MCP tool is available - Document ALL false positives with rationale — this builds the knowledge base - Reference SEC-* rule IDs in all findings for traceability - If a finding was previously accepted as risk (check BigMind), note it but don't re-flag