Project-local Vim settings the right way
published:
categories: vim
Sometimes you want to have a set of Vim settings specific only to a certain project. There are many tips out there how to achieve such a setup, but most of them expose the user to serious security risks. I am going to describe the fundamental problem and how to solve it. TL;DR: use the vim-addon-local-vimrc plugin.
Unfortunately there is a very obvious but dangerous solution that gets promoted on countless blogs and Stack Overflow questions which keep popping up in web searches. I will first describe this naive solution and what exactly the problem with it is, then I will outline the proper secure solution.
The naive solution
This is the usual people recommend, which is not secure. We can create a file
in the root directory of the project, give it a predefined name like init.vim
and set up Vim to :source
the file when it finds it:
for fname in ['init.vim', '.init.vim', 'vimrc', '.vimrc']
if filereadable(fname)
execute 'source' fname
break
endif
endfor
This is very bad! Whenever you open any file from a directory containing a local settings file, the Vim Script file will be sourced. The file could contain malicious code like
call system('rm -rf ~')
and Vim would execute it unconditionally. You might only want to take a look at
the README file of a project and you end up executing foreign code. Even worse,
if file file name starts with a period character (like .init.vim
) you might
not even be aware that the file exists.
No problem, just :set secure
and everything will be fine. It is called
secure
for a reason after all, right? Wrong, the secure
option is not good
enough, it is still possible to execute arbitrary code:
call feedkeys(":!rm -rf ~\n")
An attacker might not even have to go that extra mile, because according to the
manual “On Unix this option is only used if the ".nvimrc" or ".exrc" is not
owned by you” (see :help 'secure'
). When you clone a git repository you are
the owner of all its files, and secure
does absolutely nothing.
The secure solution
The only way to be on the safe side is to inspect the settings file yourself before sourcing it. To prevent being caught by surprise when we open a file we can prompt for confirmation.
for fname in ['init.vim', '.init.vim', 'vimrc', '.vimrc']
if filereadable(fname)
if input('Do you wish to load ' .. fname .. ' ?') == 'yes'
execute 'source' fname
endif
break
endif
endfor
This does defeat automation though, we now have to manually confirm it every
time. If we get used to typing yes
every single time we might miss when the
file contents change right under our nose (e.g. after a git pull
) and confirm
the use of a now malicious file.
To solve this issue we need to remember which files have been confirmed and do not prompt for those as long as the file contents remain unchanged. We can store our previous choices along with the file hashes in a database of sorts. When a settings file is detected perform the following steps:
Has the file been previously confirmed or denied? If not, go to step 4.
Is the hash of the current file the same as the stored hash? If not, go to step 4.
Go to step 6.
Prompt the user.
Record the choice along with the current file hash.
Source or ignore the file based on the stored settings.
We need to find a sufficiently good hashing algorithm and a way of storing the choices in a database or file and retrieving them again. The plugin vim-addon-local-vimrc does all of these things already, so there is no need to re-invent the wheel.
Conclusion
The naive approach is dangerous because of unconditional foreign code execution (i.e. you think you are only reading a file, but something else happens in the background)
Instead ask the user before sourcing a new or changed file
Store the settings to avoid repeated prompting (people form habits and start accepting things without thinking first)
Use a file hash to detect whether a file has changed
The vim-addon-local-vimrc plugin implements all of the above steps