using ClawdDotNet.Tools.DirectAPI; using Shouldly; namespace ClawdDotNet.Tools.Tests.DirectAPI; /// /// S3 from the inventory: the fetched URL went to the model as "source" — /// including apikey= in the query string. The key thus ended up in the context, was /// sent again at every following step and written into ChatContext.json. /// public sealed class UrlSanitizerTests { private const string Secret = "GEHEIM-abc123XYZ"; [Theory] [InlineData("https://api.twelvedata.com/quote?symbol=NVDA&apikey=GEHEIM-abc123XYZ")] [InlineData("https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=X&apikey=GEHEIM-abc123XYZ")] [InlineData("https://api.example.com/v1?api_key=GEHEIM-abc123XYZ&x=1")] [InlineData("https://api.example.com/v1?token=GEHEIM-abc123XYZ")] [InlineData("https://api.example.com/v1?access_token=GEHEIM-abc123XYZ")] [InlineData("https://api.example.com/v1?KEY=GEHEIM-abc123XYZ")] [InlineData("https://api.example.com/v1?ApiKey=GEHEIM-abc123XYZ&other=2")] [InlineData("https://api.example.com/v1?secret=GEHEIM-abc123XYZ")] [InlineData("https://api.example.com/v1?a=1&apikey=GEHEIM-abc123XYZ#fragment")] public void Sensitive_parameters_are_masked(string url) { var sanitized = UrlSanitizer.Sanitize(url); sanitized.ShouldNotContain(Secret); sanitized.ShouldContain("***"); } [Fact] public void Harmless_parameters_stay_readable() { var sanitized = UrlSanitizer.Sanitize( "https://api.twelvedata.com/quote?symbol=NVDA&interval=1day&apikey=GEHEIM-abc123XYZ"); sanitized.ShouldContain("symbol=NVDA"); sanitized.ShouldContain("interval=1day"); sanitized.ShouldContain("apikey=***"); } [Fact] public void The_path_is_kept() { var sanitized = UrlSanitizer.Sanitize("https://api.massive.com/v2/aggs/ticker/NVDA?apikey=x"); sanitized.ShouldStartWith("https://api.massive.com/v2/aggs/ticker/NVDA?"); } [Fact] public void A_fragment_is_kept() { var sanitized = UrlSanitizer.Sanitize("https://x.de/a?apikey=geheim#abschnitt"); sanitized.ShouldEndWith("#abschnitt"); sanitized.ShouldNotContain("geheim"); } [Theory] [InlineData("https://api.example.com/v1")] [InlineData("https://api.example.com/v1?symbol=NVDA")] public void URLs_without_sensitive_parameters_stay_unchanged(string url) { UrlSanitizer.Sanitize(url).ShouldBe(url); } [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] public void Empty_inputs_do_not_lead_to_an_error(string? url) { Should.NotThrow(() => UrlSanitizer.Sanitize(url)); } [Fact] public void A_parameter_without_value_does_no_harm() { var sanitized = UrlSanitizer.Sanitize("https://x.de/a?flag&apikey=geheim"); sanitized.ShouldContain("flag"); sanitized.ShouldNotContain("geheim"); } [Fact] public void A_key_as_part_of_another_name_is_not_wrongly_masked() { // "monkey" contains "key", but is not a sensitive parameter. var sanitized = UrlSanitizer.Sanitize("https://x.de/a?monkey=banane"); sanitized.ShouldBe("https://x.de/a?monkey=banane"); } }