Skip to content

fix(git): reject tabs in ref names, git refuses them too - #1053

Merged
BryanFRD merged 2 commits into
fix/forge-detection-by-hostfrom
fix/reject-tab-in-refnames
Sep 7, 2026
Merged

BryanFRD merged 2 commits into
fix/forge-detection-by-hostfrom
fix/reject-tab-in-refnames

Conversation

@BryanFRD

@BryanFRD BryanFRD commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Closes #798.

Two parts of that issue's analysis are wrong, and the fix here is smaller than what it proposed. Checked both before writing any code.

parse_ls_remote_tags is not broken. split_once('\t') keeps everything after the first tab, so a hypothetical sha<TAB>refs/tags/foo<TAB>bar line yields refname = "refs/tags/foo\tbar" and strip_prefix("refs/tags/") then gives "foo\tbar", which is correct. No parser change is needed.

The scenario is unreachable anyway, because git rejects tabs in ref names. A tab is an ASCII control character and check-ref-format refuses it:

$ git check-ref-format $'refs/tags/foo\tbar'; echo $?
1
$ git tag $'foo\tbar'
fatal: 'foo	bar' is not a valid tag name.

So the real defect is only in the validator. ensure_safe_refname_fragment carved tabs out of its control-character check on the premise that "tabs are technically allowed in refs", which is not true. The effect was that a tab in a tagTemplate got past our own check and failed later as an opaque fatal: '...' is not a valid tag name from a git subprocess, instead of the validator's actionable message.

Removing the carve-out collapses the whole condition to ch.is_control(), since \0, \n, \r and \x7f are all in Unicode category Cc and were already listed individually.

Verification

rejects_tab and every_rejected_control_char_is_one_git_also_refuses both fail against the previous condition:

test git::validate::tests::rejects_tab ... FAILED
test git::validate::tests::every_rejected_control_char_is_one_git_also_refuses ... FAILED

The second test is the one that matters: for each character the validator rejects it shells out to git check-ref-format and asserts git refuses it too, so the validator can never drift into rejecting a name git would have accepted. \0 is excluded from that loop because an interior NUL cannot be passed through argv at all; rejects_null_byte still covers it on our side.


Second layer of a stack of four. Based on #1052, and #1054 sits on top.

@ferrfleet ferrfleet Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change is correct. The carve-out removal is a strict narrowing over what git itself accepts, and all four call sites (create_tag, create_or_move_tag, remote name and branch name in push.rs) are real refnames, so nothing legitimate loses. The old condition already contained ch.is_control(), so collapsing to it is behaviour-preserving apart from the tab.

One nit inline on the new test's name.

Comment thread src/git/validate.rs Outdated
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

SonarQube — aucune nouvelle issue

Comparaison entre le projet bac à sable de cette PR et la branche par défaut : SonarQube Community n'analyse pas les PR, ce delta est calculé côté CI. Détail

@BryanFRD
BryanFRD force-pushed the fix/reject-tab-in-refnames branch from 004434a to 298051e Compare September 7, 2026 12:22
@BryanFRD

BryanFRD commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Renamed in 298051e to every_rejected_ascii_control_char_is_one_git_also_refuses.

Verified the premise rather than taking it on trust, and it holds. git accepts a C1 byte in a refname and rejects an ASCII one:

$ git check-ref-format "refs/tags/foo$(printf '\xc2\x85')bar"   # U+0085
ACCEPTED
$ git check-ref-format "refs/tags/foo$(printf '\x05')bar"
REJECTED

So the validator is deliberately stricter than git across the C1 range, which the existing doc comment already allows for ("not the full check-ref-format algorithm"). Only the test name was overclaiming, and the loop itself was always ASCII-only.

@BryanFRD
BryanFRD force-pushed the fix/reject-tab-in-refnames branch from 298051e to a29844a Compare September 7, 2026 13:15
@BryanFRD
BryanFRD merged commit 50d9435 into main Sep 7, 2026
37 of 56 checks passed
@BryanFRD
BryanFRD deleted the fix/reject-tab-in-refnames branch September 7, 2026 17:07
ferrflow Bot added a commit that referenced this pull request Sep 7, 2026
## [7.21.1] - 2026-09-07

### Bug Fixes

- fix(git): reject tabs in ref names, git refuses them too (#1053)
- fix(git): pick the forge from the remote host, not the whole URL (#1052)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(git): the refname validator allows TAB but the ls-remote parsers split on TAB

1 participant