using ClawdDotNet.Core.Security;
using ClawdDotNet.Web.Contracts;
using Shouldly;
namespace ClawdDotNet.Web.Tests;
///
/// No credential leaves the API.
///
/// This is the check nobody wants to do by hand: tool settings
/// are a free dictionary, and whether there is a password in it is decided by the name of the
/// key. A return agent.Tools; would have sent the whole tree including passwords onto the
/// network — the kind of error nobody notices, because everything works.
///
public sealed class SecretMaskingTests
{
[Fact]
public void Ein_Passwort_kommt_nicht_heraus()
{
var einstellungen = new Dictionary
{
["host"] = "mail.example",
["port"] = 993,
["password"] = "sehr-geheim"
};
var dto = Mapping.ToDto("Mail", einstellungen);
// Keys.ShouldContain instead of ShouldContainKey: the DTOs hand out
// IReadOnlyDictionary, and Shouldly's dictionary extensions sit
// on IDictionary.
dto.Values.Keys.ShouldContain("host");
dto.Values.Keys.ShouldContain("port");
dto.Values.Keys.ShouldNotContain("password");
// What comes out is the information "something is there" — a display
// needs no more, and may know no more.
dto.Secrets["password"].ShouldBeTrue();
}
[Fact]
public void Ein_leeres_Geheimnis_gilt_als_nicht_gesetzt()
{
var dto = Mapping.ToDto("Mail", new Dictionary
{
["password"] = "",
["apiKey"] = null
});
dto.Secrets["password"].ShouldBeFalse();
dto.Secrets["apiKey"].ShouldBeFalse();
dto.Values.ShouldBeEmpty();
}
[Theory]
[InlineData("password")]
[InlineData("apiKey")]
[InlineData("api_key")]
[InlineData("token")]
[InlineData("accessToken")]
[InlineData("botToken")]
[InlineData("clientSecret")]
[InlineData("connectionString")]
[InlineData("apiHash")]
[InlineData("webUiToken")]
public void Jeder_bekannte_Geheimnisname_wird_maskiert(string schluessel)
{
// The list comes from ConfigSecrets and not from a second one here: a
// second would be the one forgotten with the next new tool.
ConfigSecrets.IsSecretKey(schluessel).ShouldBeTrue();
Mapping.ToDto("X", new Dictionary { [schluessel] = "geheim" })
.Values.Keys.ShouldNotContain(schluessel);
}
[Fact]
public void Die_Schreibweise_des_Schluessels_spielt_keine_Rolle()
{
Mapping.ToDto("X", new Dictionary { ["PASSWORD"] = "geheim" })
.Values.ShouldBeEmpty();
}
[Fact]
public void Ein_serialisiertes_DTO_enthaelt_das_Geheimnis_nicht()
{
// The test that really counts: not the structure, but what goes over the
// line.
var dto = Mapping.ToDto("Mail", new Dictionary
{
["host"] = "mail.example",
["password"] = "sehr-geheim"
});
System.Text.Json.JsonSerializer.Serialize(dto).ShouldNotContain("sehr-geheim");
}
}