velociredactor 0.1.1

Redact secrets and PII from text and structured files, with stable numbered redactions
Documentation
# velociredactor default configuration.
#
# This file is built into the binary. --config, then VELOCIREDACTOR_CONFIG,
# then a velociredactor.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:
#
#     velociredactor 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

# ---------------------------------------------------------------------------
# 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. The name of each is what `velociredactor list` reports in its
# DETECTOR column.
# ---------------------------------------------------------------------------
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, reported under `label`. List `regex`
  # as many times as you like: each entry's patterns share its label, which is
  # how a listing keeps unrelated groups apart. `label` may be left out, and
  # is then `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:
  ##
  ##     velociredactor privacy_filter download
  ##
  ## Every setting below is optional and shown with its default, except
  ## `model_dir`; `- privacy_filter` alone is a complete entry.
  #
  # - privacy_filter:
  #     # 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: []
  # Key paths never scanned at all. Unlike the two above, which spare a value
  # after a detector reports it, these are never looked at, so they never
  # become findings.
  paths: []


# ---------------------------------------------------------------------------
# agent — files AI coding agents must read redacted.
#
# Agents given the velociredactor skills read these files through
# `velociredactor 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. `velociredactor agent init` writes this section;
# `velociredactor 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