fix: bound catalog acquisition before pilot staging
This commit is contained in:
@@ -9,11 +9,14 @@ namespace Launcher.Next.Infrastructure;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class FileSystemCatalogPackageAcquirer(string catalogRoot) : IPackageCatalogPackageAcquirer
|
public sealed class FileSystemCatalogPackageAcquirer(string catalogRoot) : IPackageCatalogPackageAcquirer
|
||||||
{
|
{
|
||||||
|
private const int MaxFiles = 256;
|
||||||
|
private const long MaxBytes = 512L * 1024 * 1024;
|
||||||
|
|
||||||
public async Task<CatalogPackage?> AcquireAsync(PackageRequirement requirement, CancellationToken cancellationToken)
|
public async Task<CatalogPackage?> AcquireAsync(PackageRequirement requirement, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var root = Path.GetFullPath(catalogRoot);
|
var root = Path.GetFullPath(catalogRoot);
|
||||||
var candidate = Path.GetFullPath(Path.Combine(root, requirement.PackageId, requirement.RequiredVersion));
|
var candidate = Path.GetFullPath(Path.Combine(root, requirement.PackageId, requirement.RequiredVersion));
|
||||||
if (!IsWithin(root, candidate) || !Directory.Exists(candidate) || IsReparsePoint(candidate))
|
if (!Directory.Exists(root) || IsReparsePoint(root) || !IsWithin(root, candidate) || !Directory.Exists(candidate) || IsReparsePoint(candidate))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var manifestPath = Path.Combine(candidate, "package.manifest.json");
|
var manifestPath = Path.Combine(candidate, "package.manifest.json");
|
||||||
@@ -21,6 +24,10 @@ public sealed class FileSystemCatalogPackageAcquirer(string catalogRoot) : IPack
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
var manifest = PackageManifestJson.Parse(await File.ReadAllTextAsync(manifestPath, cancellationToken));
|
var manifest = PackageManifestJson.Parse(await File.ReadAllTextAsync(manifestPath, cancellationToken));
|
||||||
|
if (manifest.Files.Count > MaxFiles || manifest.Files.Sum(file => file.Length) > MaxBytes ||
|
||||||
|
manifest.Files.Any(file => string.IsNullOrWhiteSpace(file.Path) || Path.IsPathRooted(file.Path) ||
|
||||||
|
file.Path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Any(segment => segment is ".." or ".")))
|
||||||
|
return null;
|
||||||
return new CatalogPackage(manifest, candidate);
|
return new CatalogPackage(manifest, candidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,3 +40,10 @@ filesystem catalog adapter returns a descriptor for acquisition only; local
|
|||||||
immutable staging remains the only active package root. These adapters and the
|
immutable staging remains the only active package root. These adapters and the
|
||||||
controlled local catalog scenarios are verified by tests; a corporate DFS pilot
|
controlled local catalog scenarios are verified by tests; a corporate DFS pilot
|
||||||
and Autodesk launch with a real roster are not runtime verified.
|
and Autodesk launch with a real roster are not runtime verified.
|
||||||
|
|
||||||
|
Catalog acquisition is bounded before staging: the configured catalog root and
|
||||||
|
package root must not be reparse points, the descriptor must remain below the
|
||||||
|
configured root, manifests may contain at most 256 files and 512 MiB declared
|
||||||
|
payload, and absolute or traversal payload paths are rejected. A corporate pilot
|
||||||
|
requires explicit configured roster and catalog roles; absent roles block before
|
||||||
|
any corporate payload is read.
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"pilotTooling": "VERIFIED_BY_TESTS",
|
||||||
|
"realCorporateRosterParsing": "BLOCKED",
|
||||||
|
"realCorporatePackageAcquisition": "BLOCKED",
|
||||||
|
"blockReason": "CORPORATE_CATALOG_AND_ROSTER_PATHS_NOT_CONFIGURED",
|
||||||
|
"localImmutableStaging": "VERIFIED_BY_TESTS",
|
||||||
|
"controlledOutageRecovery": "VERIFIED_BY_TESTS",
|
||||||
|
"productionActivation": "NOT_RUN",
|
||||||
|
"autodeskRuntime": "NOT_RUN",
|
||||||
|
"productionReadiness": "NOT_ACHIEVED",
|
||||||
|
"redaction": "No corporate paths, roster contents, package payload, session identity, or machine data were recorded."
|
||||||
|
}
|
||||||
@@ -82,5 +82,17 @@ public sealed class RosterAdaptersTests
|
|||||||
finally { Directory.Delete(root,true); }
|
finally { Directory.Delete(root,true); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FileSystemCatalog_RejectsTraversalInManifest()
|
||||||
|
{
|
||||||
|
var root=Path.Combine(Path.GetTempPath(),Guid.NewGuid().ToString("N")); var packageRoot=Path.Combine(root,"P_A","1.0.0"); Directory.CreateDirectory(packageRoot);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await File.WriteAllTextAsync(Path.Combine(packageRoot,"package.manifest.json"), """{"schemaVersion":1,"packageId":"P_A","version":"1.0.0","product":"Plant3D","productYear":2026,"architecture":"x64","entryAssembly":"entry.dll","files":[{"path":"../outside.dll","length":1,"sha256":"00"}],"startup":{"type":"netload","command":"P_A"},"rolloutChannel":"stable"}""");
|
||||||
|
Assert.Null(await new FileSystemCatalogPackageAcquirer(root).AcquireAsync(new("P_A","Plant3D",2026,"x64","stable","1.0.0","project-a",true),CancellationToken.None));
|
||||||
|
}
|
||||||
|
finally { Directory.Delete(root,true); }
|
||||||
|
}
|
||||||
|
|
||||||
private static string Json() => """{"schemaVersion":1,"rosterId":"project-a","product":"Plant3D","year":2026,"architecture":"x64","offlinePolicy":"AllowVerifiedLastKnownGood","packages":[{"id":"P_B","version":"2.0.0","channel":"stable","loadOrder":200},{"id":"P_A","version":"1.0.0","channel":"stable","loadOrder":100}]}""";
|
private static string Json() => """{"schemaVersion":1,"rosterId":"project-a","product":"Plant3D","year":2026,"architecture":"x64","offlinePolicy":"AllowVerifiedLastKnownGood","packages":[{"id":"P_B","version":"2.0.0","channel":"stable","loadOrder":200},{"id":"P_A","version":"1.0.0","channel":"stable","loadOrder":100}]}""";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user