aboutsummaryrefslogtreecommitdiff
path: root/gptel-tools/write_text_file.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-16 11:30:04 -0500
committerCraig Jennings <c@cjennings.net>2026-05-16 11:30:04 -0500
commit244d4c56768fcc60bd1b23fe45df7a57c7b293ec (patch)
tree3a83c5953f7c963c3f3ab7b28044d6decd6ec2fb /gptel-tools/write_text_file.el
parentb35d10bae7315fe3e497f1188bbe5ce86cef1bbf (diff)
downloaddotemacs-244d4c56768fcc60bd1b23fe45df7a57c7b293ec.tar.gz
dotemacs-244d4c56768fcc60bd1b23fe45df7a57c7b293ec.zip
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.
Diffstat (limited to 'gptel-tools/write_text_file.el')
-rw-r--r--gptel-tools/write_text_file.el15
1 files changed, 14 insertions, 1 deletions
diff --git a/gptel-tools/write_text_file.el b/gptel-tools/write_text_file.el
index 40482c66..1bda5446 100644
--- a/gptel-tools/write_text_file.el
+++ b/gptel-tools/write_text_file.el
@@ -22,9 +22,22 @@
(defun cj/write-text-file--validate-path (path)
"Validate PATH for write. Return the expanded path on success.
PATH must resolve inside the user's home directory."
- (let ((full (expand-file-name path "~")))
+ (let* ((home (file-name-as-directory (file-truename (expand-file-name "~"))))
+ (full (expand-file-name path "~"))
+ (existing (and (file-exists-p full) (file-truename full)))
+ (parent (file-name-directory full))
+ (resolved-parent (and parent
+ (file-exists-p parent)
+ (file-truename parent))))
(unless (string-prefix-p (expand-file-name "~") full)
(error "Path must be within home directory: %s" path))
+ (when (and existing
+ (not (string-prefix-p home existing)))
+ (error "Resolved path must be within home directory: %s" path))
+ (when (and resolved-parent
+ (not (or (string= resolved-parent (directory-file-name home))
+ (string-prefix-p home resolved-parent))))
+ (error "Resolved parent must be within home directory: %s" path))
full))
(defun cj/write-text-file--backup-name (path)