Documentation
¶
Overview ¶
Package bomhort is a Go client for the BOMHort REST API (https://docs.bomhort.dev/docs/api-reference/).
It is stdlib-only and covers the complete public API of the BOMHort api-gateway: SBOMs and their findings, dependency trees, licenses and VEX statements, projects, clusters, namespaces, fleet, statistics, search, the push-model upload endpoint and the source-attribution PATCH.
A Client is safe for concurrent use. Create one per BOMHort instance:
c := bomhort.New("https://bomhort.example.com",
bomhort.WithAPIKey(os.Getenv("BOMHORT_API_KEY")),
bomhort.WithRateLimit(90, 10*time.Second),
)
sboms, err := c.AllSBOMs(ctx, nil)
Non-2xx responses are returned as *APIError; use IsNotFound, IsUnauthorized and friends to branch on them. 429 responses are retried with Retry-After / exponential back-off (see WithMaxRetries).
The package bomhorttest provides an in-memory fake gateway for tests.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
bomhort "github.com/seebom-labs/bomhort-go"
"github.com/seebom-labs/bomhort-go/bomhorttest"
)
// fakeGateway starts a bomhorttest server with one SBOM so the examples
// run without a real BOMHort.
func fakeGateway() *bomhorttest.Server {
srv := bomhorttest.New("example-key")
srv.AddSBOM(
bomhort.SBOM{ID: bomhorttest.UUID(1), DocumentName: "payments-api", SourceFile: "payments-api.spdx.json", Project: "payments"},
[]bomhort.Vulnerability{
{VulnID: "GHSA-qppj-fm5r-hxr3", Severity: "HIGH", PURL: "pkg:golang/golang.org/x/net@v0.17.0", FixedVersion: "0.23.0"},
},
nil, nil,
)
return srv
}
func main() {
srv := fakeGateway()
defer srv.Close()
c := bomhort.New(srv.URL,
bomhort.WithAPIKey("example-key"),
bomhort.WithRateLimit(90, 10*time.Second),
)
ctx := context.Background()
for sbom, err := range c.SBOMs(ctx, nil) {
if err != nil {
log.Fatal(err)
}
vulns, err := c.Vulnerabilities(ctx, sbom.ID, nil)
if err != nil {
log.Fatal(err)
}
for _, v := range vulns {
fmt.Printf("%s: %s %s in %s\n", sbom.DocumentName, v.Severity, v.VulnID, v.PURL)
}
}
}
Output: payments-api: HIGH GHSA-qppj-fm5r-hxr3 in pkg:golang/golang.org/x/net@v0.17.0
Index ¶
- Constants
- func Collect[T any](seq Seq[T]) ([]T, error)
- func IsBadRequest(err error) bool
- func IsForbidden(err error) bool
- func IsNotFound(err error) bool
- func IsRateLimited(err error) bool
- func IsUnauthorized(err error) bool
- func IsUnavailable(err error) bool
- func StatusCode(err error) int
- func String(s string) *string
- func UploadedFilename(sourceFile string) string
- type APIError
- type AffectedProject
- type ArchivedPackage
- type BlanketLicenseException
- type Client
- func (c *Client) AffectedProjects(ctx context.Context, vulnID string) ([]AffectedProject, error)
- func (c *Client) AllSBOMs(ctx context.Context, opts *SBOMListOptions) ([]SBOM, error)
- func (c *Client) AllVEXStatements(ctx context.Context) ([]VEXStatement, error)
- func (c *Client) ArchivedPackages(ctx context.Context) ([]ArchivedPackage, error)
- func (c *Client) BaseURL() string
- func (c *Client) ClusterStats(ctx context.Context, name string) (ClusterStats, error)
- func (c *Client) Clusters(ctx context.Context) ([]Cluster, error)
- func (c *Client) DashboardStats(ctx context.Context) (DashboardStats, error)
- func (c *Client) Dependencies(ctx context.Context, sbomID string) ([]DependencyNode, error)
- func (c *Client) DependencyStats(ctx context.Context, limit int) (DependencyStats, error)
- func (c *Client) DownloadSBOM(ctx context.Context, sbomID string) ([]byte, error)
- func (c *Client) DownloadSBOMTo(ctx context.Context, sbomID string, w io.Writer) (int64, error)
- func (c *Client) FindSBOM(ctx context.Context, ref string) (SBOM, error)
- func (c *Client) Fleet(ctx context.Context) ([]FleetCluster, error)
- func (c *Client) Healthy(ctx context.Context) error
- func (c *Client) LicenseCompliance(ctx context.Context) ([]LicenseCompliance, error)
- func (c *Client) LicenseExceptions(ctx context.Context) (LicenseExceptions, error)
- func (c *Client) LicensePolicy(ctx context.Context) (LicensePolicy, error)
- func (c *Client) LicenseSources(ctx context.Context) ([]LicenseSource, error)
- func (c *Client) LicenseViolationSummaries(ctx context.Context) ([]LicenseViolationSummary, error)
- func (c *Client) LicenseViolations(ctx context.Context) ([]LicenseViolation, error)
- func (c *Client) ListClusterSBOMs(ctx context.Context, name string, opts *ListOptions) (Paginated[SBOM], error)
- func (c *Client) ListNamespaceSBOMs(ctx context.Context, name, cluster string, opts *ListOptions) (Paginated[SBOM], error)
- func (c *Client) ListProjectGroups(ctx context.Context, opts *ProjectListOptions) (Paginated[ProjectGroup], error)
- func (c *Client) ListProjectPackages(ctx context.Context, name string, opts *SearchListOptions) (Paginated[ProjectPackage], error)
- func (c *Client) ListProjectSBOMs(ctx context.Context, name string, opts *ListOptions) (Paginated[SBOM], error)
- func (c *Client) ListProjects(ctx context.Context, opts *ProjectListOptions) (Paginated[Project], error)
- func (c *Client) ListSBOMs(ctx context.Context, opts *SBOMListOptions) (Paginated[SBOM], error)
- func (c *Client) ListVEXStatements(ctx context.Context, opts *ListOptions) (Paginated[VEXStatement], error)
- func (c *Client) ListVulnerabilities(ctx context.Context, opts *VulnerabilityListOptions) (Paginated[Vulnerability], error)
- func (c *Client) Live(ctx context.Context) error
- func (c *Client) NamespaceStats(ctx context.Context, name, cluster string) (NamespaceStats, error)
- func (c *Client) Namespaces(ctx context.Context, cluster string) ([]Namespace, error)
- func (c *Client) PackageDetail(ctx context.Context, name string, opts *ListOptions) (PackageDetail, error)
- func (c *Client) PatchSBOMSource(ctx context.Context, sbomID string, patch SourcePatch) (SourceAttribution, error)
- func (c *Client) Project(ctx context.Context, name string) (ProjectDetail, error)
- func (c *Client) ProjectVulnerabilities(ctx context.Context, name string, opts *VulnerabilityOptions) ([]Vulnerability, error)
- func (c *Client) Projects(ctx context.Context, opts *ProjectListOptions) Seq[Project]
- func (c *Client) Ready(ctx context.Context) error
- func (c *Client) SBOMDetail(ctx context.Context, sbomID string) (SBOMDetail, error)
- func (c *Client) SBOMLicenses(ctx context.Context, sbomID string) ([]SBOMLicense, error)
- func (c *Client) SBOMVEXStatements(ctx context.Context, sbomID string) ([]VEXStatement, error)
- func (c *Client) SBOMs(ctx context.Context, opts *SBOMListOptions) Seq[SBOM]
- func (c *Client) Search(ctx context.Context, q string, limit int) (SearchResult, error)
- func (c *Client) SearchPackages(ctx context.Context, q string, opts *ListOptions) (PackageSearch, error)
- func (c *Client) Tags(ctx context.Context) ([]Tag, error)
- func (c *Client) Upload(ctx context.Context, filename string, doc []byte, opts *UploadOptions) (UploadResult, error)
- func (c *Client) UploadSBOM(ctx context.Context, filename string, doc []byte, opts *UploadOptions) (UploadResult, error)
- func (c *Client) UploadVEX(ctx context.Context, filename string, doc []byte, sbomID string) (UploadResult, error)
- func (c *Client) VEXStatements(ctx context.Context, opts *ListOptions) Seq[VEXStatement]
- func (c *Client) VersionSkew(ctx context.Context, opts *SearchListOptions) (VersionSkew, error)
- func (c *Client) Vulnerabilities(ctx context.Context, sbomID string, opts *VulnerabilityOptions) ([]Vulnerability, error)
- func (c *Client) VulnerabilityReportCSV(ctx context.Context, opts *VulnerabilityOptions, w io.Writer) (int64, error)
- type Cluster
- type ClusterStats
- type DashboardStats
- type DependencyNode
- type DependencyStat
- type DependencyStats
- type FleetCluster
- type FleetNamespace
- type FleetProject
- type Health
- type LicenseCompliance
- type LicenseException
- type LicenseExceptions
- type LicensePolicy
- type LicenseSource
- type LicenseViolation
- type LicenseViolationSummary
- type ListOptions
- type Namespace
- type NamespaceStats
- type Option
- func WithAPIKey(key string) Option
- func WithHTTPClient(h *http.Client) Option
- func WithMaxRetries(n int) Option
- func WithRateLimit(limit int, window time.Duration) Option
- func WithRateLimiter(l *RateLimiter) Option
- func WithServiceToken(token string) Option
- func WithStrictDecoding() Option
- func WithUserAgent(ua string) Option
- type PackageDetail
- type PackageProject
- type PackageSearch
- type PackageSearchResult
- type Paginated
- type Project
- type ProjectDetail
- type ProjectGroup
- type ProjectListOptions
- type ProjectPackage
- type RateLimiter
- type SBOM
- type SBOMDetail
- type SBOMLicense
- type SBOMLink
- type SBOMListOptions
- type SearchLicense
- type SearchListOptions
- type SearchPackage
- type SearchProject
- type SearchResult
- type SearchVulnerability
- type Seq
- type SourceAttribution
- type SourcePatch
- type Tag
- type UploadOptions
- type UploadResult
- type VEXStatement
- type VersionSkew
- type VersionSkewDetail
- type VersionSkewItem
- type Vulnerability
- type VulnerabilityListOptions
- type VulnerabilityOptions
Examples ¶
Constants ¶
const ( ScopeRoot = "root" ScopeDirect = "direct" ScopeTransitive = "transitive" ScopeUnknown = "unknown" )
Dependency scopes as sent in Vulnerability.DependencyScope and accepted by VulnerabilityOptions.Scope (#444, >= 0.8.0).
const DefaultUserAgent = "bomhort-go/" + Version
DefaultUserAgent is sent unless WithUserAgent overrides it.
const DefaultWalkPageSize = 100
DefaultWalkPageSize is the page size used by the iterators and All* helpers when none is given. 100 keeps a full walk well below BOMHort's rate limit for typical instances while avoiding huge responses.
const Version = "0.8.0"
Version is the client library version, sent in the default User-Agent.
Variables ¶
This section is empty.
Functions ¶
func IsBadRequest ¶
IsBadRequest reports whether err is a 400 (validation failure).
func IsForbidden ¶
IsForbidden reports whether err is a 403. BOMHort returns it for the write endpoints (upload, PATCH) when AUTH_ENABLED=false.
func IsNotFound ¶
IsNotFound reports whether err is a 404 from BOMHort. Note that BOMHort also answers 404 for routes an older gateway does not know.
Example ¶
package main
import (
"context"
"fmt"
bomhort "github.com/seebom-labs/bomhort-go"
"github.com/seebom-labs/bomhort-go/bomhorttest"
)
// fakeGateway starts a bomhorttest server with one SBOM so the examples
// run without a real BOMHort.
func fakeGateway() *bomhorttest.Server {
srv := bomhorttest.New("example-key")
srv.AddSBOM(
bomhort.SBOM{ID: bomhorttest.UUID(1), DocumentName: "payments-api", SourceFile: "payments-api.spdx.json", Project: "payments"},
[]bomhort.Vulnerability{
{VulnID: "GHSA-qppj-fm5r-hxr3", Severity: "HIGH", PURL: "pkg:golang/golang.org/x/net@v0.17.0", FixedVersion: "0.23.0"},
},
nil, nil,
)
return srv
}
func main() {
srv := fakeGateway()
defer srv.Close()
c := bomhort.New(srv.URL, bomhort.WithAPIKey("example-key"))
_, err := c.SBOMDetail(context.Background(), bomhorttest.UUID(404))
fmt.Println(bomhort.IsNotFound(err))
}
Output: true
func IsRateLimited ¶
IsRateLimited reports whether err is a 429 that survived all retries.
func IsUnauthorized ¶
IsUnauthorized reports whether err is a 401 (missing/invalid credentials).
func IsUnavailable ¶
IsUnavailable reports whether err is a 503 (e.g. /readyz without ClickHouse, or upload without storage).
func StatusCode ¶
StatusCode returns the HTTP status of err if it is (or wraps) an *APIError, otherwise 0.
func UploadedFilename ¶
UploadedFilename returns the filename a document was uploaded with when sourceFile has the "pushed/<uuid>-<filename>" form BOMHort stores uploads under, and "" otherwise.
Types ¶
type APIError ¶
type APIError struct {
// StatusCode is the HTTP status code.
StatusCode int
// Message is BOMHort's {"error": "..."} text, or the (truncated) body.
Message string
// Method and Path identify the failed call (path without query).
Method string
Path string
}
APIError is returned for every non-2xx response from BOMHort.
type AffectedProject ¶
type AffectedProject struct {
SBOMID string `json:"sbom_id"`
SourceFile string `json:"source_file"`
DocumentName string `json:"document_name"`
PURL string `json:"purl"`
PackageName string `json:"package_name"`
Version string `json:"version"`
Severity string `json:"severity"`
VEXStatus string `json:"vex_status,omitempty"`
// IsDirect is true exactly when DependencyScope is "direct"; new
// clients should read DependencyScope (#444, >= 0.8.0).
IsDirect bool `json:"is_direct"`
DependencyScope string `json:"dependency_scope,omitempty"`
DependencyDepth *uint32 `json:"dependency_depth,omitempty"`
}
AffectedProject is one entry of GET /api/v1/vulnerabilities/{id}/affected-projects.
type ArchivedPackage ¶
type ArchivedPackage struct {
SBOMID string `json:"sbom_id"`
SourceFile string `json:"source_file"`
ProjectName string `json:"project_name"`
ProjectVersion string `json:"project_version"`
PackageName string `json:"package_name"`
PackagePURL string `json:"package_purl"`
Repo string `json:"repo"`
LastPushed time.Time `json:"last_pushed"`
Stars uint32 `json:"stars"`
}
ArchivedPackage is one entry of GET /api/v1/packages/archived: a dependency whose upstream GitHub repository is archived. Server type: clickhouse.ArchivedPackageInfo.
type BlanketLicenseException ¶
type BlanketLicenseException struct {
ID string `json:"id"`
License string `json:"license"`
Status string `json:"status"`
ApprovedDate string `json:"approvedDate"`
Scope string `json:"scope,omitempty"`
Results string `json:"results,omitempty"`
IssueURL string `json:"issueUrl,omitempty"`
PackageURL string `json:"packageUrl,omitempty"`
Comment string `json:"comment,omitempty"`
}
BlanketLicenseException exempts an entire license.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to one BOMHort API gateway.
func New ¶
New creates a client for baseURL, e.g. "http://localhost:8080". A trailing slash and a trailing "/api/v1" are tolerated.
func (*Client) AffectedProjects ¶
AffectedProjects returns the SBOMs affected by a vulnerability id (CVE-…, GHSA-…, GO-…), including transitive dependencies.
func (*Client) AllVEXStatements ¶
func (c *Client) AllVEXStatements(ctx context.Context) ([]VEXStatement, error)
AllVEXStatements collects every ingested VEX statement.
func (*Client) ArchivedPackages ¶
func (c *Client) ArchivedPackages(ctx context.Context) ([]ArchivedPackage, error)
ArchivedPackages lists dependencies whose upstream GitHub repository is archived.
func (*Client) ClusterStats ¶
ClusterStats returns the statistics of one cluster (404 if unknown).
func (*Client) DashboardStats ¶
func (c *Client) DashboardStats(ctx context.Context) (DashboardStats, error)
DashboardStats returns the instance-wide KPIs.
func (*Client) Dependencies ¶
Dependencies returns the flat dependency node list of an SBOM (404 for an unknown id since BOMHort 0.8.0, 200 [] before).
func (*Client) DependencyStats ¶
DependencyStats returns the most used dependencies across all projects. limit <= 0 uses the server default (50, max 500).
func (*Client) DownloadSBOM ¶
DownloadSBOM returns the original SBOM document bytes.
func (*Client) DownloadSBOMTo ¶
DownloadSBOMTo streams the original SBOM document into w and returns the number of bytes written. Unlike DownloadSBOM it does not buffer the document and does not retry on 429.
func (*Client) FindSBOM ¶
FindSBOM returns the first SBOM whose ID, document name or source file equals ref. A source file also matches by its base name, and uploaded documents (stored as "pushed/<uuid>-<filename>") by the filename they were uploaded with. A non-UUID ref narrows the listing with ?search= (BOMHort matches it as a substring of document_name and source_file, so every candidate is in that result); a UUID ref scans the full listing.
func (*Client) Fleet ¶
func (c *Client) Fleet(ctx context.Context) ([]FleetCluster, error)
Fleet returns the whole cluster → namespace → project ownership tree.
func (*Client) LicenseCompliance ¶
func (c *Client) LicenseCompliance(ctx context.Context) ([]LicenseCompliance, error)
LicenseCompliance returns the license compliance overview.
func (*Client) LicenseExceptions ¶
func (c *Client) LicenseExceptions(ctx context.Context) (LicenseExceptions, error)
LicenseExceptions returns the operator's license exceptions file.
func (*Client) LicensePolicy ¶
func (c *Client) LicensePolicy(ctx context.Context) (LicensePolicy, error)
LicensePolicy returns the permissive/copyleft classification in effect.
func (*Client) LicenseSources ¶
func (c *Client) LicenseSources(ctx context.Context) ([]LicenseSource, error)
LicenseSources returns where package licenses came from, or why they are missing (GET /api/v1/licenses/sources, >= 0.8.0).
func (*Client) LicenseViolationSummaries ¶
func (c *Client) LicenseViolationSummaries(ctx context.Context) ([]LicenseViolationSummary, error)
LicenseViolationSummaries is LicenseViolations with counts instead of package lists (GET /api/v1/projects/license-compliance?summary=true, >= 0.8.0; older gateways ignore the parameter and the full rows decode into mostly-empty summaries).
func (*Client) LicenseViolations ¶
func (c *Client) LicenseViolations(ctx context.Context) ([]LicenseViolation, error)
LicenseViolations returns the SBOMs with non-compliant licenses after applying the configured exceptions (GET /api/v1/projects/license-compliance). The response lists every non-compliant package name and grows with the instance; see LicenseViolationSummaries for the counts only.
func (*Client) ListClusterSBOMs ¶
func (c *Client) ListClusterSBOMs(ctx context.Context, name string, opts *ListOptions) (Paginated[SBOM], error)
ListClusterSBOMs returns one page of the SBOMs of a cluster.
func (*Client) ListNamespaceSBOMs ¶
func (c *Client) ListNamespaceSBOMs(ctx context.Context, name, cluster string, opts *ListOptions) (Paginated[SBOM], error)
ListNamespaceSBOMs returns one page of the SBOMs of a namespace, optionally within one cluster.
func (*Client) ListProjectGroups ¶
func (c *Client) ListProjectGroups(ctx context.Context, opts *ProjectListOptions) (Paginated[ProjectGroup], error)
ListProjectGroups returns one page of parents with their member projects (GET /api/v1/projects?group_by=parent, >= 0.8.0; older gateways ignore the parameter and return plain projects).
func (*Client) ListProjectPackages ¶
func (c *Client) ListProjectPackages(ctx context.Context, name string, opts *SearchListOptions) (Paginated[ProjectPackage], error)
ListProjectPackages returns one page of the distinct components across a project's SBOMs (GET /api/v1/projects/{name}/packages, #398, >= 0.8.0). opts may be nil.
func (*Client) ListProjectSBOMs ¶
func (c *Client) ListProjectSBOMs(ctx context.Context, name string, opts *ListOptions) (Paginated[SBOM], error)
ListProjectSBOMs returns one page of a project's SBOMs (its versions) (GET /api/v1/projects/{name}/sboms, #398, >= 0.8.0).
func (*Client) ListProjects ¶
func (c *Client) ListProjects(ctx context.Context, opts *ProjectListOptions) (Paginated[Project], error)
ListProjects returns one page of projects (SBOMs grouped by project).
func (*Client) ListVEXStatements ¶
func (c *Client) ListVEXStatements(ctx context.Context, opts *ListOptions) (Paginated[VEXStatement], error)
ListVEXStatements returns one page of all ingested VEX statements.
func (*Client) ListVulnerabilities ¶
func (c *Client) ListVulnerabilities(ctx context.Context, opts *VulnerabilityListOptions) (Paginated[Vulnerability], error)
ListVulnerabilities returns one page of all findings across the instance, each with its VEX status attached. opts may be nil.
func (*Client) NamespaceStats ¶
NamespaceStats returns the statistics of a namespace, optionally within one cluster (404 if unknown).
func (*Client) Namespaces ¶
Namespaces lists namespaces with summary statistics. cluster "" lists them fleet-wide (a namespace name is only unique within a cluster).
func (*Client) PackageDetail ¶
func (c *Client) PackageDetail(ctx context.Context, name string, opts *ListOptions) (PackageDetail, error)
PackageDetail lists the projects using a package (exact name).
func (*Client) PatchSBOMSource ¶
func (c *Client) PatchSBOMSource(ctx context.Context, sbomID string, patch SourcePatch) (SourceAttribution, error)
PatchSBOMSource sets or clears source_repo/source_ref of an existing SBOM (BOMHort #332, >= 0.7.0). Requires AUTH_ENABLED=true on the gateway (403 otherwise) and credentials. The returned values are normalised by BOMHort.
func (*Client) Project ¶
Project returns one project aggregated over all of its SBOMs (GET /api/v1/projects/{name}, #398, >= 0.8.0). Names containing "/" are escaped correctly. A project literally named "license-compliance" is not reachable through this route (BOMHort limitation).
func (*Client) ProjectVulnerabilities ¶
func (c *Client) ProjectVulnerabilities(ctx context.Context, name string, opts *VulnerabilityOptions) ([]Vulnerability, error)
ProjectVulnerabilities returns the distinct findings across a project's SBOMs; Vulnerability.AffectedSBOMs says in how many SBOMs each occurs (GET /api/v1/projects/{name}/vulnerabilities, #398, >= 0.8.0). opts may be nil.
func (*Client) Ready ¶
Ready calls GET /readyz: the gateway can reach ClickHouse. A not-ready gateway yields an *APIError with status 503 (see IsUnavailable).
func (*Client) SBOMDetail ¶
SBOMDetail returns GET /api/v1/sboms/{id}/detail (metadata plus severity breakdown). Unknown ids yield a 404 *APIError.
func (*Client) SBOMLicenses ¶
SBOMLicenses returns the per-license breakdown of an SBOM.
func (*Client) SBOMVEXStatements ¶
SBOMVEXStatements returns the VEX statements scoped to an SBOM, newest first (GET /api/v1/sboms/{id}/vex).
func (*Client) SBOMs ¶
SBOMs iterates over all SBOMs matching opts, fetching pages lazily. opts.Page is ignored; opts.PageSize defaults to 100.
func (*Client) Search ¶
Search runs the faceted global search. q must be at least two characters after trimming; limit <= 0 uses the server default (5 per facet, max 50).
func (*Client) SearchPackages ¶
func (c *Client) SearchPackages(ctx context.Context, q string, opts *ListOptions) (PackageSearch, error)
SearchPackages finds packages by name across all SBOMs.
func (*Client) Upload ¶
func (c *Client) Upload(ctx context.Context, filename string, doc []byte, opts *UploadOptions) (UploadResult, error)
Upload pushes a document through POST /api/v1/sboms/upload. filename is sent as X-Filename and decides how BOMHort classifies the content: it must end in .spdx.json, .cdx.json, .openvex.json, .vex.json or .json.
BOMHort only accepts uploads when AUTH_ENABLED=true (403 otherwise) and needs writable storage (503 otherwise). Ingestion is asynchronous: a "pending" result carries the job id; "duplicate" means identical content was ingested before.
func (*Client) UploadSBOM ¶
func (c *Client) UploadSBOM(ctx context.Context, filename string, doc []byte, opts *UploadOptions) (UploadResult, error)
UploadSBOM uploads an SPDX or CycloneDX JSON document. It is Upload with a check that filename is not a VEX name.
Example ¶
package main
import (
"context"
"fmt"
"log"
bomhort "github.com/seebom-labs/bomhort-go"
"github.com/seebom-labs/bomhort-go/bomhorttest"
)
// fakeGateway starts a bomhorttest server with one SBOM so the examples
// run without a real BOMHort.
func fakeGateway() *bomhorttest.Server {
srv := bomhorttest.New("example-key")
srv.AddSBOM(
bomhort.SBOM{ID: bomhorttest.UUID(1), DocumentName: "payments-api", SourceFile: "payments-api.spdx.json", Project: "payments"},
[]bomhort.Vulnerability{
{VulnID: "GHSA-qppj-fm5r-hxr3", Severity: "HIGH", PURL: "pkg:golang/golang.org/x/net@v0.17.0", FixedVersion: "0.23.0"},
},
nil, nil,
)
return srv
}
func main() {
srv := fakeGateway()
defer srv.Close()
c := bomhort.New(srv.URL, bomhort.WithAPIKey("example-key"))
doc := []byte(`{"spdxVersion":"SPDX-2.3","name":"checkout","packages":[]}`)
res, err := c.UploadSBOM(context.Background(), "checkout.spdx.json", doc, &bomhort.UploadOptions{
Project: "checkout",
Tags: []string{"payments"},
SourceRepo: "https://github.com/example/checkout",
SourceRef: "v1.4.0",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Status, res.JobType, res.Project)
}
Output: pending sbom checkout
func (*Client) UploadVEX ¶
func (c *Client) UploadVEX(ctx context.Context, filename string, doc []byte, sbomID string) (UploadResult, error)
UploadVEX uploads an OpenVEX document. filename must end in .openvex.json or .vex.json so BOMHort classifies the job as VEX. A non-empty sbomID scopes every statement to that SBOM (?sbom_id=, #350); with "" BOMHort resolves each product @id against the SBOMs' id, source_repo and document name, and statements whose product resolves to nothing suppress nothing.
Within its SBOM a statement applies to a finding when vulnerability.name == vuln_id and the subcomponent purl == purl (plain string equality); a product without subcomponents covers every finding with that vuln_id. Copy both values verbatim from Vulnerability. See docs/VEX.md.
Example ¶
package main
import (
"context"
"fmt"
"log"
bomhort "github.com/seebom-labs/bomhort-go"
"github.com/seebom-labs/bomhort-go/bomhorttest"
)
// fakeGateway starts a bomhorttest server with one SBOM so the examples
// run without a real BOMHort.
func fakeGateway() *bomhorttest.Server {
srv := bomhorttest.New("example-key")
srv.AddSBOM(
bomhort.SBOM{ID: bomhorttest.UUID(1), DocumentName: "payments-api", SourceFile: "payments-api.spdx.json", Project: "payments"},
[]bomhort.Vulnerability{
{VulnID: "GHSA-qppj-fm5r-hxr3", Severity: "HIGH", PURL: "pkg:golang/golang.org/x/net@v0.17.0", FixedVersion: "0.23.0"},
},
nil, nil,
)
return srv
}
func main() {
srv := fakeGateway()
defer srv.Close()
c := bomhort.New(srv.URL, bomhort.WithAPIKey("example-key"))
ctx := context.Background()
sbom, err := c.FindSBOM(ctx, "payments-api")
if err != nil {
log.Fatal(err)
}
// The product is the SBOM (scoped explicitly via sbom_id); the
// subcomponent is the vulnerable package. vulnerability.name and the
// subcomponent purl must be copied verbatim from the finding: BOMHort
// matches them by exact string equality.
doc := []byte(`{
"@context": "https://openvex.dev/ns/v0.2.0",
"@id": "https://example.com/vex/payments-api-1",
"author": "security@example.com",
"timestamp": "2026-01-01T00:00:00Z",
"version": 1,
"statements": [{
"vulnerability": {"name": "GHSA-qppj-fm5r-hxr3"},
"products": [{
"@id": "payments-api",
"subcomponents": [{"@id": "pkg:golang/golang.org/x/net@v0.17.0"}]
}],
"status": "not_affected",
"justification": "vulnerable_code_not_in_execute_path"
}]
}`)
res, err := c.UploadVEX(ctx, "payments-api.openvex.json", doc, sbom.ID)
if err != nil {
log.Fatal(err)
}
fmt.Println("upload:", res.Status, res.JobType)
vulns, _ := c.Vulnerabilities(ctx, sbom.ID, nil)
fmt.Println("vex_status:", vulns[0].VEXStatus, "scope:", vulns[0].VEXScope)
}
Output: upload: pending vex vex_status: not_affected scope: sbom
func (*Client) VEXStatements ¶
func (c *Client) VEXStatements(ctx context.Context, opts *ListOptions) Seq[VEXStatement]
VEXStatements iterates over all ingested VEX statements (opts.PageSize defaults to 100, opts.Page is ignored).
func (*Client) VersionSkew ¶
func (c *Client) VersionSkew(ctx context.Context, opts *SearchListOptions) (VersionSkew, error)
VersionSkew returns packages used in inconsistent versions across projects. opts may be nil.
func (*Client) Vulnerabilities ¶
func (c *Client) Vulnerabilities(ctx context.Context, sbomID string, opts *VulnerabilityOptions) ([]Vulnerability, error)
Vulnerabilities returns all findings of an SBOM (not paginated) with their VEX status, one row per (vuln_id, purl) on BOMHort >= 0.7.0. opts may be nil.
func (*Client) VulnerabilityReportCSV ¶
func (c *Client) VulnerabilityReportCSV(ctx context.Context, opts *VulnerabilityOptions, w io.Writer) (int64, error)
VulnerabilityReportCSV streams GET /api/v1/reports/vulnerabilities ?format=csv (#266, >= 0.8.0) into w and returns the bytes written: the rows of ListVulnerabilities (same scope filter) as CSV with a header line (cve_id, vuln_id, aliases, …, vex_status). Not retried on 429.
type Cluster ¶
type Cluster struct {
Name string `json:"name"`
SBOMCount uint64 `json:"sbom_count"`
PackageCount uint64 `json:"package_count"`
VulnCount uint64 `json:"vuln_count"`
LastIngested string `json:"last_ingested,omitempty"`
}
Cluster is one entry of GET /api/v1/clusters. DTO: ClusterListItem.
type ClusterStats ¶
type ClusterStats struct {
Cluster string `json:"cluster"`
TotalSBOMs uint64 `json:"total_sboms"`
TotalPackages uint64 `json:"total_packages"`
TotalVulnerabilities uint64 `json:"total_vulnerabilities"`
CriticalVulns uint64 `json:"critical_vulns"`
HighVulns uint64 `json:"high_vulns"`
MediumVulns uint64 `json:"medium_vulns"`
LowVulns uint64 `json:"low_vulns"`
LicenseBreakdown map[string]uint64 `json:"license_breakdown"`
LastIngested string `json:"last_ingested,omitempty"`
}
ClusterStats is GET /api/v1/clusters/{name}/stats.
type DashboardStats ¶
type DashboardStats struct {
TotalSBOMs uint64 `json:"total_sboms"`
TotalPackages uint64 `json:"total_packages"`
TotalVulnerabilities uint64 `json:"total_vulnerabilities"`
EffectiveVulnerabilities uint64 `json:"effective_vulnerabilities"`
SuppressedByVEX uint64 `json:"suppressed_by_vex"`
CriticalVulns uint64 `json:"critical_vulns"`
HighVulns uint64 `json:"high_vulns"`
MediumVulns uint64 `json:"medium_vulns"`
LowVulns uint64 `json:"low_vulns"`
LicenseBreakdown map[string]uint64 `json:"license_breakdown"`
ExemptedPackages uint64 `json:"exempted_packages"`
TotalVEXStatements uint64 `json:"total_vex_statements"`
LastCVERefresh string `json:"last_cve_refresh,omitempty"`
NewVulnsSinceRefresh uint64 `json:"new_vulns_since_refresh"`
ArchivedReposCount uint64 `json:"archived_repos_count"`
}
DashboardStats is GET /api/v1/stats/dashboard.
type DependencyNode ¶
type DependencyNode struct {
Index uint32 `json:"index"`
SPDXID string `json:"spdx_id"`
Name string `json:"name"`
Version string `json:"version"`
PURL string `json:"purl"`
License string `json:"license"`
// LicenseSource says where License came from or why there is none
// (>= 0.8.0; empty for SBOMs ingested before it was recorded).
LicenseSource string `json:"license_source,omitempty"`
Children []uint32 `json:"children"`
// Stored dependency scope of the node (#444, >= 0.8.0); see Vulnerability.
DependencyScope string `json:"dependency_scope,omitempty"`
DependencyDepth *uint32 `json:"dependency_depth,omitempty"`
}
DependencyNode is one node of GET /api/v1/sboms/{id}/dependencies. The tree is a flat list; Children holds the Index values of child nodes.
type DependencyStat ¶
type DependencyStat struct {
PackageName string `json:"package_name"`
PURL string `json:"purl"`
ProjectCount uint64 `json:"project_count"`
Versions []string `json:"versions"`
VulnCount uint64 `json:"vuln_count"`
}
DependencyStat is one cross-project dependency usage entry. DTO: DependencyStatsItem.
type DependencyStats ¶
type DependencyStats struct {
TotalUniqueDeps uint64 `json:"total_unique_deps"`
TopDependencies []DependencyStat `json:"top_dependencies"`
}
DependencyStats is GET /api/v1/stats/dependencies. DTO: DependencyStatsResponse.
type FleetCluster ¶
type FleetCluster struct {
Name string `json:"name"`
SBOMCount uint64 `json:"sbom_count"`
VulnCount uint64 `json:"vuln_count"`
Namespaces []FleetNamespace `json:"namespaces"`
}
FleetCluster is the root of the ownership tree of GET /api/v1/fleet. Unassigned dimensions appear with an empty name.
type FleetNamespace ¶
type FleetNamespace struct {
Name string `json:"name"`
SBOMCount uint64 `json:"sbom_count"`
VulnCount uint64 `json:"vuln_count"`
Projects []FleetProject `json:"projects"`
}
FleetNamespace groups projects inside one cluster.
type FleetProject ¶
type FleetProject struct {
Name string `json:"name"`
SBOMCount uint64 `json:"sbom_count"`
VulnCount uint64 `json:"vuln_count"`
LastIngested string `json:"last_ingested,omitempty"`
}
FleetProject is a leaf of the ownership tree.
type LicenseCompliance ¶
type LicenseCompliance struct {
LicenseID string `json:"license_id"`
Category string `json:"category"`
PackageCount uint64 `json:"package_count"`
SBOMCount int `json:"sbom_count"`
NonCompliantPackages []string `json:"non_compliant_packages,omitempty"`
ExemptedPackages []string `json:"exempted_packages,omitempty"`
ExemptionReason string `json:"exemption_reason,omitempty"`
AffectedSBOMs []SBOMLink `json:"affected_sboms,omitempty"`
}
LicenseCompliance is one entry of GET /api/v1/licenses/compliance. DTO: LicenseComplianceItem.
type LicenseException ¶
type LicenseException struct {
ID string `json:"id"`
Package string `json:"package"`
License string `json:"license"`
Project string `json:"project,omitempty"`
Status string `json:"status"`
ApprovedDate string `json:"approvedDate"`
Scope string `json:"scope,omitempty"`
Results string `json:"results,omitempty"`
IssueURL string `json:"issueUrl,omitempty"`
PackageURL string `json:"packageUrl,omitempty"`
Comment string `json:"comment,omitempty"`
}
LicenseException exempts one package+license combination.
type LicenseExceptions ¶
type LicenseExceptions struct {
Version string `json:"version"`
LastUpdated string `json:"lastUpdated"`
Description string `json:"description,omitempty"`
BlanketExceptions []BlanketLicenseException `json:"blanketExceptions"`
Exceptions []LicenseException `json:"exceptions"`
}
LicenseExceptions is GET /api/v1/license-exceptions (the operator's license-exceptions.json). Server type: license.ExceptionsFile.
type LicensePolicy ¶
type LicensePolicy struct {
Description string `json:"description,omitempty"`
Permissive []string `json:"permissive"`
Copyleft []string `json:"copyleft"`
ExpressionMode string `json:"expressionMode,omitempty"`
}
LicensePolicy is GET /api/v1/license-policy (permissive/copyleft classification). Server type: license.PolicyFile.
type LicenseSource ¶
type LicenseSource struct {
// Source is the full value ("depsdev+latest", "not-published";
// "unrecorded" for packages ingested before sources were recorded),
// Origin the part before "+" and Modifiers the "+"-joined suffixes.
Source string `json:"source"`
Origin string `json:"origin"`
Modifiers []string `json:"modifiers"`
// Resolved is false when Source is a reason for a missing license.
Resolved bool `json:"resolved"`
PackageCount uint64 `json:"package_count"`
SBOMCount uint64 `json:"sbom_count"`
Examples []string `json:"examples"`
}
LicenseSource is one entry of GET /api/v1/licenses/sources (>= 0.8.0): where package licenses came from, or why they are missing. DTO: LicenseSourceItem.
type LicenseViolation ¶
type LicenseViolation struct {
SBOMID string `json:"sbom_id"`
SourceFile string `json:"source_file"`
DocumentName string `json:"document_name"`
CopyleftCount uint64 `json:"copyleft_count"`
UnapprovedCount uint64 `json:"unapproved_count"` // >= 0.8.0
UnknownCount uint64 `json:"unknown_count"`
ViolatingLicenses []string `json:"violating_licenses"`
NonCompliantPackages []string `json:"non_compliant_packages"`
// PackageScopes: see SBOMLicense (#444, >= 0.8.0).
PackageScopes map[string]string `json:"package_scopes,omitempty"`
}
LicenseViolation is one entry of GET /api/v1/projects/license-compliance. DTO: ProjectLicenseViolation.
type LicenseViolationSummary ¶
type LicenseViolationSummary struct {
SBOMID string `json:"sbom_id"`
SourceFile string `json:"source_file"`
DocumentName string `json:"document_name"`
CopyleftCount uint64 `json:"copyleft_count"`
UnapprovedCount uint64 `json:"unapproved_count"`
UnknownCount uint64 `json:"unknown_count"`
ViolatingLicenses []string `json:"violating_licenses"`
NonCompliantPackageCount uint64 `json:"non_compliant_package_count"`
DirectCount uint64 `json:"direct_count"`
TransitiveCount uint64 `json:"transitive_count"`
}
LicenseViolationSummary is one entry of GET /api/v1/projects/license-compliance?summary=true (>= 0.8.0): the LicenseViolation counts without the package lists. DTO: ProjectLicenseViolationSummary.
type ListOptions ¶
ListOptions selects one page of a paginated endpoint. Zero values use the server defaults (page 1, page_size 50; BOMHort caps page_size at 500).
type Namespace ¶
type Namespace struct {
Name string `json:"name"`
Cluster string `json:"cluster,omitempty"`
ClusterCount uint64 `json:"cluster_count"`
SBOMCount uint64 `json:"sbom_count"`
PackageCount uint64 `json:"package_count"`
VulnCount uint64 `json:"vuln_count"`
LastIngested string `json:"last_ingested,omitempty"`
}
Namespace is one entry of GET /api/v1/namespaces. A namespace name is only unique inside a cluster; ClusterCount tells how many clusters use it. Cluster is set only when the listing was filtered. DTO: NamespaceListItem.
type NamespaceStats ¶
type NamespaceStats struct {
Namespace string `json:"namespace"`
Cluster string `json:"cluster,omitempty"`
Clusters []string `json:"clusters"`
TotalSBOMs uint64 `json:"total_sboms"`
TotalPackages uint64 `json:"total_packages"`
TotalVulnerabilities uint64 `json:"total_vulnerabilities"`
CriticalVulns uint64 `json:"critical_vulns"`
HighVulns uint64 `json:"high_vulns"`
MediumVulns uint64 `json:"medium_vulns"`
LowVulns uint64 `json:"low_vulns"`
LicenseBreakdown map[string]uint64 `json:"license_breakdown"`
LastIngested string `json:"last_ingested,omitempty"`
}
NamespaceStats is GET /api/v1/namespaces/{name}/stats.
type Option ¶
type Option func(*Client)
Option configures a Client.
func WithAPIKey ¶
WithAPIKey authenticates every request with the X-API-Key header.
func WithHTTPClient ¶
WithHTTPClient replaces the underlying HTTP client (default: 60 s timeout).
func WithMaxRetries ¶
WithMaxRetries bounds the number of retries on HTTP 429 (default 3).
func WithRateLimit ¶
WithRateLimit paces requests to at most limit per window on the client side (limit <= 0 disables pacing). BOMHort allows 100 requests per 10 s per client IP; 90 / 10 s leaves headroom for other callers.
func WithRateLimiter ¶
func WithRateLimiter(l *RateLimiter) Option
WithRateLimiter shares one RateLimiter between several clients talking to the same gateway.
func WithServiceToken ¶
WithServiceToken authenticates every request with "Authorization: Bearer <token>" (BOMHort's SERVICE_TOKEN).
func WithStrictDecoding ¶
func WithStrictDecoding() Option
WithStrictDecoding makes response decoding fail on JSON fields the client does not know. Off by default so new server fields never break callers; the integration tests turn it on to detect API drift early.
func WithUserAgent ¶
WithUserAgent overrides the User-Agent header (default DefaultUserAgent).
type PackageDetail ¶
type PackageDetail struct {
PackageName string `json:"package_name"`
TotalProjects uint64 `json:"total_projects"`
Projects []PackageProject `json:"projects"`
Page uint64 `json:"page"`
PageSize uint64 `json:"page_size"`
}
PackageDetail is GET /api/v1/packages/detail. DTO: PackageDetailResponse.
type PackageProject ¶
type PackageProject struct {
ProjectName string `json:"project_name"`
Version string `json:"version"`
SBOMID string `json:"sbom_id"`
}
PackageProject is a project using a package. DTO: DependencySearchProject.
type PackageSearch ¶
type PackageSearch struct {
TotalResults uint64 `json:"total_results"`
Items []PackageSearchResult `json:"items"`
Page uint64 `json:"page"`
PageSize uint64 `json:"page_size"`
Query string `json:"query"`
}
PackageSearch is GET /api/v1/packages/search. DTO: DependencySearchResponse.
type PackageSearchResult ¶
type PackageSearchResult struct {
PackageName string `json:"package_name"`
PURL string `json:"purl"`
ProjectCount uint64 `json:"project_count"`
Versions []string `json:"versions"`
Projects []PackageProject `json:"projects"`
}
PackageSearchResult is one package found by search. DTO: DependencySearchResult.
type Paginated ¶
type Paginated[T any] struct { Data []T `json:"data"` Total uint64 `json:"total"` Page uint64 `json:"page"` PageSize uint64 `json:"page_size"` }
Paginated wraps the list responses of paginated endpoints.
type Project ¶
type Project struct {
ProjectName string `json:"project_name"`
SBOMCount uint64 `json:"sbom_count"`
PackageCount uint64 `json:"package_count"`
VulnCount uint64 `json:"vuln_count"`
LatestIngested string `json:"latest_ingested"`
LatestSBOMID string `json:"latest_sbom_id"`
Tags []string `json:"tags"`
// Parent is the resolved product this project belongs to and
// ParentSource how it was resolved (>= 0.8.0).
Parent string `json:"parent,omitempty"`
ParentSource string `json:"parent_source,omitempty"`
}
Project is one entry of GET /api/v1/projects. PackageCount and VulnCount are de-duplicated across the project's SBOMs (#398). DTO: ProjectListItem.
type ProjectDetail ¶
type ProjectDetail struct {
ProjectName string `json:"project_name"`
Tags []string `json:"tags"`
Parents []string `json:"parents"`
RelatedProjectCount uint64 `json:"related_project_count"`
SBOMCount uint64 `json:"sbom_count"`
PackageCount uint64 `json:"package_count"`
VulnCount uint64 `json:"vuln_count"`
CriticalVulns uint64 `json:"critical_vulns"`
HighVulns uint64 `json:"high_vulns"`
MediumVulns uint64 `json:"medium_vulns"`
LowVulns uint64 `json:"low_vulns"`
LatestIngested string `json:"latest_ingested,omitempty"`
LatestVersion string `json:"latest_version,omitempty"`
LatestSBOMID string `json:"latest_sbom_id,omitempty"`
SourceRepo string `json:"source_repo,omitempty"`
Clusters []string `json:"clusters"`
Namespaces []string `json:"namespaces"`
LicenseBreakdown map[string]uint64 `json:"license_breakdown"`
Parent string `json:"parent,omitempty"`
ParentSource string `json:"parent_source,omitempty"`
ParentOwner string `json:"parent_owner,omitempty"`
Children []string `json:"children"`
}
ProjectDetail is GET /api/v1/projects/{name} (#398, >= 0.8.0): one project aggregated across all of its SBOMs.
type ProjectGroup ¶
type ProjectGroup struct {
Name string `json:"name"`
IsProject bool `json:"is_project"`
ProjectCount uint64 `json:"project_count"`
SBOMCount uint64 `json:"sbom_count"`
PackageCount uint64 `json:"package_count"`
VulnCount uint64 `json:"vuln_count"`
LatestIngested string `json:"latest_ingested"`
Tags []string `json:"tags"`
Sources []string `json:"sources"`
Owner string `json:"owner,omitempty"`
Members []Project `json:"members"`
}
ProjectGroup is one entry of GET /api/v1/projects?group_by=parent (>= 0.8.0). DTO: ProjectGroupItem.
type ProjectListOptions ¶
type ProjectListOptions struct {
ListOptions
// Search is a substring match on the project name.
Search string
// Tag narrows the listing to one grouping label. Sub-projects of a
// parent project P are listed with Tag: P.
Tag string
}
ProjectListOptions filters GET /api/v1/projects.
type ProjectPackage ¶
type ProjectPackage struct {
Name string `json:"name"`
Version string `json:"version"`
PURL string `json:"purl,omitempty"`
SBOMCount uint64 `json:"sbom_count"`
VulnCount uint64 `json:"vuln_count"`
}
ProjectPackage is one distinct component across a project's SBOMs (GET /api/v1/projects/{name}/packages, >= 0.8.0). DTO: ProjectPackageItem.
type RateLimiter ¶
type RateLimiter struct {
// contains filtered or unexported fields
}
RateLimiter admits at most limit requests per window (sliding). BOMHort's gateway allows 100 requests / 10 s per client IP by default; staying below that avoids 429 back-off storms when several goroutines share one gateway. A RateLimiter is safe for concurrent use and may be shared between clients via WithRateLimiter.
func NewRateLimiter ¶
func NewRateLimiter(limit int, window time.Duration) *RateLimiter
NewRateLimiter returns nil (no limiting) when limit <= 0.
type SBOM ¶
type SBOM struct {
ID string `json:"sbom_id"`
SourceFile string `json:"source_file"`
SPDXVersion string `json:"spdx_version"`
DocumentName string `json:"document_name"`
// DocumentVersion is the described product's version ("" = unknown).
DocumentVersion string `json:"document_version,omitempty"`
PackageCount uint64 `json:"package_count"`
VulnCount uint64 `json:"vuln_count"`
IngestedAt string `json:"ingested_at"`
// SourceRepo and SourceRef are the product's source repository and
// commit/tag (BOMHort #332, >= 0.7.0); empty when unknown.
SourceRepo string `json:"source_repo,omitempty"`
SourceRef string `json:"source_ref,omitempty"`
// Ownership dimensions (#177); empty on single-instance deployments.
Cluster string `json:"cluster,omitempty"`
Namespace string `json:"namespace,omitempty"`
Project string `json:"project,omitempty"`
}
SBOM is one entry of GET /api/v1/sboms (and the project, cluster and namespace SBOM listings). DTO: SBOMListItem.
type SBOMDetail ¶
type SBOMDetail struct {
ID string `json:"sbom_id"`
SourceFile string `json:"source_file"`
SPDXVersion string `json:"spdx_version"`
DocumentName string `json:"document_name"`
DocumentVersion string `json:"document_version,omitempty"`
PackageCount uint64 `json:"package_count"`
VulnCount uint64 `json:"vuln_count"`
IngestedAt string `json:"ingested_at"`
SourceRepo string `json:"source_repo,omitempty"`
SourceRef string `json:"source_ref,omitempty"`
CriticalVulns uint64 `json:"critical_vulns"`
HighVulns uint64 `json:"high_vulns"`
MediumVulns uint64 `json:"medium_vulns"`
LowVulns uint64 `json:"low_vulns"`
}
SBOMDetail is GET /api/v1/sboms/{id}/detail.
type SBOMLicense ¶
type SBOMLicense struct {
LicenseID string `json:"license_id"`
Category string `json:"category"`
PackageCount uint32 `json:"package_count"`
Packages []string `json:"packages"`
ExemptedPackages []string `json:"exempted_packages,omitempty"`
ExemptionReason string `json:"exemption_reason,omitempty"`
// PackageScopes maps a name from Packages/ExemptedPackages to its
// dependency scope; absent names are unknown (#444, >= 0.8.0).
PackageScopes map[string]string `json:"package_scopes,omitempty"`
}
SBOMLicense is one entry of GET /api/v1/sboms/{id}/licenses. DTO: SBOMLicenseBreakdownItem.
type SBOMLink ¶
SBOMLink references an SBOM by id and document name. DTO: VEXAffectedSBOM, LicenseAffectedSBOM.
type SBOMListOptions ¶
type SBOMListOptions struct {
ListOptions
// Search is a substring match on document name or source path.
Search string
// Project is an exact project name (#398, >= 0.8.0; ignored by
// older gateways).
Project string
}
SBOMListOptions filters GET /api/v1/sboms.
type SearchLicense ¶
type SearchLicense struct {
LicenseID string `json:"license_id"`
Category string `json:"category"`
SBOMCount uint64 `json:"sbom_count"`
}
SearchLicense is a license hit. DTO: GlobalSearchLicense.
type SearchListOptions ¶
type SearchListOptions struct {
ListOptions
Search string
}
SearchListOptions is ListOptions plus a substring search term.
type SearchPackage ¶
type SearchPackage struct {
PackageName string `json:"package_name"`
PURL string `json:"purl"`
ProjectCount uint64 `json:"project_count"`
}
SearchPackage is a package hit. DTO: GlobalSearchPackage.
type SearchProject ¶
type SearchProject struct {
ProjectName string `json:"project_name"`
SBOMCount uint64 `json:"sbom_count"`
LatestSBOMID string `json:"latest_sbom_id"`
}
SearchProject is a project hit. DTO: GlobalSearchProject.
type SearchResult ¶
type SearchResult struct {
Query string `json:"query"`
Packages []SearchPackage `json:"packages"`
TotalPackages uint64 `json:"total_packages"`
Projects []SearchProject `json:"projects"`
TotalProjects uint64 `json:"total_projects"`
Vulnerabilities []SearchVulnerability `json:"vulnerabilities"`
TotalVulnerabilities uint64 `json:"total_vulnerabilities"`
Licenses []SearchLicense `json:"licenses"`
TotalLicenses uint64 `json:"total_licenses"`
}
SearchResult is GET /api/v1/search, a faceted search across packages, projects, vulnerabilities and licenses. DTO: GlobalSearchResponse.
type SearchVulnerability ¶
type SearchVulnerability struct {
VulnID string `json:"vuln_id"`
// CVEID and Aliases as on Vulnerability (#412, >= 0.8.0); the search
// also matches on any alias.
CVEID string `json:"cve_id,omitempty"`
Aliases []string `json:"aliases,omitempty"`
Severity string `json:"severity"`
Summary string `json:"summary"`
AffectedSBOMs uint64 `json:"affected_sboms"`
}
SearchVulnerability is a vulnerability hit. DTO: GlobalSearchVulnerability.
type Seq ¶
Seq is a lazy sequence of list items. Iteration stops at the first error, which is yielded together with the zero value of T:
for s, err := range c.SBOMs(ctx, nil) {
if err != nil { return err }
...
}
func Paginate ¶
func Paginate[T any](ctx context.Context, pageSize int, fetch func(context.Context, ListOptions) (Paginated[T], error)) Seq[T]
Paginate turns any page-wise List* call into a lazy sequence. It walks pages of pageSize (DefaultWalkPageSize if <= 0) until an empty page or the reported total is reached:
vulns := bomhort.Paginate(ctx, 200, func(ctx context.Context, o bomhort.ListOptions) (bomhort.Paginated[bomhort.Vulnerability], error) {
return c.ListVulnerabilities(ctx, &o)
})
Example ¶
package main
import (
"context"
"fmt"
"log"
bomhort "github.com/seebom-labs/bomhort-go"
"github.com/seebom-labs/bomhort-go/bomhorttest"
)
// fakeGateway starts a bomhorttest server with one SBOM so the examples
// run without a real BOMHort.
func fakeGateway() *bomhorttest.Server {
srv := bomhorttest.New("example-key")
srv.AddSBOM(
bomhort.SBOM{ID: bomhorttest.UUID(1), DocumentName: "payments-api", SourceFile: "payments-api.spdx.json", Project: "payments"},
[]bomhort.Vulnerability{
{VulnID: "GHSA-qppj-fm5r-hxr3", Severity: "HIGH", PURL: "pkg:golang/golang.org/x/net@v0.17.0", FixedVersion: "0.23.0"},
},
nil, nil,
)
return srv
}
func main() {
srv := fakeGateway()
defer srv.Close()
c := bomhort.New(srv.URL, bomhort.WithAPIKey("example-key"))
ctx := context.Background()
// Any List* call can be turned into a lazy sequence.
pages := bomhort.Paginate(ctx, 200, func(ctx context.Context, o bomhort.ListOptions) (bomhort.Paginated[bomhort.SBOM], error) {
return c.ListSBOMs(ctx, &bomhort.SBOMListOptions{ListOptions: o, Project: "payments"})
})
sboms, err := bomhort.Collect(pages)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(sboms), sboms[0].DocumentName)
}
Output: 1 payments-api
type SourceAttribution ¶
type SourceAttribution struct {
SBOMID string `json:"sbom_id"`
SourceRepo string `json:"source_repo"`
SourceRef string `json:"source_ref"`
}
SourceAttribution is the response of PATCH /api/v1/sboms/{id}.
type SourcePatch ¶
type SourcePatch struct {
SourceRepo *string `json:"source_repo,omitempty"`
SourceRef *string `json:"source_ref,omitempty"`
}
SourcePatch is the body of PATCH /api/v1/sboms/{id}. A nil field is left unchanged; a pointer to "" clears it.
type Tag ¶
type Tag struct {
Tag string `json:"tag"`
SBOMCount uint64 `json:"sbom_count"`
ProjectCount uint64 `json:"project_count"`
// IsProject is true when the tag is also a project name, i.e. a parent
// (#398, >= 0.8.0).
IsProject bool `json:"is_project"`
}
Tag is one entry of GET /api/v1/tags. DTO: TagListItem.
type UploadOptions ¶
type UploadOptions struct {
// SBOMID scopes every statement of an uploaded VEX document to one
// SBOM (BOMHort #350, >= 0.7.0). Only valid for VEX uploads; must be a
// UUID.
SBOMID string
// Cluster, Namespace and Project override the gateway's ownership
// defaults (CLUSTER_NAME, NAMESPACE, PROJECT).
Cluster string
Namespace string
Project string
// Parent names the product the upload belongs to (>= 0.8.0).
Parent string
// Tags are merged with the instance's default tags.
Tags []string
// SourceRepo (http(s) URL without credentials) and SourceRef (git ref)
// attribute the SBOM to its source (#332, >= 0.7.0), sent as
// X-Source-Repo / X-Source-Ref.
SourceRepo string
SourceRef string
}
UploadOptions are the optional parameters of POST /api/v1/sboms/upload. Empty fields are omitted, so the gateway's configured defaults apply.
type UploadResult ¶
type UploadResult struct {
// Status is "pending" (202, job enqueued) or "duplicate" (200, content
// with the same SHA-256 was already ingested).
Status string `json:"status"`
JobID string `json:"job_id,omitempty"`
SHA256Hash string `json:"sha256_hash"`
// JobType is "sbom" or "vex".
JobType string `json:"job_type,omitempty"`
Cluster string `json:"cluster,omitempty"`
Namespace string `json:"namespace,omitempty"`
Project string `json:"project,omitempty"`
Parent string `json:"parent,omitempty"` // >= 0.8.0
Message string `json:"message,omitempty"`
}
UploadResult is the response of POST /api/v1/sboms/upload.
func (UploadResult) Duplicate ¶
func (r UploadResult) Duplicate() bool
Duplicate reports whether BOMHort skipped the upload as already ingested.
type VEXStatement ¶
type VEXStatement struct {
VEXID string `json:"vex_id"`
DocumentID string `json:"document_id"`
SourceFile string `json:"source_file"`
// SBOMID is the SBOM the statement is scoped to (#350). Empty means
// the product resolved to no SBOM (yet): such statements suppress
// nothing until a matching SBOM is ingested.
SBOMID string `json:"sbom_id,omitempty"`
// ProductPURL is the subcomponent purl the statement is about, or "*"
// for a product-wide statement (product without subcomponents).
ProductPURL string `json:"product_purl"`
VulnID string `json:"vuln_id"`
// CVEID and Aliases are the other names of the flaw, looked up from
// the findings the statement can match (#412, >= 0.8.0). VulnID stays
// the id the author used; match on it, not on the aliases.
CVEID string `json:"cve_id,omitempty"`
Aliases []string `json:"aliases,omitempty"`
Status string `json:"status"`
Justification string `json:"justification"`
ImpactStatement string `json:"impact_statement,omitempty"`
ActionStatement string `json:"action_statement,omitempty"`
VEXTimestamp string `json:"vex_timestamp"`
IngestedAt string `json:"ingested_at"`
// Provenance (#334, >= 0.7.0); empty when the document has none.
Author string `json:"author,omitempty"`
Role string `json:"role,omitempty"`
Tooling string `json:"tooling,omitempty"`
StatusNotes string `json:"status_notes,omitempty"`
AffectedSBOMs []SBOMLink `json:"affected_sboms,omitempty"`
}
VEXStatement is one ingested statement: GET /api/v1/vex/statements and /sboms/{id}/vex. DTO: VEXStatementItem.
type VersionSkew ¶
type VersionSkew struct {
TotalSkewedPackages uint64 `json:"total_skewed_packages"`
Items []VersionSkewItem `json:"items"`
Page uint64 `json:"page"`
PageSize uint64 `json:"page_size"`
}
VersionSkew is GET /api/v1/stats/version-skew. DTO: VersionSkewResponse.
type VersionSkewDetail ¶
type VersionSkewDetail struct {
Version string `json:"version"`
ProjectCount uint64 `json:"project_count"`
Projects []string `json:"projects"`
}
VersionSkewDetail is the per-version breakdown of a skewed package.
type VersionSkewItem ¶
type VersionSkewItem struct {
PackageName string `json:"package_name"`
PURL string `json:"purl"`
VersionCount uint64 `json:"version_count"`
ProjectCount uint64 `json:"project_count"`
IsDirectInCount uint64 `json:"is_direct_in_count"`
Versions []VersionSkewDetail `json:"versions"`
}
VersionSkewItem is a package with inconsistent versions across projects.
type Vulnerability ¶
type Vulnerability struct {
VulnID string `json:"vuln_id"`
// CVEID is the CVE of the same flaw (VulnID itself when that is a CVE,
// else the smallest CVE among Aliases); empty when there is none.
// Aliases are every other OSV id of the flaw, sorted (#412, >= 0.8.0).
CVEID string `json:"cve_id,omitempty"`
Aliases []string `json:"aliases,omitempty"`
// DependencyScope is root|direct|transitive|unknown and DependencyDepth
// the shortest path from the described product (0 root, 1 direct, >= 2
// transitive; nil when unknown) (#444, >= 0.8.0).
DependencyScope string `json:"dependency_scope,omitempty"`
DependencyDepth *uint32 `json:"dependency_depth,omitempty"`
Severity string `json:"severity"`
PURL string `json:"purl"`
Summary string `json:"summary"`
FixedVersion string `json:"fixed_version"`
SourceFile string `json:"source_file"`
DiscoveredAt string `json:"discovered_at"`
VEXStatus string `json:"vex_status,omitempty"`
// Effective VEX statement detail (#335, >= 0.7.0): one row per
// (vuln_id, purl); the statement with the newest vex_timestamp wins.
VEXJustification string `json:"vex_justification,omitempty"`
VEXTimestamp string `json:"vex_timestamp,omitempty"`
VEXStatementID string `json:"vex_statement_id,omitempty"`
VEXAuthor string `json:"vex_author,omitempty"`
VEXTooling string `json:"vex_tooling,omitempty"`
// VEXScope is "sbom" when a statement scoped to this SBOM applies
// (#350, >= 0.7.0). BOMHort's DTO also documents "global", but since
// 0.7.0 unscoped statements never apply, so it is not sent in practice.
VEXScope string `json:"vex_scope,omitempty"`
// AffectedSBOMs is set only on project-level listings: how many of the
// project's SBOMs carry this (vuln_id, purl) pair (#398, >= 0.8.0).
AffectedSBOMs uint64 `json:"affected_sboms,omitempty"`
}
Vulnerability is one finding: GET /api/v1/vulnerabilities, /sboms/{id}/vulnerabilities and /projects/{name}/vulnerabilities. DTO: VulnerabilityListItem.
A VEX statement applies to a finding when it is scoped to the finding's SBOM, statement.vuln_id == VulnID and statement.product_purl == PURL or "*" (product-wide), all by plain string equality; never normalise VulnID or PURL before writing VEX for them. See docs/VEX.md.
type VulnerabilityListOptions ¶
type VulnerabilityListOptions struct {
ListOptions
VulnerabilityOptions
}
VulnerabilityListOptions pages and filters GET /api/v1/vulnerabilities.
type VulnerabilityOptions ¶
type VulnerabilityOptions struct {
// Scope keeps only findings whose package is pulled in that way: one
// of ScopeRoot, ScopeDirect, ScopeTransitive, ScopeUnknown (#444,
// >= 0.8.0; older gateways ignore it). Anything else is a 400.
Scope string
}
VulnerabilityOptions filters the per-SBOM and per-project finding lists. nil returns every scope.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package bomhorttest provides an in-memory fake of the BOMHort API gateway for tests of code that talks to BOMHort through bomhort.Client.
|
Package bomhorttest provides an in-memory fake of the BOMHort API gateway for tests of code that talks to BOMHort through bomhort.Client. |