Zion Boggan
repos/secure-cicd-pipeline
zionboggan.com ↗

secure-cicd-pipeline

A GitHub Actions pipeline that gates every push and pull request on four security checks before code is allowed to merge, then reports the run back to a SOC for visibility. The sample app is a small Flask task API; the point of the repo is the pipeline around it.

7 commits First commit Apr 15, 2026 Last commit Apr 22, 2026 (2 months ago)
Markdown 32.8%Python 31.5%YAML 26.9%TOML 8.9%
Files 20 entries
README.md

Secure CI/CD Pipeline

A GitHub Actions pipeline that gates every push and pull request on four security checks before code is allowed to merge, then reports the run back to a SOC for visibility. The sample app is a small Flask task API; the point of the repo is the pipeline around it.

The checks run as separate jobs so a failure tells you exactly which gate tripped instead of one long log you have to scroll through.

flowchart TD
    push[push / pull_request] --> lint[ruff lint]
    lint --> sast[Semgrep SAST]
    lint --> secrets[gitleaks secret scan]
    lint --> deps[pip-audit dependencies]
    sast --> test[pytest + coverage]
    secrets --> test
    deps --> test
    test --> notify[notify SOC webhook]

The gates

Job Tool What it stops
lint ruff style + a security rule set (S) on top of the usual lint
sast Semgrep the OWASP/Flask rule packs plus four custom rules in .semgrep/rules.yml
secrets gitleaks committed credentials, full history scanned on PRs
dependencies pip-audit known-vulnerable pinned dependencies
test pytest regressions, with coverage reported
notify-soc scripts/notify_soc.py nothing - it posts the run outcome to the SOC

lint runs first as a cheap fail-fast. The three security scans fan out in parallel, test waits on all of them, and the SOC notification runs last with if: always() so the SOC hears about failures too, not just green runs.

Custom Semgrep rules

The packs catch the common cases; .semgrep/rules.yml adds the ones I kept seeing slip through:

  • Flask started with debug=True (Werkzeug debugger RCE)
  • subprocess with shell=True on a non-literal argument
  • jwt.decode with signature verification disabled
  • binding to 0.0.0.0 without an explicit reason

The four rules running against a deliberately vulnerable file - every finding is Blocking, so the SAST job fails the pipeline before the code can merge:

Semgrep custom rules

SARIF and the Security tab

Semgrep emits SARIF that gets uploaded with github/codeql-action/upload-sarif, so findings show up under the repo's Security tab and as inline PR annotations rather than only in the job log.

The dependency gate is pip-audit, which fails the build on a pinned package with a known advisory:

pip-audit dependency scan

Run it locally

make install
make all

make all runs the same gates the pipeline does. You need semgrep and gitleaks on your PATH for the sast and secrets targets; everything else is pip-installed by make install.

SOC integration

The last job posts the run outcome - repo, commit, actor, status, and a link back to the run - to a Shuffle webhook. In my lab that webhook feeds the SOC automation lab, so a failed security gate opens a case in TheHive the same way a Wazuh alert does. Set the SHUFFLE_WEBHOOK_URL repository secret to wire it up; without it the job no-ops instead of failing.

More detail in docs/pipeline.md and docs/soc-integration.md.