mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +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,22 +1,11 @@
 | 
			
		||||
local M = {}
 | 
			
		||||
 | 
			
		||||
--- 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.
 | 
			
		||||
---@private
 | 
			
		||||
--- Reads trust database from $XDG_STATE_HOME/nvim/trust.
 | 
			
		||||
---
 | 
			
		||||
---@param path (string) Path to a file to read.
 | 
			
		||||
---
 | 
			
		||||
---@return (string|nil) The contents of the given file if it exists and is
 | 
			
		||||
---        trusted, or nil otherwise.
 | 
			
		||||
function M.read(path)
 | 
			
		||||
  vim.validate({ path = { path, 's' } })
 | 
			
		||||
  local fullpath = vim.loop.fs_realpath(vim.fs.normalize(path))
 | 
			
		||||
  if not fullpath then
 | 
			
		||||
    return nil
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
---@return (table) Contents of trust database, if it exists. Empty table otherwise.
 | 
			
		||||
local function read_trust()
 | 
			
		||||
  local trust = {}
 | 
			
		||||
  do
 | 
			
		||||
  local f = io.open(vim.fn.stdpath('state') .. '/trust', 'r')
 | 
			
		||||
  if f then
 | 
			
		||||
    local contents = f:read('*a')
 | 
			
		||||
@@ -30,7 +19,44 @@ function M.read(path)
 | 
			
		||||
    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
 | 
			
		||||
---        trusted, or nil otherwise.
 | 
			
		||||
function M.read(path)
 | 
			
		||||
  vim.validate({ path = { path, 's' } })
 | 
			
		||||
  local fullpath = vim.loop.fs_realpath(vim.fs.normalize(path))
 | 
			
		||||
  if not fullpath then
 | 
			
		||||
    return nil
 | 
			
		||||
  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
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,7 @@
 | 
			
		||||
#include "nvim/highlight_group.h"
 | 
			
		||||
#include "nvim/indent.h"
 | 
			
		||||
#include "nvim/input.h"
 | 
			
		||||
#include "nvim/lua/executor.h"
 | 
			
		||||
#include "nvim/macros.h"
 | 
			
		||||
#include "nvim/main.h"
 | 
			
		||||
#include "nvim/mark.h"
 | 
			
		||||
@@ -4960,3 +4961,29 @@ void ex_oldfiles(exarg_T *eap)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ex_trust(exarg_T *eap)
 | 
			
		||||
{
 | 
			
		||||
  const char *const p = skiptowhite(eap->arg);
 | 
			
		||||
  char *arg1 = xmemdupz(eap->arg, (size_t)(p - eap->arg));
 | 
			
		||||
  const char *action = "allow";
 | 
			
		||||
  const char *path = skipwhite(p);
 | 
			
		||||
 | 
			
		||||
  if (strcmp(arg1, "++deny") == 0) {
 | 
			
		||||
    action = "deny";
 | 
			
		||||
  } else if (strcmp(arg1, "++remove") == 0) {
 | 
			
		||||
    action = "remove";
 | 
			
		||||
  } else if (*arg1 != '\0') {
 | 
			
		||||
    semsg(e_invarg2, arg1);
 | 
			
		||||
    goto theend;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (path[0] == '\0') {
 | 
			
		||||
    path = NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  nlua_trust(action, path);
 | 
			
		||||
 | 
			
		||||
theend:
 | 
			
		||||
  xfree(arg1);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2933,6 +2933,12 @@ module.cmds = {
 | 
			
		||||
    addr_type='ADDR_OTHER',
 | 
			
		||||
    func='ex_tag',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    command='trust',
 | 
			
		||||
    flags=bit.bor(EXTRA, FILE1, TRLBAR, LOCK_OK),
 | 
			
		||||
    addr_type='ADDR_NONE',
 | 
			
		||||
    func='ex_trust',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    command='try',
 | 
			
		||||
    flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK),
 | 
			
		||||
 
 | 
			
		||||
@@ -1827,6 +1827,7 @@ static bool skip_cmd(const exarg_T *eap)
 | 
			
		||||
    case CMD_throw:
 | 
			
		||||
    case CMD_tilde:
 | 
			
		||||
    case CMD_topleft:
 | 
			
		||||
    case CMD_trust:
 | 
			
		||||
    case CMD_unlet:
 | 
			
		||||
    case CMD_unlockvar:
 | 
			
		||||
    case CMD_verbose:
 | 
			
		||||
 
 | 
			
		||||
@@ -1016,6 +1016,8 @@ EXTERN char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group
 | 
			
		||||
EXTERN char e_undobang_cannot_redo_or_move_branch[]
 | 
			
		||||
INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch"));
 | 
			
		||||
 | 
			
		||||
EXTERN char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));
 | 
			
		||||
 | 
			
		||||
EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM"));
 | 
			
		||||
EXTERN char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP"));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2217,3 +2217,51 @@ char *nlua_read_secure(const char *path)
 | 
			
		||||
 | 
			
		||||
  return buf;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool nlua_trust(const char *action, const char *path)
 | 
			
		||||
{
 | 
			
		||||
  lua_State *const lstate = global_lstate;
 | 
			
		||||
  lua_getglobal(lstate, "vim");
 | 
			
		||||
  lua_getfield(lstate, -1, "secure");
 | 
			
		||||
  lua_getfield(lstate, -1, "trust");
 | 
			
		||||
 | 
			
		||||
  lua_newtable(lstate);
 | 
			
		||||
  lua_pushstring(lstate, "action");
 | 
			
		||||
  lua_pushstring(lstate, action);
 | 
			
		||||
  lua_settable(lstate, -3);
 | 
			
		||||
  if (path == NULL) {
 | 
			
		||||
    lua_pushstring(lstate, "bufnr");
 | 
			
		||||
    lua_pushnumber(lstate, 0);
 | 
			
		||||
    lua_settable(lstate, -3);
 | 
			
		||||
  } else {
 | 
			
		||||
    lua_pushstring(lstate, "path");
 | 
			
		||||
    lua_pushstring(lstate, path);
 | 
			
		||||
    lua_settable(lstate, -3);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (nlua_pcall(lstate, 1, 2)) {
 | 
			
		||||
    nlua_error(lstate, _("Error executing vim.secure.trust: %.*s"));
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool success = lua_toboolean(lstate, -2);
 | 
			
		||||
  const char *msg = lua_tostring(lstate, -1);
 | 
			
		||||
  if (msg != NULL) {
 | 
			
		||||
    if (success) {
 | 
			
		||||
      if (strcmp(action, "allow") == 0) {
 | 
			
		||||
        smsg("Allowed \"%s\" in trust database.", msg);
 | 
			
		||||
      } else if (strcmp(action, "deny") == 0) {
 | 
			
		||||
        smsg("Denied \"%s\" in trust database.", msg);
 | 
			
		||||
      } else if (strcmp(action, "remove") == 0) {
 | 
			
		||||
        smsg("Removed \"%s\" from trust database.", msg);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      semsg(e_trustfile, msg);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Pop return values, "vim" and "secure"
 | 
			
		||||
  lua_pop(lstate, 4);
 | 
			
		||||
 | 
			
		||||
  return success;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										176
									
								
								test/functional/ex_cmds/trust_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								test/functional/ex_cmds/trust_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,176 @@
 | 
			
		||||
local helpers = require('test.functional.helpers')(after_each)
 | 
			
		||||
local Screen = require('test.functional.ui.screen')
 | 
			
		||||
 | 
			
		||||
local eq = helpers.eq
 | 
			
		||||
local clear = helpers.clear
 | 
			
		||||
local command = helpers.command
 | 
			
		||||
local pathsep = helpers.get_pathsep()
 | 
			
		||||
local is_os = helpers.is_os
 | 
			
		||||
local funcs = helpers.funcs
 | 
			
		||||
 | 
			
		||||
describe(':trust', function()
 | 
			
		||||
  local xstate = 'Xstate'
 | 
			
		||||
 | 
			
		||||
  setup(function()
 | 
			
		||||
    helpers.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim'))
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
  teardown(function()
 | 
			
		||||
    helpers.rmdir(xstate)
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
  before_each(function()
 | 
			
		||||
    helpers.write_file('test_file', 'test')
 | 
			
		||||
    clear{env={XDG_STATE_HOME=xstate}}
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
  after_each(function()
 | 
			
		||||
    os.remove('test_file')
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
  it('trust then deny then remove a file using current buffer', function()
 | 
			
		||||
    local screen = Screen.new(80, 8)
 | 
			
		||||
    screen:attach()
 | 
			
		||||
    screen:set_default_attr_ids({
 | 
			
		||||
      [1] = {bold = true, foreground = Screen.colors.Blue1},
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    local cwd = funcs.getcwd()
 | 
			
		||||
    local hash = funcs.sha256(helpers.read_file('test_file'))
 | 
			
		||||
 | 
			
		||||
    command('edit test_file')
 | 
			
		||||
    command('trust')
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
      ^test                                                                            |
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      "]] .. cwd .. pathsep .. [[test_file" trusted.{MATCH:%s+}|
 | 
			
		||||
    ]])
 | 
			
		||||
    local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
    eq(string.format('%s %s', hash, cwd .. pathsep .. 'test_file'), vim.trim(trust))
 | 
			
		||||
 | 
			
		||||
    command('trust ++deny')
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
      ^test                                                                            |
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      "]] .. cwd .. pathsep .. [[test_file" denied.{MATCH:%s+}|
 | 
			
		||||
    ]])
 | 
			
		||||
    trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
    eq(string.format('! %s', cwd .. pathsep .. 'test_file'), vim.trim(trust))
 | 
			
		||||
 | 
			
		||||
    command('trust ++remove')
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
      ^test                                                                            |
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      "]] .. cwd .. pathsep .. [[test_file" removed.{MATCH:%s+}|
 | 
			
		||||
    ]])
 | 
			
		||||
    trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
    eq(string.format(''), vim.trim(trust))
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
  it('deny then trust then remove a file using current buffer', function()
 | 
			
		||||
    local screen = Screen.new(80, 8)
 | 
			
		||||
    screen:attach()
 | 
			
		||||
    screen:set_default_attr_ids({
 | 
			
		||||
      [1] = {bold = true, foreground = Screen.colors.Blue1},
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    local cwd = funcs.getcwd()
 | 
			
		||||
    local hash = funcs.sha256(helpers.read_file('test_file'))
 | 
			
		||||
 | 
			
		||||
    command('edit test_file')
 | 
			
		||||
    command('trust ++deny')
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
      ^test                                                                            |
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      "]] .. cwd .. pathsep .. [[test_file" denied.{MATCH:%s+}|
 | 
			
		||||
    ]])
 | 
			
		||||
    local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
    eq(string.format('! %s', cwd .. pathsep .. 'test_file'), vim.trim(trust))
 | 
			
		||||
 | 
			
		||||
    command('trust')
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
      ^test                                                                            |
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      "]] .. cwd .. pathsep .. [[test_file" trusted.{MATCH:%s+}|
 | 
			
		||||
    ]])
 | 
			
		||||
    trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
    eq(string.format('%s %s', hash, cwd .. pathsep .. 'test_file'), vim.trim(trust))
 | 
			
		||||
 | 
			
		||||
    command('trust ++remove')
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
      ^test                                                                            |
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      "]] .. cwd .. pathsep .. [[test_file" removed.{MATCH:%s+}|
 | 
			
		||||
    ]])
 | 
			
		||||
    trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
    eq(string.format(''), vim.trim(trust))
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
  it('deny then remove a file using file path', function()
 | 
			
		||||
    local screen = Screen.new(80, 8)
 | 
			
		||||
    screen:attach()
 | 
			
		||||
    screen:set_default_attr_ids({
 | 
			
		||||
      [1] = {bold = true, foreground = Screen.colors.Blue1},
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    local cwd = funcs.getcwd()
 | 
			
		||||
 | 
			
		||||
    command('trust ++deny test_file')
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
      ^                                                                                |
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      "]] .. cwd .. pathsep .. [[test_file" denied.{MATCH:%s+}|
 | 
			
		||||
    ]])
 | 
			
		||||
    local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
    eq(string.format('! %s', cwd .. pathsep .. 'test_file'), vim.trim(trust))
 | 
			
		||||
 | 
			
		||||
    command('trust ++remove test_file')
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
      ^                                                                                |
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      {1:~                                                                               }|
 | 
			
		||||
      "]] .. cwd .. pathsep .. [[test_file" removed.{MATCH:%s+}|
 | 
			
		||||
    ]])
 | 
			
		||||
    trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
    eq(string.format(''), vim.trim(trust))
 | 
			
		||||
  end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -168,4 +168,111 @@ describe('vim.secure', function()
 | 
			
		||||
      eq(false, curbufmeths.get_option('modifiable'))
 | 
			
		||||
    end)
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
  describe('trust()', function()
 | 
			
		||||
    local xstate = 'Xstate'
 | 
			
		||||
 | 
			
		||||
    setup(function()
 | 
			
		||||
      helpers.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim'))
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    teardown(function()
 | 
			
		||||
      helpers.rmdir(xstate)
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    before_each(function()
 | 
			
		||||
      helpers.write_file('test_file', 'test')
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    after_each(function()
 | 
			
		||||
      os.remove('test_file')
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it('returns error when passing both path and bufnr', function()
 | 
			
		||||
      eq('path and bufnr are mutually exclusive',
 | 
			
		||||
        pcall_err(exec_lua, [[vim.secure.trust({action='deny', bufnr=0, path='test_file'})]]))
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it('trust then deny then remove a file using bufnr', function()
 | 
			
		||||
      local cwd = funcs.getcwd()
 | 
			
		||||
      local hash = funcs.sha256(helpers.read_file('test_file'))
 | 
			
		||||
      local full_path = cwd .. pathsep .. 'test_file'
 | 
			
		||||
 | 
			
		||||
      command('edit test_file')
 | 
			
		||||
      eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]))
 | 
			
		||||
      local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
      eq(string.format('%s %s', hash, full_path), vim.trim(trust))
 | 
			
		||||
 | 
			
		||||
      eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', bufnr=0})}]]))
 | 
			
		||||
      trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
      eq(string.format('! %s', full_path), vim.trim(trust))
 | 
			
		||||
 | 
			
		||||
      eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', bufnr=0})}]]))
 | 
			
		||||
      trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
      eq('', vim.trim(trust))
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it('deny then trust then remove a file using bufnr', function()
 | 
			
		||||
      local cwd = funcs.getcwd()
 | 
			
		||||
      local hash = funcs.sha256(helpers.read_file('test_file'))
 | 
			
		||||
      local full_path = cwd .. pathsep .. 'test_file'
 | 
			
		||||
 | 
			
		||||
      command('edit test_file')
 | 
			
		||||
      eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', bufnr=0})}]]))
 | 
			
		||||
      local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
      eq(string.format('! %s', full_path), vim.trim(trust))
 | 
			
		||||
 | 
			
		||||
      eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]))
 | 
			
		||||
      trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
      eq(string.format('%s %s', hash, full_path), vim.trim(trust))
 | 
			
		||||
 | 
			
		||||
      eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', bufnr=0})}]]))
 | 
			
		||||
      trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
      eq('', vim.trim(trust))
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it('trust using bufnr then deny then remove a file using path', function()
 | 
			
		||||
      local cwd = funcs.getcwd()
 | 
			
		||||
      local hash = funcs.sha256(helpers.read_file('test_file'))
 | 
			
		||||
      local full_path = cwd .. pathsep .. 'test_file'
 | 
			
		||||
 | 
			
		||||
      command('edit test_file')
 | 
			
		||||
      eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]))
 | 
			
		||||
      local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
      eq(string.format('%s %s', hash, full_path), vim.trim(trust))
 | 
			
		||||
 | 
			
		||||
      eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', path='test_file'})}]]))
 | 
			
		||||
      trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
      eq(string.format('! %s', full_path), vim.trim(trust))
 | 
			
		||||
 | 
			
		||||
      eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', path='test_file'})}]]))
 | 
			
		||||
      trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
      eq('', vim.trim(trust))
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it('deny then trust then remove a file using bufnr', function()
 | 
			
		||||
      local cwd = funcs.getcwd()
 | 
			
		||||
      local hash = funcs.sha256(helpers.read_file('test_file'))
 | 
			
		||||
      local full_path = cwd .. pathsep .. 'test_file'
 | 
			
		||||
 | 
			
		||||
      command('edit test_file')
 | 
			
		||||
      eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', path='test_file'})}]]))
 | 
			
		||||
      local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
      eq(string.format('! %s', full_path), vim.trim(trust))
 | 
			
		||||
 | 
			
		||||
      eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]))
 | 
			
		||||
      trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
      eq(string.format('%s %s', hash, full_path), vim.trim(trust))
 | 
			
		||||
 | 
			
		||||
      eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', path='test_file'})}]]))
 | 
			
		||||
      trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
 | 
			
		||||
      eq('', vim.trim(trust))
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it('trust returns error when buffer not associated to file', function()
 | 
			
		||||
      command('new')
 | 
			
		||||
      eq({false, 'buffer is not associated with a file'},
 | 
			
		||||
        exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]))
 | 
			
		||||
    end)
 | 
			
		||||
  end)
 | 
			
		||||
end)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user