forked from admin/VolcanicAgenticCore
- 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.
74 lines
2.8 KiB
C#
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));
|
|
}
|
|
}
|