-
Notifications
You must be signed in to change notification settings - Fork 253
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
Adding a proposal for the new CLI command to fix vulnerabilities #13961
base: dev
Are you sure you want to change the base?
Conversation
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.
Mid-way through I realized this was still a WIP, but I finished my review.
hopefully these are helpful.
Happy to chat more as needed.
C:\> dotnet package update | ||
|
||
Fixing outdated packages in ContosoUniversity.sln | ||
Updated UmbracoForms 8.4.1 to 8.7.1 |
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.
Packages are installed on a per project basis, so this would probably be updating projects.
Solution updates & project updates can be different.
There's some mechanics to work out potentially here, especially in non-CPM scenario.
For example, an update to package A from 1.0.0 to 2.0.0 in project 1 may succeed, but may fail in project 2.
|
||
### Fixing vulnerabilities | ||
|
||
When users run `dotnet build` or `dotnet restore` commands in CLI, if they see any warnings related to vulnerabilities in their project’s NuGet dependencies, they can run `dotnet package update --vulnerable` CLI command to fix all the mentioned issues. |
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.
Does this address direct only or both direct & transitive?
Is the audit mode considered at all?
```json | ||
"added": [ | ||
{ | ||
"name": "Microsoft.AspNetCore.MVC", | ||
"version": "2.2.0" | ||
} | ||
], | ||
"removed": [ | ||
{ | ||
"name": "anurse.testing.TestDeprecatedPackage", | ||
"version": "1.0.0" | ||
} | ||
], | ||
"updated": [ | ||
{ | ||
"name": "UmbracoForms", | ||
"version": "8.7.1", | ||
"previousVersion": "8.4.1" | ||
} | ||
], | ||
"failures": [], | ||
"warnings": [] | ||
``` |
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.
Since this command is going to be used in solutions too, I think we need to be more detailed in this json format. For example, let's say that dotnet audit fix resolves to different package version for different projects, or for different target frameworks, I think we need to reflect that in the JSON output.
Another edge case I can think of is when customer has project references. Let's say that ProjectA has a reference to ProjectB that contains a vulnerable package, since those packages are considered transitive will the solution be updating the package in ProjectB and install it in ProjectA?
With those considerations I was thinking something like this:
{
"version": "string",
"feasible": true,
"metadata":
{
"added": "number",
"removed": "number",
"updated": "number",
"promotedToTopLevel": "number",
},
"vulnerabilities":
{
"projectA":
{
"net8.0":
{
"total": "number",
"newtonsoft.json":
{
"originalVersion": "12.0.1", // Could be ranges too
"newVersion": "13.0.1",
"promotedToTopLevel": true,
"projectReference": true, // Could be a path to the project reference
...
},
"castle.core":
{
...
},
},
"net7.0":
{
...
},
},
"projectB":
{
...
},
},
}
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 have a more comments than I thought I would:
- The proposed schema doesn't provide a place to output warning or error messages. If the command fails, the error should still be in a valid JSON, to make it easier to script. I believe that having an
errors
ormessages
array removes the need for afeasible
bool, since any result without any errors is by definition feasible. - Some languages make it hard to use json properties that are not valid language variable names, so property keys like "net8.0" is problematic, as is using project names. Even when your language allows you to parse it, it can be harder to use since property/key names are dynamic. In some languages it's much easier when the schema is static. For example, consider trying to use json query to search the result, or use
Convert-FromJson
in PowerShell. - On the topic of project names, it's possible to have multiple projects in a solution have the same name, as long as they're in different subdirectories, so we have to use the full path to the project, not just the name, otherwise in edge cases they won't be unique.
- In the case of a package being transitive through a project reference, I think the tool should be smart enough to not update the package. In that case, since the package isn't being updated, there's no need for a
"projectReference"
property. dotnet package update
will be used for all kinds of updates, not just vulnerable packages. I think Olia's"added":
,"updated":
, and"removed":
properties make more sense than avulnerabilities
property key. Although in that property you have a collection of projects, soprojects
is a more appropriate name.- I haven't been looking at JSON other than NuGet's own for years, so I'm quite out of touch with JSON best practises. But I don't understand the purpose of putting the counts within a
metadata
object. Is there a benefit over putting the counts in the parent object? - I feel like the counts should be per-project, not at the root object level. Having it at the root object level raises questions, like if multiple projects have to promote the same package to top level, does that count as 1, or is the promote to top level count going to equal the number of projects? However, I don't understand the point of the count properties, since you can look at look at the length of the array of package updates.
- JSON numbers aren't quoted. Some languages/parsers might be loosey-goosey, but strict parsers will fail. I feel like NuGet had this problem when migrating one of our parsers from Newtonsoft.Json (using JToken) to System.Text.Json.
- While nuget.org doesn't have a package with id
total
, it is a valid package ID, so it should not be used in a place where package IDs are also expressed. However, I already mentioned we shouldn't have a dynamic schema, so this concern should automatically be solved when addressing where to put package ids. - Similar to the "I'm not familiar with JSON best practises" comment I made earlier, I'm not sure if long property names
originalVersion
,newVersion
,promotedToTopLevel
are commonly used.from
andto
I think are easy enough to understand, as areoriginal
andnew
. Assuming we have some docs somewhere that explains what it means, I thinkpromoted
might be a good enough name.promotedToTopLevel
might be guessable without docs. However, I just had the thought that since it's possible to tell the difference between an "addition" vs an "upgrade", there could be a propertywasTransitive
, and I think that will be more clear to people thanpromoted
|
||
<!-- One-paragraph description of the proposal. --> | ||
The `dotnet package update` feature is designed to enhance the user experience by enabling .NET developers to address security vulnerabilities, deprecated packages, and outdated versions of their NuGet dependencies with a single command. | ||
This improvement will, in turn, bolster the overall security and reliability of .NET applications. The feature will build upon the existing NuGet Audit functionality. |
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.
This improvement will, in turn, bolster the overall security and reliability of .NET applications. The feature will build upon the existing NuGet Audit functionality. | |
This improvement will, in turn, bolster the overall security and reliability of .NET applications. | |
The feature will build upon the existing NuGet Audit functionality. |
- Has a means to update all packages to latest | ||
- Has a means to update a single package to latest (which can be challenging) | ||
- Has a vulnerability filter, which in turn can allow you to update all vulnerable packages to latest | ||
dotnet |
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.
dotnet |
- Has a vulnerability filter, which in turn can allow you to update all vulnerable packages to latest | ||
dotnet | ||
|
||
CLI |
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 the comparison between VS and the dotnet CLI woud be easier to understand as a table. For example:
Capability | VS | CLI |
---|---|---|
do thing | ✔️ | ✔️ |
something else | ✔️ | ❌ |
When users want to ensure their NuGet dependencies are up to date, they can run a command in .NET CLI `dotnet package update` for a project or a solution. | ||
|
||
```CLI | ||
C:\> dotnet package update [<SOLUTION_PATH>|<PROJECT_PATH_>] |
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.
C:\> dotnet package update [<SOLUTION_PATH>|<PROJECT_PATH_>] | |
C:\> dotnet package update [<SOLUTION_PATH>|<PROJECT_PATH>] |
- 1 - The command will exit with a 1 exit code if changes were successful and the PR is submitted. | ||
- 2 - The command will exit with a 2 exit code if any error has occurred, PR will not be submitted. |
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.
- 1 - The command will exit with a 1 exit code if changes were successful and the PR is submitted. | |
- 2 - The command will exit with a 2 exit code if any error has occurred, PR will not be submitted. | |
- 1 - The command will exit with a 1 exit code if changes were successful. | |
- 2 - The command will exit with a 2 exit code if any error has occurred. |
dotnet package update
won't automate creating pull requests, just modifying local files. The developer, or other tools that use dotnet package update
, will be responsible for creating the pull request.
|
||
| Mode | Explanation | | ||
|------|-------------| | ||
| `promote` | the algorithm will always promote transitive packages to top level. This is an easy to implement feature that we will release in MVP. | |
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'm not sure where in the document it should be written, but while talking to an internal partner, they suggested that packages that are promoted to top level in order to remove a vulnerable package should have some additional metadata to signal this. For example <PackageReference Include="SomePackage" Version="1.2.3" PromotedBecauseOfVulnerability="true" />
. A much, much better name is needed though, this is just an example.
If in the future the top level package that originally caused SomePackage to be included in the package graph has a new version that removes SomePackage from the package graph, this extra metadata will allow a future improvement to dotnet update package
to detect that the PackageReference can now be removed.
I think this is a great idea for projects that aren't using CPM with transitive pinning. Although it's also a signal that transitive pinning without CPM would be a useful feature, but very much out of scope for this feature 😁
|
||
This proposal is focused on bringing the CLI experience up to funcional parity with the experience in Visual Studio and make developers more productive by providing a single command to update all packages to the latest version and a way to update all vulnerable packages. | ||
|
||
## User experience |
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 would be nice to have a summary of all features, similar to what you'd expect to see when you run dotnet package update -h
. The sub-feature examples below are good, but I had an idea for something, and a ctrl-f can't find it in the document. Without re-reading the whole thing top to bottom, I don't have an easy way to figure out if I'm searching for the wrong keyword or if it's something I need to suggest.
And that is that I think a --dry-run
option would be good. But now I feel like this was originally proposed. Did we remove it for some reason that I can't remember?
|
||
#### dotnet package update | ||
|
||
The `dotnet package update` command will update all the NuGet dependencies to the latest versions. |
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 `dotnet package update` command will update all the NuGet dependencies to the latest versions. | |
The `dotnet package update` command without any other arguments or options will update all the NuGet dependencies to the latest versions. |
|
||
```CLI | ||
C:\> dotnet package update --help | ||
dotnet package update [<PROJECT>|<SOLUTION>] [--vulnerable] [--mode <MODE>] [-v|--verbosity <LEVEL>] [--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.
above, the json option was --format json
, but here it's just --json
. As someone else commented, we already have prior art with --format json
|
||
#### --vulnerable | ||
|
||
This flag will try to update only packages that have direct or transitive vulnerabilities. The remediation is calculated with an implicit dotnet audit to then apply directly to a resulting package graph. It can add packages, remove packages, and update packages depending on the problem it's attempting to resolve. It does not take into consideration downgrading to a compatible version if a higher one has already been specified. |
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.
This flag will try to update only packages that have direct or transitive vulnerabilities. The remediation is calculated with an implicit dotnet audit to then apply directly to a resulting package graph. It can add packages, remove packages, and update packages depending on the problem it's attempting to resolve. It does not take into consideration downgrading to a compatible version if a higher one has already been specified. | |
This flag will try to update only packages that have direct or transitive vulnerabilities. | |
The remediation is calculated with an implicit dotnet audit to then apply directly to a resulting package graph. | |
It can add packages, remove packages, and update packages depending on the problem it's attempting to resolve. | |
It does not take into consideration downgrading to a compatible version if a higher one has already been specified. |
I like one sentence per line, because it makes it easier to comment on specific things.
In particular:
- In another comment, I wrote that if we tag packages promoted to top level, then later on we can remove it as a top level package when we detect it's no longer needed as top level. Without this feature, I'm not sure I can think of any scenarios where we will remove a package reference.
- I shouldn't be working this late at night, but at the moment, I don't understand the last sentence. Is this in reference to packages where new versions no longer support target frameworks that the current project is using, so while the higher package version no longer has a known vulnerability, it's not compatible with the project? The "if a higher one has already been specified" part also throws me off. At this time of night, I can't guess what this means.
No description provided.