@@ -4652,7 +4652,7 @@ export async function createCsharpBom(path, options) {
4652
4652
if ( options . lifecycle === "post-build" ) {
4653
4653
return createBinaryBom ( path , options ) ;
4654
4654
}
4655
- const parentComponent = createDefaultParentComponent ( path , "nuget" , options ) ;
4655
+ let parentComponent = createDefaultParentComponent ( path , "nuget" , options ) ;
4656
4656
const slnFiles = getAllFiles (
4657
4657
path ,
4658
4658
`${ options . multiProject ? "**/" : "" } *.sln` ,
@@ -4682,7 +4682,7 @@ export async function createCsharpBom(path, options) {
4682
4682
`${ options . multiProject ? "**/" : "" } project.assets.json` ,
4683
4683
options ,
4684
4684
) ;
4685
- const pkgLockFiles = getAllFiles (
4685
+ let pkgLockFiles = getAllFiles (
4686
4686
path ,
4687
4687
`${ options . multiProject ? "**/" : "" } packages.lock.json` ,
4688
4688
options ,
@@ -4692,12 +4692,12 @@ export async function createCsharpBom(path, options) {
4692
4692
`${ options . multiProject ? "**/" : "" } paket.lock` ,
4693
4693
options ,
4694
4694
) ;
4695
- const nupkgFiles = getAllFiles (
4695
+ let nupkgFiles = getAllFiles (
4696
4696
path ,
4697
4697
`${ options . multiProject ? "**/" : "" } *.nupkg` ,
4698
4698
options ,
4699
4699
) ;
4700
- // Support for automatic restore
4700
+ // Support for automatic restore for .Net projects
4701
4701
if (
4702
4702
options . installDeps &&
4703
4703
! projAssetsFiles . length &&
@@ -4706,47 +4706,90 @@ export async function createCsharpBom(path, options) {
4706
4706
) {
4707
4707
const filesToRestore = slnFiles . concat ( csProjFiles ) ;
4708
4708
for ( const f of filesToRestore ) {
4709
+ const buildCmd =
4710
+ options . projectType === "dotnet-framework" ? "nuget" : "dotnet" ;
4709
4711
if ( DEBUG_MODE ) {
4710
4712
const basePath = dirname ( f ) ;
4711
- console . log ( " Executing 'dotnet restore' in" , basePath ) ;
4713
+ console . log ( ` Executing '${ buildCmd } restore' in ${ basePath } ` ) ;
4712
4714
}
4713
4715
const result = spawnSync (
4714
- "dotnet" ,
4715
- [ "restore" , "--force" , "--ignore-failed-sources" , f ] ,
4716
+ buildCmd ,
4717
+ options . projectType === "dotnet-framework"
4718
+ ? [
4719
+ "restore" ,
4720
+ "-NonInteractive" ,
4721
+ "-Recursive" ,
4722
+ "-PackageSaveMode" ,
4723
+ "nuspec;nupkg" ,
4724
+ "-Verbosity" ,
4725
+ "quiet" ,
4726
+ ]
4727
+ : [ "restore" , "--force" , "--ignore-failed-sources" , f ] ,
4716
4728
{
4717
4729
cwd : path ,
4718
4730
encoding : "utf-8" ,
4719
4731
} ,
4720
4732
) ;
4721
4733
if ( DEBUG_MODE && ( result . status !== 0 || result . error ) ) {
4722
4734
console . error (
4723
- " Restore has failed. Check if dotnet is installed and available in PATH." ,
4735
+ ` Restore has failed. Check if ${ buildCmd } is installed and available in PATH.` ,
4724
4736
) ;
4725
4737
console . log (
4726
4738
"Authenticate with any private registries such as Azure Artifacts feed before running cdxgen." ,
4727
4739
) ;
4740
+ console . log (
4741
+ "Alternatively, try using the unofficial `ghcr.io/appthreat/cdxgen-dotnet6:v10` container image, which bundles nuget (mono) and a range of dotnet SDKs." ,
4742
+ ) ;
4728
4743
console . log ( result . stderr ) ;
4729
4744
options . failOnError && process . exit ( 1 ) ;
4730
4745
}
4731
4746
}
4732
- // Collect the assets file generated from restore
4747
+ // Collect the assets, lock, and nupkg files generated from restore
4733
4748
projAssetsFiles = getAllFiles (
4734
4749
path ,
4735
4750
`${ options . multiProject ? "**/" : "" } project.assets.json` ,
4736
4751
options ,
4737
4752
) ;
4753
+ pkgLockFiles = getAllFiles (
4754
+ path ,
4755
+ `${ options . multiProject ? "**/" : "" } packages.lock.json` ,
4756
+ options ,
4757
+ ) ;
4758
+ nupkgFiles = getAllFiles (
4759
+ path ,
4760
+ `${ options . multiProject ? "**/" : "" } *.nupkg` ,
4761
+ options ,
4762
+ ) ;
4738
4763
}
4739
4764
let pkgList = [ ] ;
4765
+ const parentDependsOn = new Set ( ) ;
4740
4766
if ( nupkgFiles . length && projAssetsFiles . length === 0 ) {
4741
4767
manifestFiles = manifestFiles . concat ( nupkgFiles ) ;
4768
+ // When parsing nupkg files, only version ranges will be specified under dependencies
4769
+ // To resolve the version, we need to track the mapping between name and resolved versions here
4770
+ let dependenciesMap = { } ;
4771
+ const pkgNameVersions = { } ;
4742
4772
for ( const nf of nupkgFiles ) {
4743
4773
if ( DEBUG_MODE ) {
4744
4774
console . log ( `Parsing ${ nf } ` ) ;
4745
4775
}
4746
- const dlist = await parseNupkg ( nf ) ;
4747
- if ( dlist ?. length ) {
4748
- pkgList = pkgList . concat ( dlist ) ;
4776
+ const retMap = await parseNupkg ( nf ) ;
4777
+ if ( retMap ?. pkgList . length ) {
4778
+ pkgList = pkgList . concat ( retMap . pkgList ) ;
4779
+ for ( const d of retMap . pkgList ) {
4780
+ parentDependsOn . add ( d [ "bom-ref" ] ) ;
4781
+ pkgNameVersions [ d . name ] = d . version ;
4782
+ }
4749
4783
}
4784
+ if ( retMap ?. dependenciesMap ) {
4785
+ dependenciesMap = { ...dependenciesMap , ...retMap . dependenciesMap } ;
4786
+ }
4787
+ } // end for
4788
+ for ( const k of Object . keys ( dependenciesMap ) ) {
4789
+ const dependsOn = dependenciesMap [ k ] . map (
4790
+ ( p ) => `pkg:nuget/${ p } @${ pkgNameVersions [ p ] || "latest" } ` ,
4791
+ ) ;
4792
+ dependencies . push ( { ref : k , dependsOn : dependsOn . sort ( ) } ) ;
4750
4793
}
4751
4794
}
4752
4795
// project.assets.json parsing
@@ -4780,7 +4823,7 @@ export async function createCsharpBom(path, options) {
4780
4823
"2. Use the environment variable `DOTNET_ROLL_FORWARD` to roll forward to a closest available SDK such as .Net core or dotnet 6." ,
4781
4824
) ;
4782
4825
console . log (
4783
- "3. If the project uses the legacy .Net Framework 4.6/4.7, it might require Windows operating system ." ,
4826
+ "3. If the project uses the legacy .Net Framework 4.6/4.7/4.8 , it might require execution on Windows ." ,
4784
4827
) ;
4785
4828
console . log (
4786
4829
"Alternatively, try using the unofficial `ghcr.io/appthreat/cdxgen-dotnet:v10` container image, which bundles a range of dotnet SDKs." ,
@@ -4789,7 +4832,6 @@ export async function createCsharpBom(path, options) {
4789
4832
}
4790
4833
} else if ( pkgLockFiles . length ) {
4791
4834
manifestFiles = manifestFiles . concat ( pkgLockFiles ) ;
4792
- const parentDependsOn = new Set ( ) ;
4793
4835
// packages.lock.json from nuget
4794
4836
for ( const af of pkgLockFiles ) {
4795
4837
if ( DEBUG_MODE ) {
@@ -4814,12 +4856,6 @@ export async function createCsharpBom(path, options) {
4814
4856
}
4815
4857
}
4816
4858
}
4817
- if ( parentDependsOn . size ) {
4818
- dependencies . splice ( 0 , 0 , {
4819
- ref : parentComponent [ "bom-ref" ] ,
4820
- dependsOn : Array . from ( parentDependsOn ) ,
4821
- } ) ;
4822
- }
4823
4859
} else if ( pkgConfigFiles . length ) {
4824
4860
manifestFiles = manifestFiles . concat ( pkgConfigFiles ) ;
4825
4861
// packages.config parsing
@@ -4832,9 +4868,12 @@ export async function createCsharpBom(path, options) {
4832
4868
if ( pkgData . charCodeAt ( 0 ) === 0xfeff ) {
4833
4869
pkgData = pkgData . slice ( 1 ) ;
4834
4870
}
4835
- const dlist = parseCsPkgData ( pkgData ) ;
4871
+ const dlist = parseCsPkgData ( pkgData , f ) ;
4836
4872
if ( dlist ?. length ) {
4837
4873
pkgList = pkgList . concat ( dlist ) ;
4874
+ for ( const d of dlist ) {
4875
+ parentDependsOn . add ( d [ "bom-ref" ] ) ;
4876
+ }
4838
4877
}
4839
4878
}
4840
4879
}
@@ -4857,8 +4896,19 @@ export async function createCsharpBom(path, options) {
4857
4896
}
4858
4897
}
4859
4898
}
4860
- if ( ! pkgList . length && csProjFiles . length ) {
4899
+ if (
4900
+ options . projectType === "dotnet-framework" ||
4901
+ ( ! pkgList . length && csProjFiles . length && ! nupkgFiles . length )
4902
+ ) {
4861
4903
manifestFiles = manifestFiles . concat ( csProjFiles ) ;
4904
+ // Parsing csproj is quite error prone. Some project files may not have versions specified
4905
+ // To work around this, we make use of the version from the existing list
4906
+ const pkgNameVersions = { } ;
4907
+ for ( const p of pkgList ) {
4908
+ if ( p . version ) {
4909
+ pkgNameVersions [ p . name ] = p . version ;
4910
+ }
4911
+ }
4862
4912
// .csproj parsing
4863
4913
for ( const f of csProjFiles ) {
4864
4914
if ( DEBUG_MODE ) {
@@ -4869,12 +4919,31 @@ export async function createCsharpBom(path, options) {
4869
4919
if ( csProjData . charCodeAt ( 0 ) === 0xfeff ) {
4870
4920
csProjData = csProjData . slice ( 1 ) ;
4871
4921
}
4872
- const dlist = parseCsProjData ( csProjData , f ) ;
4873
- if ( dlist ?. length ) {
4874
- pkgList = pkgList . concat ( dlist ) ;
4922
+ const retMap = parseCsProjData ( csProjData , f , pkgNameVersions ) ;
4923
+ if ( retMap ?. parentComponent ) {
4924
+ // If there are multiple project files, track the parent components using nested components
4925
+ if ( csProjFiles . length > 1 ) {
4926
+ if ( ! parentComponent . components ) {
4927
+ parentComponent . components = [ ] ;
4928
+ }
4929
+ parentComponent . components . push ( retMap . parentComponent ) ;
4930
+ } else {
4931
+ // There is only one project file. Make it the parent.
4932
+ parentComponent = retMap . parentComponent ;
4933
+ }
4934
+ }
4935
+ if ( retMap ?. pkgList . length ) {
4936
+ pkgList = pkgList . concat ( retMap . pkgList ) ;
4937
+ }
4938
+ if ( retMap . dependencies ?. length ) {
4939
+ dependencies = mergeDependencies (
4940
+ dependencies ,
4941
+ retMap . dependencies ,
4942
+ parentComponent ,
4943
+ ) ;
4875
4944
}
4876
4945
}
4877
- if ( pkgList . length ) {
4946
+ if ( pkgList . length && options . projectType !== "dotnet-framework" ) {
4878
4947
console . log (
4879
4948
`Found ${ pkgList . length } components by parsing the ${ csProjFiles . length } csproj files. The resulting SBOM will be incomplete.` ,
4880
4949
) ;
@@ -4900,6 +4969,13 @@ export async function createCsharpBom(path, options) {
4900
4969
pkgList = addEvidenceForDotnet ( pkgList , slicesFile , options ) ;
4901
4970
}
4902
4971
}
4972
+ // Parent dependency tree
4973
+ if ( parentDependsOn . size && parentComponent && parentComponent [ "bom-ref" ] ) {
4974
+ dependencies . splice ( 0 , 0 , {
4975
+ ref : parentComponent [ "bom-ref" ] ,
4976
+ dependsOn : Array . from ( parentDependsOn ) ,
4977
+ } ) ;
4978
+ }
4903
4979
if ( FETCH_LICENSE ) {
4904
4980
const retMap = await getNugetMetadata ( pkgList , dependencies ) ;
4905
4981
if ( retMap . dependencies ?. length ) {
@@ -6036,6 +6112,7 @@ export async function createBom(path, options) {
6036
6112
case "csharp" :
6037
6113
case "netcore" :
6038
6114
case "dotnet" :
6115
+ case "dotnet-framework" :
6039
6116
case "vb" :
6040
6117
return await createCsharpBom ( path , options ) ;
6041
6118
case "dart" :
0 commit comments