Files
Playa/tests/Volcanic.AgenticCore.Tests/Processes/SecretRedactorTests.cs
EugeneTes 76bda6cf55 fix(processes): address review findings
- 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.
2026-09-26 19:32:47 +02:00

74 lines
2.8 KiB
C#

using Volcanic.AgenticCore.Abstractions.Json;
using Volcanic.AgenticCore.Abstractions.Messages;
using Volcanic.AgenticCore.Abstractions.Processes;
using Volcanic.AgenticCore.Abstractions.Tools;
using Volcanic.AgenticCore.Features.Agent;
using Volcanic.AgenticCore.Features.Processes;
namespace Volcanic.AgenticCore.Tests.Processes;
public class SecretRedactorTests
{
[Fact]
public void A_secret_inside_text_becomes_REDACTED()
{
var redactor = new SecretRedactor(["123456:bot-token-value"]);
Assert.Equal("token=[REDACTED]; again [REDACTED]", redactor.Redact("token=123456:bot-token-value; again 123456:bot-token-value"));
}
[Fact]
public void Values_shorter_than_8_characters_are_ignored()
{
var redactor = new SecretRedactor(["admin", "true"]);
Assert.True(redactor.IsEmpty);
Assert.Equal("admin is true", redactor.Redact("admin is true"));
}
[Fact]
public void A_longer_overlapping_secret_wins()
{
var redactor = new SecretRedactor(["abcdefgh", "abcdefgh-ijklmnop"]);
Assert.Equal("x [REDACTED] y", redactor.Redact("x abcdefgh-ijklmnop y"));
}
[Fact]
public void An_empty_set_returns_the_same_string_instance()
{
var text = new string('a', 20);
Assert.Same(text, SecretRedactor.Empty.Redact(text));
Assert.Same(text, new SecretRedactor(["zzzzzzzzzz"]).Redact(text));
}
[Fact]
public void Tool_results_have_text_and_every_details_string_redacted_and_images_untouched()
{
const string secret = "super-secret-value";
var redactor = new SecretRedactor([secret]);
var result = new ToolResult(
[new TextContent($"a {secret}"), new ImageContent("aW1n", "image/png")],
VolcanicJson.ToElement(new { note = $"b {secret}", nested = new { list = new[] { secret, "ok" } }, count = 3 }));
var redacted = redactor.Redact(result);
Assert.Equal("a [REDACTED]", Assert.IsType<TextContent>(redacted.Content[0]).Text);
Assert.IsType<ImageContent>(redacted.Content[1]);
var json = redacted.Details!.Value.GetRawText();
Assert.DoesNotContain(secret, json);
Assert.Contains("[REDACTED]", json);
Assert.Equal(3, redacted.Details.Value.GetProperty("count").GetInt32());
}
private sealed class PassThroughRedactor : ISecretRedactor
{
public string Redact(string text) => text;
}
[Fact]
public void A_tool_result_with_nothing_to_redact_is_returned_as_is_whatever_the_redactor()
{
var result = new ToolResult([new TextContent("plain")], VolcanicJson.ToElement(new { note = "plain" }));
Assert.Same(result, new PassThroughRedactor().Redact(result));
Assert.Same(result, new SecretRedactor(["zzzzzzzzzz"]).Redact(result));
}
}