forgeseal
June 23, 2026 ยท View on GitHub
Supply chain security for JavaScript, TypeScript, Python, Go, Rust, and Java/Gradle projects. Generates CycloneDX SBOMs from lockfiles, signs them with Sigstore (keyless), produces SLSA provenance attestations, and manages VEX vulnerability documents.
Built for EU Cyber Resilience Act (CRA) compliance. forgeseal's own releases are attested by forgeseal.
Features
| Capability | Description |
|---|---|
| SBOM Generation | CycloneDX v1.4/v1.5/v1.6 from any JS/TS, Python, Go, Rust, or Java/Gradle lockfile (JSON and XML output) |
| Sigstore Signing | Keyless signing via Fulcio + Rekor transparency log (OIDC identity) |
| SLSA Provenance | In-toto attestations with SLSA v1 provenance predicate |
| VEX Management | OpenVEX v0.2 document CRUD, automated triage via OSV.dev, CVSS severity classification |
| Verification | Validate signatures, bundles, and attestation integrity |
| Pipeline | Single command: SBOM, sign, attest, triage |
Supported Lockfiles
| Package Manager | File | Parser |
|---|---|---|
| npm | package-lock.json | v2 and v3 schemas |
| Yarn Classic | yarn.lock | v1 text format (state machine) |
| Yarn Berry | yarn.lock | v2/v3/v4 YAML (auto-detected via __metadata: header) |
| pnpm | pnpm-lock.yaml | v6 and v9 schemas |
| Bun | bun.lock | JSONC text format (Bun v1.2+) |
| Bun (binary) | bun.lockb | Shells out to bun CLI; prefers bun.lock when both exist |
| pip | requirements.txt | Pinned (==) dependencies with optional hashes |
| Poetry | poetry.lock | TOML v2 format with full dependency graph |
| PDM | pdm.lock | TOML format with groups based dev detection |
| uv | uv.lock | TOML format (Astral uv v0.1+) |
| Go modules | go.mod + go.sum | Parses require/replace directives and pairs with h1: hashes |
| Cargo | Cargo.lock | TOML v3 format with SHA-256 checksums; skips workspace roots |
| Gradle | gradle.lockfile | Line-oriented group:artifact:version=configurations with test-only dev detection |
Detection Priority
When a project contains lockfiles for multiple ecosystems, forgeseal picks the first match from the ordered list below. Ecosystem buckets are ordered JS/TS > Python > Go > Rust > Java, and within each ecosystem the most precise lockfile wins.
| Rank | File | Ecosystem |
|---|---|---|
| 1 | bun.lockb | JS/TS (Bun binary) |
| 2 | bun.lock | JS/TS (Bun text) |
| 3 | pnpm-lock.yaml | JS/TS (pnpm) |
| 4 | yarn.lock | JS/TS (Yarn Classic or Berry, decided by content) |
| 5 | package-lock.json | JS/TS (npm) |
| 6 | uv.lock | Python (uv) |
| 7 | poetry.lock | Python (Poetry) |
| 8 | pdm.lock | Python (PDM) |
| 9 | requirements.txt | Python (pip) |
| 10 | go.mod | Go |
| 11 | Cargo.lock | Rust |
| 12 | gradle.lockfile | Java / Gradle |
Notes:
- If both
bun.lockbandbun.lockexist, forgeseal prefersbun.lockso nobunCLI is required. yarn.lockis probed for a__metadata:header to decide Yarn v1 (classic) vs Yarn v2+ (Berry).- In practice a project targets a single ecosystem, so this priority list only comes into play for mixed repos.
Note on Yarn Berry hashes: Yarn Berry uses a proprietary checksum format that is not compatible with standard SRI hashes or CycloneDX hash algorithms. Components from Yarn Berry lockfiles will be included in the SBOM without integrity hashes. This is a format limitation, not a forgeseal bug.
Python PURL normalization: PyPI package names are normalized per PEP 503 (lowercase, underscores and dots replaced with hyphens). This ensures correct lookups against OSV.dev for vulnerability triage.
Installation
# Homebrew (macOS / Linux)
brew install sns45/tap/forgeseal
# Quick install (latest release)
curl -sSL https://raw.githubusercontent.com/sns45/forgeseal/main/scripts/install.sh | sh
# Go
go install github.com/sns45/forgeseal/cmd/forgeseal@latest
# Docker
docker run --rm -v $(pwd):/src ghcr.io/sns45/forgeseal pipeline --dir /src
# From source
git clone https://github.com/sns45/forgeseal.git && cd forgeseal && make build
Requires Go 1.23+ for building from source.
Quick Start
# Generate an SBOM from a JS/TS project
forgeseal sbom --dir .
# Generate an SBOM from a Python project (uv, poetry, pdm, or pip)
forgeseal sbom --dir ./my-python-app
# Generate an SBOM from a Go project (go.mod + go.sum)
forgeseal sbom --dir ./my-go-service
# Generate an SBOM from a Rust project (Cargo.lock)
forgeseal sbom --dir ./my-rust-crate
# Generate an SBOM from a Gradle project (gradle.lockfile)
forgeseal sbom --dir ./my-gradle-app
# Full pipeline: SBOM + sign + attest + VEX triage
forgeseal pipeline --dir . --output-dir ./forgeseal-output --vex-triage
# Sign an existing artifact
forgeseal sign --artifact sbom.cdx.json
# Verify a signed artifact
forgeseal verify --artifact sbom.cdx.json --bundle sbom.cdx.json.sigstore.json
Commands
forgeseal sbom
Generate a CycloneDX SBOM from a lockfile.
forgeseal sbom --dir ./my-project
forgeseal sbom --lockfile ./package-lock.json --output-format xml
forgeseal sbom --include-dev --spec-version 1.6 -o sbom.json
| Flag | Default | Description |
|---|---|---|
--dir | . | Project directory to scan |
--lockfile | (auto-detect) | Explicit lockfile path |
--spec-version | 1.5 | CycloneDX spec version: 1.4, 1.5, 1.6 |
--output-format | json | Output format: json or xml |
--include-dev | false | Include devDependencies |
--no-deps | false | Only direct dependencies |
Output includes: metadata (timestamp, tool info, root component), components with PURLs and integrity hashes, and a dependency graph.
forgeseal sign
Sign an artifact with Sigstore keyless signing.
forgeseal sign --artifact sbom.cdx.json
forgeseal sign --artifact sbom.cdx.json --identity-token $OIDC_TOKEN
| Flag | Default | Description |
|---|---|---|
--artifact | (required) | Path to the artifact to sign |
--identity-token | (auto) | Explicit OIDC token; auto-detected in GitHub Actions |
--fulcio-url | https://fulcio.sigstore.dev | Fulcio CA instance |
--rekor-url | https://rekor.sigstore.dev | Rekor transparency log |
--bundle | <artifact>.sigstore.json | Output path for the Sigstore bundle |
The signing flow: obtain OIDC token, generate ephemeral ECDSA P-256 keypair, sign the artifact, and produce a .sigstore.json bundle. In GitHub Actions, the OIDC token is obtained automatically when the workflow has permissions: id-token: write.
forgeseal attest
Generate a SLSA v1 provenance attestation as an in-toto statement.
forgeseal attest --subject sbom.cdx.json
forgeseal attest --subject sbom.cdx.json --sign --repo https://github.com/org/repo --commit abc123
| Flag | Default | Description |
|---|---|---|
--subject | Path to the artifact being attested | |
--subject-digest | Explicit sha256:... digest | |
--repo | (auto in CI) | Source repository URI |
--commit | (auto in CI) | Source commit SHA |
--sign | true | Sign the attestation with Sigstore DSSE |
CI environment variables (GITHUB_REPOSITORY, GITHUB_SHA, etc.) are automatically detected.
forgeseal vex
Manage Vulnerability Exploitability eXchange (VEX) documents.
# Create an empty VEX document
forgeseal vex create -o vex.json
# Add a statement
forgeseal vex add --vex vex.json --cve CVE-2024-1234 \
--product "pkg:npm/my-app@1.0.0" \
--status not_affected \
--justification vulnerable_code_not_present
# List statements
forgeseal vex list --vex vex.json
# Automated triage: scan SBOM against OSV.dev
forgeseal vex triage --sbom sbom.cdx.json -o vex.json
forgeseal vex triage --sbom sbom.cdx.json --format cyclonedx -o sbom-with-vex.json
VEX statuses: not_affected, affected, fixed, under_investigation
Justifications (for not_affected): component_not_present, vulnerable_code_not_present, vulnerable_code_cannot_be_controlled_by_adversary, vulnerable_code_not_in_execute_path, inline_mitigations_already_exist
The triage subcommand queries the OSV.dev batch API, batching PURLs in groups of 1000, classifies each vulnerability by CVSS severity (critical, high, medium, low), and generates under_investigation stubs for each discovered vulnerability. Use --fail-on <severity> to fail the command when vulnerabilities at or above the threshold are found.
forgeseal verify
Verify Sigstore bundles and attestations.
forgeseal verify --artifact sbom.cdx.json --bundle sbom.cdx.json.sigstore.json
forgeseal verify --attestation sbom.cdx.json.intoto.jsonl
| Flag | Default | Description |
|---|---|---|
--artifact | Path to the artifact | |
--bundle | Path to .sigstore.json bundle | |
--attestation | Path to SLSA attestation | |
--expected-issuer | Expected OIDC issuer for identity verification | |
--expected-identity | Expected signer identity (regex) |
forgeseal pipeline
Run the full security pipeline in one command.
forgeseal pipeline --dir . --output-dir ./forgeseal-output
forgeseal pipeline --dir . --vex-triage --identity-token $TOKEN
| Flag | Default | Description |
|---|---|---|
--dir | . | Project directory |
--output-dir | ./forgeseal-output | Directory for all generated artifacts |
--lockfile | (auto-detect) | Explicit lockfile path |
--sign | true | Sign artifacts with Sigstore |
--attest | true | Generate SLSA provenance |
--vex-triage | false | Run VEX triage against OSV.dev |
--fail-on | (none) | Fail if vulnerabilities at or above severity: critical, high, medium, low |
--include-dev | false | Include devDependencies |
--identity-token | (auto) | Explicit OIDC token |
Setting --fail-on automatically enables VEX triage.
Pipeline steps:
- Parse lockfile, generate CycloneDX SBOM (
sbom.cdx.json) - Sign SBOM (
sbom.cdx.json.sigstore.json) - Generate SLSA provenance attestation (
sbom.cdx.json.intoto.jsonl), optionally sign it - Query OSV.dev and generate VEX document (
vex.json)
Vulnerability summary: When VEX triage runs, forgeseal prints a severity breakdown to stderr:
๐ด CRITICAL 2 CRITICAL ๐ก HIGH 5 HIGH ๐ MEDIUM 12 MEDIUM โช LOW 3 LOW
CRITICAL:
lodash@4.17.20 Prototype Pollution CVE-2021-23337
express@4.17.1 Path Traversal CVE-2024-29041
Scanned 156 components, found 42 vulnerabilities
CI gating: Use --fail-on to block builds when vulnerabilities exceed a threshold:
# Fail if any critical or high vulnerabilities found
forgeseal pipeline --dir . --fail-on high
# Fail only on critical
forgeseal pipeline --dir . --fail-on critical
GitHub Action
Available on the GitHub Marketplace.
name: Supply Chain Security
on: [push]
permissions:
id-token: write # Required for Sigstore keyless signing
contents: read
jobs:
forgeseal:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: sns45/forgeseal@v1
id: forgeseal
with:
dir: '.'
fail-on: high # Fail CI if high or critical vulnerabilities found
- uses: actions/upload-artifact@v4
with:
name: supply-chain-artifacts
path: ./forgeseal-output/
Action Inputs
| Input | Default | Description |
|---|---|---|
command | pipeline | Command to run: pipeline, sbom, sign, attest, vex |
dir | . | Project directory |
output-dir | ./forgeseal-output | Output directory for artifacts |
sign | true | Sign artifacts with Sigstore keyless signing |
attest | true | Generate SLSA v1 provenance attestation |
vex-triage | true | Run VEX vulnerability triage against OSV.dev |
fail-on | (none) | Fail if vulnerabilities at or above severity: critical, high, medium, low |
include-dev | false | Include devDependencies in SBOM |
version | latest | forgeseal version to install |
Action Outputs
| Output | Description |
|---|---|
sbom-path | Path to the generated CycloneDX SBOM |
bundle-path | Path to the Sigstore signature bundle |
attestation-path | Path to the SLSA provenance attestation |
vex-path | Path to the VEX document |
vuln-count | Number of vulnerabilities found by VEX triage |
The action downloads a pre-built binary from GitHub Releases (no Docker build overhead). OIDC tokens are obtained automatically when the workflow has permissions: id-token: write.
Configuration
forgeseal reads .forgeseal.yaml from the project directory or home directory. All settings can be overridden via flags or environment variables (prefixed FORGESEAL_).
sbom:
spec_version: "1.5"
format: json
include_dev: false
sign:
fulcio_url: https://fulcio.sigstore.dev
rekor_url: https://rekor.sigstore.dev
attest:
auto_sign: true
vex:
format: openvex
osv_ecosystem: npm
Environment variable mapping: FORGESEAL_SBOM_SPEC_VERSION, FORGESEAL_SIGN_FULCIO_URL, etc.
Architecture
cmd/forgeseal/ Entrypoint
internal/
cli/ Cobra commands (sbom, sign, attest, vex, verify, pipeline)
config/ Viper configuration loading
lockfile/ 10 parsers (6 JS/TS + 4 Python) + auto-detection + domain types
sbom/ CycloneDX BOM generation, ecosystem aware PURL construction (npm + PyPI), dependency mapping
signing/ Sigstore signer interface, bundle serialization
provenance/ SLSA v1 attestation builder, CI environment detection
vex/ OpenVEX CRUD, OSV.dev client, CycloneDX VEX embedding
verify/ Bundle and attestation verification
Key Design Decisions
Parser interface. Each lockfile format implements Parser with Parse(ctx, io.Reader), Type(), and Filenames(). Binary formats (bun.lockb) additionally implement FileParser with ParseFile(ctx, path). Detection iterates a priority ordered registry and uses content inspection for yarn v1 vs Berry disambiguation.
PURL construction. Package URLs follow the PURL spec. npm scoped packages like @babel/core@7.24.0 become pkg:npm/babel/core@7.24.0 (the @ prefix is stripped from the namespace per spec). PyPI packages use pkg:pypi/ with PEP 503 normalized names (e.g., Flask becomes pkg:pypi/flask@3.0.2). The ecosystem is determined automatically from the lockfile type.
Signing abstraction. The Signer interface provides SignBlob (raw content) and SignDSSE (in-toto envelope). The current implementation generates ephemeral ECDSA P-256 signatures. Full Sigstore integration (Fulcio certificate issuance + Rekor log recording) can be added by depending on sigstore-go without changing the interface.
VEX triage. PURLs extracted from the SBOM are batched in groups of 1000 and sent to the OSV.dev /v1/querybatch endpoint. Both npm and PyPI PURLs are supported; the correct ecosystem is encoded in the PURL type prefix. Each vulnerability is classified by CVSS v3 severity (critical 9.0+, high 7.0+, medium 4.0+, low 0.1+). Results are mapped to VEX stubs with severity metadata. The --fail-on flag enables CI gating by exiting non-zero when vulnerabilities meet or exceed the specified threshold.
Output Artifacts
| File | Content |
|---|---|
sbom.cdx.json | CycloneDX SBOM (JSON) |
sbom.cdx.json.sigstore.json | Sigstore bundle for the SBOM |
sbom.cdx.json.intoto.jsonl | SLSA v1 provenance attestation |
sbom.cdx.json.intoto.jsonl.sigstore.json | Sigstore bundle for the attestation |
vex.json | OpenVEX document with vulnerability triage results |
Development
make build # Build to ./bin/forgeseal
make test # Run tests with race detection
make lint # Run golangci-lint
make install # Install to $GOPATH/bin
make clean # Remove build artifacts
Cross-platform releases (Linux, macOS, Windows; amd64 + arm64) are handled by GoReleaser.
License
Apache 2.0