From 5519409f09e53e9edb80b0727dcb55e290bd9dc0 Mon Sep 17 00:00:00 2001 From: HackingGate Date: Fri, 21 Aug 2026 00:58:04 +0900 Subject: [PATCH] uphold 1.7.0: a rule may not be about its own declaration Two load-time refusals, one accident, reached from opposite directions. A policy file is a tracked file, so a rule's `regexp` and `require_regexp` are inside the corpus that rule scans, and an unanchored literal matches the line it is written on: regexp must find nothing, so a self-match is a finding that is always there, naming the rule instead of the tree require_regexp must find something, so a self-match is a pass that is always there, exempting the policy file from the rule forever Both are refused only when the rule ALSO selects the file its declaration is in. That second half is the whole design: measured over 82 policy files and 178 `regexp` rules, 54 matched their own text and none of them selected the file it was written in, so refusing on the text alone would have failed 54 working rules. 11 `require_regexp` rules, none self-satisfying in scope. Nothing in the fleet is refused by either check. What this replaces is three workarounds that spread with nothing recording any of them as the answer -- a narrow include, an explicit exclude, and breaking the literal with a one-character class, nine of those in a single pattern. The third is also applied where it does nothing: `^Sta[t]us:` appears in three repositories and the dodge is unnecessary in all of them, because an anchored pattern cannot match the key it is written under. REFERENCE.md now says so, so the next author does not have to guess. Documented pins move to v1.7.0 in README.md and hooks/lefthook.yml. Consumers pinning v1.6.0 will fail `no-stale-hook-pins` at pre-push until they take this, which is that rule working rather than a surprise. --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 6 +++--- docs/REFERENCE.md | 40 ++++++++++++++++++++++++++++++++++++++++ hooks/lefthook.yml | 4 ++-- 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbf2511..9ee2059 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -593,7 +593,7 @@ checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "uphold" -version = "1.6.0" +version = "1.7.0" dependencies = [ "encoding_rs", "globset", diff --git a/Cargo.toml b/Cargo.toml index 18ccd78..625e830 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uphold" -version = "1.6.0" +version = "1.7.0" edition = "2021" # 1.88, not 1.85: the ripgrep stack this crate embeds -- globset 0.4.20 and # ignore 0.4.33 in Cargo.lock -- refuses anything older. The 1.85 that stood here diff --git a/README.md b/README.md index ae4b1c0..1022ae5 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ bootstraps). default_install_hook_types: [pre-commit, commit-msg, pre-merge-commit, pre-push] repos: - repo: https://github.com/HackingGate/uphold - rev: v1.6.0 + rev: v1.7.0 hooks: - id: uphold-check # the claims still hold - id: uphold-scan # the content policy @@ -48,13 +48,13 @@ the binary must be on PATH. # lefthook.yml remotes: - git_url: https://github.com/HackingGate/uphold - ref: v1.6.0 + ref: v1.7.0 configs: - hooks/lefthook.yml ``` ```sh -cargo install --git https://github.com/HackingGate/uphold --tag v1.6.0 +cargo install --git https://github.com/HackingGate/uphold --tag v1.7.0 ``` That `ref:` is the one version a lefthook consumer pins, and **Dependabot does diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index c8267ea..103ca66 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -72,6 +72,46 @@ every git hook, and a repository whose own prose cites its issues would have every one of those citations refused — so the seam it belongs at is the command that publishes text to a forge, and only that one. +### A rule may not be about its own declaration + +A policy file is a tracked file, so a rule's `regexp` and `require_regexp` are +inside the corpus that rule scans. An unanchored literal therefore matches the +line it is written on, and both fields reach the same accident from opposite +directions: + +| field | must find | a self-match is | +|---|---|---| +| `regexp` | nothing | a finding that is always there, naming the rule instead of the tree | +| `require_regexp` | something | a pass that is always there, exempting the policy file forever | + +**A rule that matches its own declaration *and* selects the file that +declaration is in is refused at load.** Both halves are required. Most rules +never select the policy file — an `include` of `["cmd", "internal"]` with a +`glob` of `["*.go"]` cannot reach `policy/` — and a pattern matching its own +text under such a rule is harmless, so the scope test comes first. + +Three cures, and the refusal names all of them: + +```toml +[rule.no-yubikey-mentions] +regexp = '\bYubiKey\b' +[rule.no-yubikey-mentions.files] +include = ["."] +exclude = ["policy/**"] # 1. exclude the policy file +``` + +2. narrow `files.include` to what the rule is actually about; +3. anchor the pattern, so it cannot match the key it is written under. + +The third is worth knowing before reaching for the first. `^Status:` does not +match `regexp = '^Status:...'`, because that line begins with `regexp` — so an +anchored pattern needs nothing, and a one-character class written to dodge a +self-match it never had (`^Sta[t]us:`) is a defensive edit that can be deleted. + +Own rules only. A bundled set's rule is declared inside the binary and an +`inherit.paths` rule in a file the rule may not select; neither has a +declaration in this policy file to match. + Exit codes: `0` clean, `1` violations, `2` the check could not be made. There is no fourth. A reader that closes a pipe — `uphold scan | head` — is a diff --git a/hooks/lefthook.yml b/hooks/lefthook.yml index 0abdbe0..ad6de32 100644 --- a/hooks/lefthook.yml +++ b/hooks/lefthook.yml @@ -10,7 +10,7 @@ # # lefthook.yml, in the consuming repository # remotes: # - git_url: https://github.com/HackingGate/uphold -# ref: v1.6.0 +# ref: v1.7.0 # configs: # - hooks/lefthook.yml # @@ -21,7 +21,7 @@ # their manifest contract; lefthook runs commands and has no such contract, so # `uphold` must be on PATH: # -# cargo install --git https://github.com/HackingGate/uphold --tag v1.6.0 +# cargo install --git https://github.com/HackingGate/uphold --tag v1.7.0 # # The names below are prefixed `uphold-` on purpose. A remote config is # merged into the consumer's own, and two commands sharing a name under one hook