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.
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]
| 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.
The packs catch the common cases; .semgrep/rules.yml adds the ones I kept seeing
slip through:
debug=True (Werkzeug debugger RCE)subprocess with shell=True on a non-literal argumentjwt.decode with signature verification disabled0.0.0.0 without an explicit reasonThe 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 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:

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.
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.