Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: migrate to TypeScript and Node.js test_runner #197

Merged
merged 1 commit into from
Sep 14, 2023
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
3 changes: 1 addition & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"requireConfigFile": false
},
"rules": {
"no-empty": "off",
"id-length": "off"
"no-empty": "off"
}
}
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 NodeSecure
Copyright (c) 2021-2023 NodeSecure

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
97 changes: 48 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ $ yarn add @nodesecure/vulnera
```js
import * as vulnera from "@nodesecure/vulnera";

// Default strategy is currently "none".
await vulnera.setStrategy(vulnera.strategies.GITHUB_ADVISORY);
await vulnera.setStrategy(
vulnera.strategies.GITHUB_ADVISORY
);

const definition = await vulnera.getStrategy();
console.log(definition.strategy);
Expand All @@ -53,79 +54,74 @@ console.log(vulnerabilities);

The default strategy is **NONE** which mean no strategy at all (we execute nothing).

[GitHub Advisory](./docs/github_advisory.md) | [Sonatype - OSS Index](./docs/sonatype.md) | Snyk | [**DEPRECATED**] [Node.js Security WG - Database](./docs/node_security_wg.md)
:-------------------------:|:-------------------------:|:-------------------------:|:-------------------------:
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/db/Npm-logo.svg/1200px-Npm-logo.svg.png" width="300"> | <img src="https://ossindex.sonatype.org/assets/images/sonatype-image.png" width="400"> | <img src="https://res.cloudinary.com/snyk/image/upload/v1537345894/press-kit/brand/logo-black.png" width="400"> | <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Node.js_logo.svg/1280px-Node.js_logo.svg.png" width="300">
[GitHub Advisory](./docs/github_advisory.md) | [Sonatype - OSS Index](./docs/sonatype.md) | Snyk
:-------------------------:|:-------------------------:|:-------------------------:
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/db/Npm-logo.svg/1200px-Npm-logo.svg.png" width="300"> | <img src="https://ossindex.sonatype.org/assets/images/sonatype-image.png" width="400"> | <img src="https://res.cloudinary.com/snyk/image/upload/v1537345894/press-kit/brand/logo-black.png" width="400">

Those strategies are described as "string" **type** with the following TypeScript definition:
```ts
type Kind = "github-advisory" | "node" | "snyk" | "sonatype" | "none";
type Kind = "github-advisory" | "snyk" | "sonatype" | "none";
```

To add a strategy or better understand how the code works, please consult [the following guide](./docs/adding_new_strategy.md).

## API

See `types/api.d.ts` for a complete TypeScript definition.

```ts
function setStrategy(name?: Strategy.Kind, options?: Strategy.Options): Promise<Strategy.Definition>;
function getStrategy(): Promise<Strategy.Definition>;

const strategies: {
SECURITY_WG: "node";
GITHUB_ADVISORY: "github-advisory";
SONATYPE: "sonatype";
SNYK: "snyk";
NONE: "none";
};
function setStrategy<T extends Kind>(name: T): AllStrategy[T];
function getStrategy(): AnyStrategy;

const strategies: Object.freeze({
GITHUB_ADVISORY: "github-advisory",
SNYK: "snyk",
SONATYPE: "sonatype",
NONE: "none"
});

/** Equal to strategies.NONE by default **/
const defaultStrategyName: string;
const defaultStrategyName: "none";
```

Strategy `Kind`, `HydratePayloadDependenciesOptions`, `Options` are described by the following interfaces:
Strategy extend from the following set of interfaces;

```ts
export interface Options {
/** Force hydratation of the strategy local database (if the strategy has one obviously) **/
hydrateDatabase?: boolean;
}

export interface HydratePayloadDependenciesOptions {
/**
* Absolute path to the location to analyze (with a package.json and/or package-lock.json)
* Useful to NPM Audit strategy
**/
path?: string;
useStandardFormat?: boolean;
}

export interface GetVulnerabilitiesOptions {
useStandardFormat?: boolean;
}

export interface Definition<T> {
export interface BaseStrategy<T extends Kind> {
/** Name of the strategy **/
strategy: Kind;
strategy: T;
/** Method to hydrate (insert/push) vulnerabilities in the dependencies retrieved by the Scanner **/
hydratePayloadDependencies: (
dependencies: Dependencies,
options?: HydratePayloadDependenciesOptions
options?: HydratePayloadDepsOptions
) => Promise<void>;
}

export interface ExtendedStrategy<
T extends Kind, VulnFormat
> extends BaseStrategy<T> {
/** Method to get vulnerabilities using the current strategy **/
getVulnerabilities: (
path: string,
options?: GetVulnerabilitiesOptions
) => Promise<T | StandardVulnerability>;
/** Hydrate local database (if the strategy need one obviously) **/
hydrateDatabase?: () => Promise<void>;
/** Method to delete the local vulnerabilities database (if available) **/
deleteDatabase?: () => Promise<void>;
options?: BaseStrategyOptions
) => Promise<(VulnFormat | StandardVulnerability)[]>;
}

export interface BaseStrategyOptions {
/**
* @default false
*/
useStandardFormat?: boolean;
}

export interface HydratePayloadDepsOptions extends BaseStrategyOptions {
/**
* Absolute path to the location to analyze
* (with a package.json and/or package-lock.json for NPM Audit for example)
**/
path?: string;
}
```

Where `dependencies` is the dependencies **Map()** object of the scanner.
Where `dependencies` is the dependencies **Map()** object of the NodeSecure Scanner.

> [!NOTES]
> the option **hydrateDatabase** is only useful for some of the strategy (like Node.js Security WG).
Expand All @@ -151,7 +147,10 @@ export interface StandardVulnerability {
severity?: Severity;
/** Common Vulnerabilities and Exposures dictionary */
cves?: 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. **/
/**
* 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. **/
cvssVector?: string;
/** CVSS Score **/
cvssScore?: number;
Expand Down
90 changes: 29 additions & 61 deletions docs/adding_new_strategy.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
# Adding a new strategy
If you are a contributor and want to add a new strategy to this package then this guide is for you.

The first thing to understand is that this package was built to meet the needs of the [NodeSecure Scanner](https://github.com/NodeSecure/scanner) and NodeSecure CLI. What are these needs you will ask?

- Download database on the local disk for some strategies (like Node.js Security WG with **hydrateDatabase**).
- Know if the data is up to date (what cover **src/cache.js**).
- Being able to delete it at any time (**deleteDatabase** method).
- Search and attach vulnerabilities for a given list of dependencies.

Not all strategies are the same and do not work in the same way. It is therefore also important to be able to adapt while maintaining abstract interfaces.
The first thing to understand is that this package was built to meet the needs of the [NodeSecure Scanner](https://github.com/NodeSecure/scanner) and NodeSecure CLI.

![](./images/scanner.png)

Expand Down Expand Up @@ -77,7 +70,6 @@ The files that must be modified to add a new strategy are:
You must add a new constant in variable `VULN_MODE`
```js
export const VULN_MODE = Object.freeze({
SECURITY_WG: "node",
GITHUB_ADVISORY: "github-advisory",
SNYK: "snyk",
SONATYPE: "sonatype",
Expand All @@ -90,42 +82,26 @@ Also think to update the type definition of **VULN_MODE** in `types/api.d.ts`.

</details>

<details><summary>types/strategy.d.ts</summary>

It is necessary to add the name of your strategy in the exported type definitions.
```ts
declare namespace Strategy {
export type Kind = "github-advisory" | "node" | "snyk" | "sonatype" | "none" | "foobar"; // <-- add the name here
```

</details>

<details><summary>src/strategies/index.js</summary>
<details><summary>src/index.ts</summary>

This is the file we use to export and manage the initialization of a strategy.
You need to update the initStrategy function and add a new case for your strategy.

The first line to update is the one who export all strategies at once.
```js
export { GitHubAuditStrategy, SecurityWGStrategy, FooBarStrategy }; // <-- add yours here
```

And then it will be necessary to modify the function initStrategy to add a new case for your strategy.

```js
export async function initStrategy(strategy, options) {
switch (strategy) {
case VULN_MODE.SECURITY_WG:
return Object.seal(await SecurityWGStrategy(options));

case VULN_MODE.GITHUB_ADVISORY:
return Object.seal(GitHubAuditStrategy());

/** Add it at the end **/
case VULN_MODE.MY_NEW_STRATEGY:
return Object.seal(FooBarStrategy()); // <-- add options if required!
export function setStrategy<T extends Kind>(
name: T
): AllStrategy[T] {
if (name === VULN_MODE.GITHUB_ADVISORY) {
localVulnerabilityStrategy = Object.seal(GitHubAdvisoryStrategy());
}
else if (name === VULN_MODE.MY_NEW_STRATEGY) { // Add condition here
localVulnerabilityStrategy = Object.seal(FooBarStrategy());
}
else {
localVulnerabilityStrategy = Object.seal(NoneStrategy());
}

return Object.seal(NoneStrategy());
return localVulnerabilityStrategy as AllStrategy[T];
}
```

Expand All @@ -139,42 +115,34 @@ It is obviously necessary to add your strategy in the README. Also make sure tha

---

You will obviously need to add your own `.js` file in the **src/strategies** folder. The content at the start will probably look like this:
You will obviously need to add your own `.ts` file in the **src/strategies** folder. The content at the start will probably look like this:

```js
```ts
// Import Internal Dependencies
import { VULN_MODE } from "../constants.js";
import type { Dependencies } from "./types/scanner.js";
import type {
HydratePayloadDepsOptions,
BaseStrategy
} from "./types/api.js";

export function FooBarStrategy() {
export type FooBarStrategyDefinition = BaseStrategy<"foobar">;

export function FooBarStrategy(): FooBarStrategyDefinition {
return {
strategy: VULN_MODE.MY_NEW_STRATEGY,
hydratePayloadDependencies
};
}

export async function hydratePayloadDependencies(dependencies, options = {}) {
export async function hydratePayloadDependencies(
dependencies: Dependencies,
options: HydratePayloadDepsOptions = {}
) {
// Do your code here!
}
```

`hydrateDatabase` and `deleteDatabase` can also be added if required (take a look at security-wg.js for inspiration).

---

If your strategy returns information that does not match the other strategies or the standard format you will need to add definitions for this new format in `./types` (like `node-strategy.d.ts` and `github-strategy.d.ts`).

```ts
export = FooBarStrategy;

declare namespace FooBarStrategy {
export interface Vulnerability {
// Your work here!
}
}
```

Think to import/export those definitions in the root file `index.d.ts`.

---

> ⚠️ **Notes**: Documentation and testing are not specified here because it is difficult to predict what is needed. However, you are also responsible for adding them.
Expand Down
31 changes: 0 additions & 31 deletions docs/node_security_wg.md

This file was deleted.

30 changes: 0 additions & 30 deletions index.d.ts

This file was deleted.

25 changes: 0 additions & 25 deletions index.js

This file was deleted.

Loading