Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.squareup.moshi.Json;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import datadog.trace.api.Config;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
Expand All @@ -19,7 +21,7 @@
import org.slf4j.LoggerFactory;

/**
* Loads {@code sca_cves.json} from the classpath and builds the runtime index used by {@link
* Loads the bundled or configured CVE catalog and builds the runtime index used by {@link
* ScaReachabilityTransformer}.
*
* <p>The primary index maps JVM internal class names (slashes) to the list of {@link ScaEntry}
Expand All @@ -39,26 +41,35 @@ private ScaCveDatabase(Map<String, List<ScaEntry>> index) {
}

/**
* Loads and parses {@code sca_cves.json} from the classpath.
* Loads the local JSON file selected by {@code _DD_APPSEC_SCA_CVE_FILE}, or the bundled {@code
* sca_cves.json} when unset. The override uses the same schema and replaces the bundle without
* merging. A missing or malformed override produces an empty database without fallback.
*
* @return a populated database, or an empty one if the resource is missing or malformed
* @return a populated database, or an empty one if the selected source cannot be loaded
*/
public static ScaCveDatabase load() {
InputStream stream = ScaCveDatabase.class.getResourceAsStream(RESOURCE_PATH);
if (stream == null) {
log.info(
"SCA Reachability: {} not found on classpath - no vulnerabilities will be tracked",
RESOURCE_PATH);
return new ScaCveDatabase(Collections.emptyMap());
}
// "UTF-8" string literal - java.nio.* is forbidden during premain
try (InputStreamReader reader = new InputStreamReader(stream, "UTF-8")) {
return parse(reader);
return load(Config.get().getAppSecScaCveFile());
}

static ScaCveDatabase load(@Nullable String cveFile) {
String source = cveFile == null ? RESOURCE_PATH : cveFile;
try (InputStream stream =
cveFile == null
? ScaCveDatabase.class.getResourceAsStream(RESOURCE_PATH)
: new FileInputStream(cveFile)) {
if (stream == null) {
log.info(
"SCA Reachability: {} not found on classpath - no vulnerabilities will be tracked",
RESOURCE_PATH);
return new ScaCveDatabase(Collections.emptyMap());
}
// "UTF-8" string literal - java.nio.* is forbidden during premain
try (InputStreamReader reader = new InputStreamReader(stream, "UTF-8")) {
return parse(reader);
}
} catch (Exception e) {
log.error(
"SCA Reachability: failed to parse {} - no vulnerabilities will be tracked",
RESOURCE_PATH,
e);
"SCA Reachability: failed to load {} - no vulnerabilities will be tracked", source, e);
return new ScaCveDatabase(Collections.emptyMap());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* <p>Responsibilities:
*
* <ol>
* <li>Load {@code sca_cves.json} from the classpath.
* <li>Load the bundled CVE catalog or the configured local file.
* <li>Build the class-name index.
* <li>Register {@link ScaReachabilityTransformer} with the JVM.
* <li>Scan already-loaded classes so that libraries loaded before agent startup are detected.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class ScaCveDatabaseTest {

Expand All @@ -24,6 +30,44 @@ class ScaCveDatabaseTest {
+ "{\"class\":\"com/example/Bar\",\"method\":\"dangerousOp\"}"
+ "]}]}";

@TempDir Path tempDir;

@Test
void fileOverrideReplacesBundledCatalog() throws Exception {
Path catalog = tempDir.resolve("advisories.json");
Files.write(catalog, MINIMAL_JSON.getBytes(StandardCharsets.UTF_8));

ScaCveDatabase db = ScaCveDatabase.load(catalog.toString());

assertEquals(2, db.size());
assertEquals("GHSA-test-1234-5678", db.entriesForClass("com/example/Foo").get(0).vulnId());
assertNull(db.entriesForClass("com/github/junrar/LocalFolderExtractor"));
}

@ParameterizedTest
@ValueSource(strings = {"{\"version\":1,\"entries\":[]}", "{invalid json", ""})
void emptyOrMalformedOverrideDoesNotFallBack(String contents) throws Exception {
Path catalog = tempDir.resolve("advisories.json");
Files.write(catalog, contents.getBytes(StandardCharsets.UTF_8));

assertTrue(ScaCveDatabase.load(catalog.toString()).isEmpty());
}

@Test
void missingOverrideDoesNotFallBack() {
assertTrue(ScaCveDatabase.load(tempDir.resolve("missing.json").toString()).isEmpty());
}

@Test
void directoryOverrideDoesNotFallBack() {
assertTrue(ScaCveDatabase.load(tempDir.toString()).isEmpty());
}

@Test
void emptyOverridePathDoesNotFallBack() {
assertTrue(ScaCveDatabase.load("").isEmpty());
}

@Test
void loadsFromJson() throws Exception {
ScaCveDatabase db = ScaCveDatabase.parse(new StringReader(MINIMAL_JSON));
Expand Down
10 changes: 10 additions & 0 deletions internal-api/src/main/java/datadog/trace/api/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,7 @@ public static String getHostName() {
private final String appSecHttpBlockedTemplateJson;
private final UserIdCollectionMode appSecUserIdCollectionMode;
private final Boolean appSecScaEnabled;
private final String appSecScaCveFile;
private final int appSecScaMaxTrackedDependencies;
private final boolean appSecStackTraceEnabled;
private final int appSecMaxStackTraces;
Expand Down Expand Up @@ -2633,6 +2634,8 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment())
configProvider.getStringNotEmpty(APPSEC_AUTO_USER_INSTRUMENTATION_MODE, null),
configProvider.getStringNotEmpty(APPSEC_AUTOMATED_USER_EVENTS_TRACKING, null));
appSecScaEnabled = configProvider.getBoolean(APPSEC_SCA_ENABLED);
// Internal override: preserve the underscore prefix and exclude it from config telemetry.
appSecScaCveFile = ConfigHelper.env("_DD_APPSEC_SCA_CVE_FILE");
appSecScaMaxTrackedDependencies =
configProvider.getInteger(
APPSEC_SCA_MAX_TRACKED_DEPENDENCIES, DEFAULT_APPSEC_SCA_MAX_TRACKED_DEPENDENCIES);
Expand Down Expand Up @@ -6255,6 +6258,11 @@ public boolean isAppSecScaEnabled() {
return appSecScaEnabled != null && appSecScaEnabled;
}

/** Returns the local SCA catalog override, or null to use the bundled catalog. */
public String getAppSecScaCveFile() {
return appSecScaCveFile;
}

public int getAppSecScaMaxTrackedDependencies() {
return appSecScaMaxTrackedDependencies;
}
Expand Down Expand Up @@ -7125,6 +7133,8 @@ public String toString() {
+ telemetryMetricsEnabled
+ ", appSecScaEnabled="
+ appSecScaEnabled
+ ", appSecScaCveFile="
+ appSecScaCveFile
+ ", appSecScaMaxTrackedDependencies="
+ appSecScaMaxTrackedDependencies
+ ", appSecRaspEnabled="
Expand Down
18 changes: 18 additions & 0 deletions internal-api/src/test/java/datadog/trace/api/ConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3173,6 +3173,24 @@ void appsecStateWithSysAndEnv(String sys, String env, ProductActivation expected
assertEquals(expected, config.getAppSecActivation());
}

@Test
void appSecScaCveFileDefaultsToNull() throws Exception {
Config config = newConfig();

assertNull(config.getAppSecScaCveFile());
assertFalse(config.isAppSecScaEnabled());
}

@ParameterizedTest
@ValueSource(strings = {"/tmp/sca-catalog.json", ""})
void appSecScaCveFileUsesPrivateEnvironmentVariable(String path) throws Exception {
setEnv("_DD_APPSEC_SCA_CVE_FILE", path);
Config config = newConfig();

assertEquals(path, config.getAppSecScaCveFile());
assertFalse(config.isAppSecScaEnabled());
}

@Test
void hostnameDiscoveryWithEnvironmentVariables() {
String expectedHostname = "myhostname";
Expand Down
8 changes: 8 additions & 0 deletions metadata/supported-configurations.json
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,14 @@
"aliases": []
}
],
"_DD_APPSEC_SCA_CVE_FILE": [
{
"version": "A",
"type": "string",
"default": null,
"aliases": []
}
],
"DD_APPSEC_SCA_ENABLED": [
{
"version": "B",
Expand Down
Loading