Skip to content

Commit

Permalink
feat: snyk api
Browse files Browse the repository at this point in the history
  • Loading branch information
AntonioliBenjamin committed Aug 14, 2024
1 parent a2b205f commit ec29425
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 107 deletions.
1 change: 1 addition & 0 deletions src/database/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * as osv from "./osv.js";
export * as snyk from "./snyk.js";
36 changes: 36 additions & 0 deletions src/database/snyk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Import Third-Party Dependencies
import * as httpie from "@myunisoft/httpie";

// Import Internal Dependencies
import { SNYK_ORG, SNYK_TOKEN } from "../constants.js";
import { SnykAuditResponse } from "../formats/snyk/index.js";

// CONSTANTS
export const ROOT_API = "https://snyk.io";

type SnykFindOneParameters = {
files: {
target: {
contents: string;
};
additional?: {
contents: string;
}[];
};
};

export async function findOne(
parameters: SnykFindOneParameters
): Promise<SnykAuditResponse> {
const { data } = await httpie.post<SnykAuditResponse>(
new URL(`/api/v1/test/npm?org=${SNYK_ORG}`, ROOT_API),
{
headers: {
Authorization: `token ${SNYK_TOKEN}`
},
body: parameters
}
);

return data;
}
89 changes: 89 additions & 0 deletions src/formats/snyk/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
export interface SnykPatch {
id: string;
urls: string[];
version: string;
modificationTime: string;
comments: string[];
}

export interface SnykVulnerability {
/** The issue ID **/
id: string;
/** A link to the issue details on snyk.io **/
url: string;
/** The issue title **/
title: string;
/** The issue type **/
type: "vulnerability" | "license";
/** The paths to the dependencies which have an issue, and their corresponding upgrade path (if an upgrade is available) **/
paths?: Array<{
"from": Array<string>,
"upgrade": Array<string | boolean>
}>;
/** The package identifier according to its package manager **/
package: string;
/** The package version this issue is applicable to. **/
version: string;
/** The Snyk defined severity level **/
severity: "critical" | "high" | "medium" | "low";
/** The package's programming language **/
language: string;
/** The package manager **/
packageManager: string;
/** One or more semver ranges this issue is applicable to. **/
semver: Record<string, string[]>;
/** The vulnerability publication time **/
publicationTime: string;
/** The time this vulnerability was originally disclosed to the package maintainers **/
disclosureTime: string;
/** Is this vulnerability fixable by upgrading a dependency? **/
isUpgradable: boolean;
/** The detailed description of the vulnerability, why and how it is exploitable. **/
description: string;
/** Is this vulnerability fixable by using a Snyk supplied patch? **/
isPatchable: boolean;
/** Is this vulnerability fixable by pinning a transitive dependency **/
isPinnable: boolean;
/** Additional vulnerability identifiers **/
identifiers: Record<string, string[]>;
/** The reporter of the vulnerability **/
credit: string;
/**
* Common Vulnerability Scoring System (CVSS) provides a way to capture the principal characteristics
* of a vulnerability, and produce a numerical score reflecting its severity,
* as well as a textual representation of that score.
* **/
CVSSv3: string;
/** CVSS Score **/
cvssScore: number;
/** Patches to fix this issue, by snyk **/
patches: SnykPatch[];
/** The path to upgrade this issue, if applicable **/
upgradePath: string[];
/** Is this vulnerability patched? **/
isPatched: boolean;
/** The snyk exploit maturity level **/
exploitMaturity: string;
functions: any;
}

export interface SnykAuditResponse {
/** Does this package have one or more issues? **/
ok: boolean;
/** The issues found. **/
issues: {
vulnerabilities: SnykVulnerability[];
licenses: SnykVulnerability[];
};
/** The number of dependencies the package has. **/
dependencyCount: number;
/** The organization this test was carried out for. **/
org: {
id: string;
name: string;
};
/** The organization's licenses policy used for this test **/
licensesPolicy: null | object;
/** The package manager for this package **/
packageManager: string;
}
112 changes: 5 additions & 107 deletions src/strategies/snyk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,110 +2,17 @@
import path from "node:path";
import { readFile } from "node:fs/promises";

// Import Third-Party Dependencies
import * as httpie from "@myunisoft/httpie";

// Import Internal Dependencies
import { VULN_MODE, SNYK_ORG, SNYK_TOKEN } from "../constants.js";
import { VULN_MODE } from "../constants.js";
import { standardizeVulnsPayload } from "../formats/standard/index.js";
import type { Dependencies } from "./types/scanner.js";
import type {
HydratePayloadDepsOptions,
BaseStrategy
} from "./types/api.js";
import { SnykAuditResponse } from "../formats/snyk/index.js";
import { snyk } from "../database/index.js";

// CONSTANTS
const kSnykApiUrl = `https://snyk.io/api/v1/test/npm?org=${SNYK_ORG}`;

export interface SnykPatch {
id: string;
urls: string[];
version: string;
modificationTime: string;
comments: string[];
}

export interface SnykVulnerability {
/** The issue ID **/
id: string;
/** A link to the issue details on snyk.io **/
url: string;
/** The issue title **/
title: string;
/** The issue type **/
type: "vulnerability" | "license";
/** The paths to the dependencies which have an issue, and their corresponding upgrade path (if an upgrade is available) **/
paths?: Array<{
"from": Array<string>,
"upgrade": Array<string | boolean>
}>;
/** The package identifier according to its package manager **/
package: string;
/** The package version this issue is applicable to. **/
version: string;
/** The Snyk defined severity level **/
severity: "critical" | "high" | "medium" | "low";
/** The package's programming language **/
language: string;
/** The package manager **/
packageManager: string;
/** One or more semver ranges this issue is applicable to. **/
semver: Record<string, string[]>;
/** The vulnerability publication time **/
publicationTime: string;
/** The time this vulnerability was originally disclosed to the package maintainers **/
disclosureTime: string;
/** Is this vulnerability fixable by upgrading a dependency? **/
isUpgradable: boolean;
/** The detailed description of the vulnerability, why and how it is exploitable. **/
description: string;
/** Is this vulnerability fixable by using a Snyk supplied patch? **/
isPatchable: boolean;
/** Is this vulnerability fixable by pinning a transitive dependency **/
isPinnable: boolean;
/** Additional vulnerability identifiers **/
identifiers: Record<string, string[]>;
/** The reporter of the vulnerability **/
credit: string;
/**
* Common Vulnerability Scoring System (CVSS) provides a way to capture the principal characteristics
* of a vulnerability, and produce a numerical score reflecting its severity,
* as well as a textual representation of that score.
* **/
CVSSv3: string;
/** CVSS Score **/
cvssScore: number;
/** Patches to fix this issue, by snyk **/
patches: SnykPatch[];
/** The path to upgrade this issue, if applicable **/
upgradePath: string[];
/** Is this vulnerability patched? **/
isPatched: boolean;
/** The snyk exploit maturity level **/
exploitMaturity: string;
functions: any;
}

export interface SnykAuditResponse {
/** Does this package have one or more issues? **/
ok: boolean;
/** The issues found. **/
issues: {
vulnerabilities: SnykVulnerability[];
licenses: SnykVulnerability[];
};
/** The number of dependencies the package has. **/
dependencyCount: number;
/** The organization this test was carried out for. **/
org: {
id: string;
name: string;
};
/** The organization's licenses policy used for this test **/
licensesPolicy: null | object;
/** The package manager for this package **/
packageManager: string;
}

export type SnykStrategyDefinition = BaseStrategy<"snyk">;

Expand All @@ -128,24 +35,15 @@ async function hydratePayloadDependencies(
try {
const { targetFile, additionalFile } = await getNpmManifestFiles(path);

const body = {
const data = await snyk.findOne({
files: {
...(additionalFile ? { additional: [{ contents: additionalFile }] } : {}),
target: {
contents: targetFile
}
}
};
});

const { data } = await httpie.post<SnykAuditResponse>(
kSnykApiUrl,
{
headers: {
Authorization: `token ${SNYK_TOKEN}`
},
body
}
);
extractSnykVulnerabilities(dependencies, data, options);
}
catch { }
Expand Down
68 changes: 68 additions & 0 deletions test/database/snyk.unit.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Import Node.js Dependencies
import { describe, test, after } from "node:test";
import assert from "node:assert";

// Import Internal Dependencies
import { kHttpClientHeaders, setupHttpAgentMock } from "../strategies/utils";
import { snyk } from "../../src/database";
import { SNYK_ORG } from "../../src/constants";

describe("snyk", () => {
const [mockedHttpAgent, restoreHttpAgent] = setupHttpAgentMock();
const mockedHttpClient = mockedHttpAgent.get(snyk.ROOT_API);

after(() => {
restoreHttpAgent();
});

test(`should send a POST http request to the Snyk API using findOne and then return the SnykAuditResponse`, async() => {
const expectedResponse = { issues: "some issues data" };
const targetFile = "some target file content";
const additionalFile = "some additional file content";

mockedHttpClient
.intercept({
path: new URL(`/api/v1/test/npm?org=${SNYK_ORG}`, snyk.ROOT_API).href,
method: "POST",
body: JSON.stringify({
files: {
target: { contents: targetFile },
additional: [{ contents: additionalFile }]
}
})
})
.reply(200, expectedResponse, kHttpClientHeaders);

const data = await snyk.findOne({
files: {
target: { contents: targetFile },
additional: [{ contents: additionalFile }]
}
});

assert.deepStrictEqual(data, expectedResponse);
});

test(`should send a POST http request to the Snyk API using findOne without additional files`, async() => {
const expectedResponse = { issues: "some issues data" };
const targetFile = "some target file content";

mockedHttpClient
.intercept({
path: new URL(`/api/v1/test/npm?org=${SNYK_ORG}`, snyk.ROOT_API).href,
method: "POST",
body: JSON.stringify({
files: {
target: { contents: targetFile }
}
})
})
.reply(200, expectedResponse, kHttpClientHeaders);

const data = await snyk.findOne({
files: { target: { contents: targetFile } }
});

assert.deepStrictEqual(data, expectedResponse);
});
});

0 comments on commit ec29425

Please sign in to comment.