aboutsummaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/doc/editing.txt28
-rw-r--r--runtime/doc/index.txt1
-rw-r--r--runtime/doc/lua.txt24
-rw-r--r--runtime/doc/news.txt3
-rw-r--r--runtime/doc/options.txt2
-rw-r--r--runtime/lua/vim/secure.lua139
6 files changed, 171 insertions, 26 deletions
diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt
index 76c528ef3c..e379d9eeb1 100644
--- a/runtime/doc/editing.txt
+++ b/runtime/doc/editing.txt
@@ -1650,4 +1650,32 @@ There are three different types of searching:
currently work with 'path' items that contain a URL or use the double star
with depth limiter (/usr/**2) or upward search (;) notations.
+==============================================================================
+11. Trusted Files *trust*
+
+Nvim has the ability to execute arbitrary code through the 'exrc' option. In
+order to prevent executing code from untrusted sources, Nvim has the concept of
+"trusted files". An untrusted file will not be executed without the user's
+consent, and a user can permanently mark a file as trusted or untrusted using
+the |:trust| command or the |vim.secure.read()| function.
+
+ *:trust* *E5570*
+:trust [++deny] [++remove] [{file}]
+
+ Manage files in the trust database. Without any options
+ or arguments, :trust adds the file associated with the
+ current buffer to the trust database, along with the
+ SHA256 hash of its contents.
+
+ [++deny] marks the file associated with the current
+ buffer (or {file}, if given) as denied; no prompts will
+ be displayed to the user and the file will never be
+ executed.
+
+ [++remove] removes the file associated with the current
+ buffer (or {file}, if given) from the trust database.
+ Future attempts to read the file in a secure setting
+ (i.e. with 'exrc' or |vim.secure.read()|) will prompt
+ the user if the file is trusted.
+
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
index 376487ad1d..66353e05f3 100644
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -1633,6 +1633,7 @@ tag command action ~
|:topleft| :to[pleft] make split window appear at top or far left
|:tprevious| :tp[revious] jump to previous matching tag
|:trewind| :tr[ewind] jump to first matching tag
+|:trust| :trust add or remove file from trust database
|:try| :try execute commands, abort on error or exception
|:tselect| :ts[elect] list matching tags and select one
|:tunmap| :tunma[p] like ":unmap" but for |Terminal-mode|
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 005b6409d6..bcbbd69f11 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -2371,4 +2371,28 @@ read({path}) *vim.secure.read()*
(string|nil) The contents of the given file if it exists and is
trusted, or nil otherwise.
+ See also: ~
+ |:trust|
+
+trust({opts}) *vim.secure.trust()*
+ Manage the trust database.
+
+ The trust database is located at |$XDG_STATE_HOME|/nvim/trust.
+
+ Parameters: ~
+ • {opts} (table)
+ • action (string): "allow" to add a file to the trust database
+ and trust it, "deny" to add a file to the trust database and
+ deny it, "remove" to remove file from the trust database
+ • path (string|nil): Path to a file to update. Mutually
+ exclusive with {bufnr}. Cannot be used when {action} is
+ "allow".
+ • bufnr (number|nil): Buffer number to update. Mutually
+ exclusive with {path}.
+
+ Return: ~
+ (boolean, string) success, msg:
+ • true and full path of target file if operation was successful
+ • false and error message on failure
+
vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl:
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 70643cf00c..b155bae5a4 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -39,6 +39,9 @@ NEW FEATURES *news-features*
The following new APIs or features were added.
+• |vim.secure.trust()|, |:trust| allows the user to manage files in trust
+ database.
+
• |vim.diagnostic.open_float()| (and therefore |vim.diagnostic.config()|) now
accepts a `suffix` option which, by default, renders LSP error codes.
Similarly, the `virtual_text` configuration in |vim.diagnostic.config()| now
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 36eabfdcbf..33bade3545 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -2275,6 +2275,8 @@ A jump table for the options with a short description can be found at |Q_op|.
file are persisted to a trust database. The user is only prompted
again if the file contents change. See |vim.secure.read()|.
+ Use |:trust| to manage the trusted file database.
+
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua
index 341ff8df05..0b4d7d53a2 100644
--- a/runtime/lua/vim/secure.lua
+++ b/runtime/lua/vim/secure.lua
@@ -1,9 +1,50 @@
local M = {}
+---@private
+--- Reads trust database from $XDG_STATE_HOME/nvim/trust.
+---
+---@return (table) Contents of trust database, if it exists. Empty table otherwise.
+local function read_trust()
+ local trust = {}
+ local f = io.open(vim.fn.stdpath('state') .. '/trust', 'r')
+ if f then
+ local contents = f:read('*a')
+ if contents then
+ for line in vim.gsplit(contents, '\n') do
+ local hash, file = string.match(line, '^(%S+) (.+)$')
+ if hash and file then
+ trust[file] = hash
+ end
+ end
+ end
+ f:close()
+ end
+ return trust
+end
+
+---@private
+--- Writes provided {trust} table to trust database at
+--- $XDG_STATE_HOME/nvim/trust.
+---
+---@param trust (table) Trust table to write
+local function write_trust(trust)
+ vim.validate({ trust = { trust, 't' } })
+ local f = assert(io.open(vim.fn.stdpath('state') .. '/trust', 'w'))
+
+ local t = {}
+ for p, h in pairs(trust) do
+ t[#t + 1] = string.format('%s %s\n', h, p)
+ end
+ f:write(table.concat(t))
+ f:close()
+end
+
--- Attempt to read the file at {path} prompting the user if the file should be
--- trusted. The user's choice is persisted in a trust database at
--- $XDG_STATE_HOME/nvim/trust.
---
+---@see |:trust|
+---
---@param path (string) Path to a file to read.
---
---@return (string|nil) The contents of the given file if it exists and is
@@ -15,22 +56,7 @@ function M.read(path)
return nil
end
- local trust = {}
- do
- local f = io.open(vim.fn.stdpath('state') .. '/trust', 'r')
- if f then
- local contents = f:read('*a')
- if contents then
- for line in vim.gsplit(contents, '\n') do
- local hash, file = string.match(line, '^(%S+) (.+)$')
- if hash and file then
- trust[file] = hash
- end
- end
- end
- f:close()
- end
- end
+ local trust = read_trust()
if trust[fullpath] == '!' then
-- File is denied
@@ -86,21 +112,82 @@ function M.read(path)
trust[fullpath] = hash
end
- do
- local f, err = io.open(vim.fn.stdpath('state') .. '/trust', 'w')
- if not f then
- error(err)
+ write_trust(trust)
+
+ return contents
+end
+
+--- Manage the trust database.
+---
+--- The trust database is located at |$XDG_STATE_HOME|/nvim/trust.
+---
+---@param opts (table):
+--- - action (string): "allow" to add a file to the trust database and trust it,
+--- "deny" to add a file to the trust database and deny it,
+--- "remove" to remove file from the trust database
+--- - path (string|nil): Path to a file to update. Mutually exclusive with {bufnr}.
+--- Cannot be used when {action} is "allow".
+--- - bufnr (number|nil): Buffer number to update. Mutually exclusive with {path}.
+---@return (boolean, string) success, msg:
+--- - true and full path of target file if operation was successful
+--- - false and error message on failure
+function M.trust(opts)
+ vim.validate({
+ path = { opts.path, 's', true },
+ bufnr = { opts.bufnr, 'n', true },
+ action = {
+ opts.action,
+ function(m)
+ return m == 'allow' or m == 'deny' or m == 'remove'
+ end,
+ [["allow" or "deny" or "remove"]],
+ },
+ })
+
+ local path = opts.path
+ local bufnr = opts.bufnr
+ local action = opts.action
+
+ if path and bufnr then
+ error('path and bufnr are mutually exclusive', 2)
+ end
+
+ local fullpath
+ if path then
+ fullpath = vim.loop.fs_realpath(vim.fs.normalize(path))
+ else
+ local bufname = vim.api.nvim_buf_get_name(bufnr)
+ if bufname == '' then
+ return false, 'buffer is not associated with a file'
end
+ fullpath = vim.loop.fs_realpath(vim.fs.normalize(bufname))
+ end
+
+ if not fullpath then
+ return false, string.format('invalid path: %s', path)
+ end
+
+ local trust = read_trust()
+
+ if action == 'allow' then
+ assert(bufnr, 'bufnr is required when action is "allow"')
- local t = {}
- for p, h in pairs(trust) do
- t[#t + 1] = string.format('%s %s\n', h, p)
+ local newline = vim.bo[bufnr].fileformat == 'unix' and '\n' or '\r\n'
+ local contents = table.concat(vim.api.nvim_buf_get_lines(bufnr, 0, -1, false), newline)
+ if vim.bo[bufnr].endofline then
+ contents = contents .. newline
end
- f:write(table.concat(t))
- f:close()
+ local hash = vim.fn.sha256(contents)
+
+ trust[fullpath] = hash
+ elseif action == 'deny' then
+ trust[fullpath] = '!'
+ elseif action == 'remove' then
+ trust[fullpath] = nil
end
- return contents
+ write_trust(trust)
+ return true, fullpath
end
return M