Skip to content

Commit

Permalink
refactor: migrate to TypeScript and Node.js test_runner (#197)
Browse files Browse the repository at this point in the history
  • Loading branch information
fraxken authored Sep 14, 2023
1 parent 3dc2bde commit 6ec82b9
Show file tree
Hide file tree
Showing 62 changed files with 1,366 additions and 1,987 deletions.
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

0 comments on commit 6ec82b9

Please sign in to comment.