Umbau durch Gemini Flash, Durchsicht und Nacharbeit durch Claude. Umfang: - Desktop: LocalizationManager (JSON-Pakete, Rueckfall auf Englisch, Umschalten zur Laufzeit), Localize-Markup-Erweiterung, Sprachwahl in den Einstellungen (AppSettings.Language, Vorgabe "en"); 317 Schluessel je Sprache - WebUI: alle Ansichten auf t()/data-i18n, 656 Schluessel je Sprache - Agenten-Werkzeuge: Beschreibungen, Schemata und Meldungen auf Englisch - Tests an die englischen Meldungen angepasst, keine Pruefung abgeschwaecht Nacharbeit bei der Durchsicht: - ~25 Kommentarbloecke mit Begruendungen waren ersatzlos entfallen, darunter sicherheitsrelevante (NTFS-Datenstrom beim shared:-Praefix, Bremse gegen Agentenschleifen, doppelter POST beim Anlegen einer Instanz). Wieder eingesetzt: auf Englisch, wo die Datei umgestellt ist, sonst im Wortlaut. - Drei WebUI-Schluessel fehlten (logs.paused, instances.instanceOfflineError, instances.noInstanceActiveError). Weil t() bei fehlendem Schluessel den Schluessel selbst liefert, griff der "|| err.message"-Rueckfall nie — die Oberflaeche haette den Schluesselnamen angezeigt. Geprueft: Schluesselgleichheit en/de, Platzhalter je Schluessel, jeder im Code benutzte Schluessel vorhanden, node --check fuer alle Module, 1.220 Tests gruen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
85 lines
3.8 KiB
C#
85 lines
3.8 KiB
C#
using ClawdDotNet.Core.Storage;
|
|
|
|
namespace ClawdDotNet.Tools.FileRW;
|
|
|
|
/// <summary>
|
|
/// Löst relative Pfade innerhalb eines Workspace auf und stellt sicher, dass sie
|
|
/// diesen nicht verlassen.
|
|
///
|
|
/// Hintergrund (S6): Die Prüfung verglich vorher nur die Zeichenkette:
|
|
///
|
|
/// fullPath.StartsWith(rootPath, OrdinalIgnoreCase)
|
|
///
|
|
/// Ohne abschließenden Verzeichnistrenner erlaubte ein Root wie
|
|
/// "…\Agent-X\Workspace" damit auch Zugriffe auf "…\Agent-X\Workspace-Backup\…" —
|
|
/// ein fremdes Verzeichnis, das zufällig mit demselben Präfix beginnt.
|
|
///
|
|
/// Der Vergleich selbst liegt in <see cref="PathBoundary"/>: Unter Linux
|
|
/// unterscheidet das Dateisystem Groß- und Kleinschreibung, und symbolische
|
|
/// Verknüpfungen können aus dem Workspace hinauszeigen, ohne dass die reine
|
|
/// Pfadrechnung das sieht.
|
|
/// </summary>
|
|
public static class WorkspacePath
|
|
{
|
|
/// <summary>
|
|
/// Setzt <paramref name="relativePath"/> auf <paramref name="root"/> auf und prüft,
|
|
/// dass das Ergebnis innerhalb des Workspace liegt.
|
|
/// </summary>
|
|
/// <exception cref="UnauthorizedAccessException">Wenn der Pfad ausbricht.</exception>
|
|
public static string Resolve(string root, string? relativePath, string workspaceName)
|
|
{
|
|
var value = string.IsNullOrWhiteSpace(relativePath) ? "." : relativePath.Trim();
|
|
|
|
// Absolute Pfade und UNC-Freigaben würden Path.Combine den Root verwerfen lassen.
|
|
if (Path.IsPathRooted(value) || value.StartsWith("//"))
|
|
{
|
|
throw new UnauthorizedAccessException(
|
|
$"Access denied: Absolute paths are not allowed in the {workspaceName} workspace.");
|
|
}
|
|
|
|
// Doppelpunkt und Backslash werden rundheraus abgewiesen.
|
|
//
|
|
// Beide sind unter Linux gewöhnliche Namenszeichen, und genau daran scheiterte
|
|
// die Abwehr der Windows-Formen: Path.IsPathRooted(@"C:\temp\x") meldet dort
|
|
// false, und @"..\..\evil.txt" ist ein einzelner Dateiname statt eines
|
|
// Ausbruchs. Ein Ausbruch war das nie — die Datei blieb im Workspace —, aber
|
|
// eine Sonderregel je Windows-Form (Doppelpunkt für Laufwerke, "\\" für UNC)
|
|
// lässt immer eine Form übrig, die durchfällt. Hier gilt darum eine
|
|
// Schreibweise: "/" trennt Abschnitte, alles andere ist ein Name. Beide Zeichen
|
|
// sind unter Linux in Dateinamen zwar erlaubt, aber nichts, was ein Agent
|
|
// braucht; die Einschränkung kostet uns nichts.
|
|
//
|
|
// Der Doppelpunkt fängt nebenbei Alternate Data Streams (datei.txt:versteckt),
|
|
// die unter Windows die Endungsprüfung umgehen.
|
|
if (value.Contains(':') || value.Contains('\\'))
|
|
{
|
|
throw new UnauthorizedAccessException(
|
|
@"Access denied: Path contains an invalid character (':' or '\'). " +
|
|
"Sections are separated with '/'.");
|
|
}
|
|
|
|
var normalizedRoot = NormalizeDirectory(root);
|
|
var fullPath = Path.GetFullPath(Path.Combine(normalizedRoot, value));
|
|
|
|
if (!IsInside(fullPath, normalizedRoot))
|
|
{
|
|
throw new UnauthorizedAccessException(
|
|
$"Access denied: Path is outside the {workspaceName} workspace.");
|
|
}
|
|
|
|
return fullPath;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prüft, ob <paramref name="candidate"/> im Verzeichnis <paramref name="root"/> liegt.
|
|
/// Der Vergleich erfolgt auf Verzeichnisgrenzen, nicht auf Zeichenketten-Präfixen,
|
|
/// und löst symbolische Verknüpfungen auf.
|
|
/// </summary>
|
|
public static bool IsInside(string candidate, string root)
|
|
=> PathBoundary.IsInside(candidate, root);
|
|
|
|
/// <summary>Absoluter Pfad mit genau einem abschließenden Trenner.</summary>
|
|
private static string NormalizeDirectory(string path)
|
|
=> PathBoundary.NormalizeDirectory(path);
|
|
}
|