From 3c9a06192b5fb94868022edcec0053de3340f0d1 Mon Sep 17 00:00:00 2001 From: ritiksah141 Date: Fri, 17 Jul 2026 18:18:05 +0100 Subject: [PATCH 1/4] Add Semgrep SAST to CI alongside CodeQL --- .github/workflows/ci.yml | 53 +++++++++++++++++++++++++++++++++++++++- CHANGELOG.md | 1 + docs/ci-pipeline.md | 22 +++++++++++++++-- 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ddbb30..ec2c39d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -397,6 +397,54 @@ jobs: - name: Run bandit run: bandit -r api/ scanner/ ai/ -ll + # ── SAST (Semgrep) ────────────────────────────────────────────────────────── + sast-semgrep: + name: SAST (Semgrep) + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - name: Set up Python 3.11 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.11" + cache: pip + + - name: Install semgrep + run: pip install semgrep + + - name: Run semgrep + id: semgrep + continue-on-error: true + run: | + semgrep scan \ + --config p/security-audit \ + --config p/owasp-top-ten \ + --config p/python \ + --config p/javascript \ + --exclude venv \ + --exclude frontend/node_modules \ + --exclude frontend/dist \ + --metrics=off \ + --sarif --output semgrep.sarif \ + --error \ + . + + - name: Upload SARIF to GitHub code scanning + if: always() + uses: github/codeql-action/upload-sarif@411c4c9a36b3fca4d674f06b6396b2c6d23522c6 # v3.36.3 + with: + sarif_file: semgrep.sarif + category: semgrep + + - name: Fail job if Semgrep found issues + if: steps.semgrep.outcome == 'failure' + run: exit 1 + # ── SCA / dependency scanning (pip-audit) ───────────────────────────────── sca-pip-audit: name: SCA (pip-audit) @@ -602,7 +650,7 @@ jobs: name: CI Summary runs-on: ubuntu-latest needs: - [lint, rule-validation, secret-scan, sast-bandit, sca-pip-audit, sbom, container-scan, backend-tests, frontend, website, enforce-source-branch] + [lint, rule-validation, secret-scan, sast-bandit, sast-semgrep, sca-pip-audit, sbom, container-scan, backend-tests, frontend, website, enforce-source-branch] if: always() steps: - name: Build summary @@ -611,6 +659,7 @@ jobs: RULE_VALIDATION: ${{ needs.rule-validation.result }} SECRET_SCAN: ${{ needs.secret-scan.result }} SAST_BANDIT: ${{ needs.sast-bandit.result }} + SAST_SEMGREP: ${{ needs.sast-semgrep.result }} SCA_PIP_AUDIT: ${{ needs.sca-pip-audit.result }} SBOM: ${{ needs.sbom.result }} CONTAINER_SCAN: ${{ needs.container-scan.result }} @@ -627,6 +676,7 @@ jobs: ("Rule & Compliance Validation", os.environ["RULE_VALIDATION"]), ("Secret Scan (Gitleaks)", os.environ["SECRET_SCAN"]), ("SAST (Bandit)", os.environ["SAST_BANDIT"]), + ("SAST (Semgrep)", os.environ["SAST_SEMGREP"]), ("SCA (pip-audit)", os.environ["SCA_PIP_AUDIT"]), ("SBOM (Syft)", os.environ["SBOM"]), ("Container Scan (Trivy, INFRA 1 pending)", os.environ["CONTAINER_SCAN"]), @@ -679,6 +729,7 @@ jobs: needs.rule-validation.result != 'success' || needs.secret-scan.result != 'success' || needs.sast-bandit.result != 'success' || + needs.sast-semgrep.result != 'success' || needs.sca-pip-audit.result != 'success' || needs.sbom.result != 'success' || needs.backend-tests.result != 'success' || diff --git a/CHANGELOG.md b/CHANGELOG.md index 7992b49..408c9da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ OpenShield uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added +- Semgrep SAST integrated into GitHub Actions CI as an open-source, account-free complement to CodeQL - OpenSSF Best Practices Passing Badge achieved with 100% of applicable Passing-level criteria completed - Official live OpenSSF badge and verified project record added to project documentation - CBOM endpoints with per-asset quantum risk scoring and migration guidance diff --git a/docs/ci-pipeline.md b/docs/ci-pipeline.md index ba11313..094bdf5 100644 --- a/docs/ci-pipeline.md +++ b/docs/ci-pipeline.md @@ -16,6 +16,7 @@ This document explains each job, how to reproduce every check locally before ope | **Rule & Compliance Validation** | 7 static checks (below) | Rule/playbook/compliance-JSON problems | | **Secret Scan (Gitleaks)** | `gitleaks detect` | A hardcoded secret in the working tree | | **SAST (Bandit)** | `bandit -r api/ scanner/ ai/ -ll` | A medium+ severity insecure-code pattern | +| **SAST (Semgrep)** | `semgrep scan --config p/security-audit --config p/owasp-top-ten --config p/python --config p/javascript --error` | A finding from the security-audit / OWASP Top 10 / Python / JS rulesets | | **SCA (pip-audit)** | `pip-audit -r requirements.txt` | A dependency with a known CVE (minus documented ignores) | | **SBOM (Syft)** | CycloneDX SBOM generated + uploaded as an artifact | SBOM generation error | | **Container Scan (Trivy)** | Dormant scaffold — skips until a `Dockerfile` exists (INFRA 1 / #154) | (nothing today) | @@ -37,7 +38,7 @@ The **Container Scan** job is intentionally **not** a required check yet: no `Do ```bash python -m venv .venv && source .venv/bin/activate pip install -r requirements.txt -pip install ruff bandit pip-audit # lint / SAST / SCA tools +pip install ruff bandit pip-audit semgrep # lint / SAST / SCA tools ``` A local PostgreSQL is needed for the backend tests. Any Postgres 14+ works; create the CI-matching role/db once: @@ -96,6 +97,21 @@ bandit -r api/ scanner/ ai/ -ll `-ll` reports medium severity and above. Confirmed false positives are annotated inline with `# nosec ` and a one-line justification (e.g. binding `0.0.0.0` inside a container, a parameterized SQL string). +### SAST (Semgrep) + +```bash +semgrep scan \ + --config p/security-audit \ + --config p/owasp-top-ten \ + --config p/python \ + --config p/javascript \ + --exclude venv --exclude frontend/node_modules --exclude frontend/dist \ + --metrics=off \ + --error . +``` + +Uses the public Semgrep Registry rulesets (`p/...`) directly, not `--config auto`, since `auto` nudges toward a semgrep.dev account/login, which this project deliberately avoids. `--metrics=off` disables Semgrep's anonymous telemetry. `venv/`, `node_modules/`, and `dist/` are already excluded by Semgrep's built-in default ignores and this repo's `.gitignore`, so no `.semgrepignore` is needed; the `--exclude` flags above just make that exclusion visible in the command itself. In CI, findings are also written to SARIF and uploaded to the GitHub Security → Code scanning tab alongside CodeQL's results. + ### SCA (pip-audit) ```bash @@ -144,8 +160,9 @@ CI runs at **both** merge points (`on: pull_request` targets `dev` and `main`), - **Gitleaks via the release binary, not `gitleaks-action`.** The Action requires a (free) `GITLEAKS_LICENSE` for **organization**-owned repos; the CLI binary is Apache-2.0 with no key, so it runs with zero license friction. - **All third-party actions are pinned to a full commit SHA** with a `# vX.Y.Z` comment (GitHub's supply-chain hardening standard). `aquasecurity/trivy-action` is SHA-pinned specifically because its mutable tags were compromised in 2026. - **Dependabot is notify-only** (`open-pull-requests-limit: 0` for pip / github-actions / npm): it still raises alerts but does not auto-open version-bump PRs. -- **Least-privilege token:** `ci.yml` declares `permissions: contents: read`; CodeQL scopes its own `security-events: write`. +- **Least-privilege token:** `ci.yml` declares `permissions: contents: read`; CodeQL and Semgrep each scope their own `security-events: write`. - **`concurrency` cancels superseded runs** on the same PR to save runner minutes. +- **Semgrep added as a second, open-source SAST layer**, run as a pure CLI invocation (`semgrep scan --config p/...`) with `--metrics=off`, no semgrep.dev account, login, or telemetry dependency. It complements CodeQL's deeper data-flow/taint analysis with fast, pattern-based rules (OWASP Top 10, general security-audit, Python/JS-specific), uploading SARIF to the same GitHub code scanning UI. Added per maintainer direction to reduce reliance on GitHub's proprietary, soon-to-be-paid "Code Quality" product in favor of open-source tooling. --- @@ -180,6 +197,7 @@ The `ci-summary` job uses `needs: [...]` + `if: always()` so it runs after every |---|---| | `ruff check` / `format` fails | `ruff format .` then `ruff check --fix .`; re-run both | | `bandit` medium+ finding | Fix it, or if a confirmed false positive add `# nosec ` with a one-line reason | +| `semgrep` finding | Fix the flagged pattern, or if a confirmed false positive add a `# nosemgrep: ` (Python) / `// nosemgrep: ` (JS) comment with a one-line justification on the flagged line | | `pip-audit` reports a CVE | Bump the pin to a fixed version; only add `--ignore-vuln` with a documented reason | | `pytest` coverage below 25% | Add tests, or investigate the regression that removed coverage | | Frontend eslint error | Fix the reported rule (e.g. remove an unused import); warnings do not fail CI | From c4a3fe75648a4544a4ad2feaeae21badc9edfb2a Mon Sep 17 00:00:00 2001 From: ritiksah141 Date: Fri, 17 Jul 2026 18:18:13 +0100 Subject: [PATCH 2/4] Add non-root USER to Dockerfile to satisfy Semgrep gate --- Dockerfile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Dockerfile b/Dockerfile index a88354d..9dceebf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,12 @@ RUN pip install --no-cache-dir --upgrade \ COPY . . +RUN groupadd --system openshield && \ + useradd --system --gid openshield --no-create-home openshield && \ + chown -R openshield:openshield /app + +USER openshield + EXPOSE 8000 CMD ["gunicorn", "--workers", "2", "--threads", "2", "--timeout", "120", "--bind", "0.0.0.0:8000", "api.app:app"] From 4adb35014e0d2a5f80b6fc5fc69f8070402615d5 Mon Sep 17 00:00:00 2001 From: ritiksah141 Date: Fri, 17 Jul 2026 22:56:16 +0100 Subject: [PATCH 3/4] Document Dockerfile hardening intent and Semgrep registry dependency --- Dockerfile | 1 + docs/ci-pipeline.md | 1 + 2 files changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 9dceebf..991ca28 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,7 @@ RUN pip install --no-cache-dir --upgrade \ COPY . . +# Container hardening, addresses the non-root gap flagged in #185 RUN groupadd --system openshield && \ useradd --system --gid openshield --no-create-home openshield && \ chown -R openshield:openshield /app diff --git a/docs/ci-pipeline.md b/docs/ci-pipeline.md index 094bdf5..73bb149 100644 --- a/docs/ci-pipeline.md +++ b/docs/ci-pipeline.md @@ -163,6 +163,7 @@ CI runs at **both** merge points (`on: pull_request` targets `dev` and `main`), - **Least-privilege token:** `ci.yml` declares `permissions: contents: read`; CodeQL and Semgrep each scope their own `security-events: write`. - **`concurrency` cancels superseded runs** on the same PR to save runner minutes. - **Semgrep added as a second, open-source SAST layer**, run as a pure CLI invocation (`semgrep scan --config p/...`) with `--metrics=off`, no semgrep.dev account, login, or telemetry dependency. It complements CodeQL's deeper data-flow/taint analysis with fast, pattern-based rules (OWASP Top 10, general security-audit, Python/JS-specific), uploading SARIF to the same GitHub code scanning UI. Added per maintainer direction to reduce reliance on GitHub's proprietary, soon-to-be-paid "Code Quality" product in favor of open-source tooling. +- **The `p/...` rulesets are pulled from the public Semgrep Registry at run time**, not vendored, so a registry outage fails the `SAST (Semgrep)` job. Accepted as a normal SAST-gate network dependency; a spurious red on that job is worth checking against Semgrep Registry status before assuming a real finding. --- From fae1a224c0cf7b4062a3c67483a1cd02d78047d7 Mon Sep 17 00:00:00 2001 From: ritiksah141 Date: Fri, 17 Jul 2026 23:03:55 +0100 Subject: [PATCH 4/4] Remove issue reference from Dockerfile comment --- Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 991ca28..9dceebf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,6 @@ RUN pip install --no-cache-dir --upgrade \ COPY . . -# Container hardening, addresses the non-root gap flagged in #185 RUN groupadd --system openshield && \ useradd --system --gid openshield --no-create-home openshield && \ chown -R openshield:openshield /app