mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-10 03:09:50 +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:
@@ -4,6 +4,7 @@ use aether_data_contracts::repository::video_tasks::{
|
||||
};
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use crate::transport::{gemini_response_video_url, gemini_video_metadata};
|
||||
use crate::types::sanitize_video_task_error_code;
|
||||
use crate::{
|
||||
build_video_follow_up_report_context, current_unix_timestamp_secs, gemini_metadata_video_url,
|
||||
@@ -112,7 +113,16 @@ impl GeminiVideoTaskSeed {
|
||||
self.error_code = None;
|
||||
self.error_message = None;
|
||||
}
|
||||
self.metadata = json!({});
|
||||
self.metadata = if error.is_none() {
|
||||
gemini_video_metadata(
|
||||
provider_body
|
||||
.get("response")
|
||||
.and_then(gemini_response_video_url)
|
||||
.as_deref(),
|
||||
)
|
||||
} else {
|
||||
json!({})
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -365,8 +375,8 @@ mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{
|
||||
GeminiVideoTaskSeed, LocalVideoTaskPersistence, LocalVideoTaskStatus,
|
||||
LocalVideoTaskTransport,
|
||||
GeminiVideoTaskSeed, LocalVideoTaskPersistence, LocalVideoTaskSnapshot,
|
||||
LocalVideoTaskStatus, LocalVideoTaskTransport,
|
||||
};
|
||||
|
||||
use super::map_gemini_stored_task_to_read_response;
|
||||
@@ -484,7 +494,54 @@ mod tests {
|
||||
assert!(record.request_metadata.is_none());
|
||||
assert_eq!(
|
||||
record.video_url.as_deref(),
|
||||
Some("https://files.example/video.mp4?alt=media")
|
||||
Some("https://files.example/video.mp4?alt=media&token=sensitive")
|
||||
);
|
||||
assert_eq!(record.prompt.as_deref(), Some("business prompt"));
|
||||
|
||||
let transport = seed.transport.clone();
|
||||
let mut snapshot = LocalVideoTaskSnapshot::Gemini(seed);
|
||||
snapshot.apply_provider_body(json!({
|
||||
"done": true,
|
||||
"debug": "private-provider-debug",
|
||||
"response": {
|
||||
"provider_token": "private-provider-token",
|
||||
"generateVideoResponse": {
|
||||
"generatedSamples": [{
|
||||
"video": {
|
||||
"uri": "https://files.example/video.mp4?alt=media&token=signed%2Bvalue",
|
||||
"debug": "private-video-debug"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}).as_object().expect("provider body"));
|
||||
assert!(!serde_json::to_string(&snapshot)
|
||||
.expect("serialize snapshot")
|
||||
.contains("private-"));
|
||||
snapshot.sanitize_persisted_diagnostics();
|
||||
let stored = snapshot.to_upsert_record().into_stored();
|
||||
assert_eq!(stored.status, VideoTaskStatus::Completed);
|
||||
assert_eq!(stored.prompt.as_deref(), Some("business prompt"));
|
||||
assert_eq!(
|
||||
stored.video_url.as_deref(),
|
||||
Some("https://files.example/video.mp4?alt=media&token=signed%2Bvalue")
|
||||
);
|
||||
assert!(stored.request_metadata.is_none());
|
||||
assert!(stored.original_request_body.is_none());
|
||||
|
||||
let mut restored =
|
||||
LocalVideoTaskSnapshot::from_stored_task_with_transport(&stored, transport)
|
||||
.expect("stored Gemini task should reconstruct");
|
||||
restored.sanitize_persisted_diagnostics();
|
||||
let restored_record = restored.to_upsert_record();
|
||||
assert_eq!(restored_record.video_url, stored.video_url);
|
||||
assert_eq!(restored_record.prompt, stored.prompt);
|
||||
let response = restored.read_response();
|
||||
assert_eq!(response.body_json["done"], true);
|
||||
assert!(response
|
||||
.body_json
|
||||
.to_string()
|
||||
.contains("/v1beta/files/aev_"));
|
||||
assert!(!response.body_json.to_string().contains("signed%2Bvalue"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,7 +610,7 @@ impl OpenAiVideoTaskSeed {
|
||||
updated_at_unix_secs: self.completed_at_unix_secs.unwrap_or(now_unix_secs),
|
||||
error_code: self.error_code.clone(),
|
||||
error_message: None,
|
||||
video_url: None,
|
||||
video_url: self.video_url.clone(),
|
||||
request_metadata: None,
|
||||
};
|
||||
record.sanitize_for_persistence();
|
||||
@@ -626,8 +626,8 @@ mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{
|
||||
LocalVideoTaskPersistence, LocalVideoTaskStatus, LocalVideoTaskTransport,
|
||||
OpenAiVideoTaskSeed,
|
||||
LocalVideoTaskContentAction, LocalVideoTaskPersistence, LocalVideoTaskSnapshot,
|
||||
LocalVideoTaskStatus, LocalVideoTaskTransport, OpenAiVideoTaskSeed,
|
||||
};
|
||||
|
||||
use super::map_openai_stored_task_to_read_response;
|
||||
@@ -751,9 +751,33 @@ mod tests {
|
||||
assert!(record.original_request_body.is_none());
|
||||
assert!(record.progress_message.is_none());
|
||||
assert!(record.error_message.is_none());
|
||||
assert!(record.video_url.is_none());
|
||||
assert_eq!(record.video_url, seed.video_url);
|
||||
assert_eq!(record.prompt, seed.prompt);
|
||||
assert!(record.request_metadata.is_none());
|
||||
assert_eq!(record.duration_seconds, Some(4));
|
||||
assert_eq!(record.size.as_deref(), Some("1280x720"));
|
||||
|
||||
let mut stored = record.into_stored();
|
||||
stored.status = VideoTaskStatus::Completed;
|
||||
let snapshot =
|
||||
LocalVideoTaskSnapshot::from_stored_task_with_transport(&stored, seed.transport)
|
||||
.expect("stored task should reconstruct with current transport");
|
||||
let LocalVideoTaskSnapshot::OpenAi(restored) = snapshot else {
|
||||
panic!("expected OpenAI snapshot");
|
||||
};
|
||||
assert_eq!(restored.prompt, stored.prompt);
|
||||
assert_eq!(restored.to_upsert_record().video_url, stored.video_url);
|
||||
let Some(LocalVideoTaskContentAction::StreamPlan(plan)) =
|
||||
restored.build_content_stream_action(None, "trace-download")
|
||||
else {
|
||||
panic!("completed stored task should stream content");
|
||||
};
|
||||
assert_eq!(Some(plan.url.as_str()), stored.video_url.as_deref());
|
||||
assert!(plan.headers.is_empty());
|
||||
let response = map_openai_stored_task_to_read_response(stored.clone());
|
||||
assert_eq!(
|
||||
response.body_json["video_url"].as_str(),
|
||||
stored.video_url.as_deref()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use aether_data_contracts::repository::video_tasks::{StoredVideoTask, UpsertVideoTask};
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use crate::transport::{gemini_metadata_video_url, gemini_video_metadata};
|
||||
use crate::types::sanitize_video_task_error_code;
|
||||
use crate::{
|
||||
local_status_from_stored, non_empty_owned, request_body_string, GeminiVideoTaskSeed,
|
||||
@@ -104,7 +105,7 @@ impl LocalVideoTaskSnapshot {
|
||||
progress_percent: task.progress_percent,
|
||||
error_code: task.error_code.clone(),
|
||||
error_message: task.error_message.clone(),
|
||||
metadata: Value::Object(Map::new()),
|
||||
metadata: gemini_video_metadata(task.video_url.as_deref()),
|
||||
persistence,
|
||||
transport,
|
||||
}))
|
||||
@@ -128,7 +129,8 @@ impl LocalVideoTaskSnapshot {
|
||||
let previous_error_code = seed.error_code.clone();
|
||||
let error_code =
|
||||
sanitized_error_code_for_status(seed.status, seed.error_code.take());
|
||||
let safe_metadata = Value::Object(Map::new());
|
||||
let safe_metadata =
|
||||
gemini_video_metadata(gemini_metadata_video_url(&seed.metadata).as_deref());
|
||||
let changed = previous_error_code != error_code
|
||||
|| seed.error_message.is_some()
|
||||
|| seed.metadata != safe_metadata;
|
||||
|
||||
@@ -450,6 +450,49 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completed_gemini_download_survives_encrypted_file_reload() {
|
||||
let path = temp_store_path("completed-download");
|
||||
let video_url = "https://cdn.example.test/video.mp4?signature=a%2Fb%2Bc%3D";
|
||||
let mut snapshot = sensitive_gemini_snapshot();
|
||||
snapshot.apply_provider_body(
|
||||
json!({
|
||||
"done": true,
|
||||
"debug": "private-debug",
|
||||
"response": {
|
||||
"generateVideoResponse": {
|
||||
"generatedSamples": [{"video": {"uri": video_url}}]
|
||||
}
|
||||
}
|
||||
})
|
||||
.as_object()
|
||||
.expect("provider body"),
|
||||
);
|
||||
let store = FileVideoTaskStore::new(&path, DEVELOPMENT_ENCRYPTION_KEY).expect("store");
|
||||
store.insert(snapshot);
|
||||
drop(store);
|
||||
let bytes = std::fs::read_to_string(&path).expect("encrypted store file");
|
||||
assert!(bytes.starts_with(ENCRYPTED_VIDEO_TASK_STORE_PREFIX));
|
||||
assert!(!bytes.contains(video_url));
|
||||
assert!(!bytes.contains("transport-key-required-for-resume"));
|
||||
let restored =
|
||||
FileVideoTaskStore::new(&path, DEVELOPMENT_ENCRYPTION_KEY).expect("restored store");
|
||||
let task = restored
|
||||
.clone_gemini("task-sensitive")
|
||||
.expect("completed Gemini task");
|
||||
let record = task.to_upsert_record();
|
||||
assert_eq!(
|
||||
record.status,
|
||||
aether_data_contracts::repository::video_tasks::VideoTaskStatus::Completed
|
||||
);
|
||||
assert_eq!(record.video_url.as_deref(), Some(video_url));
|
||||
assert_eq!(record.prompt.as_deref(), Some("create a video"));
|
||||
assert!(!task.metadata.to_string().contains("private-debug"));
|
||||
assert!(record.request_metadata.is_none());
|
||||
drop(restored);
|
||||
cleanup_store_path(&path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loading_encrypted_store_rewrites_legacy_provider_diagnostics() {
|
||||
let path = temp_store_path("diagnostic-migration");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use serde_json::Value;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::LocalVideoTaskStatus;
|
||||
|
||||
@@ -20,9 +20,12 @@ pub fn parse_video_content_variant(query_string: Option<&str>) -> Option<&'stati
|
||||
}
|
||||
|
||||
pub fn gemini_metadata_video_url(metadata: &Value) -> Option<String> {
|
||||
metadata
|
||||
.get("response")
|
||||
.and_then(|value| value.get("generateVideoResponse"))
|
||||
metadata.get("response").and_then(gemini_response_video_url)
|
||||
}
|
||||
|
||||
pub(crate) fn gemini_response_video_url(response: &Value) -> Option<String> {
|
||||
response
|
||||
.get("generateVideoResponse")
|
||||
.and_then(|value| value.get("generatedSamples"))
|
||||
.and_then(Value::as_array)
|
||||
.and_then(|value| value.first())
|
||||
@@ -32,6 +35,19 @@ pub fn gemini_metadata_video_url(metadata: &Value) -> Option<String> {
|
||||
.map(str::to_string)
|
||||
}
|
||||
|
||||
pub(crate) fn gemini_video_metadata(video_url: Option<&str>) -> Value {
|
||||
match video_url {
|
||||
Some(video_url) => json!({
|
||||
"response": {
|
||||
"generateVideoResponse": {
|
||||
"generatedSamples": [{"video": {"uri": video_url}}]
|
||||
}
|
||||
}
|
||||
}),
|
||||
None => json!({}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_openai_task_status(status: LocalVideoTaskStatus) -> &'static str {
|
||||
match status {
|
||||
LocalVideoTaskStatus::Submitted | LocalVideoTaskStatus::Queued => "queued",
|
||||
|
||||
@@ -98,10 +98,15 @@ impl LocalVideoTaskPersistence {
|
||||
api_key_name: task.api_key_name.clone(),
|
||||
client_api_format,
|
||||
provider_api_format,
|
||||
original_request_body: task
|
||||
.original_request_body
|
||||
.clone()
|
||||
.unwrap_or_else(|| Value::Object(Map::new())),
|
||||
original_request_body: task.original_request_body.clone().unwrap_or_else(|| {
|
||||
serde_json::json!({
|
||||
"prompt": task.prompt,
|
||||
"seconds": task.duration_seconds,
|
||||
"resolution": task.resolution,
|
||||
"aspect_ratio": task.aspect_ratio,
|
||||
"size": task.size,
|
||||
})
|
||||
}),
|
||||
format_converted: task.format_converted,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user