Skip to content

Harden exported project metadata against MSBuild injection - #4320

Merged
dgrunwald merged 2 commits into
masterfrom
fix/msbuild-metadata-escaping
Nov 10, 2026
Merged

dgrunwald merged 2 commits into
masterfrom
fix/msbuild-metadata-escaping

Conversation

@siegfriedpammer

Copy link
Copy Markdown
Member

Assembly-controlled names and metadata written into exported project files could be interpreted as MSBuild syntax when the project was loaded or built. Path-like assembly references could also make MSBuild or ILSpy probe outside the export directory.

This change:

  • Escapes assembly-controlled MSBuild values in both project writers.
  • Sanitizes path-like assembly names and reference includes.
  • Rejects path-like assembly names across resolver probing paths.
  • Keeps solution project-reference matching working after escaping.
  • Covers SDK-style item-writing paths and adds regression tests.

Addresses scan finding ANT-2026-P4WLW6Q2.

Tests:

  • OPENSSL_ENABLE_SHA1_SIGNATURES=1 dotnet test --project ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj --report-trx --filter "FullyQualifiedName~MSBuildEscapingTests|FullyQualifiedName~UniversalAssemblyResolverTests" (23 passed)
  • OPENSSL_ENABLE_SHA1_SIGNATURES=1 dotnet test --project ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj --report-trx --filter "FullyQualifiedName~ProjectDecompiler" (260 passed)

An exported project repeats a lot of text the decompiled assembly chose:
resource names in LogicalName metadata, the assembly name, the names of
referenced assemblies, the target framework moniker. MSBuild expands
property references, item references and property functions inside
attribute and element values, so a resource named
"pwn$([System.IO.File]::ReadAllText('/etc/hostname')).txt" was evaluated as
soon as the exported project was opened or built - and the file contents
landed in the name. Assemblies get decompiled precisely because nobody
trusts them, so none of their text may reach MSBuild as syntax.

Quoting uses MSBuild's own escape form, which it resolves back to the
literal before anything else reads the value, and which is a no-op for text
that has nothing to quote. It also repairs a name that could not survive
the round trip before: a resource whose name contains a percent sign was
un-escaped by MSBuild and rebuilt under a different name.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
@siegfriedpammer
siegfriedpammer force-pushed the fix/msbuild-metadata-escaping branch from 7cd91a6 to dfce142 Compare November 8, 2026 11:25
/// </summary>
public static string EscapeAssemblyName(string name)
{
if (name.IndexOfAny(new[] { '/', '\\', ':' }) >= 0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sometimes skipping CleanUpFileName seems questionable?

Assembly-controlled names and metadata are consumed by MSBuild when an exported project is loaded. Escape those values, remove path-like assembly references before project emission and resolver probing, and preserve solution reference matching after escaping.

Assisted-by: OpenCode:gpt-5.6-luna:OpenCode
@siegfriedpammer
siegfriedpammer force-pushed the fix/msbuild-metadata-escaping branch from dfce142 to 02320bd Compare November 8, 2026 15:44
@dgrunwald
dgrunwald merged commit 831eb0f into master Nov 10, 2026
21 checks passed
@christophwille
christophwille deleted the fix/msbuild-metadata-escaping branch November 11, 2026 06:25
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