using ClawdDotNet.Core.Security; using ClawdDotNet.Web.Contracts; using Shouldly; namespace ClawdDotNet.Web.Tests; /// /// Kein Zugangsdatum verlässt die API. /// /// Das ist die Prüfung, die man nicht von Hand machen will: Werkzeug-Einstellungen /// sind ein freies Wörterbuch, und ob darin ein Passwort steckt, entscheidet der Name des /// Schlüssels. Ein return agent.Tools; hätte den ganzen Baum samt Passwörtern ins /// Netz geschickt — die Sorte Fehler, die niemandem auffällt, weil alles funktioniert. /// 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 statt ShouldContainKey: Die DTOs geben // IReadOnlyDictionary heraus, und Shouldlys Woerterbuch-Erweiterungen sitzen // auf IDictionary. dto.Values.Keys.ShouldContain("host"); dto.Values.Keys.ShouldContain("port"); dto.Values.Keys.ShouldNotContain("password"); // Was herauskommt, ist die Auskunft „da steht etwas" — mehr braucht eine // Anzeige nicht, und mehr darf sie nicht wissen. 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) { // Die Liste kommt aus ConfigSecrets und nicht aus einer zweiten hier: Eine // zweite waere die, die beim naechsten neuen Werkzeug vergessen wird. 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() { // Der Test, der wirklich zaehlt: nicht die Struktur, sondern das, was ueber die // Leitung geht. var dto = Mapping.ToDto("Mail", new Dictionary { ["host"] = "mail.example", ["password"] = "sehr-geheim" }); System.Text.Json.JsonSerializer.Serialize(dto).ShouldNotContain("sehr-geheim"); } }