Writing Rules
A rule is a CUE struct under rules. The rule ID is the field name. whac
loads the builtin rules and every layer in the config dir (.whac/).
Write a rule
Section titled “Write a rule”-
Create the file
.whac/acme.cue. -
Add this rule:
package whacrules: ACME_API_TOKEN: {description: "Acme API token"severity: "CRITICAL"keywords: ["acme_"]regex: #"\b(acme_[A-Za-z0-9]{32})\b"#entropy: minLen: 37tests: {mustMatch: ["token = acme_EXAMPLE0123456789abcdefghijklmno"]mustIgnore: ["token = acme_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"]}} -
Run
whac rules verify. -
Read the result. Each rule shows
PASSorFAIL.
- Expected result:
ACME_API_TOKENshowsPASS, and whac exits with code0.
How the engine evaluates a pattern rule
Section titled “How the engine evaluates a pattern rule”The engine evaluates the stages in this order. A candidate secret that fails one stage produces no finding.
- Keywords. One keyword must occur in the content. The match ignores ASCII case.
- Window. The regex runs on each line that holds a keyword. Set
multiline: truefor a secret that spans lines. The window then runs up to 64 KiB from the start of the keyword’s line. - Regex. The regex uses RE2 syntax (Go
regexp). - Secret group. The capture group
secretGroupholds the secret. The default is1. Use0for the whole match. - Safe formats. For a rule with an
entropyblock, the engine drops a UUID. - Entropy. The secret must meet the
entropyconstraints, if the rule has them. - Stopwords. The engine drops a secret that contains a stopword. The match ignores case.
- Allow regexes. The engine drops a secret that matches an
allowRegexesentry.
A keyword must occur on the first line of a multi-line secret. A ^ or $
anchor matches the start or end of the window, not of the file.
Entropy
Section titled “Entropy”whac calculates Shannon entropy in bits per character. A string of n
characters from an alphabet of k symbols reaches at most log2(min(n, k)).
- Set
entropy.minto use a fixed threshold. - Omit
entropy.minto use the default:0.75 × log2(min(n, k)). - Set
entropy.charsetto fixk. Omit it, and whac classifies each secret. - Set
entropy.minLento drop short secrets. The default is16.
| Charset | Alphabet size k |
|---|---|
hex |
16 |
alnum |
62 |
base64, base64url |
64 |
| other text | 95 |
Samples
Section titled “Samples”Every rule has samples. tests.mustMatch must contain at least one sample.
- Each
mustMatchsample must produce a finding. - Each
mustIgnoresample must produce no finding.
whac runs every sample through the engine each time it loads the rules. A
multi-line sample in a rule without multiline: true fails, and the error
tells you to set the field. A
failed sample stops the load, and whac exits with code 2. The error names
the sample by position, for example mustMatch[0]. It does not print the
sample text.
Use values that cannot be real credentials. Add an EXAMPLE marker, or use a
fictional provider such as Acme.
Check a rule without whac
Section titled “Check a rule without whac”cue vet checks that each mustMatch sample matches the regex.
- Run
whac rules export-schema. whac writes.whac/schema.cue. - Run
cue vet ./.whac.
- Expected result:
cue vetprints nothing and exits with code0.
The loader skips the exported schema.cue. Run whac rules export-schema
again after you upgrade whac.
Assignment rules
Section titled “Assignment rules”An assignment rule checks key/value pairs, not raw text. Set
kind: "assignment". Then set one or both selectors:
keyContext: a regex on the last key segment, for example(?i)token.paths: key-path globs, for example"**.credentials.password".
When a rule sets both selectors, both must match. An optional regex runs on
the value. Its secretGroup then holds the secret.
package whac
rules: ACME_CONFIG_TOKEN: { description: "Acme token in a config file" kind: "assignment" severity: "HIGH" keywords: ["acme"] keyContext: "(?i)^acme_?token$" tests: { mustMatch: [{file: "app.yaml", content: "acme_token: EXAMPLE-Q9wv7Lm2Rt"}] mustIgnore: [{file: "app.yaml", content: "acme_token: ${ACME_TOKEN}"}] }}Keywords control where an assignment rule runs on unstructured text. Without keywords, the rule runs on structured files only. See Structured Files.
Samples with a file name
Section titled “Samples with a file name”A sample is a string or a struct with file and content. The file name
selects the extractor. whac scans a string sample as an unstructured text
file.
mustMatch: [ "token = acme_EXAMPLE0123456789abcdefghijklmno", {file: "config.yaml", content: "acme:\n token: acme_EXAMPLE0123456789abcdefghijklmno\n"},]