Skip to content

CRITICAL: git guards fail open on chained inline aliases — one-level re-expansion drops command-line -c/--config-env (case C + config-env H1/H2) #964

Description

@kyle-sexton

CRITICAL — git guards fail open on chained inline aliases (real reset --hard executes)

block-dangerous-git and block-noncanonical-commit allow a dangerous git subcommand reached through a chained inline alias, and real git executes it. Ground-truth verified: a tracked file reverted DIRTY→ORIGINAL and HEAD hard-reset ("HEAD is now at … init").

Repro (both confirmed exit 0 = ALLOW, then git runs the dangerous op)

  • Case C (no config-env at all): git -c alias.rh='foo' -c alias.foo='reset --hard' rh → guard exit 0 → reset --hard runs.
  • Commit-guard variant: git -c alias.c='--config-env=alias.foo=AV foo' c → exit 0.

Root cause (isolated, airtight)

The inline-alias re-check recurses with HOOK_NO_ALIAS=1, which suppresses the alias-expansion re-check at recursion depth ≥2. It rests on the code comment at plugins/guardrails/hooks/block-dangerous-git.sh:220 — "git does not expand the first word of an expansion as another alias" — which is FALSE: git chains alias→alias (confirmed git lvl2→lvl1 exec on git-for-Windows 2.54.0). So a dangerous subcommand reached one alias-hop deeper than the guard re-checks slips to ALLOW. An UNKNOWING false premise (undisclosed in CHANGELOG/docs), not an accepted limitation.

Scope / relationship to #903

Pre-existing — parent commit c11a440 allows these identically; #903 introduces no regression. Distinct from #903's config-env reposture: the config-env-at-depth path (Case A) is being fixed within #903 (ungate the value-blind config-env SHAPE refusal from HOOK_NO_ALIAS so it fires at every depth). This issue is the BROADER hole: plain -c alias→alias chaining to a dangerous literal, which the config-env fix does not cover.

Fix direction

Reconsider the one-level-alias-recheck assumption: git genuinely chains aliases, so the alias-expansion re-check must recurse to the actual invoked subcommand (bounded by git's own alias-loop limit), not stop at depth 1. Both guards share the pattern.

Defer classification

Security-control fail-open with ground-truth destruction = defer-forbidden CLASS. Filed as a standalone CRITICAL (not deferred to a milestone) because the fix is broader than #903's reposture scope and touches the shared inline-alias resolver; must land before the guardrails work-ready freeze. Tie to the guardrails hardening umbrella (#912) as a blocker.

Origin

Independent convergence-verification of PR #903's structural reposture (tower out-of-loop, 2026-07-22 ~07:10Z), question-3 adversarial pass.

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.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions