From 799cbfff855c72282a54646f3e5e1b71b0da223c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 20 May 2026 15:27:43 -0400 Subject: [PATCH] fix(vim.secure): read() command injection vulnerability #39918 Problem: Malicious filename can execute code because of ":" cmdline expansion. Solution: Use `fnameescape()`. fix https://github.com/neovim/neovim/issues/39914 --- runtime/lua/vim/secure.lua | 2 +- test/functional/lua/secure_spec.lua | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua index 63cf4a8776..de744ed008 100644 --- a/runtime/lua/vim/secure.lua +++ b/runtime/lua/vim/secure.lua @@ -151,7 +151,7 @@ function M.read(path) return nil elseif result == 2 then -- View - vim.cmd('sview ' .. fullpath) + vim.cmd(('sview %s'):format(vim.fn.fnameescape(fullpath))) return nil elseif result == 3 then -- Deny diff --git a/test/functional/lua/secure_spec.lua b/test/functional/lua/secure_spec.lua index 59d3a3f9d3..7d0f989eb2 100644 --- a/test/functional/lua/secure_spec.lua +++ b/test/functional/lua/secure_spec.lua @@ -268,6 +268,34 @@ describe('vim.secure', function() -- Trust database is not updated eq(nil, read_file(vim.fs.joinpath(stdpath('state'), 'trust'))) end) + + it('(v)iew action does not execute malicious filename #39914', function() + if t.skip(t.is_os('win'), 'N/A: filename cannot have "|" char') then + return + end + + local evil = 'Xfile|let g:secure_poc=42' + t.write_file(evil, 'pwned\n') + finally(function() + os.remove(evil) + end) + + eq( + nil, + exec_lua(function(path) + vim.fn.confirm = function() + return 2 -- View + end + return vim.secure.read(path) + end, evil) + ) + + -- Malicious injected `:let` did NOT execute. + eq(0, fn.exists('g:secure_poc')) + -- The file is opened in a [RO] split with its literal name. + eq(true, api.nvim_get_option_value('readonly', {})) + eq(evil, vim.fs.basename(api.nvim_buf_get_name(0))) + end) end) describe('trust()', function()