Skip to content

[K9VULN-9602] Remove hard requirement for packages.lock.json when scanning .csproj files#84

Merged
piloulacdog merged 9 commits intomainfrom
pierrelouis.lacorte/K9VULN-9602-csproj-do-not-require-lockfile
Nov 25, 2025
Merged

[K9VULN-9602] Remove hard requirement for packages.lock.json when scanning .csproj files#84
piloulacdog merged 9 commits intomainfrom
pierrelouis.lacorte/K9VULN-9602-csproj-do-not-require-lockfile

Conversation

@piloulacdog
Copy link
Copy Markdown
Contributor

@piloulacdog piloulacdog commented Nov 24, 2025

🚀 Motivation

Enable the SBOM generator to scan .NET repositories that only contain *.csproj files without requiring packages.lock.json, allowing us to start reporting libraries in repositories with incomplete dependency information.

📝 Summary

This PR implements Phase 1 of the RFC to remove the hard requirement for packages.lock.json when scanning .NET projects:

  • Added parser-nuget-csproj: Directly extracts NuGet dependencies from PackageReference elements in .csproj files
  • Updated matcher-nuget-csproj: reuse logic from parser-nuget-csproj

The implementation prioritizes maintaining full backward compatibility with existing .NET repositories that use packages.lock.json while enabling basic scanning for .csproj-only repositories.

🧪 Testing

  • New tests were added for new logic.
  • Existing tests were updated for new logic, and not only so that they pass!
  • Benchmark results prove that performance is the same or better.

🚧 Staging validation

  • Deployed and monitored using Datadog dashboards.
  • Proof that it works as expected, including profiling or UX screenshots.
Screenshot 2025-11-25 at 09 42 17

🆘 Recovery

Notes for on-call - select only one:

  • The change can be rolled back.
  • Do not roll back. Why?:

📚 Documentation

Document Link or Detail
RFC RFC
Incident N/A
Jira Ticket K9VULN-9602

@github-actions
Copy link
Copy Markdown

github-actions bot commented Nov 24, 2025

Go test coverage report

Total test coverage: 91.0% (4078/4480)

Test coverage has changed in the current files, with 6 lines missing coverage.
file: uncovered: current coverage: base coverage:
pkg/lockfile/match-nuget-csproj.go 2 91.3% (21/23) 91.2% (62/68)
pkg/lockfile/parse-nuget-csproj.go 4 95.3% (82/86) /

func TestParse(t *testing.T) {
t.Parallel()

ecosystems := lockfile.KnownEcosystems()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I deleted this method as it was only existing in tests, and moved it here

@@ -1,25 +0,0 @@
package lockfile
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this was only used in tests


<ItemGroup>
<PackageReference Include="Downloader" Version="3.*" />
<PackageReference Include="MaterialDesignThemes" Version="5.1.0" />
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I modified this fixture because I realized that we were previously wrongly reporting the location for versions in csproj.
For this given example, we would have been reporting the location of: version == "5.1.0" PrivateAssets="All"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

and not version == "5.1.0"

models.FilePosition
}

func (itemGroup *ItemGroup) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

those methods and the shared logic were moved to the parse-nuget-csproj.go

content, err := io.ReadAll(sourcefile)
func (m NugetCsprojMatcher) Match(sourceFile DepFile, packages []PackageDetails) error {
// Extract all package details from the csproj file using the extractor
extractedPackages, err := NuGetCsprojExtractor{}.Extract(sourceFile)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

we are reusing the logic from parse-nuget-csproj.go


// extractPackageVersionLocation extracts the file position of a package version from a block of lines
func extractPackageVersionLocation(block []string, lineStart int, filename string) *models.FilePosition {
versionLocation := fileposition.ExtractDelimitedRegexpPositionInBlock(block, "[^\"]*", lineStart, "Version=\"", "\"")
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

the fix of the version location reported is here. It used to be:

	versionLocation := fileposition.ExtractDelimitedRegexpPositionInBlock(block, ".*", lineStart, "Version=\"", "\"")

and now it is:

	versionLocation := fileposition.ExtractDelimitedRegexpPositionInBlock(block, "[^\"]*", lineStart, "Version=\"", "\"")

but, because .* is greedy, it would match too much

@@ -1,151 +0,0 @@
package lockfile
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this file declared methods which weren't used. So dropped it and I had to update the references in tests for it.

@piloulacdog piloulacdog marked this pull request as ready for review November 24, 2025 18:29
@piloulacdog piloulacdog requested a review from a team as a code owner November 24, 2025 18:29
t.Parallel()

lockfiles := []string{
"*.csproj",
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

rename csproj

GradleLockfile = "gradle.lockfile"
GradleVerificationXML = "gradle/verification-metadata.xml"
MixLock = "mix.lock"
NugetCsProjFile = "*.csproj"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

rename

marcwieserdev
marcwieserdev previously approved these changes Nov 25, 2025
Copy link
Copy Markdown
Collaborator

@marcwieserdev marcwieserdev left a comment

Choose a reason for hiding this comment

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

Thanks for your live review and for the local compare tool ! 👏

@piloulacdog piloulacdog force-pushed the pierrelouis.lacorte/K9VULN-9602-csproj-do-not-require-lockfile branch from 68137fc to 34c6a57 Compare November 25, 2025 16:09
@piloulacdog piloulacdog merged commit 6eeddc6 into main Nov 25, 2025
10 checks passed
@piloulacdog piloulacdog deleted the pierrelouis.lacorte/K9VULN-9602-csproj-do-not-require-lockfile branch November 25, 2025 16:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants