Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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 }}
Expand All @@ -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"]),
Expand Down Expand Up @@ -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' ||
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
23 changes: 21 additions & 2 deletions docs/ci-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand All @@ -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:
Expand Down Expand Up @@ -96,6 +97,21 @@ bandit -r api/ scanner/ ai/ -ll

`-ll` reports medium severity and above. Confirmed false positives are annotated inline with `# nosec <ID>` 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
Expand Down Expand Up @@ -144,8 +160,10 @@ 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.
- **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.

---

Expand Down Expand Up @@ -180,6 +198,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 <ID>` with a one-line reason |
| `semgrep` finding | Fix the flagged pattern, or if a confirmed false positive add a `# nosemgrep: <rule-id>` (Python) / `// nosemgrep: <rule-id>` (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 |
Expand Down
Loading