SSRF-Schutz, Pfadpruefung und Retry-Logik
S5 — Die Domain-Whitelist wurde nur auf die Ausgangs-URL angewendet, HttpClient folgte Weiterleitungen aber selbst. Eine erlaubte Domain konnte damit auf beliebige interne Adressen weiterleiten: Router, NAS, der Git-Server im LAN, Cloud-Metadatendienste. Der Agent haette deren Inhalt zurueckgeliefert. UrlGuard prueft Schema, private und lokale Netzbereiche sowie die Whitelist. AllowAutoRedirect ist abgeschaltet; Weiterleitungen werden einzeln aufgeloest und JEDER Zwischenschritt erneut geprueft, begrenzt auf fuenf Spruenge. Nebenbei behoben: Die alte www-Behandlung ersetzte die Zeichenfolge ueber den ganzen Hostnamen, aus mywww.example.com wurde myexample.com. Und die Subdomain-Pruefung achtet jetzt auf den Punkt, sodass example.com.attacker.net nicht mehr als Treffer fuer example.com durchgeht. S6 — Die Pfadpruefung in FileRW verglich nur Zeichenketten-Praefixe. Ohne abschliessenden Verzeichnistrenner erlaubte ein Root wie Agent-X\Workspace damit auch Zugriffe auf Agent-X\Workspace-Backup. WorkspacePath vergleicht jetzt auf Verzeichnisgrenzen und lehnt zusaetzlich absolute Pfade, UNC-Freigaben und Alternate Data Streams ab. Das Dateisystem wird in den Tests bewusst nicht abstrahiert — sie sollen die echte Windows-Pfadsemantik pruefen. Eine Abstraktion wuerde genau die Fehlerklasse verstecken, um die es geht. B12 — Der Client warf bei jedem Nicht-2xx sofort; ein einzelnes HTTP 429 beendete damit einen kompletten geplanten Lauf, obwohl Rate-Limits und kurze 5xx bei OpenRouter Normalbetrieb sind. RetryPolicy wiederholt 408/425/429/5xx mit exponentiellem Backoff und Streuung, respektiert ein Retry-After des Servers und deckelt die Wartezeit. Dauerhafte Fehler wie 401 werden nicht wiederholt. Die Wartefunktion ist injizierbar, damit die Tests nicht wirklich warten. 264 Tests gruen (116 Core, 148 Tools). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e5067cae70
commit
cbb8ac22bc
@@ -0,0 +1,49 @@
|
||||
using System.Net;
|
||||
|
||||
namespace ClawdDotNet.Core.Tests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Liefert vorprogrammierte HTTP-Antworten, ohne dass ein Netzwerkzugriff stattfindet.
|
||||
/// </summary>
|
||||
internal sealed class FakeHttpMessageHandler : HttpMessageHandler
|
||||
{
|
||||
private readonly Queue<Func<HttpResponseMessage>> _responses = new();
|
||||
|
||||
public int RequestCount { get; private set; }
|
||||
|
||||
public FakeHttpMessageHandler Responds(HttpStatusCode status, string body = "{}", TimeSpan? retryAfter = null)
|
||||
{
|
||||
_responses.Enqueue(() =>
|
||||
{
|
||||
var response = new HttpResponseMessage(status)
|
||||
{
|
||||
Content = new StringContent(body, System.Text.Encoding.UTF8, "application/json")
|
||||
};
|
||||
if (retryAfter is { } delta)
|
||||
response.Headers.RetryAfter = new System.Net.Http.Headers.RetryConditionHeaderValue(delta);
|
||||
return response;
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public FakeHttpMessageHandler RespondsWithSuccess(string body = """{"id":"x","choices":[]}""")
|
||||
=> Responds(HttpStatusCode.OK, body);
|
||||
|
||||
public FakeHttpMessageHandler Throws(Exception ex)
|
||||
{
|
||||
_responses.Enqueue(() => throw ex);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
RequestCount++;
|
||||
|
||||
if (_responses.Count == 0)
|
||||
throw new InvalidOperationException(
|
||||
$"FakeHttpMessageHandler: unerwartete Anfrage Nr. {RequestCount}.");
|
||||
|
||||
return Task.FromResult(_responses.Dequeue()());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user