refactor(maestro): share cookie name + extraction across CLI and registry (NAP-273 F)
ci / rust (push) Successful in 6m45s

Promote `COOKIE_NAME` and a robust `extract_cookie` (iterates every `;`-pair,
so it works on both `Cookie:` and `Set-Cookie:` headers regardless of position)
into `maestro-protocol`. The registry re-exports them; the CLI drops its weaker
`extract_cookie_value` (which only inspected the first pair) and its duplicate
`COOKIE_NAME`. Both ends of the wire now agree on one definition.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-18 14:35:30 +00:00
co-authored by Claude Opus 4.8
parent 71a45db0a4
commit 75d162dea3
3 changed files with 42 additions and 50 deletions
+37
View File
@@ -463,6 +463,29 @@ pub struct JwtClaims {
/// secret (NAP-266).
pub const REGISTRY_ISSUER: &str = "maestro-registry";
/// Name of the auth cookie the registry sets and the CLI persists. Defined
/// once here so both ends of the wire agree (NAP-273).
pub const COOKIE_NAME: &str = "maestro_session";
/// Find a named cookie's value inside a `Cookie:` / `Set-Cookie:` header value.
///
/// Iterates every `;`-separated `name=value` pair, so it works whether the
/// target pair is first or buried among other cookies or `Set-Cookie`
/// attributes (`HttpOnly`, `Path=/`, …). Returns the first match, or `None`.
/// Shared by the registry's inbound-cookie check and the CLI's `Set-Cookie`
/// capture (NAP-273).
pub fn extract_cookie<'a>(header_value: &'a str, name: &str) -> Option<&'a str> {
for part in header_value.split(';') {
let part = part.trim();
if let Some((k, v)) = part.split_once('=') {
if k.trim() == name {
return Some(v.trim());
}
}
}
None
}
/// Pair-scoped attach credential. Issued by the registry inside
/// [`AttachCandidates::jwt`]; verified by the target daemon for direct
/// transports (T1/T2) and gated by `allow_inbound` after JWT verification.
@@ -488,6 +511,20 @@ pub struct PairAttachClaims {
mod tests {
use super::*;
#[test]
fn extract_cookie_finds_named_value_anywhere() {
// Cookie: header — pair may be buried among others.
let h = "foo=1; maestro_session=abc.def.ghi; bar=2";
assert_eq!(extract_cookie(h, COOKIE_NAME), Some("abc.def.ghi"));
assert_eq!(extract_cookie(h, "foo"), Some("1"));
assert_eq!(extract_cookie(h, "missing"), None);
// Set-Cookie: header — cookie first, attributes (no `=` or other keys)
// after it must be skipped, not matched.
let sc = "maestro_session=xyz; HttpOnly; SameSite=Strict; Path=/; Max-Age=2592000";
assert_eq!(extract_cookie(sc, COOKIE_NAME), Some("xyz"));
assert_eq!(extract_cookie("other=1; HttpOnly", COOKIE_NAME), None);
}
fn round_trip<T>(value: &T) -> T
where
T: Serialize + for<'de> Deserialize<'de>,