diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py
index 83ae78afd1a..599ab205e6d 100644
--- a/litellm/proxy/proxy_server.py
+++ b/litellm/proxy/proxy_server.py
@@ -620,6 +620,13 @@ class SPAStaticFiles(StaticFiles):
last_segment = path.rstrip("/").rsplit("/", 1)[-1]
return "." not in last_segment
+ def _bounded_shell(self, relative: str) -> Optional[str]:
+ base = os.path.realpath(str(self.directory))
+ candidate = os.path.realpath(os.path.join(base, relative))
+ if candidate != base and not candidate.startswith(base + os.sep):
+ return None
+ return relative if os.path.isfile(candidate) else None
+
def _spa_shell(self, path: str) -> Optional[str]:
trimmed = path.strip("/")
segments = trimmed.split("/") if trimmed else []
@@ -630,11 +637,10 @@ class SPAStaticFiles(StaticFiles):
if parent
else f"{_SPA_SHELL_PLACEHOLDER_SEGMENT}/index.html"
)
- if os.path.isfile(os.path.join(str(self.directory), placeholder)):
- return placeholder
- if os.path.isfile(os.path.join(str(self.directory), "index.html")):
- return "index.html"
- return None
+ shell = self._bounded_shell(placeholder)
+ if shell is not None:
+ return shell
+ return self._bounded_shell("index.html")
async def _try_original(self, path: str, scope: StarletteScope) -> Optional[StarletteResponse]:
try:
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/api-keys/[keyid]/KeyDetailPage.tsx b/ui/litellm-dashboard/src/app/(dashboard)/api-keys/[keyid]/KeyDetailPage.tsx
index 1c7b7d3001b..56def7f6a6d 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/api-keys/[keyid]/KeyDetailPage.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/api-keys/[keyid]/KeyDetailPage.tsx
@@ -6,6 +6,8 @@ import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import LoadingScreen from "@/components/common_components/LoadingScreen";
import KeyInfoView from "@/components/templates/key_info_view";
import { migratedHref } from "@/utils/migratedPages";
+import { ArrowLeftOutlined } from "@ant-design/icons";
+import { Button } from "antd";
import { useRouter } from "next/navigation";
import { useState } from "react";
@@ -22,19 +24,24 @@ export default function KeyDetailPage() {
const { data: keyData, isPending } = useKeyInfo(keyId);
const { data: teams } = useAllTeams();
+ const backToKeys = () => router.push(migratedHref("api-keys"));
+
if (authLoading || !isAuthorized) {
return
Key not found
+