docs(domain): state the assumptions behind the branch prefix check
No symbolic refs under refs/heads besides HEAD, and case-sensitive ref storage.
- commit
- 14b68ead1628d55bfdc5e263a745ed0bc077fe12
- author
- brice · · 2026-10-12 15:17 UTC
- parent
- e751749
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +13 -0
M internal/domain/principal.go +8 -0
| @@ -117,6 +117,14 @@ func (p Principal) BranchPrefixes() []string { return slices.Clone(p.branchPrefi | |||
| 117 | 117 | // it). Without branch prefixes every ref is allowed. With them, only a branch | |
| 118 | 118 | // "refs/heads/<name>" under one of the prefixes is: tags, every other ref and | |
| 119 | 119 | // a branch outside the prefixes are not. | |
| 120 | + | // | |
| 121 | + | // It judges the name of the ref only, and rests on two assumptions about the | |
| 122 | + | // repositories ermite serves. First, no symbolic ref lives under refs/heads | |
| 123 | + | // (HEAD is the only symref): ermite never creates one and receive-pack cannot, | |
| 124 | + | // so a name under a prefix never points at a branch outside it. Second, ref | |
| 125 | + | // names are stored case-sensitively (Linux file systems): "refs/heads/Agent/x" | |
| 126 | + | // is not "refs/heads/agent/x". On a case-insensitive file system the two could | |
| 127 | + | // name the same ref and the prefixes would not hold. | |
| 120 | 128 | func (p Principal) AllowsRef(ref string) bool { | |
| 121 | 129 | if len(p.branchPrefixes) == 0 { | |
| 122 | 130 | return true | |
| @@ -117,6 +117,14 @@ func (p Principal) BranchPrefixes() []string { return slices.Clone(p.branchPrefi | |||||
| 117 | // it). Without branch prefixes every ref is allowed. With them, only a branch | 117 | // it). Without branch prefixes every ref is allowed. With them, only a branch | ||
| 118 | // "refs/heads/<name>" under one of the prefixes is: tags, every other ref and | 118 | // "refs/heads/<name>" under one of the prefixes is: tags, every other ref and | ||
| 119 | // a branch outside the prefixes are not. | 119 | // a branch outside the prefixes are not. | ||
| 120 | + | // | |||
| 121 | + | // It judges the name of the ref only, and rests on two assumptions about the | |||
| 122 | + | // repositories ermite serves. First, no symbolic ref lives under refs/heads | |||
| 123 | + | // (HEAD is the only symref): ermite never creates one and receive-pack cannot, | |||
| 124 | + | // so a name under a prefix never points at a branch outside it. Second, ref | |||
| 125 | + | // names are stored case-sensitively (Linux file systems): "refs/heads/Agent/x" | |||
| 126 | + | // is not "refs/heads/agent/x". On a case-insensitive file system the two could | |||
| 127 | + | // name the same ref and the prefixes would not hold. | |||
| 120 | func (p Principal) AllowsRef(ref string) bool { | 128 | func (p Principal) AllowsRef(ref string) bool { | ||
| 121 | if len(p.branchPrefixes) == 0 { | 129 | if len(p.branchPrefixes) == 0 { | ||
| 122 | return true | 130 | return true | ||
M internal/usecase/pushes.go +5 -0
| @@ -104,6 +104,11 @@ func openExisting(p domain.Principal, repo domain.Repo) error { | |||
| 104 | 104 | // (protected branches, limits) will refuse here too, by returning an error | |
| 105 | 105 | // that matches domain.ErrRejected. | |
| 106 | 106 | // | |
| 107 | + | // The prefix rule is domain.Principal.AllowsRef and shares its assumptions: no | |
| 108 | + | // symbolic ref under refs/heads besides HEAD (ermite never creates one and | |
| 109 | + | // receive-pack cannot), and case-sensitive ref storage (Linux). The hook sees | |
| 110 | + | // every ref of the push, so a tag or any other ref is judged like a branch. | |
| 111 | + | // | |
| 107 | 112 | // The token of the push was valid when the request began, but a push can run | |
| 108 | 113 | // for long: it is re-validated here, so a token revoked or expired meanwhile | |
| 109 | 114 | // stops the push before Git applies it. A failure to tell refuses it too. | |
| @@ -104,6 +104,11 @@ func openExisting(p domain.Principal, repo domain.Repo) error { | |||||
| 104 | // (protected branches, limits) will refuse here too, by returning an error | 104 | // (protected branches, limits) will refuse here too, by returning an error | ||
| 105 | // that matches domain.ErrRejected. | 105 | // that matches domain.ErrRejected. | ||
| 106 | // | 106 | // | ||
| 107 | + | // The prefix rule is domain.Principal.AllowsRef and shares its assumptions: no | |||
| 108 | + | // symbolic ref under refs/heads besides HEAD (ermite never creates one and | |||
| 109 | + | // receive-pack cannot), and case-sensitive ref storage (Linux). The hook sees | |||
| 110 | + | // every ref of the push, so a tag or any other ref is judged like a branch. | |||
| 111 | + | // | |||
| 107 | // The token of the push was valid when the request began, but a push can run | 112 | // The token of the push was valid when the request began, but a push can run | ||
| 108 | // for long: it is re-validated here, so a token revoked or expired meanwhile | 113 | // for long: it is re-validated here, so a token revoked or expired meanwhile | ||
| 109 | // stops the push before Git applies it. A failure to tell refuses it too. | 114 | // stops the push before Git applies it. A failure to tell refuses it too. | ||