Summary
Three residual approval-signature bypasses in the trust gates #1097 shipped. Each was verified to let something through — not merely to be an unenumerated shape — so each is a genuine bypass rather than a stylistic gap.
Fixes are written, tested and pushed to branch fix/trust-gate-residual-bypasses (no PR opened deliberately; promote it whenever you choose).
1. markdown-format — a backslash escape in a quoted module value
0.7.0's escape tier matched only \uXXXX in JSONC and an enumerated \x/\u/\U list in YAML. So this slipped past:
JSON defines \/, which decodes to a plain /. The collector hashed the raw backslash-bearing spelling rather than the ./rules/local.cjs file markdownlint decodes it to and loads, so an approval stayed valid while that rule module was edited — repository code then executed on the next markdown edit without reapproval.
Fix: tier two now matches ANY backslash inside a quoted scalar, in both grammars. Enumerating escapes is the same unbounded shape that reopened the specifier findings across four review rounds; the only property that matters is whether the raw text the scan resolves can differ from the decoded path, and a backslash is exactly that signal whatever follows it.
2. powershell-format — Invoke-Expression with a constant argument
iex '. "$PSScriptRoot/helper.ps1"'
The argument is a constant string, so the AST walk accepted it and queued it as a path candidate. Nothing resolved, and the file the evaluated string dot-sources never entered the signature. Changing helper.ps1 preserved the approval.
Fix: Invoke-Expression/iex refuses approval outright, in every form. Binding it would mean recursively parsing evaluated text; a linter rule has no reason to evaluate text, so refusing is the honest answer rather than a capability worth preserving.
3. powershell-format — a module-qualified loader name
Microsoft.PowerShell.Core\Import-Module ./deps/helper.psm1
GetCommandName() returns the qualified spelling, which the exact-name membership test did not recognize as the loader it resolves to. The path is unquoted, so the text scan missed it too, and the dependency never entered the signature.
Fix: the bare name after the last qualifier separator is what the loader and evaluator tests now see.
Verification
On fix/trust-gate-residual-bypasses:
markdown-format 94/94, powershell-format 74/74, url-policy 15/15
- new fixtures:
escaped module value in jsonc refuses approval, escaped module value in yaml refuses approval, unpinnable load target (evaluated), unpinnable load target (qualified)
shellcheck, markdownlint, typos, check-changelog-parity --check-bump, sync-hook-utils --check/--check-bump, validate-plugins all clean
- version bumps + changelog sections:
markdown-format 0.7.1, powershell-format 0.6.1, ai-briefing 0.6.4
Caveat carried over from #1488 (url-policy tests never run in CI): the url-policy suite does not run in CI, so its 15/15 is a local result only.
Provenance
Filed from review rounds on #1097 that arrived after the merge decision. Classified at the time as instance-level rather than new-class, but each is an exploitable bypass, so they are fixed rather than deferred on the label.
Summary
Three residual approval-signature bypasses in the trust gates #1097 shipped. Each was verified to let something through — not merely to be an unenumerated shape — so each is a genuine bypass rather than a stylistic gap.
Fixes are written, tested and pushed to branch
fix/trust-gate-residual-bypasses(no PR opened deliberately; promote it whenever you choose).1.
markdown-format— a backslash escape in a quoted module value0.7.0's escape tier matched only\uXXXXin JSONC and an enumerated\x/\u/\Ulist in YAML. So this slipped past:{ "customRules": ["./rules\/local.cjs"] }JSON defines
\/, which decodes to a plain/. The collector hashed the raw backslash-bearing spelling rather than the./rules/local.cjsfile markdownlint decodes it to and loads, so an approval stayed valid while that rule module was edited — repository code then executed on the next markdown edit without reapproval.Fix: tier two now matches ANY backslash inside a quoted scalar, in both grammars. Enumerating escapes is the same unbounded shape that reopened the specifier findings across four review rounds; the only property that matters is whether the raw text the scan resolves can differ from the decoded path, and a backslash is exactly that signal whatever follows it.
2.
powershell-format—Invoke-Expressionwith a constant argumentiex '. "$PSScriptRoot/helper.ps1"'The argument is a constant string, so the AST walk accepted it and queued it as a path candidate. Nothing resolved, and the file the evaluated string dot-sources never entered the signature. Changing
helper.ps1preserved the approval.Fix:
Invoke-Expression/iexrefuses approval outright, in every form. Binding it would mean recursively parsing evaluated text; a linter rule has no reason to evaluate text, so refusing is the honest answer rather than a capability worth preserving.3.
powershell-format— a module-qualified loader nameGetCommandName()returns the qualified spelling, which the exact-name membership test did not recognize as the loader it resolves to. The path is unquoted, so the text scan missed it too, and the dependency never entered the signature.Fix: the bare name after the last qualifier separator is what the loader and evaluator tests now see.
Verification
On
fix/trust-gate-residual-bypasses:markdown-format94/94,powershell-format74/74,url-policy15/15escaped module value in jsonc refuses approval,escaped module value in yaml refuses approval,unpinnable load target (evaluated),unpinnable load target (qualified)shellcheck,markdownlint,typos,check-changelog-parity --check-bump,sync-hook-utils --check/--check-bump,validate-pluginsall cleanmarkdown-format0.7.1,powershell-format0.6.1,ai-briefing0.6.4Caveat carried over from #1488 (url-policy tests never run in CI): the
url-policysuite does not run in CI, so its 15/15 is a local result only.Provenance
Filed from review rounds on #1097 that arrived after the merge decision. Classified at the time as instance-level rather than new-class, but each is an exploitable bypass, so they are fixed rather than deferred on the label.