Skip to content

Prevent assembly loading from TaskFailureDetails.ErrorType values#735

Open
AnatoliB wants to merge 1 commit into
mainfrom
anatolib/iscausedby-hardening
Open

Prevent assembly loading from TaskFailureDetails.ErrorType values#735
AnatoliB wants to merge 1 commit into
mainfrom
anatolib/iscausedby-hardening

Conversation

@AnatoliB
Copy link
Copy Markdown
Member

@AnatoliB AnatoliB commented May 29, 2026

Summary

What changed?

  • Changed the type resolution logic in TaskFailureDetails.IsCausedBy to resolve ErrorType against already-loaded assemblies only, instead of using Type.GetType which can trigger on-demand assembly loading.

Why is this change needed?

  • Type.GetType(string) internally calls Assembly.Load when the input contains an assembly-qualified name. Since ErrorType is deserialized from the durable task hub store, this means an ErrorType value written by a different application version or workflow could cause unintended assembly loading at the point IsCausedBy is called. Restricting resolution to already-loaded assemblies makes the behavior more predictable and avoids unexpected side effects.

Issues / work items

  • N/A

Project checklist

  • Release notes are not required for the next release
  • Backport is not required
  • All required tests have been added/updated (unit tests, E2E tests)
  • Breaking change?
    • If yes:
      • Impact: Generic exception types (e.g. MyException<string>) stored in ErrorType will no longer resolve, causing IsCausedBy to return false. Non-generic exception types are unaffected.
      • Migration guidance: Callers that need to match generic exception types can compare ErrorType directly: details.ErrorType.StartsWith("MyNamespace.MyException\1")`.

AI-assisted code disclosure (required)

Was an AI tool used? (select one)

  • No
  • Yes, AI helped write parts of this PR (e.g., GitHub Copilot)
  • Yes, an AI agent generated most of this PR

If AI was used:

  • Tool(s): GitHub Copilot
  • AI-assisted areas/files: TaskFailureDetails.cs, TaskFailureDetailsTests.cs
  • What you changed after AI output: Nothing

AI verification (required if AI was used):

  • I understand the code and can explain it
  • I verified referenced APIs/types exist and are correct
  • I reviewed edge cases/failure paths (timeouts, retries, cancellation, exceptions)
  • I reviewed concurrency/async behavior
  • I checked for unintended breaking or behavior changes

Testing

Automated tests

  • Result: Passed

Notes for reviewers

  • N/A

@AnatoliB AnatoliB marked this pull request as ready for review May 29, 2026 06:23
Copilot AI review requested due to automatic review settings May 29, 2026 06:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens TaskFailureDetails.IsCausedBy by changing error type resolution to only search already-loaded assemblies (and by stripping assembly qualifiers / generic payloads), preventing attacker-controlled ErrorType values from triggering on-demand assembly loading.

Changes:

  • Updated TaskFailureDetails.IsCausedBy(Type) to resolve ErrorType via already-loaded assemblies only, avoiding Type.GetType(string) and stripping potentially dangerous qualifiers/payloads.
  • Added unit tests covering resolution semantics plus defense-in-depth scenarios ensuring AssemblyResolve is not triggered by poisoned ErrorType values.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/Abstractions/TaskFailureDetails.cs Reworks exception-type resolution to avoid assembly loading; adds safe type-name extraction and a loaded-assembly scan fallback.
test/Abstractions.Tests/TaskFailureDetailsTests.cs Adds coverage for IsCausedBy semantics and tests ensuring poisoned type strings don’t trigger assembly resolution.

Comment on lines +109 to +113
List<Type> matches = AppDomain.CurrentDomain.GetAssemblies()
.Select(a => a.GetType(safeTypeName, throwOnError: false))
.Where(t => t is not null)
.Distinct()
.ToList()!;
Comment on lines +143 to +147
List<string> resolveRequests = new();
ResolveEventHandler handler = (_, args) =>
{
resolveRequests.Add(args.Name);
return null;
Comment on lines +185 to +189
List<string> resolveRequests = new();
ResolveEventHandler handler = (_, args) =>
{
resolveRequests.Add(args.Name);
return null;
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