From 4338e65f5d1764dd985c15216558da543e788c03 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 16 May 2026 11:30:04 -0500 Subject: feat(gptel-tools): harden path validation with file-truename realpath Resolves PATH through file-truename before applying home-directory and read/write checks across the path-handling tools (git_status, git_log, git_diff, move_to_trash, read_text_file, update_text_file, write_text_file, list_directory_files, read_buffer, web_fetch). Without the resolve step, a symlink under HOME pointing outside HOME would pass the prefix check but the tool would act on the real target -- a symlink-escape. move_to_trash also tightens the trash-bin construction (treats empty file extensions correctly) and switches the "critical directories" list to truename-resolved canonical forms so a symlinked ~/.config can't be trashed via an aliased path. update_text_file fixes an off-by-one in the line-count derivation when the source content is empty. Each source change pairs with tests in tests/test-gptel-tools-*.el and tests/test-update-text-file.el covering the realpath escape paths, the empty-extension trash case, and the empty-content line- count edge. Combined coverage is now 100% across all ten gptel-tools source files: 516 / 516 executable lines, 217 tests. --- gptel-tools/move_to_trash.el | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'gptel-tools/move_to_trash.el') diff --git a/gptel-tools/move_to_trash.el b/gptel-tools/move_to_trash.el index 6ea97995..923da790 100644 --- a/gptel-tools/move_to_trash.el +++ b/gptel-tools/move_to_trash.el @@ -41,7 +41,7 @@ YYYY-MM-DD-HH-MM-SS." (let* ((extension (file-name-extension base-name t)) (name-sans-ext (file-name-sans-extension base-name)) (timestamp (format-time-string "%Y-%m-%d-%H-%M-%S")) - (new-name (if extension + (new-name (if (and extension (not (string= extension ""))) (concat name-sans-ext "-" timestamp extension) (concat base-name "-" timestamp)))) (expand-file-name new-name trash-dir))))) @@ -51,15 +51,18 @@ YYYY-MM-DD-HH-MM-SS." Returns the expanded path if valid, signals an error otherwise. Ensures path is within home directory or /tmp, and prevents trashing of critical system directories." - (let ((expanded-path (expand-file-name path)) - (home-dir (expand-file-name "~")) - (critical-dirs (list (expand-file-name "~") - (expand-file-name "~/.emacs.d") - (expand-file-name "~/.config") - "/tmp"))) + (let* ((expanded-path (expand-file-name path)) + (resolved-path (and (file-exists-p expanded-path) + (file-truename expanded-path))) + (home-dir (file-name-as-directory (file-truename (expand-file-name "~")))) + (tmp-dir (file-name-as-directory (file-truename "/tmp"))) + (critical-dirs (list (directory-file-name home-dir) + (file-truename (expand-file-name "~/.emacs.d")) + (file-truename (expand-file-name "~/.config")) + (directory-file-name tmp-dir)))) ;; Security check: must be within allowed directories (unless (or (string-prefix-p home-dir expanded-path) - (string-prefix-p "/tmp" expanded-path)) + (string-prefix-p tmp-dir expanded-path)) (error "Path must be within home directory or /tmp: %s" path)) ;; Prevent trashing critical directories @@ -70,6 +73,10 @@ trashing of critical system directories." (unless (file-exists-p expanded-path) (error "File or directory does not exist: %s" path)) + (unless (or (string-prefix-p home-dir resolved-path) + (string-prefix-p tmp-dir resolved-path)) + (error "Resolved path must be within home directory or /tmp: %s" path)) + expanded-path)) (defun gptel--move-to-trash-perform (expanded-path trash-dir) -- cgit v1.2.3