]> git.eshelyaron.com Git - emacs.git/commitdiff
trust.el: New file with improved trust API.
authorEshel Yaron <me@eshelyaron.com>
Sun, 27 Apr 2025 09:48:02 +0000 (11:48 +0200)
committerEshel Yaron <me@eshelyaron.com>
Sun, 27 Apr 2025 10:12:50 +0000 (12:12 +0200)
lisp/emacs-lisp/find-func.el
lisp/emacs-lisp/scope.el
lisp/files.el
lisp/gnus/mm-view.el
lisp/ielm.el
lisp/org/org.el
lisp/progmodes/elisp-mode.el
lisp/progmodes/flymake.el
lisp/simple.el
lisp/trust.el [new file with mode: 0644]
test/lisp/emacs-lisp/find-func-tests.el

index 6d7dede9d8b7e2c0861a59d34a00e9712ad76e3a..d9315002dba10ec6e30fc5dd81fcd17836d3b707 100644 (file)
@@ -551,7 +551,7 @@ The search is done in the source for library LIBRARY."
                 ;; macro-expansion involves arbitrary code execution,
                 ;; only attempt it in trusted buffers.
                 (cons (current-buffer)
-                      (when (trusted-content-p)
+                      (when (trust-trusted-content-p)
                         (find-function--search-by-expanding-macros
                          (current-buffer) symbol type)))))))))))
 
index 0b3d64632ad0542834b25c97fa8b83ece7d4b910..f5db0ce1fa9efe0333e4a6140556e8ad3e790f71 100644 (file)
@@ -1428,7 +1428,7 @@ a (possibly empty) list of safe macros.")
        (or (eq scope-safe-macros t)
            (memq macro scope-safe-macros)
            (get macro 'safe-macro)
-           (trusted-content-p))))
+           (trust-trusted-content-p))))
 
 (defvar warning-minimum-log-level)
 
@@ -1595,7 +1595,7 @@ a (possibly empty) list of safe macros.")
                 special-variable-p local-variable-p
                 local-variable-if-set-p
                 default-value set-default make-local-variable
-                buffer-local-value add-to-list add-to-history
+                buffer-local-value add-to-list add-to-history find-buffer
                 add-hook remove-hook run-hook-with-args run-hook-wrapped))
   (put sym 'scope-analyzer #'scope--analyze-boundp))
 
index 5006064d4770dbd988319eee65d46123a9ea15e9..4be36335482cafdbe9e1f6371ccb887bc1dbd50a 100644 (file)
@@ -702,59 +702,6 @@ Also see the `permanently-enabled-local-variables' and
 Some modes may wish to set this to nil to prevent directory-local
 settings being applied, but still respect file-local ones.")
 
-(defvar-local untrusted-content nil
-  "Non-nil means that current buffer originated from an untrusted source.
-Email clients and some other modes may set this non-nil to mark the
-buffer contents as untrusted.
-
-This variable might be subject to change without notice.")
-(put 'untrusted-content 'permanent-local t)
-
-(defcustom trusted-content nil
-  "List of files and directories whose content we trust.
-Be extra careful here since trusting means that Emacs might execute the
-code contained within those files and directories without an explicit
-request by the user.
-One important case when this might happen is when `flymake-mode' is
-enabled (for example, when it is added to a mode hook).
-Each element of the list should be a string:
-- If it ends in \"/\", it is considered as a directory name and means that
-  Emacs should trust all the files whose name has this directory as a prefix.
-- Otherwise, it is considered a file name.
-Use abbreviated file names.  For example, an entry \"~/mycode/\" means
-that Emacs will trust all the files in your directory \"mycode\".
-This variable can also be set to `:all', in which case Emacs will trust
-all files, which opens a gaping security hole.  Emacs Lisp authors
-should note that this value must never be set by a major or minor mode."
-  :type '(choice (repeat :tag "List" file)
-                 (const :tag "Trust everything (DANGEROUS!)" :all))
-  :version "30.1")
-(put 'trusted-content 'risky-local-variable t)
-
-(defun trusted-content-p ()
-  "Return non-nil if we trust the contents of the current buffer.
-Here, \"trust\" means that we are willing to run code found inside of it.
-See also `trusted-content'."
-  (and (not untrusted-content)
-       (or
-        (eq trusted-content :all)
-        (and
-         (or vc-followed-link buffer-file-name)
-         (with-demoted-errors "trusted-content-p: %S"
-           (let* ((file (expand-file-name (or vc-followed-link buffer-file-name)))
-                  (exists (file-exists-p file)))
-             (catch 'ball
-               (dolist (tf trusted-content)
-                 (let ((ef (expand-file-name tf)))
-                   (and
-                    (or (if exists (file-equal-p ef file) (equal ef file))
-                        ;; We don't use `file-in-directory-p' here,
-                        ;; because we want to err on the conservative
-                        ;; side: "guilty until proven innocent".
-                        (and (string-suffix-p "/" ef)
-                             (string-prefix-p ef file)))
-                    (throw 'ball t)))))))))))
-
 (defcustom enable-local-eval nil
   "Control processing of the \"variable\" `eval' in a file's local variables.
 The value can be t, nil or something else.
index a3162d13e91c120874fff6a8429824e7c567b84b..35230ec2f84193fa7b135693563cb253f283c968 100644 (file)
@@ -502,7 +502,7 @@ If MODE is not set, try to find mode automatically."
          (setq coding-system (mm-find-buffer-file-coding-system)))
        (setq text (buffer-string))))
     (with-temp-buffer
-      (setq untrusted-content t)
+      (trust-set-buffer-trust)
       (insert (cond ((eq charset 'gnus-decoded)
                     (with-current-buffer (mm-handle-buffer handle)
                       (buffer-string)))
index 81d22ee4405911e24df6c9770a0d4f3e97f41ef1..a7ba8fc95120fe873f28f647f1eed71e162c1203 100644 (file)
@@ -692,7 +692,7 @@ See `inferior-emacs-lisp-mode' for details."
       (with-current-buffer (get-buffer-create buf-name)
         (unless (zerop (buffer-size)) (setq old-point (point)))
         (inferior-emacs-lisp-mode)
-        (setq-local trusted-content :all)))
+        (trust-set-buffer-trust nil t)))
     (pop-to-buffer-same-window buf-name)
     (when old-point (push-mark old-point))))
 
index d783e992df007156a4a0fa43dcd60a8693da3d32..9902fc2d01bee5f49d10dbe20915c6b1ff668496 100644 (file)
@@ -1181,9 +1181,6 @@ the following lines anywhere in the buffer:
   :package-version '(Org . "8.0")
   :type 'boolean)
 
-(unless (boundp 'untrusted-content)
-  (defvar untrusted-content nil))
-(defvar untrusted-content) ; defined in files.el since Emacs 29.3
 (defvar org--latex-preview-when-risky nil
   "If non-nil, enable LaTeX preview in Org buffers from unsafe source.
 
@@ -1195,7 +1192,7 @@ fragments that originate from incoming email messages.  It has no effect
 when Org mode is unable to determine the origin of the Org buffer.
 
 An Org buffer is considered to be from unsafe source when the
-variable `untrusted-content' has a non-nil value in the buffer.
+`trust-trusted-content-p' returns nil in the buffer.
 
 If this variable is non-nil, LaTeX previews are rendered unconditionally.
 
@@ -16167,7 +16164,7 @@ fragments in the buffer."
   (interactive "P")
   (cond
    ((not (display-graphic-p)) nil)
-   ((and untrusted-content (not org--latex-preview-when-risky)) nil)
+   ((not (or (trust-trusted-content-p) org--latex-preview-when-risky)) nil)
    ;; Clear whole buffer.
    ((equal arg '(64))
     (org-clear-latex-preview (point-min) (point-max))
index a9794579fe8e7ba9799338030cccc44be089354c..dc56c35af8d499069517cf1bf9024c4db52c955d 100644 (file)
@@ -565,6 +565,44 @@ This is the `font-lock-fontify-region-function' for `emacs-lisp-mode'."
                                 (2 font-lock-function-name-face prepend t)))
   "Highlighting patterns for Emacs Lisp mode.")
 
+(defvar elisp-mode-line-trusted-buffer
+  `(:eval
+    (if (trust-trusted-content-p)
+        ,(propertize "!"
+                     'face 'success
+                     'help-echo "mouse-2: Stop trusting this buffer\nBuffer is trusted"
+                     'mouse-face 'mode-line-highlight
+                     'follow-link t
+                     'keymap
+                     (let ((map (make-sparse-keymap)))
+                       (define-key map [mode-line follow-link] 'mouse-face)
+                       (define-key map [mode-line mouse-2]
+                                   (lambda (event)
+                                     (interactive "e")
+                                     (with-current-buffer (window-buffer (posn-window (event-start event)))
+                                       (trust-set-buffer-trust)
+                                       (message "Buffer %S is now UNTRUSTED" (current-buffer))
+                                       (force-mode-line-update))))
+                       map))
+      ,(propertize "?"
+                   'face 'error
+                   'help-echo "mouse-2: Trust this buffer\nBuffer is untrusted"
+                   'mouse-face 'mode-line-highlight
+                   'follow-link t
+                   'keymap
+                   (let ((map (make-sparse-keymap)))
+                     (define-key map [mode-line follow-link] 'mouse-face)
+                     (define-key map [mode-line mouse-2]
+                                 (lambda (event)
+                                   (interactive "e")
+                                   (with-current-buffer (window-buffer (posn-window (event-start event)))
+                                     (trust-set-buffer-trust nil t)
+                                     (message "Buffer %S is now TRUSTED" (current-buffer))
+                                     (force-mode-line-update))))
+                     map)))))
+
+(put 'elisp-mode-line-trusted-buffer 'risky-local-variable t)
+
 ;;;###autoload
 (define-derived-mode emacs-lisp-mode lisp-data-mode
   `("ELisp"
@@ -576,39 +614,7 @@ mouse-1: Enable lexical-binding mode"
                                  face warning
                                  mouse-face mode-line-highlight
                                   local-map ,elisp--dynlex-modeline-map))
-    (:eval (if (trusted-content-p)
-               ,(propertize "!"
-                            'face 'success
-                            'help-echo "mouse-2: Stop trusting this buffer\nBuffer is trusted"
-                            'mouse-face 'mode-line-highlight
-                            'follow-link t
-                            'keymap
-                            (let ((map (make-sparse-keymap)))
-                              (define-key map [mode-line follow-link] 'mouse-face)
-                              (define-key map [mode-line mouse-2]
-                                          (lambda (event)
-                                            (interactive "e")
-                                            (with-current-buffer (window-buffer (posn-window (event-start event)))
-                                              (setq-local trusted-content nil)
-                                              (message "Buffer %S is now UNTRUSTED" (current-buffer))
-                                              (force-mode-line-update))))
-                              map))
-             ,(propertize "?"
-                          'face 'error
-                          'help-echo "mouse-2: Trust this buffer\nBuffer is untrusted"
-                          'mouse-face 'mode-line-highlight
-                          'follow-link t
-                          'keymap
-                          (let ((map (make-sparse-keymap)))
-                            (define-key map [mode-line follow-link] 'mouse-face)
-                            (define-key map [mode-line mouse-2]
-                                        (lambda (event)
-                                          (interactive "e")
-                                          (with-current-buffer (window-buffer (posn-window (event-start event)))
-                                            (setq-local trusted-content :all)
-                                            (message "Buffer %S is now TRUSTED" (current-buffer))
-                                            (force-mode-line-update))))
-                            map)))))
+    elisp-mode-line-trusted-buffer)
   "Major mode for editing Lisp code to run in Emacs.
 \\<emacs-lisp-mode-map>
 - \\[backward-delete-char-untabify] converts tabs to spaces as it moves back.
@@ -654,6 +660,12 @@ be used instead.
   (add-hook 'flymake-diagnostic-functions #'elisp-flymake-checkdoc nil t)
   (add-hook 'flymake-diagnostic-functions
             #'elisp-flymake-byte-compile nil t)
+  (add-hook
+   'trust-buffer-hook
+   (lambda (arg)
+     (when (and arg (bound-and-true-p flymake-mode))
+       (flymake-enable-backend 'elisp-flymake-byte-compile)))
+   nil t)
   (add-hook 'refactor-backend-functions #'elisp-refactor-backend nil t)
   (add-hook 'context-menu-functions #'elisp-context-menu 10 t)
   (setq-local imenu-create-index-function #'elisp-create-imenu-index)
@@ -1453,7 +1465,8 @@ All commands in `lisp-mode-shared-map' are inherited by this map."
     ["Evaluate Defun" eval-defun
      :help "Evaluate the top-level form containing point, or after point"]))
 
-(define-derived-mode lisp-interaction-mode emacs-lisp-mode "Lisp Interaction"
+(define-derived-mode lisp-interaction-mode emacs-lisp-mode
+  '("Lisp Interaction" elisp-mode-line-trusted-buffer)
   "Major mode for typing and evaluating Lisp forms.
 Like Lisp mode except that \\[eval-print-last-sexp] evals the Lisp expression
 before point, and prints its value into the buffer, advancing point.
@@ -2371,12 +2384,8 @@ directory of the buffer being compiled, and nothing else.")
   "A Flymake backend for elisp byte compilation.
 Spawn an Emacs process that byte-compiles a file representing the
 current buffer state and calls REPORT-FN when done."
-  (unless (trusted-content-p)
+  (unless (trust-trusted-content-p)
     ;; FIXME: Use `bwrap' and friends to compile untrusted content.
-    ;; FIXME: We emit a message *and* signal an error, because by default
-    ;; Flymake doesn't display the warning it puts into "*flmake log*".
-    (message "Disabling elisp-flymake-byte-compile in %s (untrusted content)"
-             (buffer-name))
     (user-error "Disabling elisp-flymake-byte-compile in %s (untrusted content)"
                 (buffer-name)))
   (when elisp-flymake--byte-compile-process
index 995a4dd4c0dffd1f79228d5267d4009858f78915..5008f800f4d6357042dff1bb59a5c7ae9cff7f73 100644 (file)
@@ -1392,6 +1392,12 @@ If it is running also stop it."
           (flymake--state-disabled state) explanation
           (flymake--state-reported-p state) t)))
 
+;;;###autoload
+(defun flymake-enable-backend (backend)
+  "Enable Flymake BACKEND."
+  (flymake--with-backend-state backend state
+    (setf (flymake--state-disabled state) nil)))
+
 (defun flymake--run-backend (backend &optional args)
   "Run the backend BACKEND, re-enabling if necessary.
 ARGS is a keyword-value plist passed to the backend along
index 34b34426b748fc797753c04492c25cae85bfe742..e80301a5fdb67183f4b6123eac1cdafa47fe0e03 100644 (file)
@@ -2043,7 +2043,7 @@ function `read-from-minibuffer'."
                   #'elisp-completion-at-point nil t)
         (add-hook 'minibuffer-exit-hook
                   #'minibuffer-kill-completions-buffer nil t)
-        (setq-local trusted-content :all)
+        (trust-set-buffer-trust nil t)
         (run-hooks 'eval-expression-minibuffer-setup-hook))
     (read-from-minibuffer prompt initial-contents
                           read--expression-map t
@@ -10858,7 +10858,8 @@ too short to have a dst element.
           (when initial-scratch-message
             (insert (substitute-command-keys initial-scratch-message))
             (set-buffer-modified-p nil))
-          (funcall initial-major-mode))
+          (funcall initial-major-mode)
+          (trust-set-buffer-trust nil t))
         scratch)))
 
 (defun scratch-buffer ()
diff --git a/lisp/trust.el b/lisp/trust.el
new file mode 100644 (file)
index 0000000..3961648
--- /dev/null
@@ -0,0 +1,119 @@
+;;; trust.el --- Trust API  -*- lexical-binding:t -*-
+
+;; Copyright (C) 2025  Eshel Yaron
+
+;; Author: Eshel Yaron <me@eshelyaron.com>
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(defgroup trust ()
+  "Trust related settings."
+  :group 'files)
+
+(defcustom trust--trusted-content nil
+  "List of files and directories whose content we trust.
+Be extra careful here since trusting means that Emacs might execute the
+code contained within those files and directories without an explicit
+request by the user.
+One important case when this might happen is when `flymake-mode' is
+enabled (for example, when it is added to a mode hook).
+Each element of the list should be a string:
+- If it ends in \"/\", it is considered as a directory name and means that
+  Emacs should trust all the files whose name has this directory as a prefix.
+- Otherwise, it is considered a file name.
+Use abbreviated file names.  For example, an entry \"~/mycode/\" means
+that Emacs will trust all the files in your directory \"mycode\".
+This variable can also be set to `:all', in which case Emacs will trust
+all files, which opens a gaping security hole.  Emacs Lisp authors
+should note that this value must never be set by a major or minor mode."
+  :type '(choice (repeat :tag "List" file)
+                 (const :tag "Trust everything (DANGEROUS!)" :all))
+  :version "30.1")
+(put 'trust--trusted-content 'risky-local-variable t)
+
+;;;###autoload
+(defun trust-trusted-content-p ()
+  "Return non-nil if we trust the contents of the current buffer.
+Here, \"trust\" means that we are willing to run code found inside of it.
+See also `trust--trusted-content'."
+  (or
+   (eq trust--trusted-content :all)
+   (and
+    (or vc-followed-link buffer-file-name)
+    (with-demoted-errors "trusted-content-p: %S"
+      (let* ((file (expand-file-name (or vc-followed-link buffer-file-name)))
+             (exists (file-exists-p file)))
+        (catch 'ball
+          (dolist (tf trust--trusted-content)
+            (let ((ef (expand-file-name tf)))
+              (and
+               (or (if exists (file-equal-p ef file) (equal ef file))
+                   ;; We don't use `file-in-directory-p' here,
+                   ;; because we want to err on the conservative
+                   ;; side: "guilty until proven innocent".
+                   (and (string-suffix-p "/" ef)
+                        (string-prefix-p ef file)))
+               (throw 'ball t))))))))))
+
+(defvar trust-buffer-hook nil
+  "Abnormal hook run when buffer is affected by change in trust settings.
+Functions on this hook are called with one argument, which is non-nil if
+the current buffer is now trusted, and nil if the it is now untrusted.")
+
+;;;###autoload
+(defun trust-set-buffer-trust (&optional buf trust)
+  (interactive
+   (let ((trust (unless current-prefix-arg :all)))
+     (list (read-buffer
+            (format-prompt (if trust "Trust buffer" "Untrust buffer"))
+            (buffer-name (current-buffer)) t
+            (lambda (b)
+              (not (eq trust
+                       (when (buffer-local-value
+                              'trust--trusted-content (get-buffer b))
+                         :all)))))
+           trust)))
+  (with-current-buffer (or buf (current-buffer))
+    (setq-local trust--trusted-content (when trust :all))
+    (run-hook-with-args 'trust-buffer-hook trust)))
+
+;;;###autoload
+(defun trust-set-file-trust (file &optional trust)
+  (interactive
+   (let ((trust (not current-prefix-arg))
+         (def (when default-directory (abbreviate-file-name default-directory))))
+     (list (read-file-name
+            (format-prompt (if trust "Trust file" "Untrust file") def)
+            nil def)
+           trust)))
+  (let* ((file (expand-file-name (if (file-directory-p file)
+                                     (file-name-as-directory file)
+                                   file)))
+         (exists (file-exists-p file))
+         (cur (delete file (default-value 'trust--trusted-content))))
+    (setq-default trust--trusted-content (if trust (cons file cur) cur))
+    (dolist (buf (buffer-list))
+      (with-current-buffer buf
+        (when-let ((fbuf (or vc-followed-link buffer-file-name))
+                   (fbuf (expand-file-name fbuf)))
+          (when (or (and (string-suffix-p "/" file) (string-prefix-p file fbuf))
+                    (if exists (file-equal-p fbuf file) (equal fbuf file)))
+            (run-hook-with-args 'trust-buffer-hook trust)))))))
+
+(provide 'trust)
+;;; trust.el ends here
index 325430196b4e082b9f339181fec899fa281a5814..3ba1b1e334b84b2951ff7be0f55db18b5f3794a9 100644 (file)
@@ -103,9 +103,8 @@ expected function symbol and function library, respectively."
 (declare-function compilation--message->loc nil "compile")
 
 (ert-deftest find-func-tests--locate-macro-generated-symbols () ;bug#45443
-  (let ((trusted-content
-         (list (abbreviate-file-name (find-library-name "compile"))
-               (abbreviate-file-name (find-library-name "cc-mode")))))
+  (let ((trust--trusted-content
+         (list (find-library-name "compile") (find-library-name "cc-mode"))))
     (should (cdr (find-function-search-for-symbol
                   #'compilation--message->loc nil "compile")))
     (should (cdr (find-function-search-for-symbol