mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-06 17:37:47 +08:00
fix(usage): preserve original captured HTTP headers
This commit is contained in:
@@ -865,12 +865,12 @@ pub fn build_terminal_usage_context_seed(
|
||||
request_type,
|
||||
is_stream: plan.stream,
|
||||
routing: build_runtime_routing_seed(plan, context),
|
||||
request_headers: mask_sensitive_headers_in_json_value(context_value(
|
||||
request_headers: validate_usage_headers_in_json_value(context_value(
|
||||
context,
|
||||
"original_headers",
|
||||
)),
|
||||
request_body: request_capture.request_body,
|
||||
provider_request_headers: mask_sensitive_headers_in_json_value(
|
||||
provider_request_headers: validate_usage_headers_in_json_value(
|
||||
context_usage_value(context, "provider_request_headers")
|
||||
.or_else(|| headers_to_json(&plan.headers)),
|
||||
),
|
||||
@@ -2497,7 +2497,7 @@ fn sanitize_usage_event_data(mut data: UsageEventData) -> UsageEventData {
|
||||
}
|
||||
|
||||
fn sanitize_usage_header_capture(value: Option<Value>) -> Option<Value> {
|
||||
mask_sensitive_headers_in_json_value(value).map(capture_usage_storage_value)
|
||||
validate_usage_headers_in_json_value(value).map(capture_usage_storage_value)
|
||||
}
|
||||
|
||||
fn trim_owned_non_empty_string(value: String) -> Option<String> {
|
||||
@@ -2720,30 +2720,19 @@ fn headers_to_json(headers: &BTreeMap<String, String>) -> Option<Value> {
|
||||
return None;
|
||||
}
|
||||
Some(Value::Object(Map::from_iter(headers.iter().map(
|
||||
|(key, value)| (key.clone(), Value::String(mask_header_value(key, value))),
|
||||
|(key, value)| (key.clone(), Value::String(value.clone())),
|
||||
))))
|
||||
}
|
||||
|
||||
const REDACTED_USAGE_VALUE: &str = "[redacted]";
|
||||
|
||||
fn is_sensitive_header(name: &str) -> bool {
|
||||
aether_data_contracts::repository::usage::usage_header_value_is_sensitive(name)
|
||||
}
|
||||
|
||||
fn mask_header_value(name: &str, value: &str) -> String {
|
||||
if !is_sensitive_header(name) {
|
||||
return value.to_string();
|
||||
}
|
||||
mask_sensitive_header_value(value)
|
||||
}
|
||||
|
||||
fn mask_sensitive_header_value(_value: &str) -> String {
|
||||
REDACTED_USAGE_VALUE.to_string()
|
||||
}
|
||||
|
||||
/// Non-object values cannot be established as a valid header map and are
|
||||
/// discarded instead of being persisted verbatim.
|
||||
fn mask_sensitive_headers_in_json_value(value: Option<Value>) -> Option<Value> {
|
||||
fn validate_usage_headers_in_json_value(value: Option<Value>) -> Option<Value> {
|
||||
aether_data_contracts::repository::usage::sanitize_usage_headers_for_persistence(value)
|
||||
}
|
||||
|
||||
@@ -3509,11 +3498,10 @@ mod tests {
|
||||
build_terminal_usage_event_from_seed, build_usage_event_data_seed,
|
||||
build_usage_event_data_seed_describing_request_bodies, decode_body_for_storage,
|
||||
extract_token_counts_from_json, extract_token_counts_from_value, headers_to_json,
|
||||
mask_header_value, mask_sensitive_body_fields, mask_sensitive_headers_in_json_value,
|
||||
parse_sse_body_for_storage, resolve_error_message, trim_owned_non_empty_string,
|
||||
LifecycleUsageSeed, TerminalUsageSeed, UsageBodyRefsSeed, UsageBodyStatesSeed,
|
||||
UsageRoutingSeed, UsageTerminalState, MAX_USAGE_CAPTURE_BYTES, MAX_USAGE_CAPTURE_DEPTH,
|
||||
REDACTED_USAGE_VALUE,
|
||||
mask_sensitive_body_fields, parse_sse_body_for_storage, resolve_error_message,
|
||||
trim_owned_non_empty_string, validate_usage_headers_in_json_value, LifecycleUsageSeed,
|
||||
TerminalUsageSeed, UsageBodyRefsSeed, UsageBodyStatesSeed, UsageRoutingSeed,
|
||||
UsageTerminalState, MAX_USAGE_CAPTURE_BYTES, MAX_USAGE_CAPTURE_DEPTH,
|
||||
};
|
||||
use crate::{
|
||||
build_upsert_usage_record_from_event, GatewayStreamReportRequest, GatewaySyncReportRequest,
|
||||
@@ -5660,14 +5648,14 @@ mod tests {
|
||||
assert_eq!(
|
||||
event.data.response_headers,
|
||||
Some(json!({
|
||||
"authorization": REDACTED_USAGE_VALUE,
|
||||
"authorization": "Bearer very-secret-token",
|
||||
"content-type": "application/json"
|
||||
}))
|
||||
);
|
||||
assert_eq!(
|
||||
event.data.client_response_headers,
|
||||
Some(json!({
|
||||
"authorization": REDACTED_USAGE_VALUE,
|
||||
"authorization": "Bearer very-secret-token",
|
||||
"content-type": "application/json"
|
||||
}))
|
||||
);
|
||||
@@ -6560,7 +6548,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manual_terminal_seed_event_builder_sanitizes_headers_and_metadata_but_preserves_bodies() {
|
||||
fn manual_terminal_seed_event_builder_sanitizes_metadata_but_preserves_headers_and_bodies() {
|
||||
let event = build_terminal_usage_event_from_seed(TerminalUsageSeed {
|
||||
terminal_state: UsageTerminalState::Completed,
|
||||
client_contract: "openai:chat".to_string(),
|
||||
@@ -6631,26 +6619,26 @@ mod tests {
|
||||
assert_eq!(
|
||||
event.data.request_headers,
|
||||
Some(json!({
|
||||
"authorization": REDACTED_USAGE_VALUE,
|
||||
"authorization": "Bearer very-secret-token",
|
||||
"accept": "application/json"
|
||||
}))
|
||||
);
|
||||
assert_eq!(
|
||||
event.data.provider_request_headers,
|
||||
Some(json!({
|
||||
"x-api-key": REDACTED_USAGE_VALUE
|
||||
"x-api-key": "sk-proj-super-secret"
|
||||
}))
|
||||
);
|
||||
assert_eq!(
|
||||
event.data.response_headers,
|
||||
Some(json!({
|
||||
"set-cookie": REDACTED_USAGE_VALUE
|
||||
"set-cookie": "session=extremely-secret-cookie"
|
||||
}))
|
||||
);
|
||||
assert_eq!(
|
||||
event.data.client_response_headers,
|
||||
Some(json!({
|
||||
"authorization": REDACTED_USAGE_VALUE
|
||||
"authorization": "Bearer client-secret"
|
||||
}))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -6727,7 +6715,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_event_data_seed_masks_headers_before_outcome_paths_use_it() {
|
||||
fn usage_event_data_seed_preserves_headers_before_outcome_paths_use_it() {
|
||||
let plan = ExecutionPlan {
|
||||
request_id: "req-seed-sanitize-1".to_string(),
|
||||
candidate_id: Some("cand-seed-sanitize-1".to_string()),
|
||||
@@ -6766,7 +6754,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
data.request_headers,
|
||||
Some(json!({
|
||||
"authorization": REDACTED_USAGE_VALUE,
|
||||
"authorization": "Bearer outcome-secret",
|
||||
"accept": "application/json"
|
||||
}))
|
||||
);
|
||||
@@ -6945,38 +6933,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn masks_known_sensitive_header_values() {
|
||||
let token = "Bearer eyJhbGciOiJSUzI1NiJ9.payload-here.signature-tail";
|
||||
let masked = mask_header_value("authorization", token);
|
||||
assert_eq!(masked, REDACTED_USAGE_VALUE);
|
||||
|
||||
// 大小写不敏感
|
||||
assert_eq!(
|
||||
mask_header_value("Authorization", "12345678"),
|
||||
REDACTED_USAGE_VALUE,
|
||||
);
|
||||
assert_eq!(
|
||||
mask_header_value("X-Api-Key", "abcdefghij"),
|
||||
REDACTED_USAGE_VALUE,
|
||||
);
|
||||
|
||||
// Unknown custom headers are redacted by default.
|
||||
assert_eq!(
|
||||
mask_header_value("user-agent", "codex-tui/0.1"),
|
||||
REDACTED_USAGE_VALUE,
|
||||
);
|
||||
assert_eq!(
|
||||
mask_header_value("x-custom-auth", "tenant-secret"),
|
||||
REDACTED_USAGE_VALUE,
|
||||
);
|
||||
assert_eq!(
|
||||
mask_header_value("content-type", "application/json"),
|
||||
"application/json",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headers_to_json_masks_sensitive_headers_at_source() {
|
||||
fn headers_to_json_preserves_original_headers_at_source() {
|
||||
let mut headers = BTreeMap::new();
|
||||
headers.insert(
|
||||
"authorization".to_string(),
|
||||
@@ -6995,17 +6952,17 @@ mod tests {
|
||||
.get("authorization")
|
||||
.and_then(|v| v.as_str())
|
||||
.expect("authorization should be string");
|
||||
assert_eq!(auth, REDACTED_USAGE_VALUE);
|
||||
assert_eq!(auth, "Bearer eyJhbGciOiJSUzI1NiJ9.body.signature");
|
||||
|
||||
let api_key = object
|
||||
.get("x-api-key")
|
||||
.and_then(|v| v.as_str())
|
||||
.expect("x-api-key should be string");
|
||||
assert_eq!(api_key, REDACTED_USAGE_VALUE);
|
||||
assert_eq!(api_key, "sk-proj-1234567890abcdef");
|
||||
|
||||
assert_eq!(
|
||||
object.get("user-agent").and_then(|v| v.as_str()),
|
||||
Some(REDACTED_USAGE_VALUE),
|
||||
Some("codex-tui/0.1"),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7015,27 +6972,27 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mask_sensitive_headers_in_json_value_handles_object_form() {
|
||||
fn validate_usage_headers_in_json_value_handles_object_form() {
|
||||
let value = json!({
|
||||
"Authorization": "Bearer eyJhbGciOiJSUzI1NiJ9.body.signature",
|
||||
"Cookie": "session=verylongcookievalue1234",
|
||||
"Accept": "application/json",
|
||||
});
|
||||
let masked =
|
||||
mask_sensitive_headers_in_json_value(Some(value)).expect("masked value should exist");
|
||||
let object = masked.as_object().expect("expected object");
|
||||
let captured =
|
||||
validate_usage_headers_in_json_value(Some(value)).expect("captured value should exist");
|
||||
let object = captured.as_object().expect("expected object");
|
||||
|
||||
let auth = object
|
||||
.get("Authorization")
|
||||
.and_then(|v| v.as_str())
|
||||
.expect("Authorization should be string");
|
||||
assert_eq!(auth, REDACTED_USAGE_VALUE);
|
||||
assert_eq!(auth, "Bearer eyJhbGciOiJSUzI1NiJ9.body.signature");
|
||||
|
||||
let cookie = object
|
||||
.get("Cookie")
|
||||
.and_then(|v| v.as_str())
|
||||
.expect("Cookie should be string");
|
||||
assert_eq!(cookie, REDACTED_USAGE_VALUE);
|
||||
assert_eq!(cookie, "session=verylongcookievalue1234");
|
||||
|
||||
assert_eq!(
|
||||
object.get("Accept").and_then(|v| v.as_str()),
|
||||
@@ -7044,11 +7001,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mask_sensitive_headers_discards_non_object() {
|
||||
fn validate_usage_headers_discards_non_object() {
|
||||
// None 输入返回 None
|
||||
assert!(mask_sensitive_headers_in_json_value(None).is_none());
|
||||
assert!(validate_usage_headers_in_json_value(None).is_none());
|
||||
// 非 object 不是可验证的 header map,直接丢弃。
|
||||
let masked = mask_sensitive_headers_in_json_value(Some(json!("not an object")));
|
||||
let masked = validate_usage_headers_in_json_value(Some(json!("not an object")));
|
||||
assert_eq!(masked, None);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user