Skip to content

Repository files navigation

Wolfee SCA

Wolfee SCA Platform

Three-service Docker Compose stack with PostgreSQL persistence.

Service Port Description
postgres 5432 PostgreSQL 16 — persistent storage
backend 4000 Go SCA scanner + REST API + background worker
frontend 8085 React + nginx

Quick start

cp .env.example .env
# Edit .env — add GITHUB_TOKEN, NVD_API_KEY at minimum
docker compose up --build

Open http://localhost:8085

Build speed

The first docker compose build downloads the offline OSV, NVD, EPSS, KEV, PoC, malware and Grype corpus inside the backend image. That is the one long build — expect tens of minutes and several GB. Docker caches those layers, so later rebuilds only recompile the Go server and the React application:

docker compose build backend frontend
docker compose up -d

To refresh the vulnerability corpus itself, invalidate its layers by rebuilding without cache:

docker compose build --no-cache backend

Authentication

First boot creates a default admin / admin account (forced password change on first login). Set WOLFEE_ADMIN_PASSWORD to pick the initial password instead.

  • UI sessions — JWT via POST /api/v1/auth/login, stored in an HttpOnly cookie. Browser mutations also require the CSRF cookie/header pair. TTL is WOLFEE_JWT_TTL (default 8h). Bearer tokens remain accepted for API clients.
  • CI / automation — team-scoped API keys (wsca_...) created in Settings → API Keys, sent as X-Api-Key. A key inherits its team's permissions; projects it creates are auto-attached to that team.
  • Permissions (Dependency-Track style): VIEW_PORTFOLIO, PORTFOLIO_MANAGEMENT, BOM_UPLOAD, VIEW_VULNERABILITY, VULNERABILITY_ANALYSIS, POLICY_MANAGEMENT, ACCESS_MANAGEMENT, SYSTEM_CONFIGURATION — granted to users and teams; effective set is the union.
  • Portfolio ACL — teams see only projects granted to them (Settings → Teams → Project access). Principals with ACCESS_MANAGEMENT bypass the ACL.
  • LDAP — optional second identity provider (search+bind), configured via WOLFEE_LDAP_* env vars; users are auto-provisioned on first login with no permissions.
  • Deprecated: the global API_KEY env var still works as a superuser key so existing CI keeps running — migrate to team API keys and remove it.

API

Projects

PUT    /api/v1/project           create project
GET    /api/v1/project           list all projects
GET    /api/v1/project/{uuid}    get one project
DELETE /api/v1/project/{uuid}    delete project

GET  /api/v1/project/{uuid}/branches   list branches
POST /api/v1/project/{uuid}/branches   create branch: {"name":"feature/api"}

Every project gets a default main branch. Branch creation is API-only; the project page exposes a branch switcher but no create action.

BOM Upload (async)

POST /api/v1/bom   multipart: project=<uuid>, bom=<file.json>
→ 200 { token, branchUuid }

Processing happens in the background worker. Poll /api/v1/bom/token/{token} for status.

Pass branch=<name-or-uuid> to upload into a branch. upload=true is the CI-friendly alias for autoCreate=true: if a named project and/or branch does not exist, the upload request creates it before queueing the SBOM.

curl -X POST http://localhost:4000/api/v1/bom \
  -H "X-Api-Key: $WOLFEE_API_KEY" \
  -F project=my-service \
  -F branch=feature/payment-refactor \
  -F upload=true \
  -F bom=@bom.json

JSON clients can send the equivalent payload with a base64-encoded BOM:

{
  "projectName": "my-service",
  "branch": "feature/payment-refactor",
  "upload": true,
  "bom": "<base64 CycloneDX JSON>"
}

Branch-aware reads and rescans accept ?branch=<name-or-uuid>. Without it, the API uses the project's default branch, preserving existing integrations.

WORKER_CONCURRENCY controls how many BOM scans one backend processes in parallel. The default is 3; the backend enforces a hard maximum of 4.

Findings & Components (populated after BOM job completes)

GET /api/v1/finding/project/{uuid}?branch=<name-or-uuid>        findings for the branch's active BOM
GET /api/v1/vulnerability/project/{uuid}?branch=<name-or-uuid>  unique vulnerabilities for the branch
GET /api/v1/component/project/{uuid}?branch=<name-or-uuid>      components for the branch
GET /api/v1/metrics/project/{uuid}?branch=<name-or-uuid>        severity and audit counters for the branch
GET /api/v1/quality-gate/project/{uuid}?branch=<name-or-uuid>   pass/block result for the branch

The quality-gate response is evaluated from the active BOM of only the requested branch. It includes the blocking findings so CI can explain a failure without fetching the whole project:

{
  "projectUuid": "...",
  "branch": { "uuid": "...", "name": "feature/payment-refactor" },
  "bomUuid": "...",
  "gate": "block",
  "pipeline": "failed",
  "findingsTotal": 12,
  "blockingFindings": 2,
  "critical": 1,
  "high": 4,
  "findings": [
    {
      "branchUuid": "...", "branchName": "feature/payment-refactor",
      "vulnerabilityId": "CVE-...", "severity": "CRITICAL", "cvss": 9.8,
      "componentName": "...", "componentVersion": "...",
      "gate": "block", "policyAction": "block", "policyWhy": "critical threshold exceeded"
    }
  ]
}

findings contains every finding from the branch's active BOM, ordered by severity (CRITICAL to LOW) and then by descending CVSS score.

Example CI gate (requires jq):

gate_json=$(curl -fsS --get \
  -H "X-Api-Key: $WOLFEE_API_KEY" \
  --data-urlencode "branch=$CI_COMMIT_REF_NAME" \
  "http://localhost:4000/api/v1/quality-gate/project/$WOLFEE_PROJECT_UUID")
echo "$gate_json" | jq .
test "$(echo "$gate_json" | jq -r .gate)" = "pass"

Exports

GET /api/v1/finding/project/{uuid}/export?format=sarif          SARIF 2.1.0
GET /api/v1/finding/project/{uuid}/export?format=cyclonedx-vex  CycloneDX VEX

Rules (global, persisted in DB)

GET /api/rules    current rules config
PUT /api/rules    replace rules config

Legacy (frontend compatibility)

POST /api/scan         single-package scan with rules evaluation

Architecture

cmd/server/main.go          entry point, DI wiring
internal/
  config/       env-based config
  domain/       entities: Project, BOM, Component, Finding, Job, Rules
  repository/   interfaces + memory.go + postgres.go + migrate.go
  service/      use cases (business logic)
  worker/       background job processor
  http/         thin HTTP handlers
  modules/      vulnerability data sources (OSV, NVD, EPSS, KEV…)
  scanner/      scan pipeline
migrations/     SQL migration files (auto-applied at startup)

Storage modes

  • With DATABASE_URL: PostgreSQL — data persists across restarts
  • Without DATABASE_URL: in-memory — zero setup, data lost on restart

Environment variables

See .env.example for all options.

Known limitations

Things the platform deliberately does not do yet, so you can plan around them instead of discovering them mid-triage.

Operating-system packages are scanned through the image endpoint, not the SBOM one. pkg:deb, pkg:rpm, pkg:apk and pkg:alpm components uploaded to /api/v1/bom are recorded but not resolved — the dependency scanner has no distro-aware backport logic, and reporting a backported package as vulnerable is worse than not reporting it. Upload a container report instead; the scan summary tells you how many system packages it saw (systemComponents).

The CLI cannot push container reports. wolfee scan --image --server … uploads nothing; image reports reach the platform through the UI only. Language dependencies do upload, enriched.

Findings inside an uploaded document are trusted wholesale. If any component in the document carries vulnerabilities, the server takes the document's findings as authoritative and does not scan any component itself (scanMode: sbom in the summary). A partially enriched document therefore reports its unenriched components as clean. Documents without findings are scanned normally (scanMode: scanner).

Grype is disabled by default. WOLFEE_GRYPE_HELPER=false — the binary and its database are baked into the image, but running it on every scan costs time, so enabling it is an explicit choice.

Suppressions are project-wide by vulnerability ID. Triaging a CVE silences it for the whole project, not for one package.

The portfolio report covers default branches. /api/v1/quality-gate returns one row per project, evaluated on its default branch; other branches are available per project at /api/v1/quality-gate/project/<uuid>?branch=<name>.

Production deployment

See DEPLOY.md — TLS/ingress, secrets, rate limiting, BOM retention, backup/restore, upgrades, and a production checklist.

Build speed

The first docker compose build bakes the offline vulnerability corpus into the backend image and takes a long time. Later rebuilds reuse those cached layers and only recompile the Go server and the React application:

docker compose build backend frontend

Refresh the corpus with docker compose build --no-cache backend.

About

Self-hosted software composition analysis (SCA) platform — ingest SBOMs, triage CVEs enriched with exploitability signals, and block risky builds in CI

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages