Sign the digest returned by the push step, allow a bounded Cosign registry timeout, and only generate failure SARIF when an image build succeeded.
Bulwark
Security dashboard for ingesting, viewing, and triaging vulnerability findings and SBOM inventory from CI/CD artifacts.
Built with Phoenix 1.8, LiveView 1.1, PostgreSQL, and Oban.
Features
- Multi-format ingestion — Upload Trivy JSON, SARIF, CycloneDX, Grype, and Snyk reports. Auto-detects format and tool.
- Async processing — Oban workers parse artifacts in the background with retry support.
- Real-time updates — PubSub broadcasts push scan status and finding changes to all connected clients.
- Triage workflow — Acknowledge, resolve, or mark findings as false positives inline.
- Workspace RBAC — Shared projects with SuperAdmin, Admin, Auditor, and ReadOnly permissions enforced in LiveView, controllers, and domain contexts.
- Workspace lifecycle — SuperAdmins can create and reset workspaces; members switch accessible workspaces from the top-right header menu; Admins provision projects and lower-privilege members.
- API principals — Hashed, expiring, revocable Bearer tokens with explicit
sbom:write,scan:write,security:read, andsecurity:triagescopes. - CI/CD API — First-class SARIF plus idempotent scanner/SBOM upload, status polling, inventory discovery, bounded export, and triage endpoints with a deployed OpenAPI contract.
- Push-based SBOM inventory — Multipart LiveView and token API intake for immutable CycloneDX, SPDX, and native Syft project versions.
- Offline vulnerability matching — PostgreSQL-backed OSV mirror with opt-in scheduled egress and air-gapped NDJSON import.
- Exposure lifecycle and SLA — Stable project/component/advisory records, idempotent occurrences, triage history, risk expiry, and snapshot-based due dates.
- SBOM diff and license policy — Deterministic component/edge/exposure comparison and structural SPDX expression evaluation.
- Flop-powered tables — Sortable, filterable, paginated tables across all list views with cmd+K filter bars and quick-filter pills.
- Accessible operations UI — Responsive SOC workspace with keyboard navigation, clear focus states, reduced-motion support, and locally hosted assets designed for WCAG 2.2 AA / Section 508 use.
Getting Started
Prerequisites
- Elixir 1.20.2 and Erlang/OTP 29.0.3 (pinned in
.tool-versions) - PostgreSQL 14+ (or use the included Docker Compose)
Setup
# Clone the repository
git clone <repo-url> && cd bulwark
# Start PostgreSQL
docker compose up -d
# Install dependencies, create database, and run migrations
mix setup
# Start the development server
mix phx.server
Visit localhost:4000.
External integrations should start with docs/api.md. A running
deployment serves the machine-readable contract at /openapi.yaml.
The OTP ownership model and context dependency boundaries are documented in
docs/architecture.md.
Running Tests
mix test # All tests
mix test test/path_test.exs # Single file
mix test test/path_test.exs:42 # Single test by line
Pre-commit Check
mix precommit
Runs compile --warnings-as-errors, deps.unlock --check-unused, format --check-formatted, and test.
Architecture
lib/
├── bulwark/ # Domain layer
│ ├── security.ex # Security context — CRUD, queries, triage
│ ├── security/ # Schemas: Scan, Asset, Vulnerability, Finding
│ ├── access_control.ex # Workspaces, membership policy, API principals
│ ├── access_control/ # Workspace, Membership, ApiToken, Policy
│ ├── inventory.ex # Projects, immutable SBOM snapshots, PURLs, graph
│ ├── inventory/ # Inventory schemas and format normalizers
│ ├── exposure_management.ex # Stable exposures, triage, history, SLA policy
│ ├── artifact_store.ex # Shared durable evidence-store port
│ ├── sbom_intake.ex # Durable staging, idempotency, retention
│ ├── sbom_intake/ # Artifact adapters and Oban processing jobs
│ ├── advisory_mirror.ex # Local OSV facts and offline matcher
│ ├── advisory_mirror/ # Feed client, sync job, schemas, match evidence
│ ├── ingestion/ # Pipeline: Detector, ParseJob (Oban), Parsers
│ └── repo.ex
└── bulwark_web/ # Web layer
├── components/ # CoreComponents, Layouts (sidebar shell)
├── live/ # LiveViews for each route
├── router.ex
└── endpoint.ex
Bounded Contexts
| Context | Purpose |
|---|---|
Bulwark.AccessControl |
Workspace membership, permission policy, and API tokens |
Bulwark.Inventory |
Workspace projects, immutable SBOM versions, components, PURLs, licenses, and dependency graphs |
Bulwark.ExposureManagement |
Stable exposures, occurrences, triage history, risk expiry, and SLA snapshots |
Bulwark.ArtifactStore |
Neutral durable content-addressed storage port for all evidence intake |
Bulwark.SbomIntake |
Durable artifact staging, request validation, idempotency, jobs, and retention |
Bulwark.AdvisoryMirror |
Local OSV feed facts, checkpoints, air-gapped import, and offline exposure matching |
Bulwark.Security |
Workspace-scoped CRUD, Flop queries, upserts, triage with PubSub |
Bulwark.Ingestion |
Format detection, parsing, Oban job orchestration |
Ingestion Pipeline
Upload → durable content-addressed staging → Scan record (pending) → Oban
ParseJob → Detector identifies tool/format → Parser normalizes findings →
Assets/Vulnerabilities upserted → Findings persisted → PubSub broadcast →
LiveView updates.
SBOM upload → content-addressed durable staging → immutable version registration → Oban normalization → components/licenses/dependency graph → local advisory matching.
Supported upload formats
| Intake | Formats | Common extensions |
|---|---|---|
SBOM inventory (/sboms, POST /api/v1/sbom) |
CycloneDX JSON/XML, SPDX JSON/tag-value, native Syft JSON | .json, .xml, .spdx, .tag, .txt |
Vulnerability scans (/scans, POST /api/v1/scans, POST /api/v1/sarif) |
Trivy JSON, Grype JSON, Snyk JSON, CycloneDX vulnerability JSON, SPDX JSON, SARIF | .json, .sarif |
Scanner CSV and human-readable table output are intentionally rejected because they discard identifiers and relationship data needed for reliable matching and triage.
Production SBOM and advisory configuration
SBOM_ARTIFACT_ROOT— required durable shared mount visible to all web and worker nodes.SBOM_RETENTION_DAYS— artifact and version retention, default90.ENABLE_ADVISORY_SYNC— enables scheduled OSV egress only when set totrueor1.OSV_ECOSYSTEMS— comma-separated OSV ecosystem directories to mirror.mix bulwark.advisories.import PATH— import an offline NDJSON OSV bundle.ENABLE_AUDIT_LOG— persist append-only actor audit events.ENABLE_THREAT_STREAM— start the optional threat-event PubSub adapter.
Routes
| Path | View | Purpose |
|---|---|---|
/ |
DashboardLive |
KPI overview |
/scans |
ScanLive.Index |
Scan list + upload |
/scans/:id |
ScanLive.Show |
Scan detail + findings |
/sboms |
SbomLive.Index |
Streamed SBOM upload and immutable version inventory |
/sboms/analysis |
SbomLive.Analysis |
Deterministic diff and SPDX license policy |
/exposures |
ExposureLive.Index |
Stable exposure lifecycle and triage |
/vulnerabilities |
VulnerabilityLive.Index |
Vulnerability list |
/vulnerabilities/:id |
VulnerabilityLive.Show |
Vuln detail + references |
/findings |
FindingLive.Index |
Finding list + triage |
/assets |
AssetLive.Index |
Asset inventory |
/assets/:id |
AssetLive.Show |
Asset detail + findings |
/settings |
SettingsLive |
Accent theming preferences |
/admin/workspaces |
AdminLive.Workspaces |
Workspace creation/reset and project provisioning |
/admin/users |
AdminLive.Users |
Admin/SuperAdmin workspace membership management |
/admin/tokens |
AdminLive.Tokens |
Admin/SuperAdmin scoped API-token management |
POST /workspaces/switch |
WorkspaceController |
Switch the active verified workspace |
POST /api/v1/sbom |
SbomController |
Token-authenticated multipart SBOM intake (202 Accepted) |
POST /api/v1/scans |
ScanController |
Idempotent multipart scanner-report intake (202 Accepted) |
POST /api/v1/sarif |
ScanController |
First-class SARIF 2.1 CI intake (202 Accepted) |
GET /api/v1/projects |
ProjectController |
Discover CI target project IDs |
GET /api/v1/scans/:id |
ScanController |
Poll scan processing state |
GET /api/v1/sboms/:id |
SbomController |
Poll SBOM processing state |
GET /api/v1/{findings,assets,vulnerabilities,exposures} |
API controllers | Bounded workspace security export |
PATCH /api/v1/findings/:id |
FindingController |
Finding status automation |
PATCH /api/v1/exposures/:id |
ExposureController |
Exposure triage automation |
License
See LICENSE for details.