diff --git a/src/Microsoft.ComponentDetection.Detectors/conda/CondaDependencyResolver.cs b/src/Microsoft.ComponentDetection.Detectors/conda/CondaDependencyResolver.cs
index c84637640..45c49858a 100644
--- a/src/Microsoft.ComponentDetection.Detectors/conda/CondaDependencyResolver.cs
+++ b/src/Microsoft.ComponentDetection.Detectors/conda/CondaDependencyResolver.cs
@@ -18,7 +18,7 @@ public static class CondaDependencyResolver
/// The full condaLock object.
/// The SingleFileComponentRecorder.
public static void RecordDependencyGraphFromFile(CondaLock condaLock, ISingleFileComponentRecorder singleFileComponentRecorder)
- => GetPackages(condaLock).ForEach(package => RegisterPackageWithDependencies(package, null, condaLock, singleFileComponentRecorder));
+ => GetPackages(condaLock).ForEach(package => RegisterPackageWithDependencies(package, null, condaLock, singleFileComponentRecorder, []));
///
/// Updates all registered packages that don't have any ancestors.
@@ -60,7 +60,8 @@ public static void UpdateDirectlyReferencedPackages(ISingleFileComponentRecorder
/// The id of the parent package.
/// The full condaLock object.
/// The SingleFileComponentRecorder.
- private static void RegisterPackageWithDependencies(CondaPackage package, string parentId, CondaLock condaLock, ISingleFileComponentRecorder singleFileComponentRecorder)
+ /// The component ids in the current dependency path.
+ private static void RegisterPackageWithDependencies(CondaPackage package, string parentId, CondaLock condaLock, ISingleFileComponentRecorder singleFileComponentRecorder, HashSet currentPath)
{
if (package == null)
{
@@ -72,13 +73,22 @@ private static void RegisterPackageWithDependencies(CondaPackage package, string
//// Register the package itself.
RegisterPackage(component, parentId, false, singleFileComponentRecorder);
+ //// Conda lockfiles can contain dependency cycles; retain the edge above but do not traverse the same path indefinitely.
+ if (!currentPath.Add(component.Id))
+ {
+ return;
+ }
+
//// Register all dependencies of the package.
package.Dependencies.Keys.ToList().ForEach(dependency =>
RegisterPackageWithDependencies(
condaLock?.Package.FirstOrDefault(condaPackage => condaPackage.Name == dependency && condaPackage.Platform == package.Platform),
component.Id,
condaLock,
- singleFileComponentRecorder));
+ singleFileComponentRecorder,
+ currentPath));
+
+ currentPath.Remove(component.Id);
}
///
diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/CondaLockComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/CondaLockComponentDetectorTests.cs
index 4902b205a..35223105c 100644
--- a/test/Microsoft.ComponentDetection.Detectors.Tests/CondaLockComponentDetectorTests.cs
+++ b/test/Microsoft.ComponentDetection.Detectors.Tests/CondaLockComponentDetectorTests.cs
@@ -104,6 +104,48 @@ public async Task CondaComponentDetector_TestCondaLockFileAsync()
detectedComponents.Should().HaveCount(4);
}
+ [TestMethod]
+ public async Task CondaComponentDetector_CircularDependenciesDoNotOverflowAsync()
+ {
+ var condaLockContent =
+@"version: 1
+metadata:
+ platforms:
+ - linux-64
+package:
+- name: python
+ version: 3.12.13
+ manager: conda
+ platform: linux-64
+ dependencies:
+ pip: ''
+ category: main
+ optional: false
+- name: pip
+ version: 26.2.1
+ manager: conda
+ platform: linux-64
+ dependencies:
+ python: ''
+ category: main
+ optional: false
+";
+
+ var (scanResult, componentRecorder) = await this.detectorTestUtility
+ .WithFile("conda-lock.yml", condaLockContent)
+ .ExecuteDetectorAsync();
+
+ var detectedComponents = componentRecorder.GetDetectedComponents();
+ var dependencyGraph = componentRecorder.GetDependencyGraphsByLocation().Values.First();
+ var pythonId = detectedComponents.Single(component => component.Component is CondaComponent { Name: "python" }).Component.Id;
+ var pipId = detectedComponents.Single(component => component.Component is PipComponent { Name: "pip" }).Component.Id;
+
+ scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
+ detectedComponents.Should().HaveCount(2);
+ dependencyGraph.GetDependenciesForComponent(pythonId).Should().Contain(pipId);
+ dependencyGraph.GetDependenciesForComponent(pipId).Should().Contain(pythonId);
+ }
+
private void AssertCondaLockComponentNameAndVersion(IEnumerable detectedComponents, string name, string version)
{
detectedComponents.SingleOrDefault(c =>