Christopher Fahlin 2eafa2a769
CI / mix precommit (push) Skipped
CI / All checks passed (push) Skipped
CI / Build and push to Harbor (push) Skipped
fix(ci): harden Harbor publication failure handling
Sign the digest returned by the push step, allow a bounded Cosign registry timeout, and only generate failure SARIF when an image build succeeded.
2026-07-27 19:11:43 -07:00

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, and security:triage scopes.
  • 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, default 90.
  • ENABLE_ADVISORY_SYNC — enables scheduled OSV egress only when set to true or 1.
  • 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.

S
Description
A unified security orchestration platform built with Elixir and Phoenix. Bulwark ingests and normalizes disparate CI/CD artifacts-including Trivy, TruffleHog, and SBOMs-into a human-centric dashboard for real-time vulnerability management.
Readme
586 KiB
Languages
Elixir 97.6%
CSS 1%
JavaScript 0.8%
Dockerfile 0.5%
HTML 0.1%