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