Skip to content

Commit

Permalink
added transitive vulnerability (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
rajpreet-s authored Aug 14, 2024
1 parent 64814eb commit ce56e9d
Show file tree
Hide file tree
Showing 13 changed files with 247 additions and 45 deletions.
3 changes: 2 additions & 1 deletion src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import { DebrickedCommands } from "./debricked_cli";
import { Messages } from "./messages";
import { MessageStatus } from "./messageStatus";
import { Organization } from "./organization";
import { SecondService } from "./secondService";

export { DebrickedCommands, Organization, Messages, MessageStatus };
export { DebrickedCommands, Organization, Messages, MessageStatus, SecondService };
9 changes: 3 additions & 6 deletions src/constants/organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ import * as os from "os";

export class Organization {
static readonly name = "debricked";
static readonly apiVersion = "1.0";
static readonly debrickedBaseUrl = "https://debricked.com";
static readonly baseUrl = `${Organization.debrickedBaseUrl}/api/${Organization.apiVersion}/`;

static readonly dependencyUrl = "open/dependencies/get-dependencies-hierarchy";
static readonly vulnerableUrl = "open/vulnerabilities/get-vulnerabilities";

static readonly nameCaps = "Debricked";
// Command and OS-specific constants
Expand Down Expand Up @@ -59,6 +53,9 @@ export class Organization {
static readonly bearerTokenKey = "bearerToken";
static readonly userId = "userId";

// Markdown
static readonly separator = "\n______________________________\n";

// Messages
static readonly UNSUPPORTED_OS = "Unsupported operating system";

Expand Down
9 changes: 9 additions & 0 deletions src/constants/secondService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export class SecondService {
static readonly apiVersion = "1.0";
static readonly debrickedBaseUrl = "https://debricked.com";
static readonly baseUrl = `${SecondService.debrickedBaseUrl}/api/${SecondService.apiVersion}/`;

static readonly dependencyUrl = "open/dependencies/get-dependencies-hierarchy";
static readonly vulnerableUrl = "open/vulnerabilities/get-vulnerabilities";
static readonly repositoryBaseUrl = "https://debricked.com/app/en/repository/";
}
4 changes: 2 additions & 2 deletions src/helpers/apiHelper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ApiClient } from "./apiClient";
import { RequestParam } from "../types";
import { Logger } from "./loggerHelper";
import { Organization } from "../constants/index";
import { SecondService } from "../constants";

export class ApiHelper {
constructor(
Expand All @@ -10,7 +10,7 @@ export class ApiHelper {
) {}

public async get(requestParam: RequestParam): Promise<any> {
let url = `${Organization.baseUrl}${requestParam.endpoint}`;
let url = `${SecondService.baseUrl}${requestParam.endpoint}`;

const params = [];
if (requestParam.page) {
Expand Down
4 changes: 3 additions & 1 deletion src/helpers/fileHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { MessageStatus, Organization } from "../constants/index";
import { Logger } from "./loggerHelper";
import { DebrickedDataHelper } from "./debrickedDataHelper";
import { GlobalStore } from "./globalStore";
import { ScannedData } from "types/scannedData";

export class FileHelper {
constructor(
Expand Down Expand Up @@ -41,7 +42,7 @@ export class FileHelper {
}

public async setRepoID() {
const data = JSON.parse(
const data: ScannedData = JSON.parse(
fs.readFileSync(`${Organization.reportsFolderPath}/scan-output.json`, {
encoding: "utf8",
flag: "r",
Expand All @@ -55,6 +56,7 @@ export class FileHelper {

repoId ? this.globalStore.setRepoId(repoId) : null;
commitId ? this.globalStore.setCommitId(commitId) : null;
this.globalStore.setScanData(data);

this.logger.logInfo("Found the repoId and commitId");
}
Expand Down
20 changes: 20 additions & 0 deletions src/helpers/globalStore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Dependency } from "types/dependency";
import { MessageStatus } from "../constants/index";
import { GlobalState } from "./globalState";
import { ScannedData } from "types/scannedData";
import { DependencyVulnerability } from "types/vulnerability";

export class GlobalStore {
private static instance: GlobalStore;
Expand All @@ -11,6 +13,8 @@ export class GlobalStore {
private repoData: any;
private repoId!: number;
private commitId!: number;
private scannedData!: ScannedData;
private vulnerableData!: Map<string, DependencyVulnerability[]>;

private constructor() {}

Expand Down Expand Up @@ -104,4 +108,20 @@ export class GlobalStore {
public setCommitId(commitId: number) {
this.commitId = commitId;
}

public setScanData(data: ScannedData) {
this.scannedData = data;
}

public getScanData(): ScannedData {
return this.scannedData;
}

public setVulnerableData(data: Map<string, DependencyVulnerability[]>) {
this.vulnerableData = data;
}

public getVulnerableData(): Map<string, DependencyVulnerability[]> {
return this.vulnerableData;
}
}
98 changes: 82 additions & 16 deletions src/helpers/template.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,99 @@
import { PolicyViolation } from "types/scannedData";
import { Organization } from "../constants";
import { DependencyVulnerability } from "types/vulnerability";
import { Vulnerabilities } from "types/vulnerability";
import * as vscode from "vscode";
import { SecondService } from "../constants";

export class Template {
constructor() {}
public licenseContent(data: string, contents: vscode.MarkdownString) {
contents.appendMarkdown(`License: **${data}**`);
contents.appendText("\n______________________________\n");

private policyViolation = {
failPipeline: "Pipeline failing",
warnPipeline: "Pipeline warning",
markUnaffected: "Mark vulnerability as unaffected",
markVulnerable: "Flag vulnerability as vulnerable",
sendEmail: "Notified email",
triggerWebhook: "Triggered webhook",
};

public licenseContent(license: string, contents: vscode.MarkdownString) {
contents.appendMarkdown(`License: **${license}**`);
contents.appendText(Organization.separator);
}

public vulnerableContent(data: DependencyVulnerability[], contents: vscode.MarkdownString): void {
if (data.length === 0) {
contents.appendMarkdown("No vulnerabilities found");
return;
public vulnerableContent(vulnerabilities: Vulnerabilities, contents: vscode.MarkdownString): void {
// direct vulnerabilities
if (vulnerabilities.directVulnerabilities.length === 0) {
contents.appendMarkdown("No vulnerabilities found\n\n");
} else {
contents.appendMarkdown(
`Direct Vulnerabilities Found: **${vulnerabilities.directVulnerabilities.length}**\n\n`,
);

const vulnerabilitiesToShow = vulnerabilities.directVulnerabilities.slice(0, 2);
vulnerabilitiesToShow.forEach((vulnerability) => {
contents.appendMarkdown(
`[**${vulnerability.cveId}**](${SecondService.debrickedBaseUrl + vulnerability.name.link})`,
);

if (vulnerability.cvss) {
contents.appendMarkdown(` - CVSS: ${vulnerability.cvss.text} (${vulnerability.cvss.type})`);
}

contents.appendMarkdown("\n\n");
});
}

contents.appendMarkdown(`Vulnerabilities Found: **${data.length}**\n\n`);
contents.appendText(Organization.separator);

const vulnerabilitiesToShow = data.slice(0, 2);
vulnerabilitiesToShow.forEach((vulnerability) => {
// transitive vulnerabilities
if (vulnerabilities.indirectVulnerabilities.length === 0) {
contents.appendMarkdown("No transitive vulnerabilities found");
} else {
contents.appendMarkdown(
`[**${vulnerability.cveId}**](${Organization.debrickedBaseUrl + vulnerability.name.link})`,
`Transitive Vulnerabilities Found: **${vulnerabilities.indirectVulnerabilities.length}**`,
);
contents.appendMarkdown("\n\n");
vulnerabilities.indirectVulnerabilities.forEach((indirectVulnerability) => {
const vulnerabilitiesToShow = indirectVulnerability;
contents.appendMarkdown(`${indirectVulnerability.dependencyName}`);
contents.appendMarkdown("\n\n");

if (vulnerability.cvss) {
contents.appendMarkdown(` - CVSS: ${vulnerability.cvss.text} (${vulnerability.cvss.type})`);
}
vulnerabilitiesToShow.transitiveVulnerabilities.forEach((vulnerability) => {
contents.appendMarkdown(
`[**${vulnerability.cveId}**](${SecondService.debrickedBaseUrl + vulnerability.name.link})`,
);

contents.appendMarkdown("\n\n");
if (vulnerability.cvss) {
contents.appendMarkdown(` - CVSS: ${vulnerability.cvss.text} (${vulnerability.cvss.type})`);
}

contents.appendMarkdown("\n\n");
});
});
}

contents.appendText(Organization.separator);
}

public policyViolationContent(policyViolationData: PolicyViolation[], contents: vscode.MarkdownString) {
if (policyViolationData.length === 0) {
contents.appendMarkdown("No policy violations found.\n");
return;
}

contents.appendMarkdown("Policy Violations\n\n");

policyViolationData.forEach((violation: PolicyViolation, index: number) => {
contents.appendMarkdown(`Rule - ${index + 1}`);
contents.appendMarkdown("\n");
violation.ruleActions.forEach((ruleAction: string, index: number) => {
contents.appendMarkdown(
` ${index + 1}. **${this.policyViolation[ruleAction as keyof typeof this.policyViolation]}** - [View rule](${violation.ruleLink})`,
);
contents.appendMarkdown("\n");
});
contents.appendMarkdown("\n");
});
}
}
48 changes: 41 additions & 7 deletions src/providers/manifestDependencyHoverProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import * as vscode from "vscode";
import * as path from "path";
import { globalStore, template } from "../helpers";
import { DependencyService } from "services";
import { DependencyVulnerability } from "types/vulnerability";
import { TransitiveVulnerabilities, Vulnerabilities } from "types/vulnerability";
import { Dependency } from "types/dependency";

export class ManifestDependencyHoverProvider implements vscode.HoverProvider {
private manifestFiles: string[] = [];
Expand Down Expand Up @@ -37,21 +38,50 @@ export class ManifestDependencyHoverProvider implements vscode.HoverProvider {
}

const depData = globalStore.getDependencyData().get(dependencyName);
const licenseData = depData?.licenses[0]?.name ?? "License information unavailable";
const vulnerableData = await this.getVulnerableData(depData?.id);
const licenseData = depData?.licenses[0]?.name ?? "Unknown";
const vulnerableData = await this.getVulnerableData(depData);
const policyViolationData = DependencyService.getPolicyViolationData(dependencyName);

const contents = this.createMarkdownString();
template.licenseContent(licenseData, contents);
template.vulnerableContent(vulnerableData, contents);
template.policyViolationContent(policyViolationData, contents);

return new vscode.Hover(contents);
}

private async getVulnerableData(dependencyId?: number): Promise<DependencyVulnerability[]> {
if (dependencyId) {
return await DependencyService.getVulnerableData(dependencyId);
private async getVulnerableData(dependency?: Dependency): Promise<Vulnerabilities> {
const vulnerabilities: Vulnerabilities = {
directVulnerabilities: [],
indirectVulnerabilities: [],
};
const vulnerabilityData = globalStore.getVulnerableData();
//direct dependencies
if (dependency) {
vulnerabilities.directVulnerabilities = vulnerabilityData.get(dependency.name.name) ?? [];
}
return [];
//indirect dependencies
if (dependency?.indirectDependencies) {
const vulnerabilitiesToFetch = dependency.indirectDependencies;

for (const indirectDep of vulnerabilitiesToFetch) {
const vulnerableData = vulnerabilityData.get(indirectDep.name.name) ?? [];

if (vulnerableData.length !== 0) {
const transitiveVulnerableData: TransitiveVulnerabilities = {
transitiveVulnerabilities: vulnerableData,
dependencyName: indirectDep.name.name,
dependencyId: indirectDep.id,
};
vulnerabilities.indirectVulnerabilities.push(transitiveVulnerableData);
}

if (vulnerabilities.indirectVulnerabilities.length > 1) {
break;
}
}
}
return vulnerabilities;
}

private createMarkdownString(): vscode.MarkdownString {
Expand Down Expand Up @@ -82,7 +112,11 @@ export class ManifestDependencyHoverProvider implements vscode.HoverProvider {
if (match) {
return match[1] + " (Go)";
}
break;
}

default:
break;
}
return null;
}
Expand Down
41 changes: 33 additions & 8 deletions src/services/dependencyService.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Dependency, DependencyResponse, IndirectDependency } from "types/dependency";
import { apiHelper, globalStore, Logger } from "../helpers";
import { RequestParam } from "../types";
import { DependencyVulnerabilityWrapper } from "types/vulnerability";
import { Organization } from "../constants";
import { DependencyVulnerability, DependencyVulnerabilityWrapper } from "types/vulnerability";
import { SecondService } from "../constants";

export class DependencyService {
static async getDependencyData(repoID: number, commitId: number) {
Logger.logInfo("Started fetching the Dependency Data");
const requestParam: RequestParam = {
endpoint: Organization.dependencyUrl,
endpoint: SecondService.dependencyUrl,
repoId: repoID,
commitId: commitId,
};
Expand All @@ -28,19 +28,44 @@ export class DependencyService {
globalStore.setDependencyData(dependencyMap);
}

static async getVulnerableData(depId: number) {
static async getVulnerableData() {
Logger.logInfo("Started fetching the Vulnerable Data");
const repoId = await globalStore.getRepoId();
const commitId = await globalStore.getCommitId();

const requestParam: RequestParam = {
endpoint: Organization.vulnerableUrl,
endpoint: SecondService.vulnerableUrl,
repoId: repoId,
commitId: commitId,
dependencyId: depId,
};
const response: DependencyVulnerabilityWrapper = await apiHelper.get(requestParam);
const vulnerableData = response.vulnerabilities;
return vulnerableData;
const vulnerabilityMap = new Map<string, DependencyVulnerability[]>();

response.vulnerabilities.forEach((vul: DependencyVulnerability) => {
vul.dependencies.forEach((dep) => {
const name = dep.name;
if (!vulnerabilityMap.has(name)) {
vulnerabilityMap.set(name, []);
}
vulnerabilityMap.get(name)!.push(vul);
});
});

globalStore.setVulnerableData(vulnerabilityMap);
}

static getPolicyViolationData(depName: string) {
Logger.logInfo("Started fetching Policy violation data");

const scannedData = globalStore.getScanData();

return scannedData.automationRules
.filter((automationRule) =>
automationRule.triggerEvents.some((triggerEvent) => triggerEvent.dependency === depName),
)
.map((automationRule) => ({
ruleActions: automationRule.ruleActions,
ruleLink: automationRule.ruleLink,
}));
}
}
Loading

0 comments on commit ce56e9d

Please sign in to comment.