Repository navigation
Workspace Audit
Workspace audit scans supported files under a project root or explicit path and writes redacted findings to quickfix or the current window's location-list.
It uses the same parser registry as live masking, including built-in parsers, runtime parser registrations, and custom patterns. It does not create buffers, apply extmarks, run hooks, or run network checks.
| Command | Destination | Description |
|---|---|---|
:CamouflageAudit |
audit.destination or quickfix |
Scan the current project root |
:CamouflageAudit {path} |
audit.destination or quickfix |
Scan a specific file or directory |
:CamouflageAudit! |
location-list | Scan the current project root into the current window's location-list |
:CamouflageAudit! {path} |
location-list | Scan a specific path into the current window's location-list |
If no path is provided, the root is resolved from the nearest .camouflage.yaml, then the nearest .git directory, then the current working directory.
Audit output never includes plaintext values. Findings include:
- file path
- line and column
- parser name
- key name
- nested/commented/multiline metadata
- value length
- the policy decision:
action,reason(default,rule,policy_disabled) and therule_idwhen a rule made it
The list rows read like this, here with a rule force-secrets that masks password keys:
.env|1 col 9-21 info| [env] API_KEY · 12 chars · mask
.env|2 col 7-11 info| [env] DEBUG · 4 chars · mask
config.json|2 col 30-44 info| [json] database.password · 14 chars · mask (rule force-secrets)
The rule id shows when a policy rule decided the row. Each item also carries the full record, the same one the JSON report has, as user_data (Neovim 0.10+, since 0.9 has no user_data on quickfix items):
local item = vim.fn.getqflist({ items = 0 }).items[1]
print(item.user_data.key, item.user_data.value_length, item.user_data.policy.action)The audit engine does not run Have I Been Pwned or any other network-backed check. It also does not run variable_detected hooks, so hook code cannot inspect audit values.
require('camouflage').setup({
audit = {
ignore_patterns = { '.git', '.git/**', 'node_modules', 'node_modules/**' },
max_files_per_chunk = 50,
destination = 'quickfix', -- 'quickfix' | 'loclist'
open = true,
notify = true,
},
})| Option | Default | Description |
|---|---|---|
ignore_patterns |
{'.git', '.git/**', 'node_modules', 'node_modules/**'} |
Root-relative or basename globs skipped by audit |
max_files_per_chunk |
50 |
Files processed per scheduled async chunk |
destination |
'quickfix' |
Default list target for :CamouflageAudit
|
open |
true |
Open quickfix/location-list when findings exist |
notify |
true |
Show completion notifications |
ignore_patterns are simple globs, not full .gitignore semantics. Use explicit directory patterns such as dist/** or .terraform/**.
Audit applies Rule Based Policy before reporting findings. Ignored variables are not listed, and audit stats include how many values policy ignored.
When policy metadata is attached to a finding, it is redacted:
{
action = 'mask',
reason = 'rule',
rule_id = 'force-client-secrets',
}For a CI step or a pre-commit hook, the same records can be written as JSON, with an exit code that says whether anything was found:
nvim --headless -c 'CamouflageAudit --json=audit.json --quit .'| Flag | Effect |
|---|---|
--json={path} |
Write the report to a file |
--json |
Write the report to stdout |
--quit |
Leave Neovim afterwards: exit 1 with findings, 0 without |
Without either flag the command behaves as before and opens a list.
{
"version": 1,
"plugin_version": "0.17.2",
"root": "/tmp/demo",
"cancelled": false,
"parsers": ["dockerfile", "env", "hcl", "http", "json", "netrc", "properties", "toml", "xml", "yaml"],
"stats": {
"findings": 3, "files_seen": 2, "files_supported": 2, "files_scanned": 2,
"files_skipped": 0, "policy_ignored": 0, "elapsed_ms": 0
},
"findings": [
{
"filename": "/tmp/demo/.env",
"lnum": 1, "col": 9, "end_col": 21,
"key": "API_KEY", "parser": "env",
"value_length": 12,
"is_nested": false, "is_commented": false, "is_multiline": false,
"policy": { "action": "mask", "reason": "default" }
}
],
"errors": []
}Only the first finding is shown here. The keys come out in whatever order vim.json.encode gives them.
No value appears anywhere in the report, the same rule the list output follows.
The audit module is available for advanced workflows:
local audit = require('camouflage.audit')
local result = audit.run({
path = vim.fn.getcwd(),
})
audit.set_list(result, {
destination = 'quickfix',
open = true,
})For async scanning:
local handle = require('camouflage.audit').run({
path = vim.fn.getcwd(),
async = true,
on_complete = function(result)
require('camouflage.audit').set_list(result)
end,
})
-- Optional cancellation
handle.cancel()The report is available directly:
local audit = require('camouflage.audit')
local result = audit.run({ path = vim.fn.getcwd() })
local report = audit.to_report(result) -- table, ready to encode
audit.write_report(result, 'audit.json') -- returns ok, err
audit.write_report(result, '-') -- stdout- Commands and Keymaps: command reference
- Rule Based Policy: policy filtering before audit output
- Supported File Formats: files audit can parse