From f1922e78a1df1b1d32779769432fb5586edf5fbb Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sat, 5 Nov 2022 13:37:05 -0600 Subject: feat: add vim.secure.read() This function accepts a path to a file and prompts the user if the file is trusted. If the user confirms that the file is trusted, the contents of the file are returned. The user's decision is stored in a trust database at $XDG_STATE_HOME/nvim/trust. When this function is invoked with a path that is already marked as trusted in the trust database, the user is not prompted for a response. --- runtime/doc/lua.txt | 16 +++++++ runtime/doc/news.txt | 3 ++ runtime/lua/vim/_editor.lua | 1 + runtime/lua/vim/secure.lua | 106 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+) create mode 100644 runtime/lua/vim/secure.lua (limited to 'runtime') diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index cab2f49d94..c5ede97725 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -2354,4 +2354,20 @@ parents({start}) *vim.fs.parents()* Return: ~ (function) Iterator + +============================================================================== +Lua module: secure *lua-secure* + +read({path}) *vim.secure.read()* + 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. + + Parameters: ~ + • {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. + 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 d339df8479..2aa4bea73b 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.read()| reads a file and prompts the user if it should be + trusted and, if so, returns the file's contents. + • When using Nvim inside tmux 3.2 or later, the default clipboard provider will now copy to the system clipboard. |provider-clipboard| diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 0013f38d89..ad4dc20efb 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -36,6 +36,7 @@ for k, v in pairs({ ui = true, health = true, fs = true, + secure = true, }) do vim._submodules[k] = v end diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua new file mode 100644 index 0000000000..341ff8df05 --- /dev/null +++ b/runtime/lua/vim/secure.lua @@ -0,0 +1,106 @@ +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. +--- +---@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 = {} + 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 + + if trust[fullpath] == '!' then + -- File is denied + return nil + end + + local contents + do + local f = io.open(fullpath, 'r') + if not f then + return nil + end + contents = f:read('*a') + f:close() + end + + local hash = vim.fn.sha256(contents) + if trust[fullpath] == hash then + -- File already exists in trust database + return contents + end + + -- File either does not exist in trust database or the hash does not match + local choice = vim.fn.confirm( + string.format('%s is not trusted.', fullpath), + '&ignore\n&view\n&deny\n&allow', + 1 + ) + + if choice == 0 or choice == 1 then + -- Cancelled or ignored + return nil + elseif choice == 2 then + -- View + vim.cmd('new') + local buf = vim.api.nvim_get_current_buf() + local lines = vim.split(string.gsub(contents, '\n$', ''), '\n') + vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) + vim.bo[buf].bufhidden = 'hide' + vim.bo[buf].buftype = 'nofile' + vim.bo[buf].swapfile = false + vim.bo[buf].modeline = false + vim.bo[buf].buflisted = false + vim.bo[buf].readonly = true + vim.bo[buf].modifiable = false + return nil + elseif choice == 3 then + -- Deny + trust[fullpath] = '!' + contents = nil + elseif choice == 4 then + -- Allow + 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 + + return contents +end + +return M -- cgit From 294910a1ffd11bea0081c2b92632628ef0462eb1 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sat, 5 Nov 2022 19:30:48 -0600 Subject: feat(exrc): use vim.secure.read() for 'exrc' option --- runtime/doc/deprecated.txt | 4 ---- runtime/doc/news.txt | 2 ++ runtime/doc/options.txt | 14 ++++++++++++++ runtime/doc/vim_diff.txt | 2 ++ 4 files changed, 18 insertions(+), 4 deletions(-) (limited to 'runtime') diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index 5e6bc957a1..401ac87d90 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -128,10 +128,6 @@ NORMAL COMMANDS OPTIONS - *cpo-<* *:menu-* *:menu-special* *:map-* *:map-special* `<>` notation is always enabled. -- *'exrc'* *'ex'* Security risk: downloaded files could include - a malicious .nvimrc or .exrc file. See 'secure'. - Recommended alternative: define an autocommand in your - |vimrc| to set options for a matching directory. - 'gdefault' Enables the |:substitute| flag 'g' by default. - *'fe'* 'fenc'+'enc' before Vim 6.0; no longer used. - *'highlight'* *'hl'* Names of builtin |highlight-groups| cannot be changed. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 2aa4bea73b..42a5d7e7ee 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -60,6 +60,8 @@ CHANGED FEATURES *news-changes* The following changes to existing APIs or features add new behavior. +• 'exrc' is no longer marked deprecated. + ============================================================================== REMOVED FEATURES *news-removed* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index fd76f11046..6c1edb9c69 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2264,6 +2264,20 @@ A jump table for the options with a short description can be found at |Q_op|. This option is reset when the 'paste' option is set and restored when the 'paste' option is reset. + *'exrc'* *'ex'* *'noexrc'* *'noex'* +'exrc' 'ex' boolean (default off) + global + Enables the reading of .nvimrc and .exrc files in the current + directory. + + The file is only sourced if the user indicates the file is trusted. If + it is, the SHA256 hash of the file contents and the full path of the + file are persisted to a trust database. The user is only prompted + again if the file contents change. See |vim.secure.read()|. + + This option cannot be set from a |modeline| or in the |sandbox|, for + security reasons. + *'fileencoding'* *'fenc'* *E213* 'fileencoding' 'fenc' string (default: "") local to buffer diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index fe6c28c809..357024aca0 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -417,6 +417,8 @@ Options: 'jumpoptions' "view" tries to restore the |mark-view| when moving through the |jumplist|, |changelist|, |alternate-file| or using |mark-motions|. 'shortmess' the "F" flag does not affect output from autocommands + 'exrc' searches for ".nvimrc" or ".exrc" files. The user is prompted whether + to trust the file. Shell: Shell output (|:!|, |:make|, …) is always routed through the UI, so it -- cgit From 6d9c3d903ecee2d1d21d0a0806f1a2bebe628e8e Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sun, 6 Nov 2022 19:44:30 -0700 Subject: refactor: deprecate 'secure' option Now that 'exrc' files must be explicitly marked trusted there is no need to constrain what can be done in them. --- runtime/doc/options.txt | 13 ------------- runtime/doc/vim_diff.txt | 3 +++ 2 files changed, 3 insertions(+), 13 deletions(-) (limited to 'runtime') diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 6c1edb9c69..0895d980f5 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -5129,19 +5129,6 @@ A jump table for the options with a short description can be found at |Q_op|. two letters (See |object-motions|). The default makes a section start at the nroff macros ".SH", ".NH", ".H", ".HU", ".nh" and ".sh". - *'secure'* *'nosecure'* *E523* -'secure' boolean (default off) - global - When on, ":autocmd", shell and write commands are not allowed in - ".nvimrc" and ".exrc" in the current directory and map commands are - displayed. Switch it off only if you know that you will not run into - problems, or when the 'exrc' option is off. On Unix this option is - only used if the ".nvimrc" or ".exrc" is not owned by you. This can be - dangerous if the systems allows users to do a "chown". You better set - 'secure' at the end of your |init.vim| then. - This option cannot be set from a |modeline| or in the |sandbox|, for - security reasons. - *'selection'* *'sel'* 'selection' 'sel' string (default "inclusive") global diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 357024aca0..b5222c9ddd 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -641,6 +641,9 @@ Options: *'prompt'* *'noprompt'* *'remap'* *'noremap'* *'restorescreen'* *'rs'* *'norestorescreen'* *'nors'* + *'secure'* + Everything is allowed in 'exrc' files since they must be explicitly marked + trusted. *'shelltype'* *'shortname'* *'sn'* *'noshortname'* *'nosn'* *'swapsync'* *'sws'* -- cgit