summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2026-04-24 07:37:21 -0400
committerGitHub <noreply@github.com>2026-04-24 07:37:21 -0400
commit5c88492a13f80d23e7b0d48d036ed60a7d24be0d (patch)
tree4a0b030e0641297042dc03f533413bd01ee977a8
parentc0e358f7e8ac92dbaba771a74d17d1754e3c6320 (diff)
fix(trust): always use "/" slashes in filepaths #39355
Problem: We should not use "\" (backslashes) except where absolutely required. See references in https://github.com/neovim/neovim/pull/37729 Solution: There is no reason to use "\" slashes in the trust db, so don't.
-rw-r--r--runtime/doc/news.txt2
-rw-r--r--runtime/lua/vim/secure.lua2
-rw-r--r--test/functional/core/startup_spec.lua4
-rw-r--r--test/functional/ex_cmds/trust_spec.lua19
-rw-r--r--test/functional/lua/secure_spec.lua41
5 files changed, 29 insertions, 39 deletions
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 6f70a00abe..977cc1fd91 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -74,6 +74,8 @@ DIAGNOSTICS
EDITOR
+• On Windows, the |trust| db now stores paths with "/" slashes. This means the
+ trust store will be reset.
• `stdpath("log")` moved to `stdpath("state")/logs`.
EVENTS
diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua
index 4a89139679..63cf4a8776 100644
--- a/runtime/lua/vim/secure.lua
+++ b/runtime/lua/vim/secure.lua
@@ -110,6 +110,7 @@ function M.read(path)
if not fullpath then
return nil
end
+ fullpath = vim.fs.normalize(fullpath) -- Ensure "/" slashes, even on Windows.
local trust = read_trust()
@@ -218,6 +219,7 @@ function M.trust(opts)
if not fullpath then
return false, string.format('invalid path: %s', path)
end
+ fullpath = vim.fs.normalize(fullpath) -- Ensure "/" slashes, even on Windows.
local trust = read_trust()
diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua
index 55af3bdc4c..c627a69f33 100644
--- a/test/functional/core/startup_spec.lua
+++ b/test/functional/core/startup_spec.lua
@@ -1476,11 +1476,11 @@ describe('user config init', function()
-- trust .exrc
feed(':trust<CR>')
- screen:expect({ any = 'Allowed in trust database: ".*' .. pathsep .. '%.exrc"' })
+ screen:expect({ any = 'Allowed in trust database: ".*/%.exrc"' })
feed(':q<CR>')
-- trust .nvim.lua
feed(':trust<CR>')
- screen:expect({ any = 'Allowed in trust database: ".*' .. pathsep .. '%.nvim%.lua"' })
+ screen:expect({ any = 'Allowed in trust database: ".*/%.nvim%.lua"' })
feed(':q<CR>')
-- no exrc file is executed
feed(':echo g:exrc_count<CR>')
diff --git a/test/functional/ex_cmds/trust_spec.lua b/test/functional/ex_cmds/trust_spec.lua
index 627d19b115..efb82d6cb0 100644
--- a/test/functional/ex_cmds/trust_spec.lua
+++ b/test/functional/ex_cmds/trust_spec.lua
@@ -32,13 +32,6 @@ describe(':trust', function()
return s:format(test_file)
end
- --- Joins path components using the OS-native separator.
- --- Unlike vim.fs.joinpath (which normalizes to "/"), this preserves "\" on Windows
- --- to match paths stored in the trust database.
- local function osjoin(...)
- return (table.concat({ ... }, n.get_pathsep()))
- end
-
local function assert_trust_entry(expected)
local trust = t.read_file(vim.fs.joinpath(fn.stdpath('state'), 'trust'))
eq(expected, vim.trim(trust))
@@ -56,10 +49,10 @@ describe(':trust', function()
command(fmt('edit %s'))
matches(fmt('^Allowed in trust database%%: ".*%s"$'), exec_capture('trust'))
- assert_trust_entry(('%s %s'):format(hash, osjoin(cwd, test_file)))
+ assert_trust_entry(('%s %s'):format(hash, vim.fs.joinpath(cwd, test_file)))
matches(fmt('^Denied in trust database%%: ".*%s"$'), exec_capture('trust ++deny'))
- assert_trust_entry(('! %s'):format(osjoin(cwd, test_file)))
+ assert_trust_entry(('! %s'):format(vim.fs.joinpath(cwd, test_file)))
matches(fmt('^Removed from trust database%%: ".*%s"$'), exec_capture('trust ++remove'))
assert_trust_entry('')
@@ -71,7 +64,7 @@ describe(':trust', function()
command('edit ' .. empty_file)
matches('^Allowed in trust database%: ".*' .. empty_file .. '"$', exec_capture('trust'))
- assert_trust_entry(('%s %s'):format(hash, osjoin(cwd, empty_file)))
+ assert_trust_entry(('%s %s'):format(hash, vim.fs.joinpath(cwd, empty_file)))
end)
it('deny then trust then remove a file using current buffer', function()
@@ -80,10 +73,10 @@ describe(':trust', function()
command(fmt('edit %s'))
matches(fmt('^Denied in trust database%%: ".*%s"$'), exec_capture('trust ++deny'))
- assert_trust_entry(('! %s'):format(osjoin(cwd, test_file)))
+ assert_trust_entry(('! %s'):format(vim.fs.joinpath(cwd, test_file)))
matches(fmt('^Allowed in trust database%%: ".*%s"$'), exec_capture('trust'))
- assert_trust_entry(('%s %s'):format(hash, osjoin(cwd, test_file)))
+ assert_trust_entry(('%s %s'):format(hash, vim.fs.joinpath(cwd, test_file)))
matches(fmt('^Removed from trust database%%: ".*%s"$'), exec_capture('trust ++remove'))
assert_trust_entry('')
@@ -93,7 +86,7 @@ describe(':trust', function()
local cwd = fn.getcwd()
matches(fmt('^Denied in trust database%%: ".*%s"$'), exec_capture(fmt('trust ++deny %s')))
- assert_trust_entry(('! %s'):format(osjoin(cwd, test_file)))
+ assert_trust_entry(('! %s'):format(vim.fs.joinpath(cwd, test_file)))
matches(fmt('^Removed from trust database%%: ".*%s"$'), exec_capture(fmt('trust ++remove %s')))
assert_trust_entry('')
diff --git a/test/functional/lua/secure_spec.lua b/test/functional/lua/secure_spec.lua
index b8580d77a5..59d3a3f9d3 100644
--- a/test/functional/lua/secure_spec.lua
+++ b/test/functional/lua/secure_spec.lua
@@ -17,13 +17,6 @@ local matches = t.matches
local read_file = t.read_file
describe('vim.secure', function()
- --- Joins path components using the OS-native separator.
- --- Unlike vim.fs.joinpath (which normalizes to "/"), this preserves "\" on Windows
- --- to match paths stored in the trust database.
- local function osjoin(...)
- return (table.concat({ ... }, n.get_pathsep()))
- end
-
local function assert_trust_entry(expected)
local trust = assert(read_file(vim.fs.joinpath(stdpath('state'), 'trust')))
eq(expected, vim.trim(trust))
@@ -67,7 +60,7 @@ describe('vim.secure', function()
local cwd = fn.getcwd()
local msg = 'exrc: Found untrusted code. To enable it, choose (v)iew then run `:trust`:'
- local path = osjoin(cwd, 'Xfile')
+ local path = vim.fs.joinpath(cwd, 'Xfile')
-- Need to use feed_command instead of exec_lua because of the confirmation prompt
feed_command([[lua vim.secure.read('Xfile')]])
@@ -87,7 +80,7 @@ describe('vim.secure', function()
{MATCH: +}|
]])
- assert_trust_entry(('! %s'):format(osjoin(cwd, 'Xfile')))
+ assert_trust_entry(('! %s'):format(vim.fs.joinpath(cwd, 'Xfile')))
eq(vim.NIL, exec_lua([[return vim.secure.read('Xfile')]]))
os.remove(vim.fs.joinpath(stdpath('state'), 'trust'))
@@ -107,17 +100,17 @@ describe('vim.secure', function()
screen:expect([[
^let g:foobar = 42{MATCH: +}|
{1:~{MATCH: +}}|*2
- {2:]] .. osjoin(fn.fnamemodify(cwd, ':~'), 'Xfile') .. [[ [RO]{MATCH: +}}|
+ {2:]] .. vim.fs.joinpath(fn.fnamemodify(cwd, ':~'), 'Xfile') .. [[ [RO]{MATCH: +}}|
{MATCH: +}|
{1:~{MATCH: +}}|
{4:[No Name]{MATCH: +}}|
- Allowed in trust database: "]] .. osjoin(cwd, 'Xfile') .. [["{MATCH: +}|
+ Allowed in trust database: "]] .. vim.fs.joinpath(cwd, 'Xfile') .. [["{MATCH: +}|
]])
-- close the split for the next test below.
feed(':q<CR>')
local hash = fn.sha256(assert(read_file('Xfile')))
- assert_trust_entry(('%s %s'):format(hash, osjoin(cwd, 'Xfile')))
+ assert_trust_entry(('%s %s'):format(hash, vim.fs.joinpath(cwd, 'Xfile')))
eq('let g:foobar = 42\n', exec_lua([[return vim.secure.read('Xfile')]]))
os.remove(vim.fs.joinpath(stdpath('state'), 'trust'))
@@ -156,7 +149,7 @@ describe('vim.secure', function()
screen:expect([[
^let g:foobar = 42{MATCH: +}|
{1:~{MATCH: +}}|*2
- {2:]] .. osjoin(fn.fnamemodify(cwd, ':~'), 'Xfile') .. [[ [RO]{MATCH: +}}|
+ {2:]] .. vim.fs.joinpath(fn.fnamemodify(cwd, ':~'), 'Xfile') .. [[ [RO]{MATCH: +}}|
{MATCH: +}|
{1:~{MATCH: +}}|
{4:[No Name]{MATCH: +}}|
@@ -182,7 +175,7 @@ describe('vim.secure', function()
local cwd = fn.getcwd()
local msg =
'exrc: Found untrusted code. DIRECTORY trust is decided only by name, not contents:'
- local path = osjoin(cwd, 'Xdir')
+ local path = vim.fs.joinpath(cwd, 'Xdir')
-- Need to use feed_command instead of exec_lua because of the confirmation prompt
feed_command([[lua vim.secure.read('Xdir')]])
@@ -202,7 +195,7 @@ describe('vim.secure', function()
{MATCH: +}|
]])
- assert_trust_entry(('! %s'):format(osjoin(cwd, 'Xdir')))
+ assert_trust_entry(('! %s'):format(vim.fs.joinpath(cwd, 'Xdir')))
eq(vim.NIL, exec_lua([[return vim.secure.read('Xdir')]]))
os.remove(vim.fs.joinpath(stdpath('state'), 'trust'))
@@ -226,7 +219,7 @@ describe('vim.secure', function()
-- Directories aren't hashed in the trust database, instead a slug ("directory") is stored
-- instead.
- assert_trust_entry(('directory %s'):format(osjoin(cwd, 'Xdir')))
+ assert_trust_entry(('directory %s'):format(vim.fs.joinpath(cwd, 'Xdir')))
eq(true, exec_lua([[return vim.secure.read('Xdir')]]))
os.remove(vim.fs.joinpath(stdpath('state'), 'trust'))
@@ -265,7 +258,7 @@ describe('vim.secure', function()
screen:expect([[
^{MATCH: +}|
{1:~{MATCH: +}}|*2
- {2:]] .. osjoin(fn.fnamemodify(cwd, ':~'), 'Xdir') .. [[ [RO]{MATCH: +}}|
+ {2:]] .. vim.fs.joinpath(fn.fnamemodify(cwd, ':~'), 'Xdir') .. [[ [RO]{MATCH: +}}|
{MATCH: +}|
{1:~{MATCH: +}}|
{4:[No Name]{MATCH: +}}|
@@ -318,7 +311,7 @@ describe('vim.secure', function()
it('trust then deny then remove a file using bufnr', function()
local cwd = fn.getcwd()
local hash = fn.sha256(assert(read_file(test_file)))
- local full_path = osjoin(cwd, test_file)
+ local full_path = vim.fs.joinpath(cwd, test_file)
command('edit ' .. test_file)
eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]))
@@ -334,7 +327,7 @@ describe('vim.secure', function()
it('trust an empty file using bufnr', function()
local cwd = fn.getcwd()
local hash = fn.sha256(assert(read_file(empty_file)))
- local full_path = osjoin(cwd, empty_file)
+ local full_path = vim.fs.joinpath(cwd, empty_file)
command('edit ' .. empty_file)
eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]))
@@ -344,7 +337,7 @@ describe('vim.secure', function()
it('deny then trust then remove a file using bufnr', function()
local cwd = fn.getcwd()
local hash = fn.sha256(assert(read_file(test_file)))
- local full_path = osjoin(cwd, test_file)
+ local full_path = vim.fs.joinpath(cwd, test_file)
command('edit ' .. test_file)
eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='deny', bufnr=0})}]]))
@@ -360,7 +353,7 @@ describe('vim.secure', function()
it('trust using bufnr then deny then remove a file using path', function()
local cwd = fn.getcwd()
local hash = fn.sha256(assert(read_file(test_file)))
- local full_path = osjoin(cwd, test_file)
+ local full_path = vim.fs.joinpath(cwd, test_file)
command('edit ' .. test_file)
eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]))
@@ -382,7 +375,7 @@ describe('vim.secure', function()
it('trust then deny then remove a file using path', function()
local cwd = fn.getcwd()
local hash = fn.sha256(assert(read_file(test_file)))
- local full_path = osjoin(cwd, test_file)
+ local full_path = vim.fs.joinpath(cwd, test_file)
eq(
{ true, full_path },
@@ -406,7 +399,7 @@ describe('vim.secure', function()
it('deny then trust then remove a file using bufnr', function()
local cwd = fn.getcwd()
local hash = fn.sha256(assert(read_file(test_file)))
- local full_path = osjoin(cwd, test_file)
+ local full_path = vim.fs.joinpath(cwd, test_file)
command('edit ' .. test_file)
eq(
@@ -435,7 +428,7 @@ describe('vim.secure', function()
it('trust then deny then remove a directory using bufnr', function()
local cwd = fn.getcwd()
- local full_path = osjoin(cwd, test_dir)
+ local full_path = vim.fs.joinpath(cwd, test_dir)
command('edit ' .. test_dir)
eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]))