diff --git a/src/utils/internal/path.ts b/src/utils/internal/path.ts
index 865980b..598364f 100644
--- a/src/utils/internal/path.ts
+++ b/src/utils/internal/path.ts
@@ -47,3 +47,26 @@ export function withoutBase(input: string = "", base: string = ""): string {
 export function getPathname(path: string = "/"): string {
   return path.startsWith("/") ? path.split("?")[0] : new URL(path, "http://localhost").pathname;
 }
+
+/**
+ * Resolve dot segments (`.` and `..`) in a path to prevent path traversal.
+ * Ensures the resulting path never escapes above the root `/`.
+ */
+export function resolveDotSegments(path: string): string {
+  if (!path.includes(".")) {
+    return path;
+  }
+  const segments = path.split("/");
+  const resolved: string[] = [];
+  for (const segment of segments) {
+    if (segment === "..") {
+      // Never pop past the root (first empty segment from leading slash)
+      if (resolved.length > 1) {
+        resolved.pop();
+      }
+    } else if (segment !== ".") {
+      resolved.push(segment);
+    }
+  }
+  return resolved.join("/") || "/";
+}
diff --git a/src/utils/static.ts b/src/utils/static.ts
index f541baa..4a9dadd 100644
--- a/src/utils/static.ts
+++ b/src/utils/static.ts
@@ -1,6 +1,6 @@
 import type { H3Event } from "../event.ts";
 import { HTTPError } from "../error.ts";
-import { withLeadingSlash, withoutTrailingSlash } from "./internal/path.ts";
+import { withLeadingSlash, withoutTrailingSlash, resolveDotSegments } from "./internal/path.ts";
 import { getType, getExtension } from "./internal/mime.ts";
 import { HTTPResponse } from "../response.ts";
 
@@ -83,7 +83,9 @@ export async function serveStatic(
     throw new HTTPError({ status: 405 });
   }
 
-  const originalId = decodeURI(withLeadingSlash(withoutTrailingSlash(event.url.pathname)));
+  const originalId = resolveDotSegments(
+    decodeURI(withLeadingSlash(withoutTrailingSlash(event.url.pathname))),
+  );
 
   const acceptEncodings = parseAcceptEncoding(
     event.req.headers.get("accept-encoding") || "",
