-
Notifications
You must be signed in to change notification settings - Fork 240
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
base: main
Are you sure you want to change the base?
Changes from all commits
99933cc
8eba5d8
ef757fb
cf453ab
20bd794
8c7196b
d43b5d9
f333557
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
|
||
Features: | ||
- Let users save tecisions about vulnerabilities and change `npm audit` behavior to accomodate that. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was "tecisions" supposed to say "decisions" ? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @isaacs I'm under the impression that the I would like to hear your thoughts on it There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: The key features I value in this solution:
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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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:
feels like we need to chat less asynchronously too ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 😊 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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] There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. 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 | ||
|
||
|
There was a problem hiding this comment.
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 anaudit
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
audit.json is fine.
OpenJSF work here
https://github.com/openjs-foundation/pkg-vuln-collab-space
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be fully considered that a large number of projects will either:
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:
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.