Updated (2026-05-17)
Rewrote the script so it can work without firejail and kitty
#!/bin/zsh
local CWD="$PWD"
local TARGET="$1"
[ "$1" != "" ] && shift
if [ -d "$TARGET" ]; then
TARGET="$(realpath $TARGET)"
CWD="$TARGET"
elif [ -f "$TARGET" ]; then
TARGET="$(realpath $TARGET)"
CWD="$(dirname $TARGET)"
elif [ "$TARGET" != "" ]; then
TARGET="$(realpath $TARGET)"
touch "$TARGET"
CWD="$(dirname $TARGET)"
fi
export ZDOTDIR=~/.dotfiles
local CMD=()
[ "$(which kitty)" != "" ] && [ "$DISPLAY" != "" ] && CMD+=(kitty -d $CWD --)
if [ "$(which firejail)" != "" ]; then
JAIL_RW=("$TARGET" "$HOME/.local/state/nvim" "$HOME/.local/share/nvim" "$HOME/.cache" "$HOME/.gnupg" "/var/run/docker.sock" "$HOME/.dotfiles/.config/nvim")
JAIL_RW+=("$HOME/.expo" "$HOME/.bun" "$HOME/.docker" "$HOME/.app-store" "$HOME/.rustup" "$HOME/.cargo")
which go &>/dev/null && JAIL_RW+=("$(go env GOMODCACHE)")
[ -d "$HOME/.config/lazygit" ] && JAIL_RW+=("$HOME/.config/lazygit" "$HOME/.local/state/lazygit")
JAIL_BL=(/nas /games)
CMD+=(firejail --noprofile --caps.drop=all --nonewprivs --seccomp --disable-mnt --private-tmp --private-dev --read-only="$HOME")
CMD+=(--tracelog --ipc-namespace --writable-run-user)
for v in $JAIL_RW; do
CMD+=(--read-write="$v")
done
for v in $JAIL_BL; do
CMD+=(--blacklist="$v")
done
fi
CMD+=(nvim "$TARGET" $@)
# echo $CMD
pushd $CWD
if [[ "$CMD" = *"kitty"* ]]; then
$CMD &>/dev/null &
else
$CMD
fi
popd
Old versions of the post
Updated (2025-03-05)
I decided to go with a script rather than a profile, profiles are rather complicated and didn’t work right for me.
#!/bin/zsh
# check if we passed a directory as the first arg
local cwd="$PWD"
local p="$1"
[ "$1" != "" ] && shift
if [ -d "$p" ]; then
p="$(realpath $p)"
cwd="$p"
fi
# common pathes that needs to be r/w
JAIL_RW=("$HOME/.local/state/nvim" "$HOME/.local/share/nvim" "$HOME/.cache/nvim" "$HOME/.gnupg" "$p")
# go mod cache if available
which go &>/dev/null && JAIL_RW+=("$(go env GOMODCACHE)")
# lazygit if available
[ -d "$HOME/.config/lazygit" ] && JAIL_RW+=("$HOME/.config/lazygit")
JAIL="firejail --noprofile --caps.drop=all --nonewprivs --seccomp --noroot --disable-mnt --read-only=\"$HOME\""
for jp in $JAIL_RW; do
JAIL="$JAIL --read-write=\"$jp\""
done
pushd $cwd
eval $JAIL nvim $p $@
popd
Old
I use a lot of plugins, and while I’m generally not paranoid.
After some feedback, I came to the conclusion that the next statement is wrong.
I’d say the plugin ecosystem of nvim isn’t as secure as say VSCode or JetBrains.
So after some tinkering, I got firejail to behave nicely with nvim using a custom profile, for some reason the stock profile did not work at all, might be an issue on my end though.
Simply create the profile, then start nvim with firejail --whitelist=$SRC --read-write=$SRC nvim $SRC, I’d recommend aliasing it or using a wrapper.
update 2023-10-11 updated the profile to support zsh and go better, also add xdg-open.
update 2023-10-19 up to date version of the profile @ https://github.com/OneOfOne/dotfiles/blob/master/.config/firejail/nvim.profile
~/.config/firejail/nvim.profile
include globals.local
#include disable-xdg.inc
include whitelist-run-common.inc
include whitelist-runuser-common.inc
include whitelist-usr-share-common.inc
include whitelist-var-common.inc
include whitelist-common.inc
whitelist /usr/share/nvim
whitelist ${HOME}/.git*
whitelist ${HOME}/.dotfiles
whitelist ${HOME}/.cache/nvim
whitelist ${HOME}/.config/nvim
whitelist ${HOME}/.local/share/nvim
whitelist ${HOME}/.local/state/nvim
read-write ${HOME}/.dotfiles/.config/nvim
read-write ${HOME}/.cache/nvim
read-write ${HOME}/.config/nvim
read-write ${HOME}/.local/state/nvim
# gnupg & git
writable-run-user
whitelist ${HOME}/.gnupg
noblacklist ${RUNUSER}/gnupg
whitelist ${RUNUSER}/gnupg
whitelist ${HOME}/.config/lazygit
read-write ${HOME}/.config/lazygit
# custom addons
whitelist ${HOME}/code/nvim/spm.nvim
# copilot
whitelist ${HOME}/.config/github-copilot
read-write ${HOME}/.config/github-copilot
# go
whitelist ${HOME}/code/go/
whitelist ${HOME}/sdk/go/
whitelist ${HOME}/.cache/go-build
read-write ${HOME}/.cache/go-build
#zsh & term
whitelist /usr/share/zsh
whitelist ${HOME}/.terminfo/
ipc-namespace
nogroups
nonewprivs
noroot
protocol unix,inet,inet6
seccomp
seccomp.block-secondary
tracelog
private-dev
restrict-namespaces
~/bin/wvim (nvim wrapper with kitty as a “UI”)
#!/bin/zsh
local cwd="$PWD"
local p="$1"
[ "$1" != "" ] && shift
if [ -d "$p" ]; then
p="$(realpath $p)"
cwd="$p"
fi
export ZDOTDIR=~/.dotfiles
if [[ "$WAYLAND_DISPLAY" != "" ]]; then
kitty -d $cwd -- firejail --quiet --whitelist=$p --read-write=$p nvim $p $@ &
else
firejail --quiet --whitelist=$p --read-write=$p nvim $p $@
fi
~/bin/xdg-open (if you need to open the browser or an extra app from nvim or lazygit)
#!/bin/zsh
systemd-run --user --quiet --no-block /usr/bin/xdg-open "$@" || /usr/bin/xdg-open "$@"