- Elixir 72%
- HTML 15.9%
- CSS 4.3%
- JavaScript 3.2%
- Shell 3%
- Other 1.6%
deploy.rego required the secret check to run but never inspected its findings: only sast_passed/sca_passed were enforced, and the generic critical_findings rule never fired because the CI gitleaks step tags every finding as severity "high" (gitleaks has no native severity). This demo currently has no hardcoded secret so the gap was latent, but it matched a real, live bug found in the sibling Rust-DevSecOps-Demo. Add a dedicated secret_findings rule with zero tolerance regardless of severity, matching the fix applied upstream in devsecops-attestation. Update the pinned --policy-hash to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|---|---|---|
| .github | ||
| assets | ||
| config | ||
| lib | ||
| priv | ||
| results | ||
| scripts | ||
| test | ||
| .gitignore | ||
| Dockerfile | ||
| mix.exs | ||
| mix.lock | ||
| README.md | ||
Phoenix DevSecOps Demo
A demo Phoenix application wired into the devsecops-attestation cryptographic pipeline.
Every push runs four security checks, signs each result into an Ed25519-linked attestation chain, then evaluates a deploy gate via OPA/Rego policy. Deployment only proceeds if every signature verifies against the authorized per-check-type key and the policy allows it.
Pipeline overview
flowchart TD
A([Push / PR]) --> B["Build & Test\nmix deps.get · compile --warnings-as-errors · mix test"]
B --> C["SAST\nSobelow"]
B --> D["SCA\nmix_audit"]
B --> E["Config Scan\nCheckov"]
B --> F["Secret Scan\nGitleaks"]
C --> G[results/sast.json]
D --> H[results/sca.json]
E --> I[results/config.json]
F --> J[results/secret.json]
G & H & I & J --> K[Deploy Gate]
K --> L["Sign SAST · Sign SCA · Sign Config · Sign Secret\nchain.json · Ed25519 per-check-type keys"]
L --> M["gate evaluate\nchain.json + .github/policies/deploy.rego"]
M --> N{Decision}
N -->|allow| O[Deploy]
N -->|block| P[Pipeline Fails]
Attestation chain
flowchart LR
S1["Attestation 1\ntype: sast\nsigned: Ed25519 (sast key)\nprev: null"] -->|SHA-256 digest| S2
S2["Attestation 2\ntype: sca\nsigned: Ed25519 (sca key)\nprev: hash(S1)"] -->|SHA-256 digest| S3
S3["Attestation 3\ntype: config\nsigned: Ed25519 (config key)\nprev: hash(S2)"] -->|SHA-256 digest| S4
S4["Attestation 4\ntype: secret\nsigned: Ed25519 (secret key)\nprev: hash(S3)"]
Each check type uses a dedicated key pair. A compromised SAST key cannot forge
SCA, config, or secret attestations. Any insertion, deletion, or reordering of
attestations breaks the SHA-256 chain linkage and causes gate evaluate to
reject the deployment.
Security tools
| Stage | Tool | What it checks |
|---|---|---|
| SAST | Sobelow | Phoenix-specific vulnerabilities (XSS, SQLi, CSRF...) |
| SCA | mix_audit | Known CVEs in Hex dependencies |
| Config | Checkov | Misconfigurations in Dockerfile and IaC files |
| Secret | Gitleaks | Hardcoded credentials and secrets in source code |
Project structure
Phoenix-DevSecOps-Demo/
├── .github/
│ ├── policies/
│ │ └── deploy.rego # OPA/Rego deployment policy
│ └── workflows/
│ └── devsecops-pipeline.yml # GitHub Actions CI/CD workflow
├── assets/ # JS / CSS (esbuild + Tailwind)
├── config/ # Phoenix config (dev, test, runtime)
├── lib/
│ ├── demo/ # Application, Repo, Mailer
│ └── demo_web/ # Router, Endpoint, Controllers, Components
├── priv/repo/migrations/
├── results/
│ └── .gitkeep # CI writes scan JSONs here
├── scripts/
│ └── act-debug.sh # Local pipeline runner (act)
├── test/
├── Dockerfile
└── mix.exs
Setup
1. Prerequisites
- Elixir >= 1.15 / Erlang OTP >= 26
- PostgreSQL 14+ running locally (user
postgres, passwordpostgres)# Arch / CachyOS sudo systemctl start postgresql # Docker alternative docker run -d --name demo-postgres -p 5432:5432 \ -e POSTGRES_PASSWORD=postgres postgres:16
2. Install and set up the app
mix setup # deps.get + ecto.create + ecto.migrate + assets
3. Start the server
mix phx.server
# or inside IEx:
iex -S mix phx.server
Visit http://localhost:4000.
CI/CD setup (GitHub Actions)
The pipeline uses per-check-type key pairs. Each check type has its own dedicated signing key so a compromise is contained to a single check.
1. Generate four key pairs
git clone https://github.com/MemerGamer/devsecops-attestation
cd devsecops-attestation
for check in sast sca config secret; do
go run ./cmd/keygen --out "keys/$check"
done
# Each directory contains private.hex (keep secret) and public.hex
2. Add all eight secrets to this repository
Settings -> Secrets and variables -> Actions -> New repository secret
| Secret | Value |
|---|---|
SAST_SIGNING_KEY |
Contents of keys/sast/private.hex |
SCA_SIGNING_KEY |
Contents of keys/sca/private.hex |
CONFIG_SIGNING_KEY |
Contents of keys/config/private.hex |
SECRET_SCANNING_SIGNING_KEY |
Contents of keys/secret/private.hex |
SAST_PUBLIC_KEY |
Contents of keys/sast/public.hex |
SCA_PUBLIC_KEY |
Contents of keys/sca/public.hex |
CONFIG_PUBLIC_KEY |
Contents of keys/config/public.hex |
SECRET_SCANNING_PUBLIC_KEY |
Contents of keys/secret/public.hex |
Never commit any
private.hexfile. Thekeys/directory is gitignored in the attestation repo.
3. Policy hash (keep in sync)
The gate step pins the SHA-256 of deploy.rego via --policy-hash. If you update
the policy, recompute the hash and update the workflow:
sha256sum .github/policies/deploy.rego
Then update --policy-hash in .github/workflows/devsecops-pipeline.yml.
4. (Optional) Require manual approval before deploy
The deploy-gate job targets the production environment:
Settings -> Environments -> production -> Required reviewers
Local development
# Install deps and set up DB
mix setup
# Run tests
mix test
# Run SAST locally
mix sobelow
# Run SCA locally
mix deps.audit
# Full pre-commit check (compile + format + test)
mix precommit
Local pipeline simulation (act)
# Runs the full pipeline locally via act (generates test keys automatically)
bash scripts/act-debug.sh
# Run a specific job
bash scripts/act-debug.sh deploy-gate
License
MIT