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
1 change: 1 addition & 0 deletions backend/apps/api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dependencies {
implementation(project(":libs:persistence"))
implementation(project(":libs:github-client"))
implementation(project(":libs:scanning"))
implementation(project(":libs:enrichment"))

implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication(
scanBasePackages = {
"dev.cleat.api",
"dev.cleat.persistence",
"dev.cleat.domain",
"dev.cleat.common",
"dev.cleat.scanning"
"dev.cleat.scanning",
"dev.cleat.enrichment"
})
@EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository")
@EntityScan(basePackages = "dev.cleat.persistence.entity")
@EnableScheduling
Comment thread
Arzu-N marked this conversation as resolved.
public class CleatApiApplication {
public static void main(String[] args) {
SpringApplication.run(CleatApiApplication.class);
Expand Down
4 changes: 4 additions & 0 deletions backend/apps/api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ management:
enabled: true
redis:
enabled: true

feed:
sync:
delay: 3600000
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"dev.cleat.scanning",
"dev.cleat.worker",
"dev.cleat.domain",
"dev.cleat.common"
"dev.cleat.common",
"dev.cleat.enrichment"
})
@EnableJpaRepositories(basePackages = "dev.cleat.persistence.repository")
@EntityScan(basePackages = "dev.cleat.persistence.entity")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dev.cleat.worker.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
public class SchedulingConfig {

@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(4);
scheduler.setThreadNamePrefix("scheduler-");
scheduler.initialize();
return scheduler;
}
}
6 changes: 5 additions & 1 deletion backend/apps/worker/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ management:

app:
security:
scan-interval: 600000
scan-interval: 600000

feed:
sync:
delay: 3600000
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class VulnerabilityRequestDto {
private Reachable reachable;
private UUID advisoryId;
private String cwe;
private String cve;
private String title;
private List<String> affectedRepos;
private Boolean hasFixPr;
Expand All @@ -36,6 +37,7 @@ public VulnerabilityRequestDto(
Reachable reachable,
UUID advisoryId,
String cwe,
String cve,
String title,
List<String> affectedRepos,
Boolean hasFixPr) {
Expand All @@ -51,6 +53,7 @@ public VulnerabilityRequestDto(
this.reachable = reachable;
this.advisoryId = advisoryId;
this.cwe = cwe;
this.cve = cve;
this.title = title;
this.affectedRepos = affectedRepos;
this.hasFixPr = hasFixPr;
Expand Down Expand Up @@ -155,6 +158,15 @@ public VulnerabilityRequestDto setCwe(String cwe) {
return this;
}

public String getCve() {
return cve;
}

public VulnerabilityRequestDto setCve(String cve) {
this.cve = cve;
return this;
}

public String getTitle() {
return title;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,55 @@ public class VulnerabilityResponseDto {
private Reachable reachable;
private UUID advisoryId;
private String cwe;
private String cve;
private String title;
private List<String> affectedRepos;
private Boolean hasFixPr;
private OffsetDateTime publishedAt;

public VulnerabilityResponseDto() {}

public VulnerabilityResponseDto(
UUID id,
UUID accountId,
String packageName,
String ecosystem,
String currentVersion,
String fixedVersion,
Double cvss,
Severity severity,
Priority priority,
Double epss,
Boolean kev,
Reachable reachable,
UUID advisoryId,
String cwe,
String cve,
String title,
List<String> affectedRepos,
Boolean hasFixPr,
OffsetDateTime publishedAt) {
this.id = id;
this.accountId = accountId;
this.packageName = packageName;
this.ecosystem = ecosystem;
this.currentVersion = currentVersion;
this.fixedVersion = fixedVersion;
this.cvss = cvss;
this.severity = severity;
this.priority = priority;
this.epss = epss;
this.kev = kev;
this.reachable = reachable;
this.advisoryId = advisoryId;
this.cwe = cwe;
this.cve = cve;
this.title = title;
this.affectedRepos = affectedRepos;
this.hasFixPr = hasFixPr;
this.publishedAt = publishedAt;
}

public UUID getId() {
return id;
}
Expand Down Expand Up @@ -153,6 +197,15 @@ public VulnerabilityResponseDto setCwe(String cwe) {
return this;
}

public String getCve() {
return cve;
}

public VulnerabilityResponseDto setCve(String cve) {
this.cve = cve;
return this;
}

public String getTitle() {
return title;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dev.cleat.common.exception;

public class FeedSyncException extends RuntimeException {
public FeedSyncException(String msg) {
super(msg);
}

public FeedSyncException(String msg, Throwable cause) {
super(msg, cause);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package dev.cleat.domain;

import dev.cleat.common.enums.Priority;
import dev.cleat.common.enums.Reachable;
import dev.cleat.common.enums.Severity;
import dev.cleat.domain.model.Vulnerability;
import org.springframework.stereotype.Component;

Expand All @@ -11,14 +13,18 @@ public Priority calculate(Vulnerability vulnerability) {
if (vulnerability.cvss() < 0 || vulnerability.cvss() > 10) {
throw new IllegalArgumentException("CVSS must be between 0 and 10");
}
if (vulnerability.kev() || vulnerability.cvss() >= 9.0) {
if (vulnerability.kev() || vulnerability.cvss() >= 9.0 && vulnerability.reachable() == Reachable.REACHABLE) {
return Priority.URGENT;
}

return switch (vulnerability.severity()) {
case CRITICAL -> Priority.HIGH;
case HIGH -> Priority.MEDIUM;
default -> Priority.LOW;
};
if (vulnerability.epss() > 0.1 || vulnerability.severity() == Severity.CRITICAL) {
return Priority.HIGH;
}

if (vulnerability.cvss() >= 7.0) {
return Priority.MEDIUM;
}

return Priority.LOW;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package dev.cleat.domain.model;

import dev.cleat.common.enums.Reachable;
import dev.cleat.common.enums.Severity;
import java.util.Objects;

public record Vulnerability(Boolean kev, Double cvss, Severity severity) {
public record Vulnerability(Boolean kev, Double cvss, Severity severity, Double epss, Reachable reachable) {
public Vulnerability {
Objects.requireNonNull(cvss, "cvss cannot be null");
Objects.requireNonNull(severity, "severity cannot be null");

kev = (kev != null) ? kev : false;
epss = (epss != null) ? epss : 0.0;
reachable = (reachable != null) ? reachable : Reachable.UNKNOWN;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package dev.cleat.enrichment.client;

import dev.cleat.enrichment.dto.EpssResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class EpssClient {
private final RestTemplate restTemplate;

public EpssClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}

private static final String URL = "https://api.first.org/data/v1/epss?cve={cve}";

public Double fetchScore(String cve) {

EpssResponse response = restTemplate.getForObject(URL, EpssResponse.class, cve);
if (response == null || response.getData() == null || response.getData().isEmpty()) {
return null;
}
return response.getData().getFirst().getEpss();
}
Comment thread
Arzu-N marked this conversation as resolved.

public EpssResponse fetchFeed(String cve) {
return restTemplate.getForObject(URL, EpssResponse.class, cve);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package dev.cleat.enrichment.client;

import dev.cleat.enrichment.dto.KevResponse;
import dev.cleat.enrichment.dto.KevVulnerability;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class KevClient {
private final RestTemplate restTemplate;
private Set<String> kevCache;
private Long cacheTimestamp;
private static final long CACHE_TTL_MS = 3600_000L;

public KevClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}

private static final String URL =
"https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";

public synchronized Set<String> getKevCache() {

if (kevCache == null || System.currentTimeMillis() - cacheTimestamp > CACHE_TTL_MS) {
KevResponse response = restTemplate.getForObject(URL, KevResponse.class);
kevCache = (response == null || response.getVulnerabilities() == null)
? Set.of()
: response.getVulnerabilities().stream()
.map(KevVulnerability::getCveId)
.collect(Collectors.toSet());
cacheTimestamp = System.currentTimeMillis();
}
return kevCache;
}

public boolean isKev(String cve) {
return getKevCache().contains(cve);
}

public KevResponse fetchFeed() {
return restTemplate.getForObject(URL, KevResponse.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dev.cleat.enrichment.client;

import dev.cleat.enrichment.dto.OsvPackage;
import dev.cleat.enrichment.dto.OsvRequest;
import dev.cleat.enrichment.dto.OsvResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class OsvClient {
private final RestTemplate restTemplate;

public OsvClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

private static final String URL = "https://api.osv.dev/v1/query";

public OsvResponse query(String packageName, String ecosystem) {
OsvPackage osvPackage = new OsvPackage().setName(packageName).setEcosystem(ecosystem);

OsvRequest osvRequest = new OsvRequest().setPkg(osvPackage);

return restTemplate.postForObject(URL, osvRequest, OsvResponse.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.cleat.enrichment.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {

@Bean
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(4000);
factory.setReadTimeout(10000);
return new RestTemplate(factory);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Loading
Loading