diff --git a/README.md b/README.md index 75f353d..b8c1c74 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Literal slashes (`/`) and dots (`.`) in the URL path are **rejected** to prevent ### Things to know -- **Query parameters (`?...`)** other than `i` (and `src` on [free.direct-img.link](#free-images)) are ignored — `/orange+cat?size=large` → `orange cat` +- **Query parameters (`?...`)** other than `i` (and `src` on [free.direct-img.link](#free-images)) serve `bad.webp` — `/orange+cat?size=large` ❌ - **Fragments (`#...`)** are never sent to the server by browsers - **Double-encoded values** are decoded once — `%2520` becomes `%20` (literal), not a space - Two queries that normalize to the same string share the same cached image diff --git a/functions/[[path]].js b/functions/[[path]].js index 15209fd..6abfe6d 100644 --- a/functions/[[path]].js +++ b/functions/[[path]].js @@ -26,11 +26,13 @@ export async function onRequest(context) { if (!query) return jsonResponse(400, { error: "Empty query" }); if (query.length > 200) return jsonResponse(400, { error: "Query too long (max 200 characters)" }); - // ?i= picks which result to serve; free.direct-img.link also takes ?src= and serves only unrestricted images + // ?i= picks which result to serve; free.direct-img.link also takes ?src= and serves only unrestricted images. + // Any other param (e.g. scanner probes like ?path=../../.env) is rejected before it can cost a search. const free = url.hostname.startsWith("free."); const i = parseIndex(url.searchParams); const src = free ? parseFreeSource(url.searchParams) : null; - if (!i || (free && !src)) return asset("bad.webp"); + const extra = [...url.searchParams.keys()].some(k => k !== "i" && !(free && k === "src")); + if (!i || (free && !src) || extra) return asset("bad.webp"); context.waitUntil(countHit(env, request, query, free));