From aa18286e50ecd3c854bb5c364f3d655521d226ad Mon Sep 17 00:00:00 2001 From: Kevin Codex Date: Fri, 12 Jun 2026 18:45:39 +0800 Subject: [PATCH] fix(api): blob endpoint returns 400/404 instead of 500 on bad paths Unnormalized "../.." paths from crawler-exploded relative links made `git show` fail and surfaced as 500s. Reject traversal segments with 400 up front, and map paths absent from the tree to 404 so only real git failures count as server errors. Co-Authored-By: Claude Fable 5 --- crates/gitlawb-node/src/api/repos.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/crates/gitlawb-node/src/api/repos.rs b/crates/gitlawb-node/src/api/repos.rs index a02be6f..ab6ddaf 100644 --- a/crates/gitlawb-node/src/api/repos.rs +++ b/crates/gitlawb-node/src/api/repos.rs @@ -225,6 +225,18 @@ pub async fn get_blob( use axum::http::header; use axum::response::IntoResponse; + // Unnormalized paths ("../..", "./", "//") can't resolve in `git show` + // and crawlers combinatorially explode them from relative links — that's + // a client error, not a 500. + let file_path = file_path.trim_matches('/'); + if file_path.is_empty() + || file_path + .split('/') + .any(|seg| seg.is_empty() || seg == "." || seg == "..") + { + return Err(AppError::BadRequest("invalid file path".into())); + } + let record = state .db .get_repo(&owner, &name) @@ -237,8 +249,19 @@ pub async fn get_blob( .await .map_err(|e| AppError::Git(e.to_string()))?; let head_ref = store::resolve_head(&disk_path, &record.default_branch); - let content = store::read_file(&disk_path, &head_ref, &file_path) - .map_err(|e| AppError::Git(e.to_string()))?; + let content = store::read_file(&disk_path, &head_ref, file_path).map_err(|e| { + let msg = e.to_string(); + // `git show ref:path` on a path absent from the tree is a 404, + // not a server error + if msg.contains("does not exist in") + || msg.contains("invalid object name") + || msg.contains("exists on disk, but not in") + { + AppError::NotFound(format!("file not found: {file_path}")) + } else { + AppError::Git(msg) + } + })?; // Guess content type let mime = match file_path.rsplit('.').next() {