This repository contains a parser and types to read and work with OASIS OpenEoX data. This repo also hosts the protocol buffer definitions of the OpenEoX elements from which the types are generated.
From the official website:
OpenEoX is an initiative aimed at standardizing the way End-of-Life (EOL) and End-of-Support (EOS) information is exchanged within the software and hardware industries.
OpenEoX is a simple format to communicate the end of support of software and hardware. It is designed to complement SBOM, VEX and security advisories.
To install the module, simply pull it with go get:
go get github.com/carabiner-dev/openeoxThe parser reads both OpenEoX Shell and Core documents:
parser, _ := openeox.NewParser()
// Parse a shell document (contains product info + lifecycle data)
shell, err := parser.ParseShell(data)
// Parse a standalone core document (lifecycle data only)
core, err := parser.ParseCore(data)Core documents are validated against the upstream OpenEoX core JSON
schema when parsed; validation errors wrap openeox.ErrInvalidDocument.
Set Parser.SkipValidation to parse without validating.
Documents can also be read directly from the file system, a URL or any
io.Reader:
core, err := parser.ParseCoreFile("product.eox.json")
core, err := parser.ParseCoreURL(ctx, "https://example.com/product.eox.json")
core, err := parser.ParseCoreReader(os.Stdin)
// ... and the equivalent ParseShellFile / ParseShellURL / ParseShellReaderThe OpenEoX spec allows lifecycle date fields (end_of_life,
end_of_security_support, end_of_sales, general_availability) to be
the literal string "tba" instead of an RFC 3339 timestamp, meaning the
date has not yet been announced.
Since the Go types use google.protobuf.Timestamp for these fields (giving
you real time.Time values), the parser transparently maps "tba" to a
far-future sentinel timestamp (9999-12-31T23:59:59Z). Use IsTBA to
check whether a parsed timestamp represents "tba":
core, _ := parser.ParseCore(data)
if openeox.IsTBA(core.GetEndOfLife()) {
fmt.Println("End of life has not been announced yet")
} else {
fmt.Println("End of life:", core.GetEndOfLife().AsTime())
}To create a TBA timestamp when building documents:
core := &openeox.Core{
Schema: openeox.CoreSchema,
EndOfLife: openeox.TBATimestamp(),
EndOfSecuritySupport: openeox.TBATimestamp(),
LastUpdated: timestamppb.Now(),
}The marshal functions (MarshalCore, MarshalShell) automatically convert
sentinel timestamps back to "tba" in the JSON output, producing
spec-compliant documents.
Here is a complete example that builds an OpenEoX shell document with product identification using file hashes:
shell := &openeox.Shell{
Schema: openeox.Schema,
Statements: []*openeox.Statement{
{
Core: &openeox.Core{
Schema: openeox.CoreSchema,
EndOfLife: timestamppb.New(time.Date(2028, 6, 30, 23, 59, 59, 0, time.UTC)),
EndOfSecuritySupport: timestamppb.New(time.Date(2028, 3, 31, 23, 59, 59, 0, time.UTC)),
EndOfSales: timestamppb.New(time.Date(2027, 12, 31, 23, 59, 59, 0, time.UTC)),
LastUpdated: timestamppb.Now(),
},
Product: &openeox.Product{
Schema: openeox.ProductSoftwareSchema,
ProductName: "SecureLib",
ProductVersion: "2.1.0",
VendorName: "Acme Corp",
},
ProductIdentificationHelper: &openeox.ProductIdentificationHelper{
Purls: []string{"pkg:generic/acme/securelib@2.1.0"},
Hashes: []*openeox.CryptographicHashes{
{
Filename: "securelib-2.1.0.tar.gz",
FileHashes: []*openeox.FileHash{
{
Algorithm: "sha256",
Value: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
},
},
},
},
},
},
}
data, err := openeox.MarshalShell(shell)This produces:
{
"$schema": "https://docs.oasis-open.org/openeox/tbd/schema/shell.json",
"statements": [
{
"core": {
"$schema": "https://docs.oasis-open.org/openeox/eox-core/v1.0/schema/core.json",
"end_of_life": "2028-06-30T23:59:59Z",
"end_of_security_support": "2028-03-31T23:59:59Z",
"end_of_sales": "2027-12-31T23:59:59Z",
"last_updated": "2025-04-13T12:00:00Z"
},
"product": {
"$schema": "https://docs.oasis-open.org/openeox/tbd/schema/product_software.json",
"product_name": "SecureLib",
"product_version": "2.1.0",
"vendor_name": "Acme Corp"
},
"product_identification_helper": {
"purls": ["pkg:generic/acme/securelib@2.1.0"],
"hashes": [
{
"filename": "securelib-2.1.0.tar.gz",
"file_hashes": [
{
"algorithm": "sha256",
"value": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
]
}
]
}
}
]
}Use the provided marshal functions to produce JSON that conforms to the upstream OpenEoX schema. Object keys are sorted lexicographically, as required by the specification's Library conformance profile:
data, err := openeox.MarshalCore(core)
data, err := openeox.MarshalShell(shell)
// String, stream and file system output
s, err := openeox.MarshalCoreString(core)
err = openeox.WriteCore(os.Stdout, core)
err = openeox.WriteCoreFile("product.eox.json", core)
// ... and the equivalent MarshalShellString / WriteShell / WriteShellFileThe module tracks the OASIS OpenEoX specification:
- openeox-core v1.0: Fully supported. The
Coremessage maps directly to the upstream JSON schema. Conformance is validated against the upstream schema and test data in CI. Documents using the pre-CSD01-RC3 schema URI (https://docs.oasis-open.org/openeox/v1.0/schema/core.json, exposed asCoreSchemaLegacy) are still accepted when parsing. The module targets the specification's "OpenEoX Core Library" conformance profile (Conformance Clause 9): parsed core documents are checked against the embedded upstream JSON schema, documents can be read from the file system, URLs and data streams, output keys are sorted, and documents can be written to the file system, strings and data streams. - openeox-shell: Tracks the current draft (CSD01). The
Shell,Statement,Product, andProductIdentificationHelpermessages follow the draft shell schema structure.
This module is copyright by Carabiner Systems, Inc. It is released under the Apache 2.0 license, feel free to use it for anything, contribute patches or file issues with bugs or feature requests.