Repository navigation
Custom Check API
Custom checks let trusted Lua code inspect parsed variables during masking and render redacted badges through the shared checks pipeline.
Use this for local policy hints, organization-specific validators, or carefully controlled async checks. Check functions receive plaintext values in ctx.var.value, so only register code you trust.
require('camouflage').register_check({
name = 'local_policy',
priority = 60,
run = function(ctx)
if ctx.var.key:match('TOKEN') and ctx.var.value == 'changeme' then
return {
severity = 'warning',
text = '[policy]',
hl_group = 'DiagnosticWarn',
data = {
reason = 'placeholder',
key = ctx.var.key,
value_length = #ctx.var.value,
},
}
end
end,
})| Function | Description |
|---|---|
require('camouflage').register_check(spec) |
Register a trusted Lua check |
require('camouflage').unregister_check(name) |
Remove a check and clear its rendered results |
require('camouflage').list_checks() |
List registered checks sorted by priority descending, then name |
require('camouflage').get_check(name) |
Fetch one registered check |
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
yes | Stable check name. Must start with a letter and contain only letters, digits, _, or -
|
run |
function |
yes | Sync form run(ctx) or async form run(ctx, done)
|
async |
boolean |
no | Defaults to false
|
priority |
integer |
no | Defaults to 50, higher runs first |
default_enabled |
boolean |
no | Defaults to true
|
Names are limited to 64 characters and duplicate names are rejected.
Each check receives a context table:
| Field | Description |
|---|---|
bufnr |
Buffer number |
filename |
Filename used for parser selection |
parser_name |
Parser that produced the variable |
var |
Parsed variable. ctx.var.value is plaintext |
config |
Effective data table from checks.<name>
|
run_id |
Decoration run id |
check_name |
Registered check name |
Checks run after Rule Based Policy and variable_detected hooks, so they only inspect variables that will actually be masked.
A check can return nil for no badge, or a table:
| Field | Type | Description |
|---|---|---|
severity |
'info' | 'warning' | 'error' |
Required |
text |
string |
Badge text, keep it redacted |
hl_group |
string |
Virtual text highlight group |
sign_text |
string |
Optional sign column text |
sign_hl |
string |
Optional sign highlight group |
line_hl |
string |
Optional whole-line highlight group |
priority |
integer |
Optional result metadata |
data |
table |
Optional data-only metadata |
Unknown result fields are ignored. data may contain only primitive values and tables.
camouflage drops a result when text or data directly contains the exact plaintext value. This prevents accidental leaks in the rendered badge or metadata, but check authors should still intentionally return only redacted output.
Async checks must opt in with async = true and call done(result):
require('camouflage').register_check({
name = 'remote_policy',
async = true,
run = function(ctx, done)
vim.defer_fn(function()
done({
severity = 'info',
text = '[checked]',
})
end, 10)
end,
})Old async completions are ignored when:
- the buffer changed
- the buffer was deleted
- the check was unregistered
- a newer decoration run superseded the old run
- the variable no longer matches the original line/range/key/value identity
Registered checks read data options from checks.<name>. enabled = false disables that check.
require('camouflage').setup({
checks = {
local_policy = {
enabled = false,
label = 'team',
severity = 'warning',
},
},
})Inside the check:
run = function(ctx)
if ctx.config.enabled == false then
return
end
vim.notify(ctx.config.label)
endProject config can provide data under checks.<name>, but it cannot register executable check code.
Custom checks render through the same badge renderer as built-in checks. Built-ins render first in this order:
pwnedweak_secretexpiry
Unlisted checks render after those, alphabetically by check name. The highest-severity result on a line owns the sign column and whole-line highlight.
local camouflage = require('camouflage')
camouflage.register_check({
name = 'org_policy',
run = function(ctx)
-- ...
end,
})
for _, check in ipairs(camouflage.list_checks()) do
print(check.name, check.priority)
end
camouflage.unregister_check('org_policy')unregister_check() clears rendered results for that check from loaded buffers.
- Weak Secret Check: built-in offline check
- JWT Expiry Hints: built-in local JWT check
- API: complete public Lua API