CycloneDX / CycloneDX/cyclonedx-go
`JSFSignature` cannot be unmarshalled: inline signatures are dropped and `.Algorithm` panics
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 118
- Forks
- 47
- Avg merge
- 13h 47m
- Merged PRs (30d)
- 9
Description
Description
JSFSignature embeds *JSFSigner with a json:"-" tag:
// cyclonedx.go
type JSFSignature struct {
*JSFSigner `json:"-" xml:"-"`
Signers *[]JSFSigner `json:"signers,omitempty" xml:"-"`
Chain *[]JSFSigner `json:"chain,omitempty" xml:"-"`
}
Because the embedded pointer is excluded from JSON, it is never populated during decoding. This has three consequences:
- A single inline signature is discarded.
{"signature": {"algorithm": "RS512", "value": "..."}}— the form used in the specification's own example — decodes to a non-nilSignaturewhose fields are all empty. - A round trip destroys it. Decoding and re-encoding a BOM with an inline signature produces output with no signature at all, so any tool that reads and rewrites a signed BOM silently strips the signature.
- Reading
Signature.Algorithmpanics for every signature form. The promoted field resolves through the nil embedded pointer. This is not limited to the inline case:signersandchaindocuments also leave the embedded struct nil, so the panic happens there too.
Reproducer
package main
import (
"bytes"
"fmt"
cdx "github.com/CycloneDX/cyclonedx-go"
)
func main() {
const doc = `{"bomFormat":"CycloneDX","specVersion":"1.6","version":1,` +
`"signature":{"algorithm":"RS512","value":"abc"}}`
var bom cdx.BOM
if err := cdx.NewBOMDecoder(bytes.NewReader([]byte(doc)), cdx.BOMFileFormatJSON).Decode(&bom); err != nil {
panic(err)
}
fmt.Println("Signature != nil :", bom.Signature != nil) // true
fmt.Println("JSFSigner != nil :", bom.Signature.JSFSigner != nil) // false — data was dropped
// Round trip loses the signature entirely:
var out bytes.Buffer
_ = cdx.NewBOMEncoder(&out, cdx.BOMFileFormatJSON).Encode(&bom)
fmt.Println("round trip keeps algorithm:", bytes.Contains(out.Bytes(), []byte("algorithm"))) // false
fmt.Println(bom.Signature.Algorithm) // panic: nil pointer dereference
}
Output:
Signature != nil : true
JSFSigner != nil : false
round trip keeps algorithm: false
panic: runtime error: invalid memory address or nil pointer dereference
Substituting "signature":{"signers":[{"algorithm":"RS512","value":"abc"}]} keeps the data in Signers, but the final line still panics.
Impact
Consumers cannot read a signature through the public API without either crashing or bypassing the library. Two projects have independently worked around it by parsing the raw JSON — sbomqs carries the comment "Since cyclonedx-go doesn't properly unmarshal signatures yet, we need to parse it manually from the raw JSON", and I did the same in my own tool before tracing it here.
Suggested fix
JSF allows a block to be either a single signer or a set (signers / chain), so a custom UnmarshalJSON on JSFSignature seems like the smallest change that keeps the current field layout: decode into the embedded JSFSigner when algorithm is present at the top level, and into Signers/Chain otherwise. A matching MarshalJSON would fix the round trip.
If keeping the embedded pointer is awkward, an alternative is to make the single-signer fields explicit on JSFSignature rather than promoted, which also removes the nil-dereference footgun.
Happy to open a PR if the maintainers prefer one of these shapes.
Environment
cyclonedx-go v0.11.0 (latest release) with go1.26.5. The struct definition on master (cyclonedx.go:909, checked 2026-08-07) is identical, so this is not fixed in an unreleased commit.
Related
#17 tracks implementing JSF signing/verification as a feature. This report is about the existing struct being unusable for reading a signature that is already there — I believe they are separate, but happy to fold this in if you see it as part of the same work.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the JSFSignature definition in cyclonedx.go:909 and run the JSON reproducer from the issue. Check decoding and re-encoding for inline signatures and signers/chain documents, and verify that reading Algorithm no longer panics while signature data survives the round trip.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend-api-design
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100