up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-01 21:16:22 +02:00
parent c11d87d252
commit 909d9b6220
208 changed files with 860954 additions and 832 deletions

View File

@@ -654,6 +654,140 @@ public sealed class EntryTraceAnalyzerTests
Assert.Equal(EntryTraceTerminalType.Native, terminal.Type);
}
[Fact]
public async Task ResolveAsync_PropagatesUserSwitchWrapper()
{
var fs = new TestRootFileSystem();
fs.AddBinaryFile("/usr/bin/sudo", CreateGoBinary(), executable: true);
fs.AddBinaryFile("/usr/bin/python", CreateGoBinary(), executable: true);
fs.AddFile("/srv/app.py", "print('hi')\n", executable: false);
var context = new EntryTraceContext(
fs,
ImmutableDictionary<string, string>.Empty,
ImmutableArray.Create("/usr/bin"),
"/",
"root",
"sha256:user-switch",
"scan-sudo",
NullLogger.Instance);
var spec = EntrypointSpecification.FromExecForm(
new[] { "sudo", "-u", "app", "python", "/srv/app.py" },
null);
var analyzer = CreateAnalyzer();
var result = await analyzer.ResolveAsync(spec, context);
var terminal = Assert.Single(result.Terminals);
Assert.Equal("/usr/bin/python", terminal.Path);
Assert.Equal("app", terminal.User);
Assert.Contains("/srv/app.py", terminal.Arguments);
var edge = Assert.Single(result.Edges.Where(e => e.Relationship == "wrapper"));
Assert.Equal("true", edge.Metadata?["guarded"]);
Assert.Equal("user", edge.Metadata?["state-change"]);
Assert.Equal("app", edge.Metadata?["user"]);
}
[Fact]
public async Task ResolveAsync_PropagatesEnvWrapperIntoPlan()
{
var fs = new TestRootFileSystem();
fs.AddBinaryFile("/usr/bin/env", CreateGoBinary(), executable: true);
fs.AddBinaryFile("/usr/bin/python", CreateGoBinary(), executable: true);
fs.AddFile("/srv/app.py", "print('env')\n", executable: false);
var context = new EntryTraceContext(
fs,
ImmutableDictionary<string, string>.Empty,
ImmutableArray.Create("/usr/bin"),
"/",
"root",
"sha256:env-wrapper",
"scan-env",
NullLogger.Instance);
var spec = EntrypointSpecification.FromExecForm(
new[] { "env", "FOO=bar", "python", "/srv/app.py" },
null);
var analyzer = CreateAnalyzer();
var result = await analyzer.ResolveAsync(spec, context);
var plan = Assert.Single(result.Plans);
Assert.True(plan.Environment.TryGetValue("FOO", out var value) && value == "bar");
var terminal = Assert.Single(result.Terminals);
Assert.Equal("/usr/bin/python", terminal.Path);
var edge = Assert.Single(result.Edges.Where(e => e.Relationship == "wrapper"));
Assert.Equal("env", edge.Metadata?["state-change"]);
Assert.Equal("true", edge.Metadata?["guarded"]);
}
[Fact]
public async Task ResolveAsync_AccumulatesWorkingDirectoryFromShellCd()
{
var fs = new TestRootFileSystem();
fs.AddBinaryFile("/bin/sh", CreateGoBinary(), executable: true);
fs.AddBinaryFile("/usr/bin/python", CreateGoBinary(), executable: true);
fs.AddFile("/entry.sh", "#!/bin/sh\ncd /service\nexec python /srv/service.py\n", executable: true);
fs.AddFile("/srv/service.py", "print('svc')\n", executable: false);
var context = new EntryTraceContext(
fs,
ImmutableDictionary<string, string>.Empty,
ImmutableArray.Create("/bin", "/usr/bin"),
"/",
"root",
"sha256:cd-trace",
"scan-cd",
NullLogger.Instance);
var spec = EntrypointSpecification.FromExecForm(
new[] { "/entry.sh" },
null);
var analyzer = CreateAnalyzer();
var result = await analyzer.ResolveAsync(spec, context);
var terminal = Assert.Single(result.Terminals);
Assert.Equal("/usr/bin/python", terminal.Path);
Assert.Equal("/service", terminal.WorkingDirectory);
}
[Fact]
public async Task ResolveAsync_HandlesInitShimAndGuardsEdge()
{
var fs = new TestRootFileSystem();
fs.AddBinaryFile("/sbin/tini", CreateGoBinary(), executable: true);
fs.AddBinaryFile("/usr/bin/python", CreateGoBinary(), executable: true);
fs.AddFile("/srv/app.py", "print('shim')\n", executable: false);
var context = new EntryTraceContext(
fs,
ImmutableDictionary<string, string>.Empty,
ImmutableArray.Create("/sbin", "/usr/bin"),
"/",
"root",
"sha256:init-shim",
"scan-tini",
NullLogger.Instance);
var spec = EntrypointSpecification.FromExecForm(
new[] { "/sbin/tini", "--", "python", "/srv/app.py" },
null);
var analyzer = CreateAnalyzer();
var result = await analyzer.ResolveAsync(spec, context);
var terminal = Assert.Single(result.Terminals);
Assert.Equal("/usr/bin/python", terminal.Path);
var edge = Assert.Single(result.Edges.Where(e => e.Relationship == "wrapper"));
Assert.Equal("true", edge.Metadata?["guarded"]);
Assert.Equal("init", edge.Metadata?["shim"]);
}
private static byte[] CreateGoBinary()
{
var buffer = new byte[256];

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using StellaOps.Scanner.EntryTrace;
@@ -29,6 +30,26 @@ public sealed class EntryTraceRuntimeReconcilerTests
Assert.Contains(reconciled.Diagnostics, d => d.Reason == EntryTraceUnknownReason.RuntimeMatch);
}
[Fact]
public void Reconcile_EmitsRuntimeChain_InDiagnostics()
{
var reconciler = new EntryTraceRuntimeReconciler();
var graph = CreateGraph("/usr/local/bin/app");
var procGraph = ProcGraphBuilder.Build(new FakeProvider(new[]
{
CreateProcess(1, 0, "/sbin/tini", "tini", 100),
CreateProcess(5, 1, "/usr/local/bin/app", "app", 200),
}));
var reconciled = reconciler.Reconcile(graph, procGraph);
var diag = Assert.Single(reconciled.Diagnostics, d => d.Reason == EntryTraceUnknownReason.RuntimeMatch);
Assert.Contains("tini", diag.Message, StringComparison.OrdinalIgnoreCase);
Assert.Contains("/usr/local/bin/app", diag.Message, StringComparison.OrdinalIgnoreCase);
Assert.Contains("->", diag.Message, StringComparison.Ordinal);
}
[Fact]
public void Reconcile_FlagsMismatch_WhenDifferentExecutable()
{