veloci 0.3.3

Veloci Redactor: redact secrets and PII from text and structured files, with stable numbered redactions
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# Veloci Redactor default configuration.
#
# This file is built into the binary. --config, then VELOCI_CONFIG,
# then a veloci.yml found in the current directory or a parent,
# each replaces it entirely: nothing is merged and nothing is inherited.
# To write your own, start from a copy of this one:
#
#     veloci config show > my-config.yml

# Scan comments as well as values, in the formats that have them (JSON with
# comments, YAML, TOML, HCL, INI, dotenv, XML, Java properties).
comments: false

# ---------------------------------------------------------------------------
# replacement — the token written in place of each secret.
#
# A format string. `{n}` is the 1-based number of this distinct value in the
# document; the same text always shares a number. `{reason}` is why it was
# redacted: the detector's label (`entropy`, `ruleset:github-pat`,
# `credential_key`, …; see `label` under `detectors`). Double a brace to write
# it literally (`{{` / `}}`).
#
# The format must contain exactly one `{n}`, at most one `{reason}`, literal
# text at both ends, and literal text between placeholders. Reasons contain
# only ASCII letters, digits, `_`, `.`, `:`, `/`, and `-`. The character
# immediately after `{reason}` must be outside that set (here it is `]`), so
# tokens can be found again without consuming surrounding text.
# ---------------------------------------------------------------------------
replacement: "[REDACTED-{n}]"
# replacement: "[REDACTED-{n}:{reason}]"

# ---------------------------------------------------------------------------
# formats — the input formats to recognize, and the order they are tried in
# when the format is guessed from content.
#
# Passing --raw skips all formats and uses plain text instead.
# ---------------------------------------------------------------------------
formats:
  - json
  - jsonl
  - yaml
  - toml
  - xml
  - hcl
  - ini
  - dotenv
  - properties
  - csv
  - tsv
  - psv
  - bplist
  - text

# ---------------------------------------------------------------------------
# policy — which values are looked at in the first place.
#
# Values skipped here are never scanned, so no detector below can redact them.
# ---------------------------------------------------------------------------
policy:
  # Keys ending in one of these are skipped, compared case-insensitve. These are
  # structural and high-entropy by nature, and redacting them breaks the
  # document: identifiers, UUIDs, hashes, and the thinking-block signatures in
  # LLM transcripts, which must survive intact for a transcript to replay.
  skip_key_suffixes: ["signature", "id", "ids"]

  # Keys skipped by exact (lowercase) name. Kept separate from the suffixes
  # above on purpose: as a suffix, "path" would also skip "paths" and "xpath".
  skip_keys: ["path", "filepath", "file_path", "cwd", "root", "dir", "directory"]

  # Objects carrying inline image or binary payloads, identified by a field
  # whose value starts with one of `prefixes` or equals one of `values`. The
  # field name and its value are both compared ignoring case.
  skip_object:
    key: "type"
    prefixes: ["image"]
    values: ["base64"]

  # An object holding both a host-like and a user-like key is connection
  # settings, which makes a bare `password` key inside it sensitive. Compared
  # after normalizing `-`, ` ` and `.` to `_`, so "Data Source" matches
  # "data_source".
  credential_context:
    host_keys:
      ["host", "hostname", "server", "addr", "address", "datasource", "data_source"]
    user_keys: ["user", "username", "userid", "user_id", "uid"]

# ---------------------------------------------------------------------------
# placeholder — values that look like credentials but are not.
#
# Documentation samples, masks, and earlier redactions, which would otherwise
# be reported as secrets over and over. Consulted by every detector that finds
# a credential by its surroundings or by a vendor's pattern, and so can be
# looking at a sample of one: ruleset, credentialed_uri, connection_string,
# credential_assignment, credential_key.
#
# The others do not consult it, and are not meant to: `entropy` judges the
# value itself, where a placeholder scores too low to report anyway, and
# `regex`, `value` and `path` were told exactly what to redact.
# ---------------------------------------------------------------------------
placeholder:
  # Compared in lowercase after trimming whitespace and one layer of quotes,
  # so entries must be written in lowercase.
  values:
    - "redacted"
    - "[redacted]"
    - "<redacted>"
    - "changeme"
    - "example"
    - "placeholder"
    - "your_password"
    - "your_db_password"
    - "your_secret"
    - "secret_here"
    - "password"
    - "db_password"
    - "secret"
    - "secret_key"
    - "api_key"
    - "api_secret"
    - "api_token"
    - "api_secret_key"

  # A value that is a run of one of these characters is a mask (`****`,
  # `xxxx`). Written as one string, one character per mask character.
  mask_characters: "*x.-"

  # Shortest run that counts as a mask, so "x" alone is still a secret.
  mask_min_length: 3

  # Shortest `<name>` placeholder that counts, so "<a>" is still a secret.
  # The interior must be lowercase words joined by `-` or `_`, which keeps
  # "<hunter2>" and "<RealPassword>" secret.
  bracket_min_length: 5

# ---------------------------------------------------------------------------
# detectors — what looks for secrets, in the order they are listed.
#
# Every detector that runs is listed here with its own settings; a detector
# that is not listed does not run. Detectors that take no settings are written
# as a bare name.
#
# Any entry also takes these two settings, among its own or, for a detector
# with none, as `- credential_key: { enabled: false }`:
#
#   enabled   `false` keeps the entry but does not run it, unless named on the
#             command line with `--detector <label or name>`. Default `true`.
#   label     What its findings are reported as: in `veloci list`'s DETECTOR
#             column and as `{reason}`. Default: the detector's name. A
#             detector that reports more (`ruleset:github-pat`) keeps the rest
#             (`mine:github-pat`). Same characters as a reason.
# ---------------------------------------------------------------------------
detectors:
  # Long tokens that look random.
  - entropy:
      # Bits per byte required to redact a token. Ordinary words and
      # identifiers fall below 4.5; API keys and tokens usually score above 5.
      # Lower redacts more.
      threshold: 4.5

      # Used instead when the value sits under a sensitive key. Below the
      # hex-alphabet ceiling of 4.0, so MD5/SHA-shaped digests still qualify.
      sensitive_threshold: 3.5

      # Shortest run of token characters ([A-Za-z0-9+_=-]) considered at all.
      # `/` is not a token character, so a whole file path is not one token.
      min_token_length: 10

      # Key segments that mark a field as likely holding a secret. Matched as
      # whole segments after splitting camelCase and `_`, so "apiKey" is
      # sensitive but "keyboard" is not. Entries must be single lowercase
      # words.
      sensitive_segments:
        ["key", "secret", "token", "pass", "password", "passwd", "pwd"]

      # Key names that contain a sensitive segment but are structural rather
      # than credentials. Matched exactly or as an `_`-delimited suffix, so
      # "user_foreign_key" matches. These keep the ordinary threshold.
      structural_keys:
        - "foreign_key"
        - "primary_key"
        - "sort_key"
        - "partition_key"
        - "lookup_key"
        - "cache_key"
        - "public_key"
        - "idempotency_key"

      # Hex strings of exactly these lengths (MD5, SHA-1, SHA-256) are
      # redacted under a sensitive key whatever their entropy.
      hex_digest_lengths: [32, 40, 64]

  # betterleaks/gitleaks rules: several hundred vendor-specific secret
  # formats. Findings are labelled `ruleset:<rule id>`.
  - ruleset:
      # Rulesets to load, in order; a later rule replaces an earlier one with
      # the same id. `builtin:betterleaks` is the release vendored into this
      # binary. Anything else is a path to a betterleaks/gitleaks TOML file,
      # relative to this config file.
      rules:
        - builtin:betterleaks # See https://github.com/phayes/velociredactor/blob/master/vendor/betterleaks/betterleaks.toml
        # - ./my-rules.toml

      # Comments marking a line as intentionally containing a secret. Matched
      # case-sensitively anywhere on the line.
      allow_signatures: ["betterleaks:allow", "gitleaks:allow"]

      # Rules to switch off, by id. No separator, so `*` matches anything:
      # ["github-pat", "aws-*"].
      exclude_rules: []

  # Every match of a regular expression. List `regex` as many times as you
  # like: give each entry its own `label` to keep unrelated groups of patterns
  # apart in a listing. Without one, findings are reported as `regex`.
  #
  # These patterns identify credentials by prefix and length alone, so they
  # are caught whatever their entropy or surrounding key. They are not
  # anchored to a word boundary, so a key glued to a preceding identifier is
  # still found; the cost is that a long identifier merely starting with a
  # prefix is redacted too.
  - regex:
      label: provider_token
      patterns:
        # Supabase secret API key (bypasses row-level security).
        - 'sb_secret_[A-Za-z0-9_-]{20,}'
        # Supabase personal access token. Publishable keys (sb_publishable_…)
        # are meant to be embedded in client code and are deliberately not
        # matched.
        - 'sbp_[a-z0-9_-]{20,}'

  # URLs with a password in the userinfo (postgres://user:pass@host/db). The
  # whole URL is redacted, unless the password is a `placeholder` value.
  - credentialed_uri

  # JDBC URLs, libpq DSNs, and ADO.NET connection strings.
  - connection_string

  # DB_PASSWORD=value assignments inside free text.
  - credential_assignment

  # The whole value of a password field in structured data.
  - credential_key

  ## Personal data. Off by default: these patterns are broad, and whether a
  ## name or an address is a secret depends on the document.
  #
  # - pii:email:
  #     # Addresses belonging to automation rather than to a person. A
  #     # leading `@` matches the domain suffix, a trailing `@` the
  #     # local-part prefix, and anything else is an exact address.
  #     allowlist:
  #       - "noreply@"
  #       - "actions@"
  #       - "info@"
  #       - "@users.noreply.github.com"
  #       - "@noreply.github.com"
  #
  # - pii:phone
  #
  # - pii:address

  ## OpenAI Privacy Filter: a language model that reads each value in context
  ## and finds names, addresses, dates, emails, phone numbers, URLs, account
  ## numbers, and secrets that no pattern above describes. Findings are
  ## labelled `privacy_filter:<category>`.
  ##
  ## It is slow next to everything else here, and heavy: loading it takes up
  ## to 16 GB of memory at its peak. Fetch the model (2.6 GB) first:
  ##
  ##     veloci privacy_filter download
  ##
  ## `enabled: false` keeps it for when it is asked for, with
  ## `--detector privacy_filter`, rather than in every scan of a whole project.
  ##
  ## Every setting below is optional and shown with its default, except
  ## `model_dir` and `enabled`; `- privacy_filter` alone is a complete entry.
  #
  # - privacy_filter:
  #     enabled: false
  #
  #     # The downloaded model, relative to this file. When left out, the
  #     # copy in the Hugging Face cache, which is where the download command
  #     # puts it unless given --dir: $HF_HUB_CACHE, else $HF_HOME/hub, else
  #     # ~/.cache/huggingface/hub.
  #     model_dir: ./privacy-filter
  #
  #     # auto, cpu, cuda (the first GPU), or cuda:N. `auto` picks a CUDA GPU
  #     # when this build supports one and one is present, else the CPU.
  #     device: auto
  #
  #     # What the model sees before each value: `none`, its `key`
  #     # (`name: Alice`), or its key `path` (`users.name: Alice`). Keys are
  #     # context only; they are never redacted.
  #     context: key
  #
  #     # How sure the model must be of a finding, from 0 to 1.
  #     min_score: 0.5
  #
  #     # Categories to report. 
  #     categories:
  #       - account_number
  #       - private_address
  #       - private_date
  #       - private_email
  #       - private_person
  #       - private_phone
  #       - private_url
  #       - secret
  #
  #     # Tokens read at once; longer documents are read in overlapping
  #     # windows. Memory grows with the square of this.
  #     max_tokens: 1024
  #
  #     # How labels are decoded into spans: an operating point from the
  #     # model's viterbi_calibration.json, or the six transition biases
  #     # written out (transition_bias_background_stay, ...).
  #     viterbi: default
  #
  #     # The model's architecture, when not the one in its config.json.
  #     # model: { ... }

  ## Exact strings, redacted wherever they appear, even mid-sentence. For
  ## secrets you already know the text of.
  # - value:
  #     values:
  #       - hunter2

  ## Every value at a matching key path, redacted whatever it holds. A path is
  ## the keys of the enclosing objects joined with `.`, ending in the value's
  ## own key; array nesting adds nothing, so one entry covers every element. In
  ## these globs `*` stays inside one key, `**` spans any number, `?` is one
  ## character.
  ##   users.ssn          the ssn of every element of `users`
  ##   **.customer_name   a customer_name at any depth
  ##   db.*.password      the password of each immediate child of `db`
  ##   secrets.**         everything under `secrets`, and `secrets` itself
  ##
  ## A key listed under `policy.skip_keys` or `skip_key_suffixes` above is
  ## never scanned, so listing it here does nothing: take it out of `policy`
  ## first.
  # - path:
  #     paths:
  #       - users.ssn


# ---------------------------------------------------------------------------
# allow — don't ever redact these values.
#
# Allowing is the last word: a value listed here stays in the document even
# when a detector above reported it.
# ---------------------------------------------------------------------------
allow:
  # Exact values to leave in place, for confirmed false positives.
  values: []
  # Patterns a secret must match IN FULL to be left in place.
  regexes: []
  # Patterns matched against the whole value around a secret: a secret lying
  # entirely inside a match is left in place, and never becomes a finding.
  # Use these when the secret alone looks random but its surroundings show it
  # is harmless, such as a file name in a font URL. Not anchored, and `.` does
  # not cross a newline. Keep them tight: a match that runs on past the
  # harmless text, like `https://fonts\.gstatic\.com/.*`, would also spare a
  # real secret later on the same line.
  #   within:
  #     - 'https://fonts\.gstatic\.com/[^\s"'')]+'
  within: []
  # Key paths never scanned at all. Unlike the first two above, which spare a
  # value after a detector reports it, these are never looked at, so they
  # never become findings.
  paths: []
  # Files never redacted: fixtures, sample data, documentation. Everything in
  # them is left in place, and `scan` does not report them. Patterns are
  # relative to this file and follow the same .gitignore conventions as the
  # `agent` section below: a pattern with no `/` matches a file name in any
  # directory, one with a `/` matches from this directory, and a trailing `/`
  # covers a whole directory. `*` stays within one path segment and `**`
  # spans any number.
  #   files:
  #     - "*.example"
  #     - tests/fixtures/
  #     - docs/**/*.md
  files: []
  # Key paths never scanned in some files only, written FILE#KEY.PATH: a file
  # pattern as in `files` above, `#`, then a key path as in `paths`. Both
  # halves take globs.
  #   file_paths:
  #     - example.yaml#user.name
  #     - "example.*.yml#user.name"
  #     - config/**/*.yml#db.*.password
  file_paths: []


# ---------------------------------------------------------------------------
# agent — files AI coding agents must read redacted.
#
# Agents given the Veloci Redactor skills read these files through
# `veloci redact` instead of directly, so the model never sees the
# secrets in them. With `enforce`, agents that support hooks are blocked from
# reading them any other way. `veloci agent init` writes this section;
# `veloci agent check FILE` tells whether a file is covered.
#
# Patterns are relative to this file and follow .gitignore conventions: a
# pattern with no `/` matches a file name in any directory, one with a `/`
# matches from this directory, and a trailing `/` covers a whole directory.
# `*` stays within one path segment and `**` spans any number.
# ---------------------------------------------------------------------------
# agent:
#   protected:
#     - .env*
#     - "*.pem"
#     - secrets/
#   exclude:
#     - .env.example
#   enforce: false