Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9fa02af
Initial shell of new algorithm to enable sign-tool signing outside of…
mmitche Dec 2, 2025
61035db
Enable already signed bit, and simplify graph initialization
mmitche Jan 13, 2026
b11ad46
Crisp up stream disposal
mmitche Jan 13, 2026
d3b7c61
Zip containers WIP
mmitche Feb 2, 2026
990c6c8
Merge branch 'main' into recursive-signing-rewrite
mmitche Feb 3, 2026
04bbfc9
Merge remote-tracking branch 'upstream/main' into recursive-signing-r…
mmitche Feb 4, 2026
c2aaf80
Go back to recursive evaluation and fixup mock file system
mmitche Feb 6, 2026
a44b67a
Merge branch 'recursive-signing-rewrite' of https://github.com/mmitch…
mmitche Feb 6, 2026
2540f3f
WIP
mmitche Mar 3, 2026
5c6de61
WIP
mmitche Mar 4, 2026
e33d1a7
Add ESRP CLI signing provider with pipeline integration
mmitche Mar 5, 2026
32be3b8
Add PE Authenticode signed file detection
mmitche Mar 5, 2026
1e31004
Improve ESRP signing diagnostics: log files and failure details
mmitche Mar 5, 2026
27d6c19
Restore FluentAssertions for non-recursive-signing tests, fix System.…
mmitche Mar 5, 2026
7c8d2b9
Implement already-signed file skipping with signRegardless support
mmitche Mar 5, 2026
1f07346
Enable real signing again
mmitche Mar 5, 2026
379c838
Remove NuGet package signing from config for prototype build
mmitche Mar 5, 2026
639a02d
Merge remote-tracking branch 'upstream/main' into dev/mmitche/recursi…
mmitche Mar 5, 2026
c0eff82
Merge remote-tracking branch 'upstream/main' into recursive-signing-r…
mmitche Mar 5, 2026
3cc99f2
Package CLI as dotnet tool targeting net10.0, fix signing config JSON
mmitche Mar 6, 2026
8f803e9
RecursiveSigning: review cleanup - terminology, CLI, and schema
mmitche Mar 6, 2026
1b2da15
Make RecursiveSigning packages self-contained, push to test feed
mmitche Mar 6, 2026
2100c2c
Move package push inline as build step using 1ES.PublishNuget
mmitche Mar 6, 2026
b910068
Move signing-config-schema.json to library docs, add references
mmitche Mar 6, 2026
aee3580
Add sample signing configs to docs/samples/
mmitche Mar 6, 2026
1604d07
Add comments and alwaysSign examples to sample configs
mmitche Mar 6, 2026
2cfd9ca
Code review cleanups: simplify analyzers and remove unsafe JSON escaping
mmitche Mar 6, 2026
2be3380
Merge src/ changes from dev/mmitche/recursive-signing-esrp
mmitche Mar 6, 2026
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
14 changes: 9 additions & 5 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ Arcade SDK is the core infrastructure tooling used across the .NET ecosystem for
**NEVER CANCEL BUILDS OR TESTS** - they may take 90+ minutes. Always use appropriate timeouts.

```bash

# Full restore, build, and test - TAKES 90+ MINUTES - NEVER CANCEL
timeout 6000 ./build.sh --restore --build
# Set timeout to 100+ minutes (6000 seconds) for build commands
Expand All @@ -35,8 +34,6 @@ timeout 1800 ./build.sh --restore

# Clean build artifacts
./build.sh --clean
```

### Platform-Specific Commands
- **Linux/macOS**: `./build.sh`, `./test.sh`, `./restore.sh`
- **Windows**: `Build.cmd`, `Test.cmd`, `Restore.cmd`
Expand All @@ -62,15 +59,13 @@ timeout 1800 ./build.sh --restore
- **Microsoft.DotNet.PackageTesting**: Automated package validation testing

## Build Artifacts Structure
```
artifacts/
├── bin/ # Compiled binaries by project/configuration
├── packages/ # Generated NuGet packages (Shipping/NonShipping)
├── TestResults/ # Unit and integration test results
├── log/ # Build logs and binary logs (.binlog)
├── tmp/ # Temporary build artifacts
└── toolset/ # Downloaded build tools and dependencies
```

## Validation and Testing

Expand All @@ -88,6 +83,9 @@ timeout 6000 ./build.sh --restore --build --configuration Release --test
- **SDK Tests**: Validate Arcade SDK works in sample projects
- **Packaging Tests**: Ensure generated packages are valid

### Writing Tests
- When writing tests in this repo, **do not implement new mock file system wrappers**; use the existing `Microsoft.Arcade.Test.Common.MockFileSystem`. Update the shared `IFileSystem`/`MockFileSystem` to support binary-safe operations needed for in-place updates, rather than adding separate binary storage dictionaries or per-test filesystem adapters.

## Common Development Tasks

### Adding New Build Tasks
Expand Down Expand Up @@ -166,3 +164,9 @@ timeout 6000 ./build.sh --restore --build --configuration Release --test
- **Discussions**: Use dotnet/arcade discussions for questions
- **Documentation**: See `/Documentation/` folder for detailed guides
- **Contact**: @dotnet/dnceng team for infrastructure issues

## Project-Specific Notes
- Microsoft.DotNet.RecursiveSigning architecture and design references now live in `src/Microsoft.DotNet.RecursiveSigning/docs/`. Consult the markdown files in that directory for component guidance before editing related code.

### Iterative Signing Workflow
- Sign all nodes ready for signing, update the graph, then repack containers whose signable children are signed; proceed round-by-round.
3 changes: 3 additions & 0 deletions Arcade.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<Project Path="src/Microsoft.DotNet.NuGetRepack/tests/Microsoft.DotNet.NuGetRepack.Tests.csproj" />
<Project Path="src/Microsoft.DotNet.PackageTesting.Tests/Microsoft.DotNet.PackageTesting.Tests.csproj" />
<Project Path="src/Microsoft.DotNet.RemoteExecutor/tests/Microsoft.DotNet.RemoteExecutor.Tests.csproj" />
<Project Path="src/Microsoft.DotNet.RecursiveSigning.Tests/Microsoft.DotNet.RecursiveSigning.Tests.csproj" />
<Project Path="src/Microsoft.DotNet.SetupNugetSources.Tests/Microsoft.DotNet.SetupNugetSources.Tests.csproj" />
<Project Path="src/Microsoft.DotNet.SignTool.Tests/Microsoft.DotNet.SignTool.Tests.csproj" />
<Project Path="src/Microsoft.DotNet.SourceBuild/tests/Microsoft.DotNet.SourceBuild.Tasks.Tests.csproj" />
Expand Down Expand Up @@ -71,6 +72,8 @@
<Project Path="src/Microsoft.DotNet.RemoteExecutor/src/Microsoft.DotNet.RemoteExecutor.csproj" />
<Project Path="src/Microsoft.DotNet.SharedFramework.Sdk/Microsoft.DotNet.SharedFramework.Sdk.csproj" />
<Project Path="src/Microsoft.DotNet.SignTool/Microsoft.DotNet.SignTool.csproj" />
<Project Path="src/Microsoft.DotNet.RecursiveSigning/Microsoft.DotNet.RecursiveSigning.csproj" />
<Project Path="src/Microsoft.DotNet.RecursiveSigning.Cli/Microsoft.DotNet.RecursiveSigning.Cli.csproj" />
<Project Path="src/Microsoft.DotNet.SourceBuild/tasks/Microsoft.DotNet.SourceBuild.Tasks.csproj" />
<Project Path="src/Microsoft.DotNet.StrongName/Microsoft.DotNet.StrongName.csproj" />
<Project Path="src/Microsoft.DotNet.Tar/Microsoft.DotNet.Tar.csproj" />
Expand Down
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
<PackageVersion Include="Microsoft.Extensions.Http" Version="$(MicrosoftExtensionsHttpVersion)" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Abstractions" Version="$(MicrosoftExtensionsFileProvidersAbstractionsVersion)" />
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="$(MicrosoftExtensionsFileSystemGlobbingVersion)" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsLoggingAbstractionsVersion)" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsoleVersion)" />
<PackageVersion Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersion)" />
<PackageVersion Include="System.Composition" Version="$(SystemCompositionVersion)" />
Expand Down Expand Up @@ -92,6 +93,7 @@
<PackageVersion Include="Microsoft.Data.OData" Version="5.8.4" />
<PackageVersion Include="Microsoft.DataServices.Client" Version="$(MicrosoftDataServicesClientVersion)" />
<PackageVersion Include="Microsoft.Diagnostics.Runtime" Version="1.0.5" />
<PackageVersion Include="Microsoft.Ess.EsrpCli" Version="5.1.20250306.4" />
<PackageVersion Include="Microsoft.Identity.Client" Version="4.73.1" />
<PackageVersion Include="Microsoft.OpenApi" Version="1.3.2" />
<PackageVersion Include="Microsoft.OpenApi.Readers" Version="1.3.2" />
Expand Down
4 changes: 4 additions & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<add key="dotnet10-transport" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet10-transport/nuget/v3/index.json" />
<add key="dotnet11" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet11/nuget/v3/index.json" />
<add key="dotnet11-transport" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet11-transport/nuget/v3/index.json" />
<add key="MicroBuildToolset" value="https://pkgs.dev.azure.com/devdiv/_packaging/Engineering/nuget/v3/index.json" />
</packageSources>
<packageSourceMapping>
<clear />
Expand Down Expand Up @@ -59,6 +60,9 @@
<packageSource key="dotnet11-transport">
<package pattern="microsoft.*" />
</packageSource>
<packageSource key="MicroBuildToolset">
<package pattern="microsoft.*" />
</packageSource>
</packageSourceMapping>
<disabledPackageSources>
<clear />
Expand Down
1 change: 1 addition & 0 deletions eng/BuildTask.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<PackageVersion Update="Microsoft.Extensions.DependencyModel" Version="8.0.2" />
<PackageVersion Update="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Update="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageVersion Update="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
</ItemGroup>

</Project>
2 changes: 2 additions & 0 deletions eng/Version.Details.props
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ This file should be imported by eng/Versions.props
<MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>9.0.0</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>
<MicrosoftExtensionsFileSystemGlobbingPackageVersion>9.0.0</MicrosoftExtensionsFileSystemGlobbingPackageVersion>
<MicrosoftExtensionsHttpPackageVersion>9.0.0</MicrosoftExtensionsHttpPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>9.0.0</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>9.0.0</MicrosoftExtensionsLoggingConsolePackageVersion>
<SystemCollectionsImmutablePackageVersion>9.0.0</SystemCollectionsImmutablePackageVersion>
<SystemFormatsAsn1PackageVersion>9.0.0</SystemFormatsAsn1PackageVersion>
Expand Down Expand Up @@ -88,6 +89,7 @@ This file should be imported by eng/Versions.props
<MicrosoftExtensionsFileProvidersAbstractionsVersion>$(MicrosoftExtensionsFileProvidersAbstractionsPackageVersion)</MicrosoftExtensionsFileProvidersAbstractionsVersion>
<MicrosoftExtensionsFileSystemGlobbingVersion>$(MicrosoftExtensionsFileSystemGlobbingPackageVersion)</MicrosoftExtensionsFileSystemGlobbingVersion>
<MicrosoftExtensionsHttpVersion>$(MicrosoftExtensionsHttpPackageVersion)</MicrosoftExtensionsHttpVersion>
<MicrosoftExtensionsLoggingAbstractionsVersion>$(MicrosoftExtensionsLoggingAbstractionsPackageVersion)</MicrosoftExtensionsLoggingAbstractionsVersion>
<MicrosoftExtensionsLoggingConsoleVersion>$(MicrosoftExtensionsLoggingConsolePackageVersion)</MicrosoftExtensionsLoggingConsoleVersion>
<SystemCollectionsImmutableVersion>$(SystemCollectionsImmutablePackageVersion)</SystemCollectionsImmutableVersion>
<SystemFormatsAsn1Version>$(SystemFormatsAsn1PackageVersion)</SystemFormatsAsn1Version>
Expand Down
4 changes: 4 additions & 0 deletions eng/Version.Details.xml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@
<Uri>https://github.com/dotnet/runtime</Uri>
<Sha>d3981726bc8b0e179db50301daf9f22d42393096</Sha>
</Dependency>
<Dependency Name="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0">
<Uri>https://github.com/dotnet/runtime</Uri>
<Sha>d3981726bc8b0e179db50301daf9f22d42393096</Sha>
</Dependency>
<Dependency Name="System.Text.Encodings.Web" Version="9.0.0">
<Uri>https://github.com/dotnet/runtime</Uri>
<Sha>d3981726bc8b0e179db50301daf9f22d42393096</Sha>
Expand Down
14 changes: 14 additions & 0 deletions src/Common/Microsoft.Arcade.Common/FileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,19 @@ public void WriteToFile(string path, string content)
/// <param name="targetPath">Target path that is relative to base path.</param>
/// <exception cref="NotImplementedException"></exception>
public virtual string GetRelativePath(string basePath, string targetPath) => throw new NotImplementedException("Not supported in default FileSystem implementation");

public byte[] ReadAllBytes(string path) => File.ReadAllBytes(path);

public void WriteAllBytes(string path, byte[] bytes)
{
string? dirPath = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(dirPath))
{
Directory.CreateDirectory(dirPath);
}
File.WriteAllBytes(path, bytes);
}

public long GetFileLength(string path) => new FileInfo(path).Length;
}
}
6 changes: 6 additions & 0 deletions src/Common/Microsoft.Arcade.Common/IFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,11 @@ public interface IFileSystem
FileAttributes GetAttributes(string path);

string GetRelativePath(string basePath, string targetPath);

byte[] ReadAllBytes(string path);

void WriteAllBytes(string path, byte[] bytes);

long GetFileLength(string path);
}
}
88 changes: 75 additions & 13 deletions src/Common/Microsoft.Arcade.Test.Common/MockFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,23 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Unicode;
using Microsoft.Arcade.Common;

#nullable enable
namespace Microsoft.Arcade.Test.Common
{

public class MockFileSystem : IFileSystem
{
#region File system state

public HashSet<string> Directories { get; }
public HashSet<string> Directories { get; }

public Dictionary<string, string> Files { get; }
public Dictionary<string, byte[]> Files { get; }



public List<string> RemovedFiles { get; } = new();

Expand All @@ -29,7 +34,7 @@ public MockFileSystem(
string directorySeparator = "/")
{
Directories = new(directories ?? new string[0]);
Files = files ?? new();
Files = files?.ToDictionary(f => f.Key, f => System.Text.Encoding.UTF8.GetBytes(f.Value)) ?? new();
DirectorySeparator = directorySeparator;
}

Expand Down Expand Up @@ -59,12 +64,17 @@ public void DeleteFile(string path)

public string PathCombine(string path1, string path2, string path3) => path1 + DirectorySeparator + path2 + DirectorySeparator + path3;

public void WriteToFile(string path, string content) => Files[path] = content;
public void WriteToFile(string path, string content) => Files[path] = System.Text.Encoding.UTF8.GetBytes(content);

public string ReadToString(string path) => System.Text.Encoding.UTF8.GetString(Files[path]);

public void CopyFile(string sourceFileName, string destFileName, bool overwrite = false) => Files[destFileName] = Files[sourceFileName];

public Stream GetFileStream(string path, FileMode mode, FileAccess access)
=> FileExists(path) ? new MemoryStream() : new MockFileStream(this, path);
{
// Always use MockFileStream which handles both read and write correctly
return new MockFileStream(this, path, mode, access);
}

public FileAttributes GetAttributes(string path)
{
Expand All @@ -90,34 +100,86 @@ public string GetRelativePath(string basePath, string targetPath)
return targetPath.Replace(basePath, "").TrimStart('/', '\\');
}

public byte[] ReadAllBytes(string path)
{
if (!Files.ContainsKey(path))
{
throw new FileNotFoundException($"File not found: {path}");
}
return Files[path];
}

public void WriteAllBytes(string path, byte[] bytes)
{
Files[path] = bytes;
}

public long GetFileLength(string path)
{
if (!Files.ContainsKey(path))
{
throw new FileNotFoundException($"File not found: {path}");
}
return Files[path].LongLength;
}

#endregion

/// <summary>
/// Allows to write to a stream that will end up in the MockFileSystem.
/// Allows to read and write to a stream that will end up in the MockFileSystem.
/// </summary>
private class MockFileStream : MemoryStream
{
private readonly MockFileSystem _fileSystem;
private readonly string _path;
private readonly FileAccess _access;
private bool _disposed = false;

public MockFileStream(MockFileSystem fileSystem, string path)
: base(fileSystem.FileExists(path) ? System.Text.Encoding.UTF8.GetBytes(fileSystem.Files[path]) : new byte[2048])
public MockFileStream(MockFileSystem fileSystem, string path, FileMode mode, FileAccess access)
{
_fileSystem = fileSystem;
_path = path;
_access = access;

// Initialize the stream based on mode and existing file content
if (mode == FileMode.Open || mode == FileMode.Append)
{
if (fileSystem.FileExists(path))
{
byte[] existingContent = fileSystem.Files[path];
Write(existingContent, 0, existingContent.Length);

if (mode == FileMode.Open)
{
Seek(0, SeekOrigin.Begin); // Reset to beginning for read
}
// For Append mode, position is already at the end
}
else if (mode == FileMode.Open)
{
throw new FileNotFoundException($"File not found: {path}");
}
}
else if (mode == FileMode.Create || mode == FileMode.CreateNew || mode == FileMode.Truncate)
{
// Start with an empty stream
if (mode == FileMode.CreateNew && fileSystem.FileExists(path))
{
throw new IOException($"File already exists: {path}");
}
}
}

protected override void Dispose(bool disposing)
{
// flush file to our system
if (!_disposed)
// Flush to file system if we have write access
if (!_disposed && (_access == FileAccess.Write || _access == FileAccess.ReadWrite))
{
_disposed = true;
using var sr = new StreamReader(this);
Seek(0, SeekOrigin.Begin);
_fileSystem.WriteToFile(_path, sr.ReadToEnd().Replace("\0", ""));
_fileSystem.WriteAllBytes(_path, ToArray());
}

base.Dispose(disposing);
}
}
}
Expand Down
Loading