151b3f0f
Hoist the delegate ceiling above the governance-state match
a73x 2026-08-18 17:26
Commit message
src/server/governance/hook.rs
| Old | New | ||
|---|---|---|---|
| @@ -122,6 +122,23 @@ pub fn run(refname: &str, old: &str, new: &str) -> Result<(), String> { | |||
| 122 | // Empty is legitimate: an ungoverned server has no names to pass. | 122 | // Empty is legitimate: an ungoverned server has no names to pass. |
| 123 | let principal = std::env::var(ENV_PRINCIPAL).unwrap_or_default(); | 123 | let principal = std::env::var(ENV_PRINCIPAL).unwrap_or_default(); |
| 124 | 124 | ||
| 125 | // The delegate ceiling. Checked ahead of and independent of governance | ||
| 126 | // state: ENV_DELEGATE is set only by the server, only for a delegate | ||
| 127 | // session, so its presence alone is a sufficient trigger. Nesting this | ||
| 128 | // inside `GovernanceState::Active` would let a settings repository that | ||
| 129 | // goes briefly unreadable-as-Absent between the session's regime check | ||
| 130 | // and this hook's own re-read skip the ceiling entirely — hard-coded | ||
| 131 | // rather than configured, so no line in access.conf can widen it either. | ||
| 132 | let delegate = std::env::var(ENV_DELEGATE).ok().filter(|v| !v.is_empty()); | ||
| 133 | if let Some(key_id) = &delegate { | ||
| 134 | if !refname.starts_with("refs/collab/") { | ||
| 135 | return Err(format!( | ||
| 136 | "delegate {key_id} of {principal} may only write refs/collab/*, \ | ||
| 137 | not {refname}" | ||
| 138 | )); | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 125 | let old = parse_oid(old)?; | 142 | let old = parse_oid(old)?; |
| 126 | let new = parse_oid(new)?; | 143 | let new = parse_oid(new)?; |
| 127 | 144 | ||
| @@ -138,17 +155,6 @@ pub fn run(refname: &str, old: &str, new: &str) -> Result<(), String> { | |||
| 138 | if principal.is_empty() { | 155 | if principal.is_empty() { |
| 139 | return Err("no authenticated principal on this push".to_string()); | 156 | return Err("no authenticated principal on this push".to_string()); |
| 140 | } | 157 | } |
| 141 | // The delegate ceiling. Hard-coded rather than configured: no line in | ||
| 142 | // access.conf can widen what a certificate may write. | ||
| 143 | let delegate = std::env::var(ENV_DELEGATE).ok().filter(|v| !v.is_empty()); | ||
| 144 | if let Some(key_id) = &delegate { | ||
| 145 | if !refname.starts_with("refs/collab/") { | ||
| 146 | return Err(format!( | ||
| 147 | "delegate {key_id} of {principal} may only write refs/collab/*, \ | ||
| 148 | not {refname}" | ||
| 149 | )); | ||
| 150 | } | ||
| 151 | } | ||
| 152 | 158 | ||
| 153 | let creator = creator_of(&repo_path); | 159 | let creator = creator_of(&repo_path); |
| 154 | let subject = Subject::with_creator(&principal, creator.as_deref()); | 160 | let subject = Subject::with_creator(&principal, creator.as_deref()); |