SreeragPramod/securepipe
SecurePipe is a small demo GitHub repo showing a GitHub Actions CI/CD pipeline for a Python project that runs Flake8, Black, Bandit SAST scanning, and pytest with an 80% coverage gate before code can be merged. Its commit history documents a real vulnerability lifecycle: a hardcoded database password (Bandit rule B105) and a shell-injectable subprocess call (Bandit rule B602) were intentionally introduced, automatically blocked by the Bandit gate, then fixed by moving secrets to environment variables and replacing shell=True with an explicit argument list and host allowlist.
What it is
SecurePipe is a demo repository for a GitHub Actions pipeline on a small Python project (a calculator and a data-processing module). The pipeline is defined in .github/workflows/security-pipeline.yml, and a dependabot.yml file handles dependency monitoring. It is a teaching example, not a reusable template or action.
How it works
On each push to main, an Ubuntu runner sets up Python 3.13, installs dependencies, and runs the gates in a fixed order. A failure at any step stops everything after it.
flowchart TD
A[Push to main] --> B[Flake8]
B -->|pass| C[Black check]
C -->|pass| D[Bandit scan]
D -->|pass| E[Pytest, coverage 80%+]
E -->|pass| F[Ready for deployment]
B -->|fail| X[Blocked]
C -->|fail| X
D -->|fail| X
E -->|fail| XBandit runs before pytest, so a Bandit failure skips the test suite entirely. Bandit and coverage reports are uploaded as artifacts. Coverage is enforced with --cov-fail-under=80.
What to do
The README gives the local equivalents of each gate:
bandit -r src/ -f txt
flake8 src/ tests/ --max-line-length=88 --extend-ignore=E203
black --check src/ tests/
python -m pytest tests/ -v --cov=src --cov-report=term-missingThe fixes the author applied to data_processor.py are a reusable pattern:
- Read secrets from environment variables inside functions, not at import time. Module-level reads made the code untestable.
- Replace
shell=Truewithshell=False, an explicit argument list and a host allowlist. - Mock
subprocess.runin tests and useassert_called_once_with. This avoids real network calls in CI, and the test fails ifshell=Trueis reintroduced. - Add a coverage threshold. The author reports it exposed that
data_processor.pyhad zero test coverage, which they had not noticed.
Caveats
The README lists several things as not yet done: branch protection rules, so the gate is advisory rather than mandatory; CodeQL; GitHub secret scanning; DAST with OWASP ZAP; Docker; and pre-commit hooks. The pipeline stops at "ready for deployment" and has no deploy stage. Bandit is a pattern-matching scanner, and the README itself names CodeQL as the route to deeper detection. The vulnerabilities were planted by the author, so the repo shows the gate catching two known Bandit rules (B105, B602), not general detection performance.
Organisations named here
- B105
- B602
Technologies named here
- Python
- GitHub Actions
- Bandit
- Flake8
- Black
- Pytest
- pytest-cov
- Dependabot
- OWASP Zap
- CodeQL
- GitHub Advanced Security secret scanning
Techniques named here
- Shift-Left security
- Hardcoded credential
- Shell injection via subprocess
- Pipeline-as-Code
- Mocking subprocess calls in tests
Related in DevSecOps
JFrog: The Governance Gap Between Your Policy and Your Pipeline

Dev Machine Guard Now Supports Windows - StepSecurity

Socket: TanStack npm Packages Compromised
