Skip to content

security(trust-gates): three residual approval-signature bypasses — escaped scalars, evaluated code, qualified loader names #1489

Description

@kyle-sexton

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:

{ "customRules": ["./rules\/local.cjs"] }

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-formatInvoke-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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: securitySecurity-relevant: vulnerability, hardening, or disclosure follow-up.needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.priority: highSignificant impact, or blocks an imminent release; staff this cycle.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions