Skip to content

Commit 8133d8a

Browse files
authored
Merge branch 'master' into dougqh/set-benchmark
2 parents e1a6de3 + e292db6 commit 8133d8a

600 files changed

Lines changed: 7832 additions & 3507 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

buildSrc/src/main/kotlin/datadog/gradle/plugin/instrument/BuildTimeInstrumentationPlugin.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ class BuildTimeInstrumentationPlugin : Plugin<Project> {
5656
project.extensions.create<BuildTimeInstrumentationExtension>("buildTimeInstrumentation")
5757

5858
project.configurations.register(BUILD_TIME_INSTRUMENTATION_PLUGIN_CONFIGURATION) {
59-
isVisible = false
6059
isCanBeConsumed = false
6160
isCanBeResolved = true
6261
}

buildSrc/src/main/kotlin/datadog/gradle/plugin/sca/ScaEnrichmentsPlugin.kt

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import datadog.gradle.sca.GhsaEnrichmentParser
44
import groovy.json.JsonOutput
55
import groovy.json.JsonSlurper
66
import java.net.HttpURLConnection
7-
import java.net.URL
7+
import java.net.URI
88
import org.gradle.api.GradleException
99
import org.gradle.api.Plugin
1010
import org.gradle.api.Project
1111

1212
/**
13-
* Registers the [generateScaCvesJson] task that downloads GHSA enrichments from
14-
* `sca-reachability-database` and generates `sca_cves.json` bundled in the appsec JAR.
13+
* Registers the [generateScaCvesJson] task that downloads GHSA symbol files from
14+
* `sca-reachability-symbols` and generates `sca_cves.json` bundled in the appsec JAR.
1515
*
1616
* This is a **temporary** build-time approach. The symbol database will be delivered
1717
* via Remote Config in a future iteration, at which point this plugin and the committed
@@ -25,7 +25,7 @@ class ScaEnrichmentsPlugin : Plugin<Project> {
2525

2626
companion object {
2727
private const val SCA_ENRICHMENTS_API_DEFAULT =
28-
"https://api.github.com/repos/DataDog/sca-reachability-database/contents/enrichments"
28+
"https://api.github.com/repos/DataDog/sca-reachability-symbols/contents/jvm"
2929
}
3030

3131
override fun apply(project: Project) {
@@ -34,7 +34,7 @@ class ScaEnrichmentsPlugin : Plugin<Project> {
3434
val generateTask =
3535
project.tasks.register("generateScaCvesJson") {
3636
description =
37-
"Downloads GHSA enrichments from sca-reachability-database and updates " +
37+
"Downloads GHSA symbol files from sca-reachability-symbols and updates " +
3838
"src/main/resources/sca_cves.json. Run with -PrefreshSca to force a refresh. " +
3939
"Override the source URL with -PscaEnrichmentsUrl=<url>. " +
4040
"sca_cves.json is committed to the repo so CI does not need network access."
@@ -51,27 +51,26 @@ class ScaEnrichmentsPlugin : Plugin<Project> {
5151
val apiUrl =
5252
project.findProperty("scaEnrichmentsUrl")?.toString() ?: SCA_ENRICHMENTS_API_DEFAULT
5353

54-
logger.lifecycle("Fetching GHSA enrichment index from $apiUrl ...")
54+
logger.lifecycle("Fetching GHSA symbol index from $apiUrl ...")
5555
@Suppress("UNCHECKED_CAST")
5656
val fileList = githubFetch(apiUrl, token) as List<Map<String, Any>>
5757
val ghsaFiles =
5858
fileList.filter {
5959
it["name"]?.toString()?.endsWith(".json") == true && it["type"] == "file"
6060
}
61-
logger.lifecycle("Found ${ghsaFiles.size} enrichment files")
61+
logger.lifecycle("Found ${ghsaFiles.size} symbol files")
6262

6363
val entries = mutableListOf<Any>()
6464
ghsaFiles.forEach { fileInfo ->
65-
val ghsaId = fileInfo["name"]!!.toString().removeSuffix(".json")
6665
val rawContent = githubFetchRaw(fileInfo["download_url"]!!.toString(), token)
67-
entries.addAll(GhsaEnrichmentParser.parse(ghsaId, rawContent))
66+
entries.addAll(GhsaEnrichmentParser.parse(rawContent))
6867
}
6968

7069
outputFile.writeText(JsonOutput.toJson(mapOf("version" to 1, "entries" to entries)))
7170
logger.lifecycle(
7271
"sca_cves.json: ${entries.size} entries from ${ghsaFiles.size} GHSA files")
7372
logger.lifecycle(
74-
"Remember to commit src/main/resources/sca_cves.json after updating the database.")
73+
"Remember to commit src/main/resources/sca_cves.json after updating the symbols.")
7574
}
7675
}
7776

@@ -90,7 +89,7 @@ class ScaEnrichmentsPlugin : Plugin<Project> {
9089
}
9190

9291
private fun githubConnect(url: String, token: String?): HttpURLConnection {
93-
val connection = URL(url).openConnection() as HttpURLConnection
92+
val connection = URI.create(url).toURL().openConnection() as HttpURLConnection
9493
connection.setRequestProperty("Accept", "application/vnd.github+json")
9594
connection.setRequestProperty("X-GitHub-Api-Version", "2022-11-28")
9695
if (!token.isNullOrEmpty()) {

buildSrc/src/main/kotlin/datadog/gradle/sca/GhsaEnrichmentParser.kt

Lines changed: 55 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,78 +4,90 @@ import com.fasterxml.jackson.databind.JsonNode
44
import com.fasterxml.jackson.databind.ObjectMapper
55

66
/**
7-
* Parses GHSA enrichment JSON files from the sca-reachability-database into the internal
7+
* Parses GHSA symbol JSON files from the sca-reachability-symbols repository into the internal
88
* sca_cves.json format consumed by SCA Reachability at runtime.
99
*
1010
* Key transformations:
11-
* - Filters entries to JVM language only
12-
* - Expands multi-package GHSA entries into N records (one per Maven artifact), because
13-
* each artifact may have different version ranges for the same set of class symbols
11+
* - Filters entries to Maven ecosystem only (lang == "maven")
12+
* - Each array entry maps 1:1 to one sca_cves.json record (one dependency_name per entry)
13+
* - Parses target strings "package:ClassName.method" using lastIndexOf to split at the colon
14+
* and lastIndexOf on the class+method part to split class from method
1415
* - Converts class FQNs to JVM internal format (slashes) so the ClassFileTransformer
15-
* can do O(1) map lookups without per-class string conversion
16-
* - Sets method=null for all symbols — field exists for forward compatibility when the
17-
* database adds method-level symbols in the future (see APPSEC-62260)
16+
* can do O(1) map lookups without per-class string conversion at runtime
1817
*/
1918
object GhsaEnrichmentParser {
2019

2120
private val mapper = ObjectMapper()
2221

2322
/**
24-
* Parses a single GHSA enrichment file.
23+
* Parses a single GHSA symbols file.
2524
*
26-
* @param ghsaId the GHSA identifier (e.g. "GHSA-645p-88qh-w398"), used as vuln_id
27-
* @param jsonContent the raw JSON content of the enrichment file
25+
* @param jsonContent the raw JSON content of the symbols file
2826
* @return list of sca_cves.json entry maps, one per affected Maven artifact
2927
*/
30-
fun parse(ghsaId: String, jsonContent: String): List<Map<String, Any?>> {
28+
fun parse(jsonContent: String): List<Map<String, Any?>> {
3129
val root = mapper.readTree(jsonContent)
32-
require(root.isArray) { "GHSA enrichment file $ghsaId must be a JSON array, got ${root.nodeType}" }
30+
require(root.isArray) { "GHSA enrichment file must be a JSON array, got ${root.nodeType}" }
3331

3432
val entries = mutableListOf<Map<String, Any?>>()
3533

3634
for (entry in root) {
37-
if (entry.path("language").asText() != "jvm") continue
35+
if (entry.path("lang").asText() != "maven") continue
3836

39-
val symbols = extractSymbols(entry)
40-
if (symbols.isEmpty()) continue
37+
val ghsaId =
38+
entry.path("vulnerability").path("id").asText().takeIf { it.isNotEmpty() } ?: continue
39+
val artifact = entry.path("dependency_name").asText().takeIf { it.isNotEmpty() } ?: continue
40+
val versionRanges = entry.path("package_versions").map { it.asText() }
4141

42-
for (pkg in entry.path("package")) {
43-
if (pkg.path("ecosystem").asText() != "maven") continue
44-
val artifact = pkg.path("name").asText().takeIf { it.isNotEmpty() } ?: continue
45-
val versionRanges = pkg.path("version_range").map { it.asText() }
42+
val symbols = extractTargets(entry)
43+
if (symbols.isEmpty()) continue
4644

47-
entries += mapOf(
48-
"vuln_id" to ghsaId,
49-
"artifact" to artifact,
50-
"version_ranges" to versionRanges,
51-
"symbols" to symbols,
52-
)
53-
}
45+
entries +=
46+
mapOf(
47+
"vuln_id" to ghsaId,
48+
"artifact" to artifact,
49+
"version_ranges" to versionRanges,
50+
"symbols" to symbols,
51+
)
5452
}
5553

5654
return entries
5755
}
5856

59-
private fun extractSymbols(entry: JsonNode): List<Map<String, Any?>> {
57+
/**
58+
* Parses the targets array from a GHSA entry.
59+
*
60+
* Each target string has the format "package:ClassName.method". Parsing uses
61+
* lastIndexOf(':') to split package from class+method, then lastIndexOf('.') on the
62+
* class+method part to split class name from method name. Malformed targets (missing ':'
63+
* or missing '.' after ':') are silently skipped.
64+
*
65+
* Targets within one entry may come from different packages; no assumption is made that
66+
* all targets share a common package prefix.
67+
*
68+
* TODO(APPSEC-62260): if the database adds inner-class targets (e.g. "pkg:Outer.Inner.method"),
69+
* the current replace('.', '/') will produce pkg/Outer/Inner instead of the correct
70+
* pkg/Outer$Inner. Update when the database team defines the inner-class format.
71+
*/
72+
private fun extractTargets(entry: JsonNode): List<Map<String, Any?>> {
6073
val symbols = mutableListOf<Map<String, Any?>>()
61-
val imports = entry.path("ecosystem_specific").path("imports")
62-
if (imports.isMissingNode || !imports.isArray) return symbols
74+
val targets = entry.path("targets")
75+
if (targets.isMissingNode || !targets.isArray) return symbols
6376

64-
for (importGroup in imports) {
65-
for (symbol in importGroup.path("symbols")) {
66-
if (symbol.path("type").asText() != "class") continue
67-
val pkg = symbol.path("value").asText().takeIf { it.isNotEmpty() } ?: continue
68-
val name = symbol.path("name").asText().takeIf { it.isNotEmpty() } ?: continue
77+
for (target in targets) {
78+
val t = target.asText().takeIf { it.isNotEmpty() } ?: continue
79+
val colonIdx = t.lastIndexOf(':')
80+
if (colonIdx < 0) continue
81+
val pkg = t.substring(0, colonIdx)
82+
val classAndMethod = t.substring(colonIdx + 1)
83+
val dotIdx = classAndMethod.lastIndexOf('.')
84+
if (dotIdx < 0) continue
85+
val simpleClass = classAndMethod.substring(0, dotIdx)
86+
val method = classAndMethod.substring(dotIdx + 1)
87+
if (pkg.isEmpty() || simpleClass.isEmpty() || method.isEmpty()) continue
6988

70-
// JVM internal format (slashes) — avoids per-class conversion in the
71-
// ClassFileTransformer hot path at runtime.
72-
// TODO(APPSEC-62260): verify inner-class format when database adds method-level symbols.
73-
// If GHSA uses dot notation for inner classes (e.g. name="Outer.Inner"), the replace below
74-
// produces com/example/Outer/Inner instead of the correct com/example/Outer$Inner.
75-
// When the database team defines the format, update this to handle the $ separator.
76-
val internalName = "$pkg.$name".replace('.', '/')
77-
symbols += mapOf("class" to internalName, "method" to null)
78-
}
89+
val internalName = "$pkg.$simpleClass".replace('.', '/')
90+
symbols += mapOf("class" to internalName, "method" to method)
7991
}
8092

8193
return symbols

buildSrc/src/main/kotlin/dd-trace-java.gradle-debug.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ fun getJdkFromCompilerOptions(co: CompileOptions): String? {
4141
fun printJdkForProjectTasks(project: Project, logFile: File) {
4242
project.tasks.forEach { task ->
4343
val data = mutableMapOf<String, String>()
44-
data["task"] = task.path.toString()
44+
data["task"] = task.path
4545
if (task is JavaExec) {
4646
val launcher = task.javaLauncher.get()
4747
data["jdk"] = launcher.metadata.languageVersion.toString()

0 commit comments

Comments
 (0)