secure_spec.lua (15585B)
1 local t = require('test.testutil') 2 local n = require('test.functional.testnvim')() 3 local Screen = require('test.functional.ui.screen') 4 5 local eq = t.eq 6 local clear = n.clear 7 local command = n.command 8 local pathsep = n.get_pathsep() 9 local is_os = t.is_os 10 local api = n.api 11 local exec_lua = n.exec_lua 12 local feed_command = n.feed_command 13 local feed = n.feed 14 local fn = n.fn 15 local stdpath = fn.stdpath 16 local pcall_err = t.pcall_err 17 local matches = t.matches 18 local read_file = t.read_file 19 20 describe('vim.secure', function() 21 describe('read()', function() 22 local xstate = 'Xstate_lua_secure' 23 local screen ---@type test.functional.ui.screen 24 25 before_each(function() 26 clear { env = { XDG_STATE_HOME = xstate } } 27 n.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim')) 28 29 t.mkdir('Xdir') 30 t.mkdir('Xdir/Xsubdir') 31 t.write_file('Xdir/Xfile.txt', [[hello, world]]) 32 33 t.write_file( 34 'Xfile', 35 [[ 36 let g:foobar = 42 37 ]] 38 ) 39 screen = Screen.new(500, 8) 40 end) 41 42 after_each(function() 43 screen:detach() 44 os.remove('Xfile') 45 n.rmdir('Xdir') 46 n.rmdir(xstate) 47 end) 48 49 it('regular file', function() 50 screen:set_default_attr_ids({ 51 [1] = { bold = true, foreground = Screen.colors.Blue1 }, 52 [2] = { bold = true, reverse = true }, 53 [3] = { bold = true, foreground = Screen.colors.SeaGreen }, 54 [4] = { reverse = true }, 55 }) 56 57 local cwd = fn.getcwd() 58 local msg = 'exrc: Found untrusted code. To enable it, choose (v)iew then run `:trust`:' 59 local path = ('%s%sXfile'):format(cwd, pathsep) 60 61 -- Need to use feed_command instead of exec_lua because of the confirmation prompt 62 feed_command([[lua vim.secure.read('Xfile')]]) 63 screen:expect([[ 64 {MATCH: +}| 65 {1:~{MATCH: +}}|*2 66 {2:{MATCH: +}}| 67 :lua vim.secure.read('Xfile'){MATCH: +}| 68 {3:]] .. msg .. [[}{MATCH: *}| 69 {3:]] .. path .. [[}{MATCH: *}| 70 {3:[i]gnore, (v)iew, (d)eny: }^{MATCH: +}| 71 ]]) 72 feed('d') 73 screen:expect([[ 74 ^{MATCH: +}| 75 {1:~{MATCH: +}}|*6 76 {MATCH: +}| 77 ]]) 78 79 local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 80 eq(string.format('! %s', cwd .. pathsep .. 'Xfile'), vim.trim(trust)) 81 eq(vim.NIL, exec_lua([[return vim.secure.read('Xfile')]])) 82 83 os.remove(stdpath('state') .. pathsep .. 'trust') 84 85 feed_command([[lua vim.secure.read('Xfile')]]) 86 screen:expect([[ 87 {MATCH: +}| 88 {1:~{MATCH: +}}|*2 89 {2:{MATCH: +}}| 90 :lua vim.secure.read('Xfile'){MATCH: +}| 91 {3:]] .. msg .. [[}{MATCH: *}| 92 {3:]] .. path .. [[}{MATCH: *}| 93 {3:[i]gnore, (v)iew, (d)eny: }^{MATCH: +}| 94 ]]) 95 feed('v') 96 feed(':trust<CR>') 97 screen:expect([[ 98 ^let g:foobar = 42{MATCH: +}| 99 {1:~{MATCH: +}}|*2 100 {2:]] .. fn.fnamemodify(cwd, ':~') .. pathsep .. [[Xfile [RO]{MATCH: +}}| 101 {MATCH: +}| 102 {1:~{MATCH: +}}| 103 {4:[No Name]{MATCH: +}}| 104 Allowed in trust database: "]] .. cwd .. pathsep .. [[Xfile"{MATCH: +}| 105 ]]) 106 -- close the split for the next test below. 107 feed(':q<CR>') 108 109 local hash = fn.sha256(assert(read_file('Xfile'))) 110 trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 111 eq(string.format('%s %s', hash, cwd .. pathsep .. 'Xfile'), vim.trim(trust)) 112 eq('let g:foobar = 42\n', exec_lua([[return vim.secure.read('Xfile')]])) 113 114 os.remove(stdpath('state') .. pathsep .. 'trust') 115 116 feed_command([[lua vim.secure.read('Xfile')]]) 117 screen:expect([[ 118 {MATCH: +}| 119 {1:~{MATCH: +}}|*2 120 {2:{MATCH: +}}| 121 :lua vim.secure.read('Xfile'){MATCH: +}| 122 {3:]] .. msg .. [[}{MATCH: *}| 123 {3:]] .. path .. [[}{MATCH: *}| 124 {3:[i]gnore, (v)iew, (d)eny: }^{MATCH: +}| 125 ]]) 126 feed('i') 127 screen:expect([[ 128 ^{MATCH: +}| 129 {1:~{MATCH: +}}|*6 130 {MATCH: +}| 131 ]]) 132 133 -- Trust database is not updated 134 eq(nil, read_file(stdpath('state') .. pathsep .. 'trust')) 135 136 feed_command([[lua vim.secure.read('Xfile')]]) 137 screen:expect([[ 138 {MATCH: +}| 139 {1:~{MATCH: +}}|*2 140 {2:{MATCH: +}}| 141 :lua vim.secure.read('Xfile'){MATCH: +}| 142 {3:]] .. msg .. [[}{MATCH: +}| 143 {3:]] .. path .. [[}{MATCH: *}| 144 {3:[i]gnore, (v)iew, (d)eny: }^{MATCH: +}| 145 ]]) 146 feed('v') 147 screen:expect([[ 148 ^let g:foobar = 42{MATCH: +}| 149 {1:~{MATCH: +}}|*2 150 {2:]] .. fn.fnamemodify(cwd, ':~') .. pathsep .. [[Xfile [RO]{MATCH: +}}| 151 {MATCH: +}| 152 {1:~{MATCH: +}}| 153 {4:[No Name]{MATCH: +}}| 154 {MATCH: +}| 155 ]]) 156 157 -- Trust database is not updated 158 eq(nil, read_file(stdpath('state') .. pathsep .. 'trust')) 159 160 -- Cannot write file 161 pcall_err(command, 'write') 162 eq(true, api.nvim_get_option_value('readonly', {})) 163 end) 164 165 it('directory', function() 166 screen:set_default_attr_ids({ 167 [1] = { bold = true, foreground = Screen.colors.Blue1 }, 168 [2] = { bold = true, reverse = true }, 169 [3] = { bold = true, foreground = Screen.colors.SeaGreen }, 170 [4] = { reverse = true }, 171 }) 172 173 local cwd = fn.getcwd() 174 local msg = 175 'exrc: Found untrusted code. DIRECTORY trust is decided only by name, not contents:' 176 local path = ('%s%sXdir'):format(cwd, pathsep) 177 178 -- Need to use feed_command instead of exec_lua because of the confirmation prompt 179 feed_command([[lua vim.secure.read('Xdir')]]) 180 screen:expect([[ 181 {MATCH: +}| 182 {1:~{MATCH: +}}|*2 183 {2:{MATCH: +}}| 184 :lua vim.secure.read('Xdir'){MATCH: +}| 185 {3:]] .. msg .. [[}{MATCH: +}| 186 {3:]] .. path .. [[}{MATCH: +}| 187 {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}| 188 ]]) 189 feed('d') 190 screen:expect([[ 191 ^{MATCH: +}| 192 {1:~{MATCH: +}}|*6 193 {MATCH: +}| 194 ]]) 195 196 local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 197 eq(string.format('! %s', cwd .. pathsep .. 'Xdir'), vim.trim(trust)) 198 eq(vim.NIL, exec_lua([[return vim.secure.read('Xdir')]])) 199 200 os.remove(stdpath('state') .. pathsep .. 'trust') 201 202 feed_command([[lua vim.secure.read('Xdir')]]) 203 screen:expect([[ 204 {MATCH: +}| 205 {1:~{MATCH: +}}|*2 206 {2:{MATCH: +}}| 207 :lua vim.secure.read('Xdir'){MATCH: +}| 208 {3:]] .. msg .. [[}{MATCH: +}| 209 {3:]] .. path .. [[}{MATCH: +}| 210 {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}| 211 ]]) 212 feed('a') 213 screen:expect([[ 214 ^{MATCH: +}| 215 {1:~{MATCH: +}}|*6 216 {MATCH: +}| 217 ]]) 218 219 -- Directories aren't hashed in the trust database, instead a slug ("directory") is stored 220 -- instead. 221 local expected_hash = 'directory' 222 trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 223 eq(string.format('%s %s', expected_hash, cwd .. pathsep .. 'Xdir'), vim.trim(trust)) 224 eq(true, exec_lua([[return vim.secure.read('Xdir')]])) 225 226 os.remove(stdpath('state') .. pathsep .. 'trust') 227 228 feed_command([[lua vim.secure.read('Xdir')]]) 229 screen:expect([[ 230 {MATCH: +}| 231 {1:~{MATCH: +}}|*2 232 {2:{MATCH: +}}| 233 :lua vim.secure.read('Xdir'){MATCH: +}| 234 {3:]] .. msg .. [[}{MATCH: +}| 235 {3:]] .. path .. [[}{MATCH: +}| 236 {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}| 237 ]]) 238 feed('i') 239 screen:expect([[ 240 ^{MATCH: +}| 241 {1:~{MATCH: +}}|*6 242 {MATCH: +}| 243 ]]) 244 245 -- Trust database is not updated 246 eq(nil, read_file(stdpath('state') .. pathsep .. 'trust')) 247 248 feed_command([[lua vim.secure.read('Xdir')]]) 249 screen:expect([[ 250 {MATCH: +}| 251 {1:~{MATCH: +}}|*2 252 {2:{MATCH: +}}| 253 :lua vim.secure.read('Xdir'){MATCH: +}| 254 {3:]] .. msg .. [[}{MATCH: +}| 255 {3:]] .. path .. [[}{MATCH: +}| 256 {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}| 257 ]]) 258 feed('v') 259 screen:expect([[ 260 ^{MATCH: +}| 261 {1:~{MATCH: +}}|*2 262 {2:]] .. fn.fnamemodify(cwd, ':~') .. pathsep .. [[Xdir [RO]{MATCH: +}}| 263 {MATCH: +}| 264 {1:~{MATCH: +}}| 265 {4:[No Name]{MATCH: +}}| 266 {MATCH: +}| 267 ]]) 268 269 -- Trust database is not updated 270 eq(nil, read_file(stdpath('state') .. pathsep .. 'trust')) 271 end) 272 end) 273 274 describe('trust()', function() 275 local xstate = 'Xstate_lua_secure' 276 local test_file = 'Xtest_functional_lua_secure' 277 local test_dir = 'Xtest_functional_lua_secure_dir' 278 279 setup(function() 280 clear { env = { XDG_STATE_HOME = xstate } } 281 end) 282 283 before_each(function() 284 n.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim')) 285 t.write_file(test_file, 'test') 286 t.mkdir(test_dir) 287 end) 288 289 after_each(function() 290 os.remove(test_file) 291 n.rmdir(test_dir) 292 n.rmdir(xstate) 293 end) 294 295 it('returns error when passing both path and bufnr', function() 296 matches( 297 '"path" and "bufnr" are mutually exclusive', 298 pcall_err(exec_lua, [[vim.secure.trust({action='deny', bufnr=0, path=...})]], test_file) 299 ) 300 end) 301 302 it('returns error when passing neither path or bufnr', function() 303 matches( 304 'one of "path" or "bufnr" is required', 305 pcall_err(exec_lua, [[vim.secure.trust({action='deny'})]]) 306 ) 307 end) 308 309 it('trust then deny then remove a file using bufnr', function() 310 local cwd = fn.getcwd() 311 local hash = fn.sha256(assert(read_file(test_file))) 312 local full_path = cwd .. pathsep .. test_file 313 314 command('edit ' .. test_file) 315 eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) 316 local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 317 eq(string.format('%s %s', hash, full_path), vim.trim(trust)) 318 319 eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='deny', bufnr=0})}]])) 320 trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 321 eq(string.format('! %s', full_path), vim.trim(trust)) 322 323 eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='remove', bufnr=0})}]])) 324 trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 325 eq('', vim.trim(trust)) 326 end) 327 328 it('deny then trust then remove a file using bufnr', function() 329 local cwd = fn.getcwd() 330 local hash = fn.sha256(assert(read_file(test_file))) 331 local full_path = cwd .. pathsep .. test_file 332 333 command('edit ' .. test_file) 334 eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='deny', bufnr=0})}]])) 335 local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 336 eq(string.format('! %s', full_path), vim.trim(trust)) 337 338 eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) 339 trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 340 eq(string.format('%s %s', hash, full_path), vim.trim(trust)) 341 342 eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='remove', bufnr=0})}]])) 343 trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 344 eq('', vim.trim(trust)) 345 end) 346 347 it('trust using bufnr then deny then remove a file using path', function() 348 local cwd = fn.getcwd() 349 local hash = fn.sha256(assert(read_file(test_file))) 350 local full_path = cwd .. pathsep .. test_file 351 352 command('edit ' .. test_file) 353 eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) 354 local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 355 eq(string.format('%s %s', hash, full_path), vim.trim(trust)) 356 357 eq( 358 { true, full_path }, 359 exec_lua([[return {vim.secure.trust({action='deny', path=...})}]], test_file) 360 ) 361 trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 362 eq(string.format('! %s', full_path), vim.trim(trust)) 363 364 eq( 365 { true, full_path }, 366 exec_lua([[return {vim.secure.trust({action='remove', path=...})}]], test_file) 367 ) 368 trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 369 eq('', vim.trim(trust)) 370 end) 371 372 it('trust then deny then remove a file using path', function() 373 local cwd = fn.getcwd() 374 local hash = fn.sha256(assert(read_file(test_file))) 375 local full_path = cwd .. pathsep .. test_file 376 377 eq( 378 { true, full_path }, 379 exec_lua([[return {vim.secure.trust({action='allow', path=...})}]], test_file) 380 ) 381 local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 382 eq(string.format('%s %s', hash, full_path), vim.trim(trust)) 383 384 eq( 385 { true, full_path }, 386 exec_lua([[return {vim.secure.trust({action='deny', path=...})}]], test_file) 387 ) 388 trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 389 eq(string.format('! %s', full_path), vim.trim(trust)) 390 391 eq( 392 { true, full_path }, 393 exec_lua([[return {vim.secure.trust({action='remove', path=...})}]], test_file) 394 ) 395 trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 396 eq('', vim.trim(trust)) 397 end) 398 399 it('deny then trust then remove a file using bufnr', function() 400 local cwd = fn.getcwd() 401 local hash = fn.sha256(assert(read_file(test_file))) 402 local full_path = cwd .. pathsep .. test_file 403 404 command('edit ' .. test_file) 405 eq( 406 { true, full_path }, 407 exec_lua([[return {vim.secure.trust({action='deny', path=...})}]], test_file) 408 ) 409 local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 410 eq(string.format('! %s', full_path), vim.trim(trust)) 411 412 eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) 413 trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 414 eq(string.format('%s %s', hash, full_path), vim.trim(trust)) 415 416 eq( 417 { true, full_path }, 418 exec_lua([[return {vim.secure.trust({action='remove', path=...})}]], test_file) 419 ) 420 trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 421 eq('', vim.trim(trust)) 422 end) 423 424 it('trust returns error when buffer not associated to file', function() 425 command('new') 426 eq( 427 { false, 'buffer is not associated with a file' }, 428 exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]) 429 ) 430 end) 431 432 it('trust then deny then remove a directory using bufnr', function() 433 local cwd = fn.getcwd() 434 local full_path = cwd .. pathsep .. test_dir 435 command('edit ' .. test_dir) 436 437 eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) 438 local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 439 eq(string.format('directory %s', full_path), vim.trim(trust)) 440 441 eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='deny', bufnr=0})}]])) 442 trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 443 eq(string.format('! %s', full_path), vim.trim(trust)) 444 445 eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='remove', bufnr=0})}]])) 446 trust = assert(read_file(stdpath('state') .. pathsep .. 'trust')) 447 eq('', vim.trim(trust)) 448 end) 449 end) 450 end)