mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 10:27:46 +08:00
fix: restore security hardening compatibility and validation
Restore authorized rule reveal, explicit full HTTP capture and retention, video task business fields, and valid payment URLs. Add opt-in credential preservation for trusted recovery, fix frontend type contracts and async races, and eliminate PostgreSQL test fixture resource leaks. Document audit coverage and successful fmt and CI-scoped Clippy checks.
This commit is contained in:
@@ -2752,6 +2752,40 @@ fn hydrate_client_family(item: &mut StoredRequestUsageAudit) {
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_usage_body_capture(
|
||||
incoming_body: Option<Value>,
|
||||
incoming_ref: Option<String>,
|
||||
incoming_state: Option<UsageBodyCaptureState>,
|
||||
existing: Option<&StoredRequestUsageAudit>,
|
||||
field: UsageBodyField,
|
||||
) -> (Option<Value>, Option<String>, Option<UsageBodyCaptureState>) {
|
||||
if matches!(
|
||||
incoming_state,
|
||||
Some(
|
||||
UsageBodyCaptureState::None
|
||||
| UsageBodyCaptureState::Disabled
|
||||
| UsageBodyCaptureState::Unavailable
|
||||
)
|
||||
) {
|
||||
return (None, None, incoming_state);
|
||||
}
|
||||
if incoming_body.is_some() {
|
||||
return (
|
||||
incoming_body,
|
||||
None,
|
||||
incoming_state.or(Some(UsageBodyCaptureState::Inline)),
|
||||
);
|
||||
}
|
||||
if incoming_ref.is_some() {
|
||||
return (None, incoming_ref, Some(UsageBodyCaptureState::Reference));
|
||||
}
|
||||
(
|
||||
existing.and_then(|item| item.body_value(field).cloned()),
|
||||
existing.and_then(|item| item.body_ref(field).map(ToOwned::to_owned)),
|
||||
incoming_state.or_else(|| existing.and_then(|item| item.body_state(field))),
|
||||
)
|
||||
}
|
||||
|
||||
fn request_body_capture_replaces_derived_facts(
|
||||
request_body: Option<&Value>,
|
||||
request_body_state: Option<UsageBodyCaptureState>,
|
||||
@@ -2883,36 +2917,47 @@ impl UsageWriteRepository for InMemoryUsageReadRepository {
|
||||
return Ok(existing.clone());
|
||||
}
|
||||
}
|
||||
let capture_usage = sanitize_usage_capture_controls_for_persistence(capture_usage);
|
||||
let mut capture_usage = sanitize_usage_capture_controls_for_persistence(capture_usage);
|
||||
if let Some(existing) = by_request_id.get_mut(&usage.request_id) {
|
||||
existing.request_headers = None;
|
||||
existing.request_body = None;
|
||||
existing.request_body_ref = None;
|
||||
existing.request_body_state = None;
|
||||
existing.provider_request_headers = None;
|
||||
existing.provider_request_body = None;
|
||||
existing.provider_request_body_ref = None;
|
||||
existing.provider_request_body_state = None;
|
||||
existing.response_headers = None;
|
||||
existing.response_body = None;
|
||||
existing.response_body_ref = None;
|
||||
existing.response_body_state = None;
|
||||
existing.client_response_headers = None;
|
||||
existing.client_response_body = None;
|
||||
existing.client_response_body_ref = None;
|
||||
existing.client_response_body_state = None;
|
||||
existing.request_metadata =
|
||||
sanitize_usage_request_metadata(existing.request_metadata.take());
|
||||
}
|
||||
{
|
||||
let mut detached_bodies = self.detached_bodies.write().expect("usage repository lock");
|
||||
for field in [
|
||||
UsageBodyField::RequestBody,
|
||||
UsageBodyField::ProviderRequestBody,
|
||||
UsageBodyField::ResponseBody,
|
||||
UsageBodyField::ClientResponseBody,
|
||||
for (field, state, body) in [
|
||||
(
|
||||
UsageBodyField::RequestBody,
|
||||
capture_usage.request_body_state,
|
||||
&capture_usage.request_body,
|
||||
),
|
||||
(
|
||||
UsageBodyField::ProviderRequestBody,
|
||||
capture_usage.provider_request_body_state,
|
||||
&capture_usage.provider_request_body,
|
||||
),
|
||||
(
|
||||
UsageBodyField::ResponseBody,
|
||||
capture_usage.response_body_state,
|
||||
&capture_usage.response_body,
|
||||
),
|
||||
(
|
||||
UsageBodyField::ClientResponseBody,
|
||||
capture_usage.client_response_body_state,
|
||||
&capture_usage.client_response_body,
|
||||
),
|
||||
] {
|
||||
detached_bodies.remove(&usage_body_ref(&usage.request_id, field));
|
||||
if body.is_some()
|
||||
|| matches!(
|
||||
state,
|
||||
Some(
|
||||
UsageBodyCaptureState::None
|
||||
| UsageBodyCaptureState::Disabled
|
||||
| UsageBodyCaptureState::Unavailable
|
||||
)
|
||||
)
|
||||
{
|
||||
detached_bodies.remove(&usage_body_ref(&usage.request_id, field));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2977,10 +3022,36 @@ impl UsageWriteRepository for InMemoryUsageReadRepository {
|
||||
}
|
||||
});
|
||||
let request_metadata = sanitize_memory_request_metadata(request_metadata);
|
||||
let request_body_ref = None;
|
||||
let provider_request_body_ref = None;
|
||||
let response_body_ref = None;
|
||||
let client_response_body_ref = None;
|
||||
let (request_body, request_body_ref, request_body_state) = merge_usage_body_capture(
|
||||
capture_usage.request_body.take(),
|
||||
capture_usage.request_body_ref.take(),
|
||||
capture_usage.request_body_state,
|
||||
existing.as_ref(),
|
||||
UsageBodyField::RequestBody,
|
||||
);
|
||||
let (provider_request_body, provider_request_body_ref, provider_request_body_state) =
|
||||
merge_usage_body_capture(
|
||||
capture_usage.provider_request_body.take(),
|
||||
capture_usage.provider_request_body_ref.take(),
|
||||
capture_usage.provider_request_body_state,
|
||||
existing.as_ref(),
|
||||
UsageBodyField::ProviderRequestBody,
|
||||
);
|
||||
let (response_body, response_body_ref, response_body_state) = merge_usage_body_capture(
|
||||
capture_usage.response_body.take(),
|
||||
capture_usage.response_body_ref.take(),
|
||||
capture_usage.response_body_state,
|
||||
existing.as_ref(),
|
||||
UsageBodyField::ResponseBody,
|
||||
);
|
||||
let (client_response_body, client_response_body_ref, client_response_body_state) =
|
||||
merge_usage_body_capture(
|
||||
capture_usage.client_response_body.take(),
|
||||
capture_usage.client_response_body_ref.take(),
|
||||
capture_usage.client_response_body_state,
|
||||
existing.as_ref(),
|
||||
UsageBodyField::ClientResponseBody,
|
||||
);
|
||||
let stored = StoredRequestUsageAudit {
|
||||
id: existing
|
||||
.as_ref()
|
||||
@@ -3089,22 +3160,38 @@ impl UsageWriteRepository for InMemoryUsageReadRepository {
|
||||
),
|
||||
status: usage.status,
|
||||
billing_status: usage.billing_status,
|
||||
request_headers: None,
|
||||
request_body: None,
|
||||
request_headers: capture_usage.request_headers.or_else(|| {
|
||||
existing
|
||||
.as_ref()
|
||||
.and_then(|item| item.request_headers.clone())
|
||||
}),
|
||||
request_body,
|
||||
request_body_ref,
|
||||
request_body_state: capture_usage.request_body_state,
|
||||
provider_request_headers: None,
|
||||
provider_request_body: None,
|
||||
request_body_state,
|
||||
provider_request_headers: capture_usage.provider_request_headers.or_else(|| {
|
||||
existing
|
||||
.as_ref()
|
||||
.and_then(|item| item.provider_request_headers.clone())
|
||||
}),
|
||||
provider_request_body,
|
||||
provider_request_body_ref,
|
||||
provider_request_body_state: capture_usage.provider_request_body_state,
|
||||
response_headers: None,
|
||||
response_body: None,
|
||||
provider_request_body_state,
|
||||
response_headers: capture_usage.response_headers.or_else(|| {
|
||||
existing
|
||||
.as_ref()
|
||||
.and_then(|item| item.response_headers.clone())
|
||||
}),
|
||||
response_body,
|
||||
response_body_ref,
|
||||
response_body_state: capture_usage.response_body_state,
|
||||
client_response_headers: None,
|
||||
client_response_body: None,
|
||||
response_body_state,
|
||||
client_response_headers: capture_usage.client_response_headers.or_else(|| {
|
||||
existing
|
||||
.as_ref()
|
||||
.and_then(|item| item.client_response_headers.clone())
|
||||
}),
|
||||
client_response_body,
|
||||
client_response_body_ref,
|
||||
client_response_body_state: capture_usage.client_response_body_state,
|
||||
client_response_body_state,
|
||||
candidate_id: if replace_routing_snapshot {
|
||||
capture_usage.candidate_id
|
||||
} else {
|
||||
|
||||
@@ -136,6 +136,76 @@ fn sample_upsert_usage_record(request_id: &str) -> UpsertUsageRecord {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn upsert_preserves_full_http_captures_across_lifecycle_updates() {
|
||||
let repository = InMemoryUsageReadRepository::default();
|
||||
let mut pending = sample_upsert_usage_record("req-full-capture");
|
||||
pending.request_headers =
|
||||
Some(json!({"content-type": "application/json", "authorization": "Bearer private"}));
|
||||
pending.request_body =
|
||||
Some(json!({"messages": [{"role": "user", "content": "original request"}]}));
|
||||
pending.provider_request_body = Some(json!({"input": "provider request"}));
|
||||
pending.request_body_state = Some(UsageBodyCaptureState::Inline);
|
||||
pending.provider_request_body_state = Some(UsageBodyCaptureState::Inline);
|
||||
let stored_pending = repository.upsert(pending.clone()).await.unwrap();
|
||||
assert_eq!(stored_pending.request_body, pending.request_body);
|
||||
assert_eq!(
|
||||
stored_pending.provider_request_body,
|
||||
pending.provider_request_body
|
||||
);
|
||||
assert_eq!(
|
||||
stored_pending.request_headers,
|
||||
Some(json!({"content-type": "application/json", "authorization": "[redacted]"}))
|
||||
);
|
||||
|
||||
let mut streaming = sample_upsert_usage_record(&pending.request_id);
|
||||
streaming.status = "streaming".to_string();
|
||||
streaming.updated_at_unix_secs += 1;
|
||||
let stored_streaming = repository.upsert(streaming).await.unwrap();
|
||||
assert_eq!(stored_streaming.request_body, pending.request_body);
|
||||
assert_eq!(
|
||||
stored_streaming.provider_request_body,
|
||||
pending.provider_request_body
|
||||
);
|
||||
|
||||
let mut terminal = sample_upsert_usage_record(&pending.request_id);
|
||||
terminal.status = "completed".to_string();
|
||||
terminal.updated_at_unix_secs += 2;
|
||||
terminal.finalized_at_unix_secs = Some(terminal.updated_at_unix_secs);
|
||||
terminal.response_headers =
|
||||
Some(json!({"content-type": "text/event-stream", "set-cookie": "private"}));
|
||||
terminal.response_body = Some(json!("data: upstream response\n\ndata: [DONE]\n\n"));
|
||||
terminal.client_response_body =
|
||||
Some(json!({"choices": [{"message": {"content": "client response"}}]}));
|
||||
let stored_terminal = repository.upsert(terminal.clone()).await.unwrap();
|
||||
assert_eq!(stored_terminal.request_body, pending.request_body);
|
||||
assert_eq!(
|
||||
stored_terminal.provider_request_body,
|
||||
pending.provider_request_body
|
||||
);
|
||||
assert_eq!(stored_terminal.response_body, terminal.response_body);
|
||||
assert_eq!(
|
||||
stored_terminal.client_response_body,
|
||||
terminal.client_response_body
|
||||
);
|
||||
assert_eq!(
|
||||
stored_terminal.response_headers,
|
||||
Some(json!({"content-type": "text/event-stream", "set-cookie": "[redacted]"}))
|
||||
);
|
||||
|
||||
let found = repository
|
||||
.find_by_request_id(&pending.request_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(found.request_body, pending.request_body);
|
||||
assert_eq!(found.response_body, terminal.response_body);
|
||||
assert_eq!(
|
||||
repository.upsert(pending).await.unwrap().response_body,
|
||||
terminal.response_body
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn upsert_uses_typed_provider_capture_as_the_fast_fact_snapshot() {
|
||||
for (name, state, incoming_tier, expected_tier) in [
|
||||
|
||||
Reference in New Issue
Block a user