skip to content

How do you add an internal-token rule to Trivy 0.74's trivy-secret.yaml, and how do allow-rules cut noise without hiding real keys?

level: middleimportance: should knowfreq 20%

answer

  1. a config file next to the scan
  2. required fields: id to regex
  3. skip by match or by path
  4. built-in allow rules exist too
  5. a missing file is silent

basics

~20 s

Add a rules entry with id, category, title, severity and a Go regex, plus keywords for speed. Allow-rules drop a hit by matched text or file path, globally or per rule. Keep them narrow: built-in ones already skip test and example paths.

solid answer

~50 s

Trivy loads `trivy-secret.yaml` from the current directory, or the file named by `--secret-config`. A custom rule needs `id`, `category`, `title`, `severity` (`CRITICAL`, `HIGH`, `MEDIUM` or `LOW`) and a Go `regex`; `keywords` makes it fast, `secret-group-name` marks the part to mask, `path` limits where it applies. Custom rules add to the built-ins. Allow-rules, globally or under one rule, take an `id` plus a `regex` matched against the found secret or a `path` matched against the file. Noise control means narrow allow-rules, `disable-rules` for services you do not use, or `enable-builtin-rules` to keep only some. The trap is the other direction: built-in allow rules already skip paths containing `test` or `example` and Markdown files, so a real key in `config/test_settings.py` is never scanned unless you list `tests` under `disable-allow-rules`. And a mistyped `--secret-config` path silently means built-ins only.

code

yaml · 21 lines
yaml
rules:
  - id: acme-deploy-token
    category: Acme
    title: Acme deploy token
    severity: HIGH
    keywords:
      - acmedt_
    regex: (?P<secret>acmedt_[0-9a-f]{32})
    secret-group-name: secret
    allow-rules:
      - id: acme-placeholder
        description: documented all-zero placeholder
        regex: acmedt_0{32}
allow-rules:
  - id: generated-fixtures
    description: fixtures regenerated by the test harness
    path: ^fixtures/generated/
disable-rules:
  - slack-web-hook
disable-allow-rules:
  - tests

go deeper

for a junior

Know that Trivy reads trivy-secret.yaml for extra secret rules and that a rule is mainly a regex with an id, title and severity.

for a middle

Explain path versus regex allow-rules, global versus per-rule scope, keywords, and how disable-rules and enable-builtin-rules interact.

for a senior

Show you would audit the built-in allow rules and the skip list for blind spots, and prove a tuned config loads and fires.

for a principal

Decide how a shared secret config is owned: who may add an allow rule, how narrow it must be, and how blind spots are reviewed.

## Where Trivy's secret rules come from Trivy's secret scanner runs by default on `image`, `fs`, `repo` and `rootfs` scans (it is not part of `trivy config`). It reads every plaintext file and applies **rules**: Go regular expressions with metadata. A large set of **built-in rules** covers common credentials, for example `aws-access-key-id`, `github-pat`, `private-key`, `slack-access-token`. A match produces a finding with the rule ID, category, severity, title, line and a masked `Match`. You extend or tune this through a YAML file. Trivy looks for `trivy-secret.yaml` in the **current working directory**, or the path given with `--secret-config`. When the file is found it logs `Loading the config file for secret scanning...`. When it is **not** found, even a path you passed explicitly, it logs that only at debug level and carries on with the built-in rules alone. ## Writing a custom rule | Field | Required | Purpose | |---|---|---| | `id` | yes | unique rule identifier, shown as `RuleID` | | `category` | yes | grouping label in reports | | `title` | yes | short human-readable name | | `severity` | yes | `CRITICAL`, `HIGH`, `MEDIUM` or `LOW` | | `regex` | yes | Go `regexp` syntax; use `(?m)` if you need `^` and `$` per line | | `keywords` | no, recommended | cheap substring pre-filter before the regex runs | | `secret-group-name` | no | named group holding the secret itself | | `path` | no | regex restricting the rule to matching file paths | | `allow-rules` | no | exceptions that apply to this rule only | Custom rules are **added** to the built-ins; `enable-builtin-rules` limits which built-ins run and does not affect custom rules. ## How allow-rules work An allow rule has an `id`, an optional `description`, and at least one of: - `regex`: matched against the **detected secret**; a match drops that finding. - `path`: matched against the **file path**; a match means the file is not scanned at all. They can sit at the top level (all rules) or under one rule (that rule only). Use them for documented placeholders, generated fixtures, a vendored directory you do not own. Each one is a blind spot, so keep the regex as narrow as the false positive. Other noise controls in the same file: - `disable-rules`: switch off built-in rules for services you do not use; it wins over `enable-builtin-rules` when an ID is in both. - `enable-builtin-rules`: run only the listed built-ins, which is much faster. - `skip-patterns`: doublestar globs for paths to skip. Setting it **replaces** the default list (`.git`, `node_modules`, lockfiles, images, archives) rather than adding to it. ## The built-in allow rules that hide real keys Trivy ships allow rules of its own, and they apply unless you disable them by ID with `disable-allow-rules`: 1. `tests`: paths matching `(^(?i)test|\/test|-test|_test|\.test)`, so `config/test_settings.py` and `src/test/` are never scanned. 2. `examples`: paths containing `example`, and any match containing `example` in any case, so `.env.example` is skipped. 3. `markdown`: files ending in `.md`. 4. `vendor`, `usr-dirs`, `locale-dir` and language-runtime directories such as `usr/local/go/`. That is sensible for third-party code, but teams commit real credentials into test settings and example env files more often than they admit. If a scan is silent on a key you can see, check these before you doubt the regex. ## Verifying a tuned config - Look for the `Loading the config file` line in the log; its absence means built-ins only. - Plant a known fake token in a scratch file and confirm your rule fires. - Re-run with the relevant built-in allow rule disabled and compare the findings. ## How custom findings flow into the report A custom rule's finding is treated like a built-in one: - its `severity` feeds the same `--severity` filter, so a `LOW` rule disappears from a report trimmed to `HIGH,CRITICAL`; - its `id` appears as `RuleID` and its `category` as `Category` in JSON output; - the part of the match captured by `secret-group-name` is masked with `*` in `Match`, so the report itself does not republish the credential. A sensible tuning loop is therefore: write the rule, set an honest severity, prove it fires on a planted fake, then add the narrowest allow rule that clears each confirmed false positive, recording why next to it in `description`.

  • Why add keywords to a custom rule if the regex already matches?
    Keywords are a cheap substring test run before the regex: a file without any keyword is skipped for that rule. On large images and monorepos that saves most of the regex work. A keyword must appear in every real secret, or the rule silently misses it.
  • You set `skip-patterns` to add `**/testdata/**`. What else changed?
    Setting `skip-patterns` replaces the default list, so `.git`, `node_modules`, lockfiles and binary files are scanned again unless you list them too. Expect slower runs and new noise; copy the defaults in alongside your pattern.

saying these in an interview costs you the question

  • A missing --secret-config file makes the scan fail loudly
  • Custom rules in trivy-secret.yaml replace all the built-in rules
  • An allow-rule regex is matched against the whole file content
  • Files under test directories are scanned like any other file
  • skip-patterns adds to the default skip list