- Fill TimeZoneId only for a configured session/host zone with a resolvable IANA id (IExtensionHost.TimeZoneId); otherwise children inherit the host's TZ instead of TZ=Local/UTC. - Host hardening logs a warning instead of throwing when libc/prctl can't be resolved. - A hook or process_spawn handler returning a null or incomplete context fails the spawn with InvalidOperationException, so bash reports "process environment hook failed". - Move tool-result redaction into the Agent slice with a behavioural no-change check. - ProcessSpawnContext.ApplyTo(psi) sets cwd and replaces the environment; used by bash and ExecAsync. - Split the Telegram process environment types into their own files; move the Extensions.Tests NoHostHardening under Support/; check the Claude Code assembly never references the spawn policy. - Spec and docs: TZ inheritance, incomplete contexts, ApplyTo, async spawn handlers, root skip.
65 lines
2.8 KiB
C#
65 lines
2.8 KiB
C#
using System.Reflection.Metadata;
|
|
using System.Reflection.PortableExecutable;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Volcanic.AgenticCore.Abstractions.Processes;
|
|
using Volcanic.AgenticCore.Extensions.ClaudeCode;
|
|
using Volcanic.AgenticCore.Hosting;
|
|
|
|
namespace Volcanic.AgenticCore.Extensions.Tests.ClaudeCode;
|
|
|
|
/// <summary>
|
|
/// Pins down that the Claude Code provider never consults the host's process spawn policy:
|
|
/// <c>claude</c> already gets a replaced, allow-listed environment (spec ยง7).
|
|
/// </summary>
|
|
public class ClaudeArgsProcessPolicyTests
|
|
{
|
|
[Fact]
|
|
public void BuildEnvironment_is_unchanged_when_a_host_hook_is_configured()
|
|
{
|
|
var parent = new Dictionary<string, string?> { ["HOME"] = "/home/me", ["PATH"] = "/usr/bin", ["LANG"] = "C.UTF-8" };
|
|
var before = ClaudeArgs.BuildEnvironment(parent, 64_000);
|
|
|
|
var hookRuns = 0;
|
|
var services = new ServiceCollection();
|
|
services.AddAgenticCore(o =>
|
|
{
|
|
o.ProcessEnvironment.HardenHostProcess = false;
|
|
o.ProcessEnvironment.Hooks.Add(c =>
|
|
{
|
|
hookRuns++;
|
|
return c with { Environment = new Dictionary<string, string>(c.Environment) { ["INJECTED_BY_HOOK"] = "1" } };
|
|
});
|
|
});
|
|
using var provider = services.BuildServiceProvider();
|
|
_ = provider.GetRequiredService<IProcessSpawnPolicy>();
|
|
|
|
var after = ClaudeArgs.BuildEnvironment(parent, 64_000);
|
|
|
|
Assert.Equal(before.OrderBy(kv => kv.Key), after.OrderBy(kv => kv.Key));
|
|
Assert.False(after.ContainsKey("INJECTED_BY_HOOK"));
|
|
Assert.Equal(0, hookRuns);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The test above can't see a spawn path outside <c>BuildEnvironment</c>, so also check the provider's IL metadata:
|
|
/// nothing in the assembly references the policy, the per-session preparation or the spawn event.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Provider_assembly_never_references_the_process_spawn_policy()
|
|
{
|
|
var path = typeof(ClaudeArgs).Assembly.Location;
|
|
using var stream = File.OpenRead(path);
|
|
using var pe = new PEReader(stream);
|
|
var metadata = pe.GetMetadataReader();
|
|
|
|
var typeRefs = metadata.TypeReferences.Select(h => metadata.GetString(metadata.GetTypeReference(h).Name)).ToHashSet();
|
|
var memberRefs = metadata.MemberReferences.Select(h => metadata.GetString(metadata.GetMemberReference(h).Name)).ToHashSet();
|
|
|
|
Assert.DoesNotContain(nameof(IProcessSpawnPolicy), typeRefs);
|
|
Assert.DoesNotContain(nameof(ProcessSpawnEvent), typeRefs);
|
|
Assert.DoesNotContain("PrepareProcessAsync", memberRefs);
|
|
Assert.DoesNotContain("get_Processes", memberRefs);
|
|
Assert.DoesNotContain(nameof(IProcessSpawnPolicy.Prepare), memberRefs);
|
|
}
|
|
}
|