Skip to content

Rule Based Policy

Ahmet Zeybek edited this page Sep 27, 2026 · 4 revisions

Rule Based Policy

Rule-based policy lets you declare data-only rules for deciding which already-detected variables should be masked or ignored.

Policy can be configured in setup() or .camouflage.yaml. It never executes project code and it cannot make unsupported files parseable. A mask rule can only affect values that a parser or custom pattern already found.

Precedence

Policy decisions are deterministic:

  1. terminal_path_ignores ignore matching root-relative paths first.
  2. A matching action = 'mask' rule with allow_force = true can override a terminal path ignore.
  3. For normal ordered rules, the first matching rule wins.
  4. A later allow_force = true mask rule can override an earlier broad ignore rule.
  5. If nothing matches, default_action is used. The default is mask.

When policy is disabled, variables are masked normally and policy reports policy_disabled.

Configuration

require('camouflage').setup({
  policy = {
    enabled = true,
    default_action = 'mask', -- 'mask' | 'ignore'
    terminal_path_ignores = { '.git/**', 'node_modules/**' },
    rules = {
      {
        id = 'ignore-debug-flags',
        action = 'ignore',
        key = { '^DEBUG$', '^PORT$' },
        parser = { 'env', 'json', 'yaml' },
      },
      {
        id = 'force-client-secrets',
        action = 'mask',
        allow_force = true,
        ignore_case = true,
        key = { 'client[_%.%-]?secret', 'private[_%.%-]?key' },
      },
    },
  },
})

Key patterns are case-sensitive, like any Lua pattern: password matches db_password but not DB_PASSWORD. Environment files use upper case, JSON and YAML usually don't, so a rule that should cover both sets ignore_case = true and writes its patterns in lower case. See ignore_case.

The same policy can live in .camouflage.yaml:

version: 1
policy:
  enabled: true
  default_action: mask
  terminal_path_ignores:
    - .git/**
    - node_modules/**
  rules:
    - id: ignore-debug-flags
      action: ignore
      key: ['^DEBUG$', '^PORT$']
      parser: [env, json, yaml]
    - id: force-client-secrets
      action: mask
      allow_force: true
      ignore_case: true
      key:
        - client[_%.%-]?secret
        - private[_%.%-]?key

Rule Fields

Field Type Description
id string Stable identifier shown in redacted metadata
action 'mask' | 'ignore' Decision when the rule matches
allow_force boolean Lets a mask rule override path ignores or broad ignore rules
path string|string[] Project-root-relative glob
basename string|string[] Basename glob
parser string|string[] Parser name such as env, json, yaml, toml, hcl
key string|string[] Lua pattern matched against parsed variable keys, case-sensitive
ignore_case boolean Also match key against the lower-cased key, default false
nested boolean Match nested-key metadata
commented boolean Match commented-line metadata
value_length { min?: number, max?: number } Match plaintext length without exposing the value
value_shape string|string[] Safe shape predicate, listed below
value_prefix string|string[] Literal prefix predicate
value_suffix string|string[] Literal suffix predicate

Supported value_shape values:

Shape Matches
empty Empty string
non_empty Any non-empty string
numeric Numeric-looking values
boolean true or false
quoted Single- or double-quoted values
jwt_like Three base64url-ish JWT segments
token_like Non-space token-like values with at least 8 characters

Value predicates inspect the value only to make the decision. Warnings, status output, audit findings, and policy stats do not include plaintext values.

Common Patterns

Ignore safe operational toggles:

version: 1
policy:
  rules:
    - id: ignore-non-secret-flags
      action: ignore
      key: ['^DEBUG$', '^LOG_LEVEL$', '^PORT$']
      value_shape: [boolean, numeric, non_empty]

Ignore fixture directories but force real-looking secrets back on:

version: 1
policy:
  terminal_path_ignores:
    - tests/fixtures/**
  rules:
    - id: force-private-keys
      action: mask
      allow_force: true
      key: ['private[_%.%-]?key']

Mask only specific keys by default. Everything the rules don't match stays readable, so let the rule match every case:

version: 1
policy:
  default_action: ignore
  rules:
    - id: mask-sensitive-keys
      action: mask
      ignore_case: true
      key:
        - password
        - secret
        - token
        - api[_%-]*key

This masks DB_PASSWORD, api_token and ApiKey alike, and leaves LOG_LEVEL readable. Without ignore_case the lower-case patterns would miss the upper-case keys of a .env, and those would be left unmasked. default_action: ignore also makes camouflage warn about the file, since it can leave values unmasked.

Status and Audit

:CamouflageStatus reports whether policy is enabled and how many variables were ignored in the current buffer.

Workspace Audit applies the same policy and includes redacted policy metadata in each finding.

ignore_case

Key patterns are case-sensitive by default. With ignore_case: true a rule's key patterns are tried against the key as written and against the lower-cased key, the way the Weak Secret Check patterns are, so a pattern written in lower case covers every case:

Pattern ignore_case Matches
password false (default) db_password
password true db_password, DB_PASSWORD, DbPassword
^DB_PASSWORD$ true DB_PASSWORD, db_password, Db_Password
^%u+_TOKEN$ true API_TOKEN only

An exact key, a pattern wrapped in ^ and $ with no other pattern characters in it, is compared in any case. A pattern itself isn't lower-cased, so a class like %u still means upper case, and a pattern written in upper case (PASSWORD) still only matches upper case.

How a rule masks

A rule can also say how its values are masked, not only whether they are:

Field Type Description
style string text, dotted, stars, scramble or partial
mask_char string Single character used for the mask
mask_length integer Fixed mask width, which hides the value's length
show_start integer With partial: characters kept at the start
show_end integer With partial: characters kept at the end
version: 1
policy:
  rules:
    - id: aws-key-id
      action: mask
      key: ['^AWS_ACCESS_KEY_ID$']
      style: partial
      show_end: 4
    - id: fixed-width
      action: mask
      ignore_case: true
      key: ['password']
      mask_char: '#'
      mask_length: 10
AWS_ACCESS_KEY_ID=****************MPLE
AWS_SECRET_ACCESS_KEY=*************
DB_PASSWORD=##########

These fields are data, a name and a few numbers, so a .camouflage.yaml can carry them with nothing executable in it. A rule with an invalid style or a fractional number is dropped with a warning, and its values are masked the usual way.

partial reveals what it is asked to reveal. Use it on identifiers, not on passwords.

See Also

Clone this wiki locally