mirror of
https://github.com/neovim/neovim.git
synced 2025-09-21 02:38:19 +00:00
feat(secure): add :trust
command and vim.secure.trust() (#21107)
Introduce vim.secure.trust() to programmatically manage the trust database. Use this function in a new :trust ex command which can be used as a simple frontend. Resolves: https://github.com/neovim/neovim/issues/21092 Co-authored-by: Gregory Anders <greg@gpanders.com> Co-authored-by: ii14 <ii14@users.noreply.github.com>
This commit is contained in:
@@ -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:
|
||||
|
@@ -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|
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
@@ -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.
|
||||
|
||||
|
@@ -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)
|
||||
end
|
||||
|
||||
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
|
||||
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 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
|
||||
local hash = vim.fn.sha256(contents)
|
||||
|
||||
trust[fullpath] = hash
|
||||
elseif action == 'deny' then
|
||||
trust[fullpath] = '!'
|
||||
elseif action == 'remove' then
|
||||
trust[fullpath] = nil
|
||||
end
|
||||
|
||||
write_trust(trust)
|
||||
return true, fullpath
|
||||
end
|
||||
|
||||
return M
|
||||
|
Reference in New Issue
Block a user