
Facilitates serialization and deserialization of COSV schema extensions for OSV, inspired by detekt/sarif4k. Supports multiplatform, KotlinX Serialization, and Jackson annotations for JVM.
Kotlin and Java model for the serialization and deserialization of COSV Schema (extension for OSV).
This library is inspired by the tool detekt/sarif4k.
See the project website for documentation and APIs.
The latest release is available from both GitHub Packages and Maven Central.
dependencies {
implementation("com.saveourtool.cosv4k:cosv4k:1.0.0")
}<dependency>
<groupId>com.saveourtool.cosv4k</groupId>
<artifactId>cosv4k-jvm</artifactId>
<version>1.0.0</version>
</dependency>For GitHub Packages, the repository can be added as follows.
Update build.gradle.kts:
repositories {
maven {
name = "saveourtool/cosv4k"
url = uri("https://maven.pkg.github.com/saveourtool/cosv4k")
content {
includeGroup("com.saveourtool.cosv4k")
}
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
}
}
}Update settings.gradle.kts:
dependencyResolutionManagement {
repositories {
maven {
name = "saveourtool/cosv4k"
url = uri("https://maven.pkg.github.com/saveourtool/cosv4k")
content {
includeGroup("com.saveourtool.cosv4k")
}
credentials {
username = providers.gradleProperty("gpr.user").orNull
?: System.getenv("GITHUB_ACTOR")
password = providers.gradleProperty("gpr.key").orNull
?: System.getenv("GITHUB_TOKEN")
}
}
}
}OSV Schema has extension points for database and ecosystem specific fields:
database_specific.affected[] object:
affected[].ecosystem_specific;affected[].database_specific.affected[].ranges[].database.COSV4K Model implements it using generic type:
/**
* @param D The top level `database_specific`.
* @param A_E `affected[].ecosystem_specific`.
* @param A_D `affected[].database_specific`.
* @param A_R_D `affected[].ranges[].database_specific`.
*/
data class OsvSchema<D, A_D, A_E, A_R_D>Requirements:
kotlinx.serialization.json.JsonObject for KotlinX Serialization or Map<String, Object> for Jackson for raw access to these fields.com.saveourtool.osv4k.RawOsvSchema for KotlinX Serialization which uses kotlinx.serialization.json.JsonObject as a raw type for all generic types.kotlinx.serialization.json.JsonObject (KotlinX Serialization) or Map<String, Object> (Jackson) for required fields.Unit (KotlinX Serialization) or Void (Jackson) for required fields.
Note: if field's type is
UnitorVoidbut any value is provided, serialization exception will be thrown.
Go vulnerability uses OSV schema. Will use GO-2020-0015 as example:
{
"schema_version": "1.3.1",
"id": "GO-2020-0015",
"modified": "2023-06-12T18:45:41Z",
"published": "2021-04-14T20:04:52Z",
"aliases": [
"CVE-2020-14040",
"GHSA-5rcv-m4m3-hfh7"
],
"summary": "Infinite loop when decoding some inputs in golang.org/x/text",
"details": "An attacker could provide a single byte to a UTF16 decoder instantiated with UseBOM or ExpectBOM to trigger an infinite loop if the String function on the Decoder is called, or the Decoder is passed to transform.String. If used to parse user supplied input, this may be used as a denial of service vector.",
"affected": [
{
"package": {
"name": "golang.org/x/text",
"ecosystem": "Go"
},
"ranges": [
{
"type": "SEMVER",
"events": [
{
"introduced": "0"
},
{
"fixed": "0.3.3"
}
]
}
],
"ecosystem_specific": {
"imports": [
{
"path": "golang.org/x/text/encoding/unicode",
"symbols": [
"bomOverride.Transform",
"utf16Decoder.Transform"
]
},
{
"path": "golang.org/x/text/transform",
"symbols": [
"String"
]
}
]
}
}
],
"references": [
{
"type": "FIX",
"url": "https://go.dev/cl/238238"
},
{
"type": "FIX",
"url": "https://go.googlesource.com/text/+/23ae387dee1f90d29a23c0e87ee0b46038fbed0e"
},
{
"type": "REPORT",
"url": "https://go.dev/issue/39491"
},
{
"type": "WEB",
"url": "https://groups.google.com/g/golang-announce/c/bXVeAmGOqz0"
}
],
"credits": [
{
"name": "@abacabadabacaba"
},
{
"name": "Anton Gyllenberg"
}
],
"database_specific": {
"url": "https://pkg.go.dev/vuln/GO-2020-0015"
}
}import com.saveourtool.osv4k.*
import kotlinx.serialization.json.Json
fun readFromFile(content: String) {
val schema: RawOsvSchema = Json.decodeFromString(content)
// do something with OsvSchema
// for example: prints credits
println(schema.credits?.joinToString(", ") { it.name })
// @abacabadabacaba, Anton Gyllenberg
}import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JavaType;
import java.util.stream.Collectors;
class Test {
private static final ObjectMapper objectMapper = new ObjectMapper();
static void readFromFile(final String content) {
final OsvSchema result = objectMapper.readValue(content, OsvSchema.class);
// do something with OsvSchema
// for example: prints credits
System.out.println(result.getCredits().stream().map(Credit::getName).collect(Collectors.joining(", ")));
// @abacabadabacaba, Anton Gyllenberg
}
}Go vulnerability has specific fields. They will be presented by the following classes in our example:
@Serializable
data class GoImports(
val imports: List<GoImport>,
)
@Serializable
data class GoImport(
val path: String,
val symbols: List<String>,
)
@Serializable
data class GoUrl(
val url: String,
)public class GoImports {
private final List<GoImport> imports;
public GoImports(List<GoImport> imports) {
this.imports = imports;
}
public List<GoImport> getImports() {
return Collections.unmodifiableList(imports);
}
}
public class GoImport {
private final String path;
private final List<String> symbols;
public GoImport(String path, List<String> symbols) {
this.path = path;
this.symbols = symbols;
}
public String getPath() {
return path;
}
public List<String> getSymbols() {
return Collections.unmodifiableList(symbols);
}
}
public class GoUrl {
private final String url;
public GoUrl(String url) {
this.url = url;
}
public String getUrl() {
return url;
}
}import com.saveourtool.osv4k.*
import kotlinx.serialization.json.Json
fun readFromFile(content: String) {
val schema: OsvSchema<GoUrl, GoImports, Unit, Unit> = Json.decodeFromString(content)
// do something with OsvSchema
// for example: prints credits
println(schema.credits?.joinToString(", ") { it.name })
// @abacabadabacaba, Anton Gyllenberg
}import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JavaType;
import java.util.stream.Collectors;
class Test {
private static final ObjectMapper objectMapper = new ObjectMapper();
static void readFromFile(final String content) {
final JavaType jacksonType = objectMapper.getTypeFactory()
.constructParametricType(OsvSchema.class, GoUrl.class, GoImports.class, Void.class, Void.class);
final OsvSchema<GoUrl, GoImports, Void, Void> result = objectMapper.readValue(content, jacksonType);
// do something with OsvSchema
// for example: prints credits
System.out.println(result.getCredits().stream().map(Credit::getName).collect(Collectors.joining(", ")));
// @abacabadabacaba, Anton Gyllenberg
}
}val osvSchema = OsvSchema<GoUrl, GoImports, Unit, Unit>(
schemaVersion = "1.3.1",
id = "GO-2020-0015",
modified = LocalDateTime(2023, 6, 12, 18, 45, 41),
published = LocalDateTime(2021, 4, 14, 20, 4, 52),
aliases = listOf("CVE-2020-14040", "GHSA-5rcv-m4m3-hfh7"),
summary = "Infinite loop when decoding some inputs in golang.org/x/text",
details = "An attacker could provide a single byte to a UTF16 decoder instantiated with UseBOM or ExpectBOM to trigger an infinite loop if the String function on the Decoder is called, or the Decoder is passed to transform.String. If used to parse user supplied input, this may be used as a denial of service vector.",
affected = listOf(
Affected(
`package` = Package(
ecosystem = "Go",
name = "golang.org/x/text",
),
ranges = listOf(
Range(
type = RangeType.SEMVER,
events = listOf(
Event(introduced = "0"),
Event(fixed = "0.3.3"),
),
),
),
ecosystemSpecific = GoImports(
imports = listOf(
GoImport(
path = "golang.org/x/text/encoding/unicode",
symbols = listOf("bomOverride.Transform", "utf16Decoder.Transform"),
),
GoImport(
path = "golang.org/x/text/transform",
symbols = listOf("String"),
),
),
),
)
),
references = listOf(
Reference(
type = ReferenceType.FIX,
url = "https://go.dev/cl/238238",
),
Reference(
type = ReferenceType.FIX,
url = "https://go.googlesource.com/text/+/23ae387dee1f90d29a23c0e87ee0b46038fbed0e",
),
Reference(
type = ReferenceType.REPORT,
url = "https://go.dev/issue/39491",
),
Reference(
type = ReferenceType.WEB,
url = "https://groups.google.com/g/golang-announce/c/bXVeAmGOqz0",
),
),
credits = listOf(
Credit(name = "@abacabadabacaba"),
Credit(name = "Anton Gyllenberg"),
),
databaseSpecific = GoUrl(url = "https://pkg.go.dev/vuln/GO-2020-0015"),
)package com.saveourtool.osv4k;
import kotlinx.datetime.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public final class GoExamples {
public static OsvSchema<GoUrl, GoImports, Void, Void> go_2020_00115() {
return new OsvSchema<GoUrl, GoImports, Void, Void>(
"1.3.1",
"GO-2020-0015",
new LocalDateTime(2023, 6, 12, 18, 45, 41, 0),
new LocalDateTime(2021, 4, 14, 20, 4, 52, 0),
null,
Arrays.asList("CVE-2020-14040", "GHSA-5rcv-m4m3-hfh7"),
null,
"Infinite loop when decoding some inputs in golang.org/x/text",
"An attacker could provide a single byte to a UTF16 decoder instantiated with UseBOM or ExpectBOM to trigger an infinite loop if the String function on the Decoder is called, or the Decoder is passed to transform.String. If used to parse user supplied input, this may be used as a denial of service vector.",
null,
Arrays.asList(
new Affected<GoImports, Void, Void>(
new Package(
"Go",
"golang.org/x/text",
null
),
null,
Arrays.asList(
new Range<>(
RangeType.SEMVER,
null,
Arrays.asList(
new Event("0", null, null, null),
new Event(null, "0.3.3", null, null)
),
null
)
),
null,
new GoImports(
Arrays.asList(
new GoImport(
"golang.org/x/text/encoding/unicode",
Arrays.asList("bomOverride.Transform", "utf16Decoder.Transform")
),
new GoImport(
"golang.org/x/text/transform",
Arrays.asList("String")
)
)
),
null
)
),
Arrays.asList(
new Reference(ReferenceType.FIX, "https://go.dev/cl/238238"),
new Reference(ReferenceType.FIX, "https://go.googlesource.com/text/+/23ae387dee1f90d29a23c0e87ee0b46038fbed0e" ),
new Reference(ReferenceType.REPORT, "https://go.dev/issue/39491"),
new Reference(ReferenceType.WEB, "https://groups.google.com/g/golang-announce/c/bXVeAmGOqz0")
),
Arrays.asList(
new Credit("@abacabadabacaba", null, null),
new Credit("Anton Gyllenberg", null, null)
),
new GoUrl("https://pkg.go.dev/vuln/GO-2020-0015")
);
}
}Kotlin and Java model for the serialization and deserialization of COSV Schema (extension for OSV).
This library is inspired by the tool detekt/sarif4k.
See the project website for documentation and APIs.
The latest release is available from both GitHub Packages and Maven Central.
dependencies {
implementation("com.saveourtool.cosv4k:cosv4k:1.0.0")
}<dependency>
<groupId>com.saveourtool.cosv4k</groupId>
<artifactId>cosv4k-jvm</artifactId>
<version>1.0.0</version>
</dependency>For GitHub Packages, the repository can be added as follows.
Update build.gradle.kts:
repositories {
maven {
name = "saveourtool/cosv4k"
url = uri("https://maven.pkg.github.com/saveourtool/cosv4k")
content {
includeGroup("com.saveourtool.cosv4k")
}
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
}
}
}Update settings.gradle.kts:
dependencyResolutionManagement {
repositories {
maven {
name = "saveourtool/cosv4k"
url = uri("https://maven.pkg.github.com/saveourtool/cosv4k")
content {
includeGroup("com.saveourtool.cosv4k")
}
credentials {
username = providers.gradleProperty("gpr.user").orNull
?: System.getenv("GITHUB_ACTOR")
password = providers.gradleProperty("gpr.key").orNull
?: System.getenv("GITHUB_TOKEN")
}
}
}
}OSV Schema has extension points for database and ecosystem specific fields:
database_specific.affected[] object:
affected[].ecosystem_specific;affected[].database_specific.affected[].ranges[].database.COSV4K Model implements it using generic type:
/**
* @param D The top level `database_specific`.
* @param A_E `affected[].ecosystem_specific`.
* @param A_D `affected[].database_specific`.
* @param A_R_D `affected[].ranges[].database_specific`.
*/
data class OsvSchema<D, A_D, A_E, A_R_D>Requirements:
kotlinx.serialization.json.JsonObject for KotlinX Serialization or Map<String, Object> for Jackson for raw access to these fields.com.saveourtool.osv4k.RawOsvSchema for KotlinX Serialization which uses kotlinx.serialization.json.JsonObject as a raw type for all generic types.kotlinx.serialization.json.JsonObject (KotlinX Serialization) or Map<String, Object> (Jackson) for required fields.Unit (KotlinX Serialization) or Void (Jackson) for required fields.
Note: if field's type is
UnitorVoidbut any value is provided, serialization exception will be thrown.
Go vulnerability uses OSV schema. Will use GO-2020-0015 as example:
{
"schema_version": "1.3.1",
"id": "GO-2020-0015",
"modified": "2023-06-12T18:45:41Z",
"published": "2021-04-14T20:04:52Z",
"aliases": [
"CVE-2020-14040",
"GHSA-5rcv-m4m3-hfh7"
],
"summary": "Infinite loop when decoding some inputs in golang.org/x/text",
"details": "An attacker could provide a single byte to a UTF16 decoder instantiated with UseBOM or ExpectBOM to trigger an infinite loop if the String function on the Decoder is called, or the Decoder is passed to transform.String. If used to parse user supplied input, this may be used as a denial of service vector.",
"affected": [
{
"package": {
"name": "golang.org/x/text",
"ecosystem": "Go"
},
"ranges": [
{
"type": "SEMVER",
"events": [
{
"introduced": "0"
},
{
"fixed": "0.3.3"
}
]
}
],
"ecosystem_specific": {
"imports": [
{
"path": "golang.org/x/text/encoding/unicode",
"symbols": [
"bomOverride.Transform",
"utf16Decoder.Transform"
]
},
{
"path": "golang.org/x/text/transform",
"symbols": [
"String"
]
}
]
}
}
],
"references": [
{
"type": "FIX",
"url": "https://go.dev/cl/238238"
},
{
"type": "FIX",
"url": "https://go.googlesource.com/text/+/23ae387dee1f90d29a23c0e87ee0b46038fbed0e"
},
{
"type": "REPORT",
"url": "https://go.dev/issue/39491"
},
{
"type": "WEB",
"url": "https://groups.google.com/g/golang-announce/c/bXVeAmGOqz0"
}
],
"credits": [
{
"name": "@abacabadabacaba"
},
{
"name": "Anton Gyllenberg"
}
],
"database_specific": {
"url": "https://pkg.go.dev/vuln/GO-2020-0015"
}
}import com.saveourtool.osv4k.*
import kotlinx.serialization.json.Json
fun readFromFile(content: String) {
val schema: RawOsvSchema = Json.decodeFromString(content)
// do something with OsvSchema
// for example: prints credits
println(schema.credits?.joinToString(", ") { it.name })
// @abacabadabacaba, Anton Gyllenberg
}import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JavaType;
import java.util.stream.Collectors;
class Test {
private static final ObjectMapper objectMapper = new ObjectMapper();
static void readFromFile(final String content) {
final OsvSchema result = objectMapper.readValue(content, OsvSchema.class);
// do something with OsvSchema
// for example: prints credits
System.out.println(result.getCredits().stream().map(Credit::getName).collect(Collectors.joining(", ")));
// @abacabadabacaba, Anton Gyllenberg
}
}Go vulnerability has specific fields. They will be presented by the following classes in our example:
@Serializable
data class GoImports(
val imports: List<GoImport>,
)
@Serializable
data class GoImport(
val path: String,
val symbols: List<String>,
)
@Serializable
data class GoUrl(
val url: String,
)public class GoImports {
private final List<GoImport> imports;
public GoImports(List<GoImport> imports) {
this.imports = imports;
}
public List<GoImport> getImports() {
return Collections.unmodifiableList(imports);
}
}
public class GoImport {
private final String path;
private final List<String> symbols;
public GoImport(String path, List<String> symbols) {
this.path = path;
this.symbols = symbols;
}
public String getPath() {
return path;
}
public List<String> getSymbols() {
return Collections.unmodifiableList(symbols);
}
}
public class GoUrl {
private final String url;
public GoUrl(String url) {
this.url = url;
}
public String getUrl() {
return url;
}
}import com.saveourtool.osv4k.*
import kotlinx.serialization.json.Json
fun readFromFile(content: String) {
val schema: OsvSchema<GoUrl, GoImports, Unit, Unit> = Json.decodeFromString(content)
// do something with OsvSchema
// for example: prints credits
println(schema.credits?.joinToString(", ") { it.name })
// @abacabadabacaba, Anton Gyllenberg
}import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JavaType;
import java.util.stream.Collectors;
class Test {
private static final ObjectMapper objectMapper = new ObjectMapper();
static void readFromFile(final String content) {
final JavaType jacksonType = objectMapper.getTypeFactory()
.constructParametricType(OsvSchema.class, GoUrl.class, GoImports.class, Void.class, Void.class);
final OsvSchema<GoUrl, GoImports, Void, Void> result = objectMapper.readValue(content, jacksonType);
// do something with OsvSchema
// for example: prints credits
System.out.println(result.getCredits().stream().map(Credit::getName).collect(Collectors.joining(", ")));
// @abacabadabacaba, Anton Gyllenberg
}
}val osvSchema = OsvSchema<GoUrl, GoImports, Unit, Unit>(
schemaVersion = "1.3.1",
id = "GO-2020-0015",
modified = LocalDateTime(2023, 6, 12, 18, 45, 41),
published = LocalDateTime(2021, 4, 14, 20, 4, 52),
aliases = listOf("CVE-2020-14040", "GHSA-5rcv-m4m3-hfh7"),
summary = "Infinite loop when decoding some inputs in golang.org/x/text",
details = "An attacker could provide a single byte to a UTF16 decoder instantiated with UseBOM or ExpectBOM to trigger an infinite loop if the String function on the Decoder is called, or the Decoder is passed to transform.String. If used to parse user supplied input, this may be used as a denial of service vector.",
affected = listOf(
Affected(
`package` = Package(
ecosystem = "Go",
name = "golang.org/x/text",
),
ranges = listOf(
Range(
type = RangeType.SEMVER,
events = listOf(
Event(introduced = "0"),
Event(fixed = "0.3.3"),
),
),
),
ecosystemSpecific = GoImports(
imports = listOf(
GoImport(
path = "golang.org/x/text/encoding/unicode",
symbols = listOf("bomOverride.Transform", "utf16Decoder.Transform"),
),
GoImport(
path = "golang.org/x/text/transform",
symbols = listOf("String"),
),
),
),
)
),
references = listOf(
Reference(
type = ReferenceType.FIX,
url = "https://go.dev/cl/238238",
),
Reference(
type = ReferenceType.FIX,
url = "https://go.googlesource.com/text/+/23ae387dee1f90d29a23c0e87ee0b46038fbed0e",
),
Reference(
type = ReferenceType.REPORT,
url = "https://go.dev/issue/39491",
),
Reference(
type = ReferenceType.WEB,
url = "https://groups.google.com/g/golang-announce/c/bXVeAmGOqz0",
),
),
credits = listOf(
Credit(name = "@abacabadabacaba"),
Credit(name = "Anton Gyllenberg"),
),
databaseSpecific = GoUrl(url = "https://pkg.go.dev/vuln/GO-2020-0015"),
)package com.saveourtool.osv4k;
import kotlinx.datetime.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public final class GoExamples {
public static OsvSchema<GoUrl, GoImports, Void, Void> go_2020_00115() {
return new OsvSchema<GoUrl, GoImports, Void, Void>(
"1.3.1",
"GO-2020-0015",
new LocalDateTime(2023, 6, 12, 18, 45, 41, 0),
new LocalDateTime(2021, 4, 14, 20, 4, 52, 0),
null,
Arrays.asList("CVE-2020-14040", "GHSA-5rcv-m4m3-hfh7"),
null,
"Infinite loop when decoding some inputs in golang.org/x/text",
"An attacker could provide a single byte to a UTF16 decoder instantiated with UseBOM or ExpectBOM to trigger an infinite loop if the String function on the Decoder is called, or the Decoder is passed to transform.String. If used to parse user supplied input, this may be used as a denial of service vector.",
null,
Arrays.asList(
new Affected<GoImports, Void, Void>(
new Package(
"Go",
"golang.org/x/text",
null
),
null,
Arrays.asList(
new Range<>(
RangeType.SEMVER,
null,
Arrays.asList(
new Event("0", null, null, null),
new Event(null, "0.3.3", null, null)
),
null
)
),
null,
new GoImports(
Arrays.asList(
new GoImport(
"golang.org/x/text/encoding/unicode",
Arrays.asList("bomOverride.Transform", "utf16Decoder.Transform")
),
new GoImport(
"golang.org/x/text/transform",
Arrays.asList("String")
)
)
),
null
)
),
Arrays.asList(
new Reference(ReferenceType.FIX, "https://go.dev/cl/238238"),
new Reference(ReferenceType.FIX, "https://go.googlesource.com/text/+/23ae387dee1f90d29a23c0e87ee0b46038fbed0e" ),
new Reference(ReferenceType.REPORT, "https://go.dev/issue/39491"),
new Reference(ReferenceType.WEB, "https://groups.google.com/g/golang-announce/c/bXVeAmGOqz0")
),
Arrays.asList(
new Credit("@abacabadabacaba", null, null),
new Credit("Anton Gyllenberg", null, null)
),
new GoUrl("https://pkg.go.dev/vuln/GO-2020-0015")
);
}
}