Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 39 additions & 19 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ on:
tags:
- '*.*.*'
permissions:
id-token: write
contents: read
checks: write

jobs:
build-windows:
Expand All @@ -17,12 +19,20 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Azure Login via OIDC
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
9.0.x
- name: Install NuGetKeyVaultSignTool
run: dotnet tool install --global NuGetKeyVaultSignTool
- name: Build and Test
run: |
dotnet build --configuration Release
Expand All @@ -32,25 +42,16 @@ jobs:
dotnet test --configuration Release --no-build --results-directory ".\artifacts" -l trx .\src\AutoMapper.DI.Tests
shell: pwsh
build:
needs: build-windows
strategy:
fail-fast: false
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
9.0.x
- name: Build and Test
run: ./Build.ps1
- name: Generate SBOM
run: |
dotnet tool install --global Microsoft.Sbom.DotNetTool --version 4.1.5
sbom-tool generate -b artifacts -bc src/AutoMapper -pn AutoMapper -pv ${{ github.ref_name }} -ps LuckyPennySoftware -nsb https://automapper.io/sbom
shell: pwsh
- name: Sign packages
run: |-
foreach ($f in Get-ChildItem "./artifacts" -Filter "*.nupkg") {
NuGetKeyVaultSignTool sign $f.FullName --file-digest sha256 --timestamp-rfc3161 http://timestamp.digicert.com --azure-key-vault-managed-identity --azure-key-vault-url ${{ secrets.AZURE_KEYVAULT_URI }} --azure-key-vault-certificate ${{ secrets.CODESIGN_CERT_NAME }}
}
- name: Push to MyGet
env:
NUGET_URL: https://f.feedz.io/lucky-penny-software/automapper/nuget/index.json
Expand All @@ -67,4 +68,23 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: artifacts
path: artifacts/**/*
path: artifacts/**/*
build:
needs: build-windows
strategy:
fail-fast: false
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
9.0.x
- name: Build and Test
run: ./Build.ps1
shell: pwsh
2 changes: 1 addition & 1 deletion docs/source/5.0-Upgrade-Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ cfg.CreateMap<Category, CategoryDto>().MaxDepth(3);
cfg.CreateMap<User, UserDto>().PreserveReferences();
```

Starting from 6.1.0 PreserveReferences is set automatically at config time whenever the recursion can be detected statically. If you're still getting `StackOverflowException`, open an issue with a full repro and we'll look into it.
Starting from 6.1.0 PreserveReferences is set automatically at config time whenever the recursion can be detected statically. Starting from 15.1.1, a default MaxDepth of 64 is also applied automatically, preventing a StackOverflowException from deeply nested (but non-circular) object graphs (see GHSA-rvv3-g6hj-g44x). If you need deeper mapping, call `.MaxDepth(n)` explicitly. To rely solely on object-identity caching without a depth limit, call `.PreserveReferences()` explicitly.

## UseDestinationValue

Expand Down
18 changes: 17 additions & 1 deletion docs/source/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,20 @@ Compilation times increase with the size of the execution plan and that depends
You can set `MapAtRuntime` per member or `MaxExecutionPlanDepth` globally (the default is one, set it to zero).

These will reduce the size of the execution plan by replacing the execution plan for a child object with a method call. The compilation will be faster, but the mapping itself might be slower. Search the repo for more details and use a profiler to better understand the effect.
Avoiding `PreserveReferences` and `MaxDepth` also helps.
Avoiding `PreserveReferences` and `MaxDepth` also helps.

## Circular and Self-Referential Types

When AutoMapper detects a self-referential type mapping (e.g., `CreateMap<Node, Node>()` where `Node` has a `Node` property), it automatically enables `PreserveReferences` to avoid re-mapping the same object instance. It also applies a default `MaxDepth` of **64** — matching System.Text.Json and Newtonsoft.Json — to prevent a Denial-of-Service condition from deeply nested object graphs (see GHSA-rvv3-g6hj-g44x).

If your object graphs legitimately exceed 64 levels, increase the limit explicitly:

```c#
cfg.CreateMap<Node, Node>().MaxDepth(128);
```

To disable the depth limit entirely and rely solely on object-identity caching, call `.PreserveReferences()` explicitly:

```c#
cfg.CreateMap<Node, Node>().PreserveReferences();
```
4 changes: 4 additions & 0 deletions src/AutoMapper/Execution/TypeMapPlanBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ private static void CheckForCycles(IGlobalConfiguration configuration, TypeMap t
}

memberTypeMap.PreserveReferences = true;
if (memberTypeMap.MaxDepth == 0)
{
memberTypeMap.MaxDepth = 64;
}
Trace(typeMap, memberTypeMap, memberMap);
if (memberMap.Inline)
{
Expand Down
47 changes: 47 additions & 0 deletions src/UnitTests/Bug/DeepNestingStackOverflow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace AutoMapper.UnitTests.Bug;

public class DeepNestingStackOverflow
{
class Circular { public Circular Self { get; set; } }

// Verifies that mapping a deeply nested self-referential object does not
// crash the process with a StackOverflowException (GHSA-rvv3-g6hj-g44x).
// AutoMapper auto-applies a default MaxDepth of 64 (matching System.Text.Json
// and Newtonsoft.Json) when it detects a self-referential type mapping.
[Fact]
public void Mapping_deeply_nested_self_referential_object_should_not_stackoverflow()
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<Circular, Circular>());
var mapper = config.CreateMapper();

var root = new Circular();
var current = root;
for (int i = 0; i < 30_000; i++)
{
current.Self = new Circular();
current = current.Self;
}

// Should complete without crashing; mapping is truncated at default MaxDepth (64)
var result = mapper.Map<Circular>(root);
result.ShouldNotBeNull();

int depth = 0;
current = result;
while (current.Self != null)
{
depth++;
current = current.Self;
}
depth.ShouldBeLessThanOrEqualTo(64);
}

// Verifies that configuration validation does not detect the vulnerability —
// only the runtime mapping is affected, not the configuration itself.
[Fact]
public void AssertConfigurationIsValid_does_not_detect_deep_nesting_vulnerability()
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<Circular, Circular>());
config.AssertConfigurationIsValid();
}
}
4 changes: 2 additions & 2 deletions src/UnitTests/Bug/MultiThreadingIssues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,7 @@ public async Task Should_work()
};
var tasks =
types
.Concat(types.Select(t => t.Reverse().ToArray()))
.Concat(types.Select(t => t.AsEnumerable().Reverse().ToArray()))
.Select(t=>(SourceType: sourceType.MakeGenericType(t[0]), DestinationType: destinationType.MakeGenericType(t[1])))
.ToArray()
.Select(s => Task.Factory.StartNew(() => c.ResolveTypeMap(s.SourceType, s.DestinationType)))
Expand Down Expand Up @@ -1173,7 +1173,7 @@ public async Task Should_work()
};
var tasks =
types
.Concat(types.Select(t => t.Reverse().ToArray()))
.Concat(types.Select(t => t.AsEnumerable().Reverse().ToArray()))
.Select(t=>(SourceType: sourceType.MakeGenericType(t[0]), DestinationType: destinationType.MakeGenericType(t[1])))
.ToArray()
.Select(s => Task.Factory.StartNew(() => mapper.Map(null, s.SourceType, s.DestinationType)))
Expand Down
Loading