20 KiB
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/<MODULE>/<TICKET_KEY>/<TICKET_KEY>-security-review.md
Steps
1. Get the diff
cd /Users/pplate/git/paisy-<TICKET_KEY>
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).
cd /Users/pplate/git/paisy-<TICKET_KEY>
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:
# Read the full file content
file_content = read_file("<path_to_changed_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/passworddirekt 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:
# 1. Verdächtige Dateien identifizieren
cd /Users/pplate/git/paisy-<TICKET_KEY>
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
# 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
// â FAIL â javax namespace (legacy)
import javax.xml.bind.annotation.*;
// â
PASS â jakarta namespace
import jakarta.xml.bind.annotation.*;
SEC-057: ServiceCenter F; validation
// â 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
// â 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
// â 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
# 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
// â 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
// â 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
// â 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.
// â
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()oderENC[AES256,...]fĂźr verschlĂźsselte Werte
Präzedenzfall: ESIDEPAISY-12154 dokumentiert das Pattern user=password=<short_value> 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:
memory_search_facts("false positive security")
memory_search_facts("SecBench accepted")
7. Generate security review document
Write docs/<MODULE>/<TICKET_KEY>/<TICKET_KEY>-security-review.md:
# Security Review: <TICKET_KEY> â <Summary>
**Datum:** <today>
**Modul:** <MODULE>
**Reviewer:** Roo (Security Reviewer)
**Branch:** <branch name>
**Verdict:** â
PASS / â FAIL
---
## Scan-Ergebnisse
| Tool | Status | Befunde |
|------|--------|---------|
| SonarQube (SAST) | â
Sauber / â ď¸ N Befunde | <details> |
| Datarake (Secrets) | â
Sauber / â ď¸ N Befunde / âď¸ Nicht verfĂźgbar | <details> |
| Snyk Code | â
Sauber / â ď¸ N Befunde / âď¸ Nicht verfĂźgbar | <details> |
## 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. **<file>:<line>** â [SEC-XXX] <Beschreibung>
- Schweregrad: Critical/High
- Empfehlung: <Behebungsvorschlag>
### â ď¸ Mittel (sollte behoben werden)
1. **<file>:<line>** â [SEC-XXX] <Beschreibung>
- Empfehlung: <Behebungsvorschlag>
### âšď¸ Niedrig / Info
1. **<file>:<line>** â [SEC-XXX] <Beschreibung>
## Identifizierte False Positives
| Tool | Datei | Befund | BegrĂźndung |
|------|-------|--------|-----------|
| <tool> | <file> | <finding> | <why it's safe> |
## 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
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/<MODULE>/<TICKET_KEY>/<TICKET_KEY>-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-<TICKET_KEY> 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_snippetwhen 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