diff --git a/plugins/guardrails/CHANGELOG.md b/plugins/guardrails/CHANGELOG.md index de79350f1..9152b3123 100644 --- a/plugins/guardrails/CHANGELOG.md +++ b/plugins/guardrails/CHANGELOG.md @@ -12,6 +12,19 @@ All notable changes to the `guardrails` plugin are documented here. Format follo stringified `"true"` / `"false"` values the jq filter produces; any other shape skips verification rather than proceeding on the permissive branch. +### Changed + +- **Eight prose references still named 0.26.0 as the release that made `block-hook-bypass`'s + exemptions operand-keyed. It is 0.27.0.** No behaviour changes, no assertions moved. The #2226 work + was written against 0.26.0 and renumbered when `main` took that version for the + `block-dangerous-git` / `block-no-verify` `jq` fail-closed change (#2146) while the branch was + open. The renumber reached the CHANGELOG heading, the manifest version and the entry's own + comparison table; it did not reach the narrative around them, so the README caveat paragraph (2), + the note above `scratch_target_exempt` (1), three comments in the contract test, and the erratum + inside the 0.25.1 entry (2) each pointed a reader at a release that documents something else + entirely. 0.26.0's own heading and entry are untouched — that is the one 0.26.0 reference in this + plugin that is correct. + ## [0.27.1] ### Fixed diff --git a/plugins/guardrails/README.md b/plugins/guardrails/README.md index 92bb03bbd..2217ee00a 100644 --- a/plugins/guardrails/README.md +++ b/plugins/guardrails/README.md @@ -115,13 +115,13 @@ out of scope until such a signal exists. fails closed rather than being documented: the quote strip drops a kept target's quotes, and the segment split would then read a `;`, `|`, `&` or space *inside* the operand as syntax, so `> "/tmp/scratch/a;/../../etc/passwd"` — - one pathname to bash — would be judged on `/tmp/scratch/a`. Since **0.26.0** + one pathname to bash — would be judged on `/tmp/scratch/a`. Since **0.27.0** the operand is **marked** wherever that would happen, so it reaches the compare as one word and the decision is made on the whole thing: an operand carrying whitespace, `;`, `|`, `&`, `(`, `)`, a newline or a backslash escape exempts nothing, and a merely quoted operand is refused by this axis on its shipped floor. **Quotes and backslashes elsewhere in the command no longer matter.** - Before 0.26.0 this test read the whole raw command tail after the first `>` + Before 0.27.0 this test read the whole raw command tail after the first `>` *character*, so a quote in an unrelated later segment, or a `>` inside quoted content, cancelled the exemption for an earlier plain write. Both were friction rather than protection and both are gone — `echo x > /tmp/scratch/f && grep foo diff --git a/plugins/guardrails/hooks/block-hook-bypass.sh b/plugins/guardrails/hooks/block-hook-bypass.sh index 72e88a781..81bdbe9e7 100755 --- a/plugins/guardrails/hooks/block-hook-bypass.sh +++ b/plugins/guardrails/hooks/block-hook-bypass.sh @@ -674,7 +674,7 @@ devnull_target_exempt() { # root only in case is therefore also exempt. # # A QUOTED OR ESCAPED redirect operand is never exempt — see the fail-closed -# tests at the top of scratch_target_exempt. Since 0.26.0 that decision is made +# tests at the top of scratch_target_exempt. Since 0.27.0 that decision is made # on the OPERAND, from the marks strip_literals attaches to it, not on the raw # command: an operand carrying whitespace, `;`, `|`, `&`, `(`, `)`, a newline or # a backslash escape is OPAQUE and exempts nothing, and a merely quoted one is diff --git a/plugins/guardrails/hooks/block-hook-bypass.test.sh b/plugins/guardrails/hooks/block-hook-bypass.test.sh index 9bd31d7a3..98b8fd921 100755 --- a/plugins/guardrails/hooks/block-hook-bypass.test.sh +++ b/plugins/guardrails/hooks/block-hook-bypass.test.sh @@ -873,7 +873,7 @@ run "scratch: case-insensitive compare (documented residual, allowed)" \ "echo hello > /tmp/SCRATCH/f" 0 "$SCRATCH_ENV=/tmp/scratch" # TRUNCATED-OPERAND FAIL-CLOSED. strip_literals keeps a quoted write target but -# drops its quotes, and before 0.26.0 normalize_segments then resolved a `;`, +# drops its quotes, and before 0.27.0 normalize_segments then resolved a `;`, # `|`, `&`, newline or space inside that operand as SYNTAX — so a quoted pathname # reached the compare as a safe-looking prefix of itself. bash treats the whole # quoted word as one pathname, so exempting the prefix would be a one-token @@ -897,7 +897,7 @@ run "scratch: even a benign quoted target is not exempted (blocked)" \ run "scratch: quoted content, unquoted target (allowed)" \ "echo \"hello world\" > /tmp/scratch/f" 0 "$SCRATCH_ENV=/tmp/scratch" # THE BREADTH THAT WAS, still pinned on both sides — but the verdicts moved in -# 0.26.0 and these four cases are where that is visible. 0.25.x could not tell +# 0.27.0 and these four cases are where that is visible. 0.25.x could not tell # operand quotes from content quotes, so it read `${COMMAND#*>}` and refused the # exemption on ANY quote or backslash after the first `>` CHARACTER anywhere in # the command (#2236). The operand marks supply that association, so the test is @@ -925,7 +925,7 @@ run "scratch: > inside single-quoted content keeps it (allowed)" \ "echo 'x > y' > /tmp/scratch/f" 0 "$SCRATCH_ENV=/tmp/scratch" # --- Redirect-operand marking (#2226) --------------------------------------- # THE REPORTED BYPASS AND ITS FAMILY. A quoted redirect operand is one pathname -# to bash. Until 0.26.0 the exemptions were decided on its first whitespace- or +# to bash. Until 0.27.0 the exemptions were decided on its first whitespace- or # separator-delimited fragment, so `> "/dev/null ../../etc/pw"` was exempted on # the word `/dev/null` while nothing named `/dev/null` was the destination. The # operand now carries an opaque mark over every character whose literal value