Created
May 20, 2026 18:12
-
-
Save woodruffw/2c4a62b2bff3df6cb09cd9b743e9e7a5 to your computer and use it in GitHub Desktop.
osv repro
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Command reprotest is a standalone reproducer for the empty `{}` OSV event | |
| // regression. It depends only on the serialization libraries (yaml.v2, goccy, | |
| // protojson, osvschema) so it builds without the rest of the vulnfeeds module. | |
| // | |
| // Run with: go run ./reprotest | |
| package main | |
| import ( | |
| "fmt" | |
| "strings" | |
| goccyyaml "github.com/goccy/go-yaml" | |
| "github.com/ossf/osv-schema/bindings/go/osvschema" | |
| "google.golang.org/protobuf/encoding/protojson" | |
| "gopkg.in/yaml.v2" | |
| ) | |
| // buildVuln constructs a PyJWT-style advisory: affected from version 0 up to and | |
| // including 2.12.1, with no released fix — i.e. an `introduced` + `last_affected` | |
| // pair. This is exactly what AddPkgInfo produces for such an advisory. | |
| func buildVuln() *osvschema.Vulnerability { | |
| return &osvschema.Vulnerability{ | |
| Id: "PYSEC-0000-TEST", | |
| Affected: []*osvschema.Affected{ | |
| { | |
| Package: &osvschema.Package{ | |
| Name: "pyjwt", | |
| Ecosystem: "PyPI", | |
| Purl: "pkg:pypi/pyjwt", | |
| }, | |
| Ranges: []*osvschema.Range{ | |
| { | |
| Type: osvschema.Range_ECOSYSTEM, | |
| Events: []*osvschema.Event{ | |
| {Introduced: "0"}, | |
| {LastAffected: "2.12.1"}, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| } | |
| } | |
| // oldToYAML reproduces the buggy ToYAML: encode the protobuf struct directly. | |
| func oldToYAML(v *osvschema.Vulnerability) []byte { | |
| out, err := yaml.Marshal(v) | |
| if err != nil { | |
| panic(err) | |
| } | |
| return out | |
| } | |
| // newToYAML reproduces the fixed ToYAML: protojson then JSON->YAML. | |
| func newToYAML(v *osvschema.Vulnerability) []byte { | |
| jsonBytes, err := protojson.Marshal(v) | |
| if err != nil { | |
| panic(err) | |
| } | |
| out, err := goccyyaml.JSONToYAML(jsonBytes) | |
| if err != nil { | |
| panic(err) | |
| } | |
| return out | |
| } | |
| // fromYAML reproduces the current FromYAML: YAML->JSON then protojson decode | |
| // with DiscardUnknown (as introduced in PR #5397). | |
| func fromYAML(data []byte) *osvschema.Vulnerability { | |
| jsonBytes, err := goccyyaml.YAMLToJSON(data) | |
| if err != nil { | |
| panic(err) | |
| } | |
| var v osvschema.Vulnerability | |
| opts := protojson.UnmarshalOptions{DiscardUnknown: true} | |
| if err := opts.Unmarshal(jsonBytes, &v); err != nil { | |
| panic(err) | |
| } | |
| return &v | |
| } | |
| func isEmpty(e *osvschema.Event) bool { | |
| return e.GetIntroduced() == "" && e.GetFixed() == "" && | |
| e.GetLastAffected() == "" && e.GetLimit() == "" | |
| } | |
| func describeEvents(label string, v *osvschema.Vulnerability) { | |
| fmt.Printf("%s\n", label) | |
| events := v.GetAffected()[0].GetRanges()[0].GetEvents() | |
| emptyCount := 0 | |
| for i, e := range events { | |
| if isEmpty(e) { | |
| emptyCount++ | |
| } | |
| fmt.Printf(" event[%d]: introduced=%q fixed=%q last_affected=%q limit=%q empty=%v\n", | |
| i, e.GetIntroduced(), e.GetFixed(), e.GetLastAffected(), e.GetLimit(), isEmpty(e)) | |
| } | |
| // Re-serialize the decoded proto the way cmd/ids writes final records. | |
| jsonBytes, _ := protojson.Marshal(v) | |
| finalYAML, _ := goccyyaml.JSONToYAML(jsonBytes) | |
| fmt.Printf(" re-serialized record (protojson -> YAML, as cmd/ids writes it):\n") | |
| for _, line := range strings.Split(strings.TrimRight(string(finalYAML), "\n"), "\n") { | |
| fmt.Printf(" %s\n", line) | |
| } | |
| fmt.Printf(" => empty events: %d\n\n", emptyCount) | |
| } | |
| func main() { | |
| v := buildVuln() | |
| fmt.Println("=== BUGGY PATH: old ToYAML (yaml.v2 on the protobuf struct) ===") | |
| buggyYAML := oldToYAML(v) | |
| fmt.Printf("ToYAML output (note the key name):\n%s\n", buggyYAML) | |
| buggyDecoded := fromYAML(buggyYAML) | |
| describeEvents("After FromYAML round-trip:", buggyDecoded) | |
| fmt.Println("=== FIXED PATH: new ToYAML (protojson -> JSONToYAML) ===") | |
| fixedYAML := newToYAML(v) | |
| fmt.Printf("ToYAML output:\n%s\n", fixedYAML) | |
| fixedDecoded := fromYAML(fixedYAML) | |
| describeEvents("After FromYAML round-trip:", fixedDecoded) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment