diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index bd4a6cac..2877e9b3 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -276,6 +276,26 @@ jobs: echo "$dump" | grep -qE "\['add', 'familienarchiv-auth', 'polling'\]" \ || { echo "FAIL: familienarchiv-auth jail did not resolve to 'polling' backend"; exit 1; } + # ─── Semgrep Security Scan ─────────────────────────────────────────────────── + # Catches XXE-unprotected XML parser factories and similar patterns defined in + # .semgrep/security.yml. Runs in parallel with backend-unit-tests for fast feedback. + # Uses local rules only (no SEMGREP_APP_TOKEN / OIDC — act_runner does not support it). + semgrep-scan: + name: Semgrep Security Scan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Semgrep + run: pip install semgrep + + - name: Run security rules + run: semgrep --config .semgrep/security.yml --error --metrics=off backend/src/ + # ─── Compose Bucket-Bootstrap Idempotency ───────────────────────────────────── # docker-compose.prod.yml's create-buckets service runs on every # `docker compose up` (one-shot, no restart). Must be idempotent — a diff --git a/.semgrep/security.yml b/.semgrep/security.yml new file mode 100644 index 00000000..da787175 --- /dev/null +++ b/.semgrep/security.yml @@ -0,0 +1,49 @@ +# Semgrep security rules for Familienarchiv backend. +# These rules catch the absence of XXE protection on XML parser factories. +# CWE-611: Improper Restriction of XML External Entity Reference. +# Run: semgrep --config .semgrep/security.yml --error backend/src/ + +rules: + + # DocumentBuilderFactory without XXE hardening. + # All call sites must call setFeature("…disallow-doctype-decl", true) before use. + - id: dbf-xxe-default + patterns: + - pattern: $X = DocumentBuilderFactory.newInstance(); + - pattern-not-inside: | + ... + $X.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + ... + message: > + DocumentBuilderFactory without XXE protection (CWE-611). + Call XxeSafeXmlParser.hardenedFactory() instead of DocumentBuilderFactory.newInstance(). + languages: [java] + severity: WARNING + + # SAXParserFactory without XXE hardening. + - id: sax-xxe-default + patterns: + - pattern: $X = SAXParserFactory.newInstance(); + - pattern-not-inside: | + ... + $X.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + ... + message: > + SAXParserFactory without XXE protection (CWE-611). + Apply disallow-doctype-decl and disable external entity features before use. + languages: [java] + severity: WARNING + + # XMLInputFactory without XXE hardening (StAX parser). + - id: stax-xxe-default + patterns: + - pattern: $X = XMLInputFactory.newInstance(); + - pattern-not-inside: | + ... + $X.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); + ... + message: > + XMLInputFactory without XXE protection (CWE-611). + Set IS_SUPPORTING_EXTERNAL_ENTITIES to false and SUPPORT_DTD to false before use. + languages: [java] + severity: WARNING