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.
CRITICAL — git guards fail open on chained inline aliases (real
reset --hardexecutes)block-dangerous-gitandblock-noncanonical-commitallow 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)
git -c alias.rh='foo' -c alias.foo='reset --hard' rh→ guard exit 0 →reset --hardruns.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 atplugins/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 (confirmedgit 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_ALIASso it fires at every depth). This issue is the BROADER hole: plain-c alias→aliaschaining 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.