Hosted lane: pin the key shape rule and the absent prompt count
Adds hosted_binding_refuses_a_key_shape_that_cannot_ride_a_header (interior newline, CR, tab, NUL, leading space, trailing space, non-ASCII, leading newline; typed refusal at open, no key or path echoed) and the missing prompt_tokens half of the usage-absence test. Builds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b0f57a009b
commit
8d263bb60e
1 changed files with 35 additions and 2 deletions
|
|
@ -1990,6 +1990,36 @@ mod tests {
|
||||||
assert_eq!(read_key_file(&ok).unwrap().as_str(), "fine-key");
|
assert_eq!(read_key_file(&ok).unwrap().as_str(), "fine-key");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A bearer token is one run of visible ASCII. Each shape that cannot ride
|
||||||
|
/// an Authorization header is refused at open with the typed fault, and the
|
||||||
|
/// refusal carries neither the key text nor the path.
|
||||||
|
#[test]
|
||||||
|
fn hosted_binding_refuses_a_key_shape_that_cannot_ride_a_header() {
|
||||||
|
let directory = tempfile::tempdir().unwrap();
|
||||||
|
let cases: [(&str, &[u8]); 8] = [
|
||||||
|
("interior newline", b"keycanary\nsecondline"),
|
||||||
|
("interior carriage return", b"keycanary\rsecondline"),
|
||||||
|
("interior tab", b"keycanary\tsecondline"),
|
||||||
|
("interior NUL", b"keycanary\0secondline"),
|
||||||
|
("leading space", b" keycanary"),
|
||||||
|
("trailing space before the newline", b"keycanary \n"),
|
||||||
|
("non-ascii", "keycanary-\u{e9}".as_bytes()),
|
||||||
|
("leading newline", b"\nkeycanary"),
|
||||||
|
];
|
||||||
|
for (label, content) in cases {
|
||||||
|
let path = write_key_file(&directory, content);
|
||||||
|
let canary = path.display().to_string();
|
||||||
|
let error = open_hosted_port(hosted_binding("https://api.example.test", path))
|
||||||
|
.err()
|
||||||
|
.unwrap_or_else(|| panic!("{label}: a key that cannot ride a header opened"));
|
||||||
|
assert!(matches!(error, ControllerOpenError::HostedKeyUnreadable), "{label}");
|
||||||
|
let rendered = format!("{error} | {error:?}");
|
||||||
|
assert!(!rendered.contains("keycanary"), "{label}: the key leaked");
|
||||||
|
assert!(!rendered.contains("secondline"), "{label}: the key leaked");
|
||||||
|
assert!(!rendered.contains(&canary), "{label}: the path leaked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn hosted_request_carries_the_bearer_and_loopback_carries_none() {
|
async fn hosted_request_carries_the_bearer_and_loopback_carries_none() {
|
||||||
let directory = tempfile::tempdir().unwrap();
|
let directory = tempfile::tempdir().unwrap();
|
||||||
|
|
@ -2119,6 +2149,7 @@ mod tests {
|
||||||
(200, absent),
|
(200, absent),
|
||||||
(200, usage_reply(json!({"prompt_tokens": 0, "completion_tokens": 0}))),
|
(200, usage_reply(json!({"prompt_tokens": 0, "completion_tokens": 0}))),
|
||||||
(200, usage_reply(json!({"prompt_tokens": 9}))),
|
(200, usage_reply(json!({"prompt_tokens": 9}))),
|
||||||
|
(200, usage_reply(json!({"completion_tokens": 9}))),
|
||||||
])
|
])
|
||||||
.await;
|
.await;
|
||||||
let port = port(responder.endpoint());
|
let port = port(responder.endpoint());
|
||||||
|
|
@ -2126,10 +2157,12 @@ mod tests {
|
||||||
let prepared = port.prepare(plain_request()).expect("the port prepares");
|
let prepared = port.prepare(plain_request()).expect("the port prepares");
|
||||||
let none = port.infer(prepared.clone()).await.unwrap();
|
let none = port.infer(prepared.clone()).await.unwrap();
|
||||||
let zeroes = port.infer(prepared.clone()).await.unwrap();
|
let zeroes = port.infer(prepared.clone()).await.unwrap();
|
||||||
let half = port.infer(prepared).await.unwrap();
|
let half = port.infer(prepared.clone()).await.unwrap();
|
||||||
|
let other_half = port.infer(prepared).await.unwrap();
|
||||||
assert_eq!(none.usage(), None);
|
assert_eq!(none.usage(), None);
|
||||||
assert_eq!(zeroes.usage(), Some(TokenUsage { prompt: 0, completion: 0, cached_prompt: None }));
|
assert_eq!(zeroes.usage(), Some(TokenUsage { prompt: 0, completion: 0, cached_prompt: None }));
|
||||||
assert_eq!(half.usage(), None, "a reply missing a count reported a zero");
|
assert_eq!(half.usage(), None, "a reply missing the completion count reported a zero");
|
||||||
|
assert_eq!(other_half.usage(), None, "a reply missing the prompt count reported a zero");
|
||||||
assert_eq!(none.receipt_digest(), zeroes.receipt_digest());
|
assert_eq!(none.receipt_digest(), zeroes.receipt_digest());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue