Add safe-local-variable-directories variable.
authorAntero Mejr <antero@mailbox.org>
Tue, 9 May 2023 20:51:14 +0000 (20:51 +0000)
committerEli Zaretskii <eliz@gnu.org>
Thu, 11 May 2023 13:33:16 +0000 (16:33 +0300)
This variable can be set to automatically load risky dir-local
variables from a list of trusted directories.

* lisp/emacs-lisp/files.el (safe-local-variable-directories,
hack-local-variables-filter, hack-local-variables-confirm): New
variable and associated logic.
* test/lisp/files-tests.el
(files-tests-safe-local-variable-directories): Add tests for same.
* doc/emacs/custom.texi (Safe File Variables): Add documentation for
same.
* doc/lispref/variables.texi (File Local Variables): Add documentation
for same.
* etc/NEWS (Lisp Changes in Emacs 30.1): Add news entry for
same.  (Bug#61901)

doc/emacs/custom.texi
doc/lispref/variables.texi
etc/NEWS
lisp/files.el
test/lisp/files-tests.el

index 87290734cc9f745fd33778a879cda1b6a8485929..2ddd39be31f630694fd1c6bd70303996c15c9220 100644 (file)
@@ -1328,6 +1328,13 @@ pairs in the file, by typing @kbd{i} at the confirmation prompt --
 these pairs will thereafter be ignored in this file and in all other
 files.
 
+  For directory-local variable/value pairs
+(@pxref{Directory Variables}), typing @kbd{+} at the confirmation
+prompt will set all the variable/value pairs, and recognize all
+variables in that directory as safe in the future.  This option should
+only be used for directories whose contents you trust.  The @kbd{+}
+confirmation prompt option is not available for file-local variables.
+
 @vindex safe-local-variable-values
 @vindex ignored-local-variable-values
 @cindex risky variable
@@ -1344,6 +1351,21 @@ record safe values for risky variables, do it directly by customizing
 Similarly, if you want to record values of risky variables that should
 be permanently ignored, customize @code{ignored-local-variable-values}.
 
+@vindex safe-local-variable-directories
+  Sometimes it is helpful to always trust risky variables in a certain
+directory, and skip the confirmation prompt when directory-local
+variables are loaded there.  When @kbd{+} is pressed at the risky
+variable confirmation prompt, the directory in question is added to
+the @samp{safe-local-variable-directories} variable, and risky
+directory-local variables there will be loaded without prompting in
+the future.  If customizing @samp{safe-local-variable-directories}
+manually, the directories in this list must be fully-expanded absolute
+paths that end in a directory separator character.  Directory paths
+may be remote directory paths (@pxref{Remote Files}), if the
+@code{enable-remote-dir-locals} variable is set to @code{t}.
+Directory paths in this list are case-sensitive, even if the
+filesystem is not.
+
 @vindex enable-local-variables
   The variable @code{enable-local-variables} allows you to change the
 way Emacs processes local variables.  Its default value is @code{t},
index eadb5c36de653e494c95e1113f1a2d6f7bf8b6c7..52a94db370344ae60890bfd813314921949a7eed 100644 (file)
@@ -1977,6 +1977,18 @@ this can be controlled by using this variable, which is a list of
 symbols.
 @end defvar
 
+@defvar safe-local-variable-directories
+This is a list of directories where local variables are always
+enabled.  Directory-local variables loaded from these directories,
+such as the variables in @file{.dir-locals.el}, will be enabled even
+if they are risky.  The directories in this list must be
+fully-expanded absolute paths that end in a directory separator
+character.  They may also be remote directory paths if the
+@code{enable-remote-dir-locals} variable is set to @code{t}.
+Directory paths in this list are case-sensitive, even if the
+filesystem is not.
+@end defvar
+
 @defun hack-local-variables &optional handle-mode
 This function parses, and binds or evaluates as appropriate, any local
 variables specified by the contents of the current buffer.  The variable
index 3c71e52fff470ca283afe55585cd90b8de3ca2fc..3bef9d2ed2a284ea91a88a9b75e53938718928c5 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -380,6 +380,11 @@ hooks named after the feature name, like 'esh-mode-unload-hook'.
 \f
 * Lisp Changes in Emacs 30.1
 
++++
+** New variable 'safe-local-variable-directories'.
+This variable names directories in which Emacs will treat all
+directory-local variables as safe.
+
 ** New variable 'inhibit-auto-fill' to temporarily prevent auto-fill.
 
 ** Functions and variables to transpose sexps
index c6f53e5eaf87cd01571873b291f86248bfe53e76..aa01e638c984c84f02319db78a31e1a462b99c4f 100644 (file)
@@ -681,7 +681,8 @@ The command \\[normal-mode], when used interactively,
 always obeys file local variable specifications and the -*- line,
 and ignores this variable.
 
-Also see the `permanently-enabled-local-variables' variable."
+Also see the `permanently-enabled-local-variables' and
+`safe-local-variable-directories' variables."
   :risky t
   :type '(choice (const :tag "Query Unsafe" t)
                 (const :tag "Safe Only" :safe)
@@ -3696,6 +3697,15 @@ variable to set.")
   "A list of file-local variables that are always enabled.
 This overrides any `enable-local-variables' setting.")
 
+(defcustom safe-local-variable-directories '()
+  "A list of directories where local variables are always enabled.
+Directory-local variables loaded from these directories, such as the
+variables in .dir-locals.el, will be enabled even if they are risky."
+  :version "30.1"
+  :type '(repeat string)
+  :risky t
+  :group 'find-file)
+
 (defun hack-local-variables-confirm (all-vars unsafe-vars risky-vars dir-name)
   "Get confirmation before setting up local variable values.
 ALL-VARS is the list of all variables to be set up.
@@ -3734,7 +3744,11 @@ n  -- to ignore the local variables list.")
 !  -- to apply the local variables list, and permanently mark these
       values (*) as safe (in the future, they will be set automatically.)
 i  -- to ignore the local variables list, and permanently mark these
-      values (*) as ignored\n\n")
+      values (*) as ignored"
+                    (if dir-name "
++  -- to apply the local variables list, and trust all directory-local
+      variables in this directory\n\n"
+                      "\n\n"))
          (insert "\n\n"))
        (dolist (elt all-vars)
          (cond ((member elt unsafe-vars)
@@ -3758,7 +3772,11 @@ i  -- to ignore the local variables list, and permanently mark these
        (pop-to-buffer buf '(display-buffer--maybe-at-bottom))
        (let* ((exit-chars '(?y ?n ?\s))
               (prompt (format "Please type %s%s: "
-                              (if offer-save "y, n, ! or i" "y or n")
+                              (if offer-save
+                                   (if dir-name
+                                       "y, n, !, i, +"
+                                     "y, n, !, i")
+                                 "y or n")
                               (if (< (line-number-at-pos (point-max))
                                      (window-body-height))
                                   ""
@@ -3766,8 +3784,13 @@ i  -- to ignore the local variables list, and permanently mark these
               char)
          (when offer-save
             (push ?i exit-chars)
-            (push ?! exit-chars))
+            (push ?! exit-chars)
+            (when dir-name
+              (push ?+ exit-chars)))
          (setq char (read-char-choice prompt exit-chars))
+          (when (and offer-save dir-name (= char ?+))
+            (customize-push-and-save 'safe-local-variable-directories
+                                     (list dir-name)))
          (when (and offer-save
                      (or (= char ?!) (= char ?i))
                      unsafe-vars)
@@ -3776,7 +3799,7 @@ i  -- to ignore the local variables list, and permanently mark these
                  'safe-local-variable-values
                'ignored-local-variable-values)
              unsafe-vars))
-         (prog1 (memq char '(?! ?\s ?y))
+         (prog1 (memq char '(?! ?\s ?y ?+))
            (quit-window t)))))))
 
 (defconst hack-local-variable-regexp
@@ -3908,6 +3931,7 @@ DIR-NAME is the name of the associated directory.  Otherwise it is nil."
                  (null unsafe-vars)
                  (null risky-vars))
             (memq enable-local-variables '(:all :safe))
+             (member dir-name safe-local-variable-directories)
             (hack-local-variables-confirm all-vars unsafe-vars
                                           risky-vars dir-name))
         (dolist (elt all-vars)
index aadb60e1de7e468c7a0b8e0b11fc034e66d44759..e87bb3cfa0ad4304afb2fcd2ec702ca1f6ba2380 100644 (file)
@@ -166,6 +166,27 @@ form.")
       (hack-local-variables)
       (should (eq lexical-binding nil)))))
 
+(ert-deftest files-tests-safe-local-variable-directories ()
+  ;; safe-local-variable-directories should be risky,
+  ;; so use it as an arbitrary risky variable.
+  (let ((test-alist '((safe-local-variable-directories . "some_val")))
+        (fakedir "/test1/test2/")
+        (enable-local-eval t))
+    (with-temp-buffer
+      (setq safe-local-variable-directories (list fakedir))
+      (hack-local-variables-filter test-alist fakedir)
+      (should (equal file-local-variables-alist test-alist)))
+    (with-temp-buffer
+      (setq safe-local-variable-directories (list fakedir))
+      (setq noninteractive t)
+      (hack-local-variables-filter test-alist "wrong")
+      (should-not (equal file-local-variables-alist test-alist)))
+    (with-temp-buffer
+      (setq safe-local-variable-directories '())
+      (setq noninteractive t)
+      (hack-local-variables-filter test-alist fakedir)
+      (should-not (equal file-local-variables-alist test-alist)))))
+
 (defvar files-test-bug-18141-file
   (ert-resource-file "files-bug18141.el.gz")
   "Test file for bug#18141.")