GOOGLE_CLIENT_SECRET is no longer denied by name in the core defaults (the *_SECRET glob still removes it); the Telegram host allows it by default so workspace skills (gmail, gdrive) can use the Google OAuth client. Its value stays in SecretValues: redacted from tool output, and aliases with the same value are still removed.
173 lines
8.4 KiB
C#
173 lines
8.4 KiB
C#
using System.Text.Json;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Options;
|
|
using Volcanic.AgenticCore.Abstractions.Json;
|
|
using Volcanic.AgenticCore.Abstractions.Messages;
|
|
using Volcanic.AgenticCore.Abstractions.Processes;
|
|
using Volcanic.AgenticCore.Extensions.GoogleCalendar;
|
|
using Volcanic.AgenticCore.Features.Processes;
|
|
using Volcanic.AgenticCore.Hosting;
|
|
using Volcanic.AgenticCore.Telegram.Tests.Support;
|
|
using Volcanic.AgenticCore.Testing;
|
|
|
|
namespace Volcanic.AgenticCore.Telegram.Tests;
|
|
|
|
public sealed class ProcessEnvironmentTests
|
|
{
|
|
private const string Token = "123456789:AAH-test-bot-token-value";
|
|
private const string GoogleSecret = "GOCSPX-test-google-client-secret";
|
|
|
|
[Fact]
|
|
public void Configure_adds_the_bot_token_section_lists_and_PROCESS_ENV_ALLOW()
|
|
{
|
|
var target = new ProcessEnvironmentOptions();
|
|
var section = new ProcessEnvironmentSection { Deny = ["EXTRA_*"], Allow = ["NPM_TOKEN"], HardenHostProcess = false };
|
|
TelegramProcessEnvironment.Configure(target, new TelegramOptions { BotToken = Token }, section,
|
|
name => name == "PROCESS_ENV_ALLOW" ? "GH_TOKEN, HF_TOKEN" : null);
|
|
|
|
Assert.Contains(Token, target.SecretValues);
|
|
Assert.Equal(ProcessEnvironmentOptions.DefaultDeny.Count + 1, target.Deny.Count);
|
|
Assert.Contains("EXTRA_*", target.Deny);
|
|
Assert.Equal(["NPM_TOKEN", "GH_TOKEN", "HF_TOKEN", "GOOGLE_CLIENT_SECRET"], target.Allow);
|
|
Assert.False(target.HardenHostProcess);
|
|
Assert.Empty(target.Validate());
|
|
}
|
|
|
|
[Fact]
|
|
public void GOOGLE_CLIENT_SECRET_in_PROCESS_ENV_ALLOW_is_valid_and_not_duplicated()
|
|
{
|
|
var target = new ProcessEnvironmentOptions();
|
|
TelegramProcessEnvironment.Configure(target, new TelegramOptions { BotToken = Token }, new ProcessEnvironmentSection(),
|
|
name => name == "PROCESS_ENV_ALLOW" ? "google_client_secret" : null);
|
|
|
|
Assert.Equal(["google_client_secret"], target.Allow);
|
|
Assert.Empty(target.Validate());
|
|
_ = new ProcessSpawnPolicy(target, null, () => new Dictionary<string, string?>());
|
|
}
|
|
|
|
[Fact]
|
|
public void Configure_adds_the_google_client_secret_to_the_secret_values()
|
|
{
|
|
var target = new ProcessEnvironmentOptions();
|
|
TelegramProcessEnvironment.Configure(target, new TelegramOptions { BotToken = Token }, new ProcessEnvironmentSection(), _ => null,
|
|
new GoogleCalendarOptions { ClientSecret = GoogleSecret });
|
|
Assert.Contains(GoogleSecret, target.SecretValues);
|
|
}
|
|
|
|
[Fact]
|
|
public void Configure_adds_the_speech_api_key_to_the_secret_values()
|
|
{
|
|
var target = new ProcessEnvironmentOptions();
|
|
var telegram = new TelegramOptions { BotToken = Token };
|
|
telegram.Speech.ApiKey = "sk-speech-secret";
|
|
TelegramProcessEnvironment.Configure(target, telegram, new ProcessEnvironmentSection(), _ => null);
|
|
Assert.Contains("sk-speech-secret", target.SecretValues);
|
|
}
|
|
|
|
[Fact]
|
|
public void The_section_binds_from_configuration()
|
|
{
|
|
var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?>
|
|
{
|
|
["ProcessEnvironment:Allow:0"] = "GH_TOKEN",
|
|
["ProcessEnvironment:HardenHostProcess"] = "false",
|
|
}).Build();
|
|
var section = config.GetSection(ProcessEnvironmentSection.SectionName).Get<ProcessEnvironmentSection>()!;
|
|
Assert.Equal(["GH_TOKEN"], section.Allow);
|
|
Assert.False(section.HardenHostProcess);
|
|
}
|
|
|
|
[Fact]
|
|
public void Describe_lists_the_rule_count_and_allowed_names_only()
|
|
{
|
|
var options = new ProcessEnvironmentOptions();
|
|
options.Allow.Add("GH_TOKEN");
|
|
options.SecretValues.Add(Token);
|
|
var line = TelegramProcessEnvironment.Describe(options);
|
|
Assert.Equal($"Process environment: {ProcessEnvironmentOptions.DefaultDeny.Count} deny rules, allow: GH_TOKEN", line);
|
|
Assert.Equal($"Process environment: {ProcessEnvironmentOptions.DefaultDeny.Count} deny rules, allow: none", TelegramProcessEnvironment.Describe(new ProcessEnvironmentOptions()));
|
|
}
|
|
|
|
private static ChatHost Host(IReadOnlyDictionary<string, string?>? environment = null) => new((h, services) =>
|
|
{
|
|
h.Options.BotToken = Token;
|
|
services.Configure<AgenticCoreOptions>(o =>
|
|
TelegramProcessEnvironment.Configure(o.ProcessEnvironment, h.Options, new ProcessEnvironmentSection { HardenHostProcess = false }, _ => null,
|
|
new GoogleCalendarOptions { ClientSecret = GoogleSecret }));
|
|
if (environment is not null)
|
|
services.AddSingleton<IProcessSpawnPolicy>(sp =>
|
|
new ProcessSpawnPolicy(sp.GetRequiredService<IOptions<AgenticCoreOptions>>().Value.ProcessEnvironment, null, () => environment));
|
|
});
|
|
|
|
private static string LastToolResult(ChatHost host) =>
|
|
JsonSerializer.Serialize(host.Faux.Requests[^1].Messages.OfType<ToolResultMessage>().Last(), VolcanicJson.Options);
|
|
|
|
[Fact]
|
|
public async Task Bash_printing_the_bot_token_from_a_file_shows_REDACTED_to_the_model()
|
|
{
|
|
await using var host = Host();
|
|
var chat = await host.ChatAsync();
|
|
await File.WriteAllTextAsync(Path.Combine(chat.Session.Cwd, "token.txt"), Token);
|
|
host.Faux.Enqueue(FauxTurn.Tool("bash", new { command = OperatingSystem.IsWindows() ? "type token.txt" : "cat token.txt" }), FauxTurn.Say("done"));
|
|
|
|
await host.SendAsync("print the file");
|
|
await host.SettleAsync();
|
|
|
|
var result = LastToolResult(host);
|
|
Assert.Contains("[REDACTED]", result);
|
|
Assert.DoesNotContain(Token, result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Env_in_a_chat_does_not_show_the_bot_token_under_any_name()
|
|
{
|
|
var environment = new Dictionary<string, string?>(OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
|
|
foreach (var name in new[] { "PATH", "HOME", "SystemRoot", "ComSpec", "TEMP", "TMP" })
|
|
if (Environment.GetEnvironmentVariable(name) is { } value) environment[name] = value;
|
|
environment["TELEGRAM_BOT_TOKEN"] = Token;
|
|
environment["COOLIFY_BOT_ALIAS"] = Token;
|
|
await using var host = Host(environment);
|
|
host.Faux.Enqueue(FauxTurn.Tool("bash", new { command = OperatingSystem.IsWindows() ? "set" : "env" }), FauxTurn.Say("done"));
|
|
|
|
await host.SendAsync("show the environment");
|
|
await host.SettleAsync();
|
|
|
|
var result = LastToolResult(host);
|
|
Assert.Contains("PATH=", result, StringComparison.OrdinalIgnoreCase);
|
|
Assert.DoesNotContain("TELEGRAM_BOT_TOKEN", result);
|
|
Assert.DoesNotContain("COOLIFY_BOT_ALIAS", result);
|
|
Assert.DoesNotContain(Token, result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Bash_sees_GOOGLE_CLIENT_SECRET_but_the_model_sees_REDACTED_and_other_secrets_stay_out()
|
|
{
|
|
var environment = new Dictionary<string, string?>(OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
|
|
foreach (var name in new[] { "PATH", "HOME", "SystemRoot", "ComSpec", "TEMP", "TMP" })
|
|
if (Environment.GetEnvironmentVariable(name) is { } value) environment[name] = value;
|
|
environment["GOOGLE_CLIENT_SECRET"] = GoogleSecret;
|
|
environment["GOOGLE_ALIAS_COPY"] = GoogleSecret;
|
|
environment["TELEGRAM_BOT_TOKEN"] = Token;
|
|
environment["OPENAI_API_KEY"] = "sk-openai-test-key-123";
|
|
environment["OTHER_SERVICE_SECRET"] = "other-service-secret-456";
|
|
await using var host = Host(environment);
|
|
var command = OperatingSystem.IsWindows()
|
|
? "if defined GOOGLE_CLIENT_SECRET (echo HAS_GOOGLE_SECRET) & set"
|
|
: "[ \"$GOOGLE_CLIENT_SECRET\" = '" + GoogleSecret + "' ] && echo HAS_GOOGLE_SECRET; env";
|
|
host.Faux.Enqueue(FauxTurn.Tool("bash", new { command }), FauxTurn.Say("done"));
|
|
|
|
await host.SendAsync("show the environment");
|
|
await host.SettleAsync();
|
|
|
|
var result = LastToolResult(host);
|
|
Assert.Contains("HAS_GOOGLE_SECRET", result);
|
|
Assert.Contains("GOOGLE_CLIENT_SECRET=[REDACTED]", result);
|
|
Assert.DoesNotContain(GoogleSecret, result);
|
|
Assert.DoesNotContain("GOOGLE_ALIAS_COPY", result);
|
|
Assert.DoesNotContain("TELEGRAM_BOT_TOKEN", result);
|
|
Assert.DoesNotContain("OPENAI_API_KEY", result);
|
|
Assert.DoesNotContain("OTHER_SERVICE_SECRET", result);
|
|
}
|
|
}
|