diff --git a/src/Jellyfin.Extensions/PathHelper.cs b/src/Jellyfin.Extensions/PathHelper.cs
new file mode 100644
index 0000000000..f519cbb651
--- /dev/null
+++ b/src/Jellyfin.Extensions/PathHelper.cs
@@ -0,0 +1,77 @@
+using System;
+using System.IO;
+
+namespace Jellyfin.Extensions;
+
+///
+/// Helpers for safely composing filesystem paths from untrusted input.
+///
+///
+/// has two issues that matter in
+/// any code that joins a trusted directory with an externally-supplied name:
+/// it neither normalises .. nor rejects a rooted second argument
+/// (a rooted second arg silently discards the first). Use the helpers below
+/// any time the name comes from media metadata, request input, archive
+/// entries, or any other channel that can be influenced by a third party.
+///
+public static class PathHelper
+{
+ ///
+ /// Reduces a possibly-untrusted file name to a safe leaf-only name with no
+ /// directory components.
+ ///
+ /// The candidate file name.
+ ///
+ /// The leaf component of , or null if
+ /// the input has no usable leaf (empty, ., or ..).
+ ///
+ public static string? GetSafeLeafFileName(string? fileName)
+ {
+ if (string.IsNullOrEmpty(fileName))
+ {
+ return null;
+ }
+
+ var leaf = Path.GetFileName(fileName);
+ if (string.IsNullOrEmpty(leaf) || leaf == "." || leaf == "..")
+ {
+ return null;
+ }
+
+ return leaf;
+ }
+
+ ///
+ /// Returns whether resolves to a path that
+ /// equals or is contained inside .
+ ///
+ /// The directory the candidate must remain inside.
+ /// The candidate absolute or relative path.
+ /// true if the candidate is inside or equal to root; otherwise false.
+ ///
+ /// Both arguments are resolved via
+ /// so .. segments are collapsed before the comparison. The root is
+ /// compared with a trailing directory separator to prevent prefix
+ /// collisions (e.g. /var/data must not be accepted as a parent of
+ /// /var/dataset).
+ ///
+ public static bool IsContainedIn(string root, string candidate)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(root);
+ ArgumentException.ThrowIfNullOrEmpty(candidate);
+
+ var fullRoot = Path.GetFullPath(root);
+ var fullCandidate = Path.GetFullPath(candidate);
+
+ if (string.Equals(fullCandidate, fullRoot, StringComparison.Ordinal))
+ {
+ return true;
+ }
+
+ var rootWithSep = fullRoot.EndsWith(Path.DirectorySeparatorChar)
+ ? fullRoot
+ : fullRoot + Path.DirectorySeparatorChar;
+
+ return fullCandidate.StartsWith(rootWithSep, StringComparison.Ordinal);
+ }
+}