Files
ClawdDotNet/tests/ClawdDotNet.Web.Tests/SecretMaskingTests.cs
T
RichardandClaude Opus 5.5 77062facc9 Stufe 4c (#764): Bezeichner auf Englisch - Typen, Methoden, Felder, Parameter, Locals, Testnamen
Mit Roslyn umbenannt (Verweise folgen, Zeichenketten bleiben unberuehrt).
Nicht umbenannt, weil gespeichert oder ueber die API gebunden (Stufe 4e):
Properties, Record-Parameter, Enum-Member sowie die Parameter der
Endpunkt-Handler (Routen-/Query-Bindung ueber den Namen). Die JSON-Felder
anonymer Objekte bleiben ausdruecklich benannt (JsonFilter: pfad).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:18:43 +02:00

97 lines
3.0 KiB
C#

using ClawdDotNet.Core.Security;
using ClawdDotNet.Web.Contracts;
using Shouldly;
namespace ClawdDotNet.Web.Tests;
/// <summary>
/// No credential leaves the API.
///
/// <para>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 <c>return agent.Tools;</c> would have sent the whole tree including passwords onto the
/// network — the kind of error nobody notices, because everything works.</para>
/// </summary>
public sealed class SecretMaskingTests
{
[Fact]
public void A_password_does_not_come_out()
{
var settings = new Dictionary<string, object?>
{
["host"] = "mail.example",
["port"] = 993,
["password"] = "sehr-geheim"
};
var dto = Mapping.ToDto("Mail", settings);
// 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 An_empty_secret_counts_as_not_set()
{
var dto = Mapping.ToDto("Mail", new Dictionary<string, object?>
{
["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 Every_known_secret_name_is_masked(string key)
{
// 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(key).ShouldBeTrue();
Mapping.ToDto("X", new Dictionary<string, object?> { [key] = "geheim" })
.Values.Keys.ShouldNotContain(key);
}
[Fact]
public void The_spelling_of_the_key_does_not_matter()
{
Mapping.ToDto("X", new Dictionary<string, object?> { ["PASSWORD"] = "geheim" })
.Values.ShouldBeEmpty();
}
[Fact]
public void A_serialised_DTO_does_not_contain_the_secret()
{
// The test that really counts: not the structure, but what goes over the
// line.
var dto = Mapping.ToDto("Mail", new Dictionary<string, object?>
{
["host"] = "mail.example",
["password"] = "sehr-geheim"
});
System.Text.Json.JsonSerializer.Serialize(dto).ShouldNotContain("sehr-geheim");
}
}