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"); } }