Skip to content

Commit 2bea693

Browse files
committed
Ensure duplicate layer IDs are handled properly
1 parent 23a8d45 commit 2bea693

2 files changed

Lines changed: 67 additions & 1 deletion

File tree

src/Microsoft.ComponentDetection.Orchestrator/Services/GraphTranslation/DefaultGraphTranslationService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ internal List<DetectedComponent> FilterOutBaseImageComponents(
9494
{
9595
if (details.Layers != null)
9696
{
97-
layerLookup[id] = details.Layers.ToDictionary(l => l.LayerIndex);
97+
layerLookup[id] = details.Layers.GroupBy(l => l.LayerIndex).ToDictionary(g => g.Key, g => g.First());
9898
}
9999
}
100100

test/Microsoft.ComponentDetection.Detectors.Tests/LinuxScannerTests.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,4 +1652,70 @@ public void TestLinuxScanner_ProcessSyftOutput_UsesMetadataFilesForLayerAttribut
16521652

16531653
layer2Entry?.Components.Should().BeEmpty();
16541654
}
1655+
1656+
[TestMethod]
1657+
public void TestLinuxScanner_ProcessSyftOutput_HandlesMultipleLayersWithSameDiffId()
1658+
{
1659+
// When layers have the same content (e.g., empty layers), they share the same DiffId.
1660+
// The scanner should handle this without throwing and correctly attribute components.
1661+
var syftOutputJson = """
1662+
{
1663+
"distro": { "id": "ubuntu", "versionID": "22.04" },
1664+
"artifacts": [
1665+
{
1666+
"name": "curl",
1667+
"version": "7.81.0",
1668+
"type": "deb",
1669+
"locations": [
1670+
{
1671+
"path": "/usr/bin/curl",
1672+
"layerID": "sha256:duplicated-layer"
1673+
}
1674+
]
1675+
},
1676+
{
1677+
"name": "wget",
1678+
"version": "1.21",
1679+
"type": "deb",
1680+
"locations": [
1681+
{
1682+
"path": "/usr/bin/wget",
1683+
"layerID": "sha256:unique-layer"
1684+
}
1685+
]
1686+
}
1687+
],
1688+
"source": {
1689+
"id": "sha256:abc",
1690+
"name": "test-image",
1691+
"type": "image",
1692+
"version": "sha256:abc"
1693+
}
1694+
}
1695+
""";
1696+
var syftOutput = SyftOutput.FromJson(syftOutputJson);
1697+
1698+
// Two layers share the same DiffId ("sha256:duplicated-layer") but have different indexes
1699+
var containerLayers = new List<DockerLayer>
1700+
{
1701+
new() { DiffId = "sha256:duplicated-layer", LayerIndex = 0, IsBaseImage = true },
1702+
new() { DiffId = "sha256:duplicated-layer", LayerIndex = 1, IsBaseImage = true },
1703+
new() { DiffId = "sha256:unique-layer", LayerIndex = 2, IsBaseImage = false },
1704+
};
1705+
var enabledTypes = new HashSet<ComponentType> { ComponentType.Linux };
1706+
1707+
var result = this.linuxScanner.ProcessSyftOutput(syftOutput, containerLayers, enabledTypes).ToList();
1708+
1709+
// Should not throw; should produce entries for the two distinct DiffIds
1710+
result.Should().HaveCount(2);
1711+
1712+
var duplicatedLayerEntry = result.First(r => r.DockerLayer.DiffId == "sha256:duplicated-layer");
1713+
duplicatedLayerEntry.DockerLayer.LayerIndex.Should().Be(0, "the first occurrence of the duplicate DiffId should be used");
1714+
duplicatedLayerEntry.Components.Should().ContainSingle();
1715+
((LinuxComponent)duplicatedLayerEntry.Components.First()).Name.Should().Be("curl");
1716+
1717+
var uniqueLayerEntry = result.First(r => r.DockerLayer.DiffId == "sha256:unique-layer");
1718+
uniqueLayerEntry.Components.Should().ContainSingle();
1719+
((LinuxComponent)uniqueLayerEntry.Components.First()).Name.Should().Be("wget");
1720+
}
16551721
}

0 commit comments

Comments
 (0)