camouflage.nvim
Hide sensitive values in configuration files during screen sharing.
A Neovim plugin that visually masks secrets in .env, .json, .yaml, .toml, .properties, .netrc, .xml, .http, Terraform/HCL (.tf, .tfvars, .hcl), and Dockerfile files using extmarks - without modifying the actual file content.
Demo

Features
- Multi-format support:
.env,.json,.yaml,.yml,.toml,.properties,.ini,.conf,.sh,.netrc,.xml,.http,.tf,.tfvars,.hcl,Dockerfile,Containerfile - Nested key support: Handles
database.connection.passwordin JSON/YAML/XML - All value types: Masks strings, numbers, and booleans
- Multiple styles:
stars,dotted,text,scramble - Reveal & Yank: Temporarily reveal or copy masked values
- Follow Cursor Mode: Auto-reveal current line as you navigate
- Have I Been Pwned: Check passwords against breach database (Neovim 0.10+)
- JWT Expiry Hints: Decode
expclaim and show "expires in 2h" badges - Hot Reload: Config changes apply immediately
- Event System: Hooks for extending functionality
- TreeSitter Support: Enhanced parsing for JSON/YAML/TOML/XML/HTTP/HCL/Dockerfile
- Telescope/Snacks Integration: Mask values in preview buffers
- Zero file modification: All masking is purely visual
- Extensible: Register custom parsers for unsupported formats via a public API
Security Model
camouflage hides sensitive values visually, by drawing over them with virtual text. It does not change the file, and it does not encrypt or remove anything.
It protects against casual exposure of secrets on screen: shoulder-surfing, screen sharing, pair programming, screenshots, and demos.
It does not protect against anything that reads the buffer or file contents directly, because the real text is still there underneath the mask:
- search results and grep tools, including Telescope
live_grepresult lines (only the preview buffer is masked, not the matched result rows) - LSP servers, completion sources, and AI assistants
:%print,:substitutepreviews,:w/:saveas, and yanking withyy/"+y- the
+/*clipboard registers (use:CamouflageYank, which copies the real value deliberately with a confirm prompt and timed auto-clear)
For per-repo .camouflage.yaml files, masking config is applied as data only
(no code execution). If you don't trust the repositories you open, set
project_config.secure = true to gate the file behind Neovim's
vim.secure/:trust mechanism.
The scramble style is cosmetic, not protective: the mask is a shuffle of
the real characters, so it leaks the value's length and character set.
Installation
lazy.nvim
{
'zeybek/camouflage.nvim',
event = 'VeryLazy',
opts = {},
keys = {
{ '<leader>ct', '<cmd>CamouflageToggle<cr>', desc = 'Toggle Camouflage' },
{ '<leader>cr', '<cmd>CamouflageReveal<cr>', desc = 'Reveal Line' },
{ '<leader>cy', '<cmd>CamouflageYank<cr>', desc = 'Yank Value' },
{ '<leader>cf', '<cmd>CamouflageFollowCursor<cr>', desc = 'Follow Cursor' },
},
}
Other package managers
packer.nvim
use {
'zeybek/camouflage.nvim',
config = function()
require('camouflage').setup()
end
}
vim-plug
Plug 'zeybek/camouflage.nvim'
" In your init.lua or after/plugin/camouflage.lua:
lua require('camouflage').setup()
mini.deps
local add = MiniDeps.add
add({
source = 'zeybek/camouflage.nvim',
})
require('camouflage').setup()
Manual Installation
git clone https://github.com/zeybek/camouflage.nvim.git \
~/.local/share/nvim/site/pack/plugins/start/camouflage.nvim
Then add to your init.lua:
require('camouflage').setup()
Configuration
The plugin works with zero configuration. Here's a quick overview of common options:
require('camouflage').setup({
enabled = true,
auto_enable = true,
style = 'stars', -- 'text' | 'dotted' | 'stars' | 'scramble'
mask_char = '*',
debounce_ms = 150,
max_lines = 5000,
reveal = {
follow_cursor = false, -- Auto-reveal current line
},
yank = {
confirm = true, -- Require confirmation before copying
auto_clear_seconds = 30, -- Auto-clear clipboard
},
integrations = {
telescope = true,
cmp = { disable_in_masked = true },
},
})
Full configuration reference on the wiki.
Commands
| Command | Description |
|---|---|
:CamouflageToggle |
Toggle camouflage on/off |
:CamouflageReveal |
Reveal masked values on current line |
:CamouflageYank |
Copy unmasked value at cursor to clipboard |
:CamouflageFollowCursor |
Toggle follow cursor mode |
:CamouflageStatus |
Show status and masked count |
:CamouflageRefresh |
Refresh decorations |
:CamouflagePwnedCheck |
Check if value under cursor is pwned |
:CamouflagePwnedCheckBuffer |
Check all values in buffer |
:CamouflageExpiryToggle |
Toggle JWT expiry check on/off |
:CamouflageInit |
Create .camouflage.yaml in project root |
:CamouflageParsers |
List registered parsers (debug) |
Full commands list on the wiki.
Supported File Formats
| Format | Extensions | Nested Keys |
|---|---|---|
| Environment | .env, .env.*, .envrc, .sh |
No |
| JSON | .json |
Yes |
| YAML | .yaml, .yml |
Yes |
| TOML | .toml |
Yes (sections) |
| Properties | .properties, .ini, .conf, credentials |
Yes (sections) |
| Netrc | .netrc, _netrc |
No |
| XML | .xml |
Yes |
| HTTP | .http |
No |
| HCL / Terraform | .tf, .tfvars, .hcl |
Yes |
| Dockerfile | Dockerfile, Containerfile, *.dockerfile |
No |
For unsupported formats, you can define custom patterns.
Documentation
For detailed documentation, visit the Wiki:
- Getting Started — Installation and first steps
- Configuration — Full configuration reference
- Commands & Keymaps — All commands and suggested keybindings
- API Reference — Lua API for programmatic control
- Events & Hooks — Extend functionality with event listeners
- Have I Been Pwned — Password breach checking
- Integrations — Telescope, Snacks.nvim, nvim-cmp, Lualine
- Project Config — Repo-level
.camouflage.yaml - TreeSitter — Custom TreeSitter queries
- Architecture — Internal design and code flow
- Troubleshooting — Common issues and solutions
You can also use :help camouflage within Neovim.
Also Available
- Camouflage for VS Code - The original VS Code extension
License
MIT License - see LICENSE for details.