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

npm audit and audit-resolve.json #18

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
155 changes: 155 additions & 0 deletions accepted/0003-interactive-audit-resolver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Audit resolutions

## Summary

Historically this proposal was titled "Interactive audit resolver". That idea was too wide to introduce in one go and parts of it are better served in userland packages. This is now the core subset of the original proposal containing support for resolutions file.

This proposal is for adding means for a human to resolve issues from `npm audit` by making and documenting decisions to ignore particular false positives.
The implementation should introduce support for reading and applying decisions from `audit-resolve.json` file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer either doing this through a more generic audit.json or through an audit property in package.json. Having a whole new file for a single purpose in the npm CLI feels… off.

There was discussion of additional config setups that I had with @darcyclarke that it could also potentially fit in, but they don’t exist yet so I can’t really recommend them.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't go with a package.json field because everyone kept saying we use it for too many things already and the audit file tends to be larger (from my practical experience of running the tool for 3-4 years)
Also, there's a plan to use it as vehicle for sharing recommendation. More on this here:
https://dev.to/naugtur/do-you-need-help-with-your-npm-audit-3olf

I'm not opinionated about the name. The final decision on the name will have to happen when we standardize the format in OpenJSF

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other advantages of keeping it in a separate file include more granular codeowners targeting.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make it explicit here so it can be referred to in whatever OpenJSF work you're talking about: I'm a hard -1 on audit-resolve.json.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's not exactly good security practice

users should actually be doing their due diligence here with their dependencies and that

I think it should be fully considered that a large number of projects will either:

  • not have maintainers that have the bandwidth to do this
  • not have maintainers that know how to do this
  • not have maintainers that know this needs to be done at all
  • not have maintainers (unmaintained / insufficiently maintained)

I work with a bunch of beginners every three months in our coding bootcamp at @upleveled and this is one example of maintainers who will not do this. And if you pick random packages on npm, more likely than not, you'll have something that is not well maintained.

So to save and check the audit decision as close as possible to the actual usage of the vulnerable code seems like actually a better security practice overall for the industry (at least as a fallback option).

Affecting sea change so that everyone in the industry spends time to check the security of their dependencies is unrealistic.

I think @naugtur's suggestion here could be one interesting way to implement this:

package maintainers could publish ignore lists for the dependencies of their package when they did the research to verify the package is not affected.

Such lists could be aggregated into recommendations maintained by security professionals.

To be clear, again, I'm not proposing this as the ONLY security measure - teams which have security processes, budget and practice in place should continue to do that and thus will be even more secure.

But for the majority of projects which will not have this level of focus on security, there should be a base level of security by default, provided by the ecosystem.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to give people tools to do the best they can with the bandwidth they have. If we force them to address everything on their own, they're likely to just drop security from their pipeline

Copy link

@dead-claudia dead-claudia Jul 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I'm just saying we should just not hide it by default, and instead force the user to take some explicit action, even if it's not necessarily actually looking at the code and confirming everything.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't - the user should never see something that isn't actionable for them.

Copy link

@dead-claudia dead-claudia Jul 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll partially agree to disagree and move on.


Features:
- Let users save tecisions about vulnerabilities and change `npm audit` behavior to accomodate that.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was "tecisions" supposed to say "decisions" ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's definitely one of the possibilities.

- Decision options include 'ignore', 'remind later', 'fix', 'none'.
- Allow tracking who resolved an issue and when using git history.
- Define a format to be used by userland packages when helping users make and store decisions.

## Motivation

At times, running `npm audit fix` won't fix all audit issues. Fixes might not be available or the user might not want to apply some of them, eg. major version updates or build/test dependencies.

It should be possible to make `npm audit` a step in a CI pipeline to go red every time a new vulnerability affects the project. In cases when `npm audit` reports a vulnerability that is not affecting the project, user should be able to ignore it.

examples:
- yargs-parser as a transitive dependency of an API gateway is not something that should break a CI run if vulnerable
- ReDoS vulnerability in a dependency of a commandline tool

Managing security of dependencies should be quick to update, effective, easy to audit over time and secure in itself.


## Detailed Explanation

`npm audit` consumes decisions about what to ignore or warn about when an issue comes back that was already fixed.

All decisions are stored in `audit-resolve.json` file as key-value, where key is `${advisory id}|${dependency path}` and value is:
Copy link
Contributor

@ruyadorno ruyadorno May 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@isaacs I'm under the impression that the ${dependencyPath} bit here is very non-arborist and not necessary overall in the context of an audit triage system.

I would like to hear your thoughts on it

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a native speaker and I'm not familiar enough with the culture to grasp what you mean exactly when calling something non-arborist. I'm going to focus on the "~necessary" part.

Mixing advisory and dependency path as a key in the audit-resolve file is the core of the whole idea.

It's the first time I was able to safely ignore vulnerabilities I knew won't affect me.

A story:
My adventure with checking dependencies started with adding nsp check to our projects.
The number of reports in software we used was low, so I didn't know what I'm getting myself into when I made nsp check a step in CI.
REDoS vulnerabilities got very popular after that and it's been an annoyance. nsp allowed creating an ignore list with just the advisoty id, so after finding out that a 5th level dependency of an old testing tool we use (or something) is unlikely to ever be fixed, we had to ignore all REDoS cases in all 20+ projects.
Then npm audit showed up so I switched. It had no means of ignoring anything. CI step was removed and I started working on a wrapper that ended up being the npm-audit-resolver.

The key features I value in this solution:

  • if I ignore the vulnerability in one package, it won't transfer to another
  • if I ignore a vulnerability in a package coming in as a dependency of a dependency I don't really invoke in the app, I'm not automatically ignoring it elsewhere.
  • if a new instance of the vulnerability shows up in dependencies of something else or in a dep tree of newly installed dependency - it doesn't get ignored

While ignoring vulnerabilities seems like a thing that doesn't improve security when you hear about it, in this case it's a tool to really improve visibility of the reports you should actually look into and increase security.

Ignoring vulnerability report is not here for convenience, but to make npm audit a viable step in the CI pipeline.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah 😄 sorry about that, I meant https://github.com/npm/arborist


```js
{
decision: "fix|ignore|postpone|none",
madeAt: <timestamp of when the decision was made>
expiresAt: <timestamp of when the decision was made>
}
```

For human-writeability the timestamp could support readable date fromats as well.

`npm audit` reads `audit-resolve.json` to get decisions

`audit-resolve.json` file could be created manually, but the expected UX of that file would be via a userland package that reads audit output, helps the user decide what to do and saves the decision. This tool will be referenced as *audit resolver* below.



### resolutions
- **fix** - a fix was applied and *audit resolver* marked issue as fixed in `audit-resolve.json`. On each subsequent run of `npm audit` if the issue comes up again, the user is also warned that the problem was supposed to be fixed already.
- **ignore** - *audit resolver* marks the issue as ignored and `npm audit` will no longer fail because of it (but should display a single line warning about the issue being ignored). If a new vulnerability is found in the same dependency, it does not get ignored. If another dependency is installed with the same vulnerability advisory id it is not ignored. If the same package is installed as a dependency to another dependency (different path) it is not ignored.
- **postpone** *audit resolver* marks the issue as postponed to a timestamp 24h in the future. Instead of ignoring an issue permanently just to make a build pass, one can postpone it when in rush, but make it show up as a CI faiure on the next working day. The option is separate to ignore for better visibility of the intention of a person in a rush. This is to build better security culture in a team.
- **none** an entry in decisions list was generated but the decision was not made or was explicitly cancelled in the future


Proposed npm functionality is implemented in userland here: https://www.npmjs.com/package/npm-audit-resolver
The `check-audit` command from the above package is providing the exact functionality proposed here.

## Rationale and Alternatives

**Ignoring is necessary but must be under control at all times**
- `nsp check` used to support ignoring a particular issue in all dependencies, but that's not enough to be in control and remain secure. It wasn't uncommon for projects to ignore an advisory id because it was coming up as an issue in code that didn't run in production, thus risking it getting into the production code when updating another dependency to a version with the same issue.
- ignoring just dev dependencies is not enough. Sometimes the developer knows a vulnerability can be ignored because a package with a DOS is used in tooling for the project (not a dev dependency but eg. a migration script), not production code. To be really secure, ignoring must be explicit.
- Editing a file to specify what to ignore will not suffice any more at that level of detail, so automation is necessary.

**Why audit-resolve.json?**
Resolutions must be recorded in a file and committed to git(or alternative) for edit history and full audit on who decided what.
Resolution format must be readable for a JavaScript developer.

- `package-lock.json` is not a good option for storing resolutions because even if we overlook the fact that it'd be adding another purpose to a single-purpose file, the community at large is still used to removing and regenerating it often.
- `package.json` is not a good option, because it is already overloaded for so many functionalities. The output of resolutions could be lengthy and clutter the file. User will need the resolution information much less often than they look in the package.json file. ALso, resolution file is not meant to be edited by hand.
- `.npmrc` is not a good option because it doesn't live in the repository and being a dotfile is easy to miss, so a developer could overlook it affecting the audit. Also using it for the purpose of storing resolutions to project specific issues seems counter-intuitive.
ruyadorno marked this conversation as resolved.
Show resolved Hide resolved

A separate file referred to as `audit-resolve.json` has the benefit of being single purpose, easy to track and audit, easy to version and migrate between versions of `npm` and comfortable for the users to review in git history and pull requests.

**postpone, remove**
Other options are helpers for more elaborate actions a developer would take when confronted with an issue they can't or don't want to fix.

Why is postpone useful at all? It's designed to build a secure development culture where one didn't yet form. Without it, a developer under time pressure would mark an issue as ignored with intention to un-mark it later.
While shipping with a known vulnerability is a bad practice, NPM's mission with the community should be to empower people to build more secure products and trust their skill and understanding of their project's particular needs. We should also aspire to help teams introduce more secure workflows effortlessly, so letting a build pass without risking compromising security long-term is a win.

*audit-resolver* could perform more actions, like let the user remove a package entirely or help find a patch in the future.

### Alternatives

Currently the only alternative is to manage the issues manually and keep track of the resolutions using conventions created by a team individually. Ignoring dev dependencies and certain level of severity will be provided for `npm audit` so it lowers the barrier of entry to using it in a CI system.

Paid alternatives for managing security of a node.js project are available, including Snyk, with focus on providing patches to their customers.

## Implementation

prototype of a working *audit-resolver* (in production use) https://www.npmjs.com/package/npm-audit-resolver

Core capability covering reading, parsing, validating and representing the `audit-resolve.json` file was extracted from npm-audit-resolver into https://www.npmjs.com/package/audit-resolve-core - API of that package to be discussed. Functionality is there.

The implementation is, and should remain, runnable standalone as a separate package with minor wrapping code - useful for testing new features without bundling unfinished work with npm cli versions and therefore node.js
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to describe a bit more the implementation over here, an example of the cli UX would be very welcomed, maybe just copy or start from the example I floated over npm/cli#525 (comment)

$ npm audit resolve list
1325 [high]     handlebars: Prototype Pollution
1324 [high]     handlebars: Arbitrary Code Execution
788  [moderate] js-yaml: Denial of Service

$ npm audit resolve get 788
Status: Pending
Level: Moderate
Type: Denial of Service
Package name: js-yaml
Dependency of: tap [dev]
Path: tap > tap-parser > js-yaml
More info: https://npmjs.com/advisories/788

$ npm audit resolve set 788 --status=Read

$ npm audit resolve list
1325 [high]     handlebars: Prototype Pollution
1324 [high]     handlebars: Arbitrary Code Execution

$ npm audit resolve get 788
Status: Read
Level: Moderate
Type: Denial of Service
Package name: js-yaml
Dependency of: tap [dev]
Path: tap > tap-parser > js-yaml
More info: https://npmjs.com/advisories/788

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe I found a different way to tackle this.

I think the first step is for npm audit to start supporting the audit-resolve.json file and leave creating it to external tools with various options for UI.
I'm leaning towards limiting this RFC to just that functionality and then as audit-resolve file standard is accepted, propose a way to edit it.

I've got a few ideas for the resolve command without interactions, but npm-audit-resolver is more comfortable to use than these. I wouldn't bother to implement it as interactive if I didn't need it ;)

Some ideas for non-interactive that I could develop into more detailed RFCs:

  • audit resolve runs fix and marks as fixed, puts the rest in and marks as 'none' so user can edit
  • audit resolve lists unresolved items and lets the user resolve all items in a certain way with one command where the filters are advisory_id, package, dev
  • an almost interactive one where the --continue flag and alike are used like with git rebasing

feels like we need to chat less asynchronously too ;)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for sure it would be nice to catch up, I tried reaching out on twitter but let me know if there's a better place to start a conversation 😊

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not seeing anything on twitter, let's switch to email for scheduling - [email protected]
I'll explain the motivations behind what resolver is now and then we can brainstorm what to do about the UX

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, twitter sucks these days... it used to notify people whenever someone starts following them

but no worries, I’ll shoot you an email them 😊



### audit-resolve.json
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would - to me - be extremely preferable to have a less scoped file. Whether this ends up being package.json or audit.json, I'd very strongly prefer to avoid adding one-off files. Personally, I'd prefer to be able to just have this in my package.json since that's a file I already actively maintain in an audit property but I can see the potential value in splitting this out into an audit.json that could also define additional audit things for the future.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a section with the analysis of why the file is separate from package.json etc.
As for audit-resolve vs audit I don't really mind and I also don't see much of a difference, but I get the sentiment.

The tooling I built supports extending the file over time and there's already a not-yet-defined section 'rules' next to 'decisions' I introduced to the spec in the package and plan to use to automate some decision making.

I strongly believe there's nothing blocking this file from getting used in the extent it's described here in the initial implementation and then growing in scope as we figure out more.


Audit resolution file format:
map key-value where key is the advisory number concatenated with package path

```js
{
"version": 1,
"decisions": {
"ADVISORY_NUMBER|DEPENDENCY_PATH":{
"decision": "RESOLUTION_TYPE",
"reason": "Reason provided by the person making the decision (optional)",
"madeAt": timestamp
"expiresAt": timestamp
Copy link

@dominykas dominykas Jul 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this please also (only?) support a human readable ISO date string?

Use case: extending the expiry time without using any tooling (an extremely common operation, from my experience with .snyk policy files...)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I believe it should not be limited to ISO string because of the ending I always mess up.
2020-09-21 should also work if we want to cater to humans :)

Next iteration of the core package that reads and writes the resolve file will have that.

Thanks for the data point!

}
}
}
```

`madeAt` should be generated by all tools using the file but is not mandatory for the sake of manually adding ignore decisions.

example

```js
{
"version": 1,
"decisions": {
"717|spawn-shell>merge-options": {
"decision":"remind",
"madeAt": 1542440172844
},
...
}
}
```

JSON schema

https://github.com/naugtur/audit-resolve-core/blob/master/auditFile/versions/v1.js

Schema defines `version` and `decisions` fields.
The `rules` field is meant for the *audit-resolver* as instructions how to behave.

*initially npm-audit-resolver used a different format, audit-resolve-core supports detecting old format and converting it*

## Prior Art

I recall a tool wrapping `nsp check` that gave me the idea to store exact paths of dependencies set to ignore (or any resolution)
Not aware of other prior art. Didn't find much in the npm packages ecosystem when researching it at the time of release of npm audit.

## Unresolved Questions and Bikeshedding

- adding means to fill in the content of `audit-resolve.json` file to npm itself should be a matter of another RFC