feat(security): harden gateway boundaries and usage policies

Consolidate subscription usage policy enforcement, privacy-safe persistence, and gateway security hardening into one reviewable change.

Includes bounded HTTP and execution envelopes, header and protocol guards, DNS and relay validation, authentication and secret projection hardening, secure backup/install paths, and regression coverage.
This commit is contained in:
elky
2026-09-04 03:45:52 +08:00
parent ddcbeb3ae9
commit 579f2c7cc1
1019 changed files with 190437 additions and 26080 deletions
+99 -32
View File
@@ -4,11 +4,12 @@ use aether_data_contracts::repository::video_tasks::{
};
use serde_json::{json, Map, Value};
use crate::types::sanitize_video_task_error_code;
use crate::{
build_video_follow_up_report_context, current_unix_timestamp_secs, gemini_metadata_video_url,
request_body_string, request_body_u32, resolve_follow_up_auth, GeminiVideoTaskSeed,
LocalVideoTaskFollowUpPlan, LocalVideoTaskReadResponse, LocalVideoTaskSnapshot,
LocalVideoTaskStatus, VideoFollowUpReportContextInput, DEFAULT_VIDEO_TASK_MAX_POLL_COUNT,
LocalVideoTaskFollowUpPlan, LocalVideoTaskReadResponse, LocalVideoTaskStatus,
VideoFollowUpReportContextInput, DEFAULT_VIDEO_TASK_MAX_POLL_COUNT,
DEFAULT_VIDEO_TASK_POLL_INTERVAL_SECONDS,
};
@@ -66,10 +67,9 @@ fn build_gemini_failed_body(task: StoredVideoTask) -> Value {
"name": stored_task_operation_name(&task),
"done": true,
"error": {
"code": task.error_code.unwrap_or_else(|| "UNKNOWN".to_string()),
"message": task
.error_message
.unwrap_or_else(|| "Video generation failed".to_string()),
"code": sanitize_video_task_error_code(task.error_code)
.unwrap_or_else(|| "unknown".to_string()),
"message": "Video generation failed",
}
})
}
@@ -99,14 +99,13 @@ impl GeminiVideoTaskSeed {
if let Some(error) = error {
self.status = LocalVideoTaskStatus::Failed;
self.progress_percent = 100;
self.error_code = error
.get("code")
.and_then(Value::as_str)
.map(str::to_string);
self.error_message = error
.get("message")
.and_then(Value::as_str)
.map(str::to_string);
self.error_code = sanitize_video_task_error_code(
error
.get("code")
.and_then(Value::as_str)
.map(str::to_string),
);
self.error_message = None;
} else {
self.status = LocalVideoTaskStatus::Completed;
self.progress_percent = 100;
@@ -121,10 +120,7 @@ impl GeminiVideoTaskSeed {
self.progress_percent = 50;
self.error_code = None;
self.error_message = None;
self.metadata = provider_body
.get("metadata")
.cloned()
.unwrap_or_else(|| json!({}));
self.metadata = json!({});
}
pub fn build_get_follow_up_plan(&self, trace_id: &str) -> Option<ExecutionPlan> {
@@ -273,17 +269,15 @@ impl GeminiVideoTaskSeed {
"name": operation_name,
"done": true,
"error": {
"code": self.error_code.clone().unwrap_or_else(|| "UNKNOWN".to_string()),
"message": self
.error_message
.clone()
.unwrap_or_else(|| "Video generation failed".to_string()),
"code": sanitize_video_task_error_code(self.error_code.clone())
.unwrap_or_else(|| "unknown".to_string()),
"message": "Video generation failed",
}
}),
_ => json!({
"name": operation_name,
"done": false,
"metadata": self.metadata.clone(),
"metadata": {},
}),
}
}
@@ -298,7 +292,7 @@ impl GeminiVideoTaskSeed {
),
_ => None,
};
UpsertVideoTask {
let mut record = UpsertVideoTask {
id: self.local_short_id.clone(),
short_id: Some(self.local_short_id.clone()),
request_id: self.persistence.request_id.clone(),
@@ -316,7 +310,7 @@ impl GeminiVideoTaskSeed {
model: Some(self.model.clone()),
prompt: request_body_string(&self.persistence.original_request_body, "prompt")
.or_else(|| Some(String::new())),
original_request_body: Some(self.persistence.original_request_body.clone()),
original_request_body: None,
duration_seconds: request_body_u32(&self.persistence.original_request_body, "seconds")
.or_else(|| {
request_body_u32(&self.persistence.original_request_body, "duration_seconds")
@@ -340,13 +334,12 @@ impl GeminiVideoTaskSeed {
completed_at_unix_secs: None,
updated_at_unix_secs: now_unix_secs,
error_code: self.error_code.clone(),
error_message: self.error_message.clone(),
error_message: None,
video_url: gemini_metadata_video_url(&self.metadata),
request_metadata: Some(json!({
"rust_owner": "async_task",
"rust_local_snapshot": LocalVideoTaskSnapshot::Gemini(self.clone()),
})),
}
request_metadata: None,
};
record.sanitize_for_persistence();
record
}
fn resolve_operation_path(&self) -> Option<String> {
@@ -366,7 +359,15 @@ impl GeminiVideoTaskSeed {
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use aether_data_contracts::repository::video_tasks::{StoredVideoTask, VideoTaskStatus};
use serde_json::json;
use crate::{
GeminiVideoTaskSeed, LocalVideoTaskPersistence, LocalVideoTaskStatus,
LocalVideoTaskTransport,
};
use super::map_gemini_stored_task_to_read_response;
@@ -420,4 +421,70 @@ mod tests {
assert_eq!(response.status_code, 404);
assert_eq!(response.body_json["detail"], "Video task was cancelled");
}
#[test]
fn builds_minimal_gemini_persistence_record_and_strips_signed_url_query() {
let seed = GeminiVideoTaskSeed {
local_short_id: "gemini-sensitive".to_string(),
upstream_operation_name: "operations/upstream-sensitive".to_string(),
user_id: Some("user-1".to_string()),
api_key_id: Some("api-key-1".to_string()),
model: "veo-3".to_string(),
status: LocalVideoTaskStatus::Completed,
progress_percent: 100,
error_code: Some("provider-secret-diagnostic".to_string()),
error_message: Some("provider response contained secret".to_string()),
metadata: json!({
"response": {
"generateVideoResponse": {
"generatedSamples": [{
"video": {
"uri": "https://files.example/video.mp4?alt=media&token=sensitive#fragment"
}
}]
}
}
}),
persistence: LocalVideoTaskPersistence {
request_id: "request-gemini-sensitive".to_string(),
username: Some("alice".to_string()),
api_key_name: Some("primary".to_string()),
client_api_format: "gemini:video".to_string(),
provider_api_format: "gemini:video".to_string(),
original_request_body: json!({
"prompt": "business prompt",
"provider_token": "sensitive"
}),
format_converted: false,
},
transport: LocalVideoTaskTransport {
upstream_base_url: "https://generativelanguage.example".to_string(),
provider_name: Some("gemini".to_string()),
provider_id: "provider-1".to_string(),
endpoint_id: "endpoint-1".to_string(),
key_id: "provider-key-1".to_string(),
headers: BTreeMap::from([(
"x-goog-api-key".to_string(),
"sensitive-api-key".to_string(),
)]),
content_type: Some("application/json".to_string()),
model_name: Some("veo-3".to_string()),
proxy: None,
transport_profile: None,
timeouts: None,
},
};
let record = seed.to_upsert_record();
assert_eq!(record.error_code.as_deref(), Some("provider_error"));
assert!(record.original_request_body.is_none());
assert!(record.progress_message.is_none());
assert!(record.error_message.is_none());
assert!(record.request_metadata.is_none());
assert_eq!(
record.video_url.as_deref(),
Some("https://files.example/video.mp4?alt=media")
);
}
}
+4 -1
View File
@@ -32,7 +32,10 @@ pub use path::{
resolve_video_task_hydration_lookup_key, resolve_video_task_read_lookup_key,
resolve_video_task_report_lookup, VideoTaskReportLookup,
};
pub use read_side::{read_data_backed_video_task_response, StoredVideoTaskReadSide};
pub use read_side::{
read_data_backed_video_task_response, read_data_backed_video_task_response_for_user,
StoredVideoTaskReadSide,
};
pub use service::VideoTaskService;
pub use store::VideoTaskStore;
pub use store_backend::{FileVideoTaskStore, InMemoryVideoTaskStore};
+101 -38
View File
@@ -6,13 +6,13 @@ use aether_data_contracts::repository::video_tasks::{
};
use serde_json::{json, Map, Value};
use crate::types::sanitize_video_task_error_code;
use crate::{
build_video_follow_up_report_context, current_unix_timestamp_secs, map_openai_task_status,
parse_video_content_variant, request_body_string, request_body_u32, resolve_follow_up_auth,
LocalVideoTaskContentAction, LocalVideoTaskFollowUpPlan, LocalVideoTaskReadResponse,
LocalVideoTaskSnapshot, LocalVideoTaskStatus, OpenAiVideoTaskSeed,
VideoFollowUpReportContextInput, DEFAULT_VIDEO_TASK_MAX_POLL_COUNT,
DEFAULT_VIDEO_TASK_POLL_INTERVAL_SECONDS,
LocalVideoTaskStatus, OpenAiVideoTaskSeed, VideoFollowUpReportContextInput,
DEFAULT_VIDEO_TASK_MAX_POLL_COUNT, DEFAULT_VIDEO_TASK_POLL_INTERVAL_SECONDS,
};
fn openai_video_resource_url(api_root: &str, suffix: &str) -> String {
@@ -71,10 +71,9 @@ fn build_openai_stored_task_body(task: StoredVideoTask, status: VideoTaskStatus)
VideoTaskStatus::Failed | VideoTaskStatus::Expired | VideoTaskStatus::Cancelled
) {
body["error"] = json!({
"code": task.error_code.unwrap_or_else(|| "unknown".to_string()),
"message": task
.error_message
.unwrap_or_else(|| "Video generation failed".to_string()),
"code": sanitize_video_task_error_code(task.error_code)
.unwrap_or_else(|| "unknown".to_string()),
"message": "Video generation failed",
});
}
@@ -119,14 +118,13 @@ impl OpenAiVideoTaskSeed {
self.completed_at_unix_secs = provider_body.get("completed_at").and_then(Value::as_u64);
self.expires_at_unix_secs = provider_body.get("expires_at").and_then(Value::as_u64);
let error = provider_body.get("error").and_then(Value::as_object);
self.error_code = error
.and_then(|value| value.get("code"))
.and_then(Value::as_str)
.map(str::to_string);
self.error_message = error
.and_then(|value| value.get("message"))
.and_then(Value::as_str)
.map(str::to_string);
self.error_code = sanitize_video_task_error_code(
error
.and_then(|value| value.get("code"))
.and_then(Value::as_str)
.map(str::to_string),
);
self.error_message = None;
self.video_url = provider_body
.get("video_url")
.or_else(|| provider_body.get("url"))
@@ -157,14 +155,7 @@ impl OpenAiVideoTaskSeed {
LocalVideoTaskStatus::Failed | LocalVideoTaskStatus::Expired => {
return Some(LocalVideoTaskContentAction::Immediate {
status_code: 422,
body_json: json!({
"detail": format!(
"Video generation failed: {}",
self.error_message
.clone()
.unwrap_or_else(|| "Unknown error".to_string())
)
}),
body_json: json!({"detail": "Video generation failed"}),
});
}
LocalVideoTaskStatus::Cancelled => {
@@ -281,11 +272,9 @@ impl OpenAiVideoTaskSeed {
|| self.status == LocalVideoTaskStatus::Expired
{
body["error"] = json!({
"code": self.error_code.clone().unwrap_or_else(|| "unknown".to_string()),
"message": self
.error_message
.clone()
.unwrap_or_else(|| "Video generation failed".to_string()),
"code": sanitize_video_task_error_code(self.error_code.clone())
.unwrap_or_else(|| "unknown".to_string()),
"message": "Video generation failed",
});
}
@@ -582,7 +571,7 @@ impl OpenAiVideoTaskSeed {
),
_ => None,
};
UpsertVideoTask {
let mut record = UpsertVideoTask {
id: self.local_task_id.clone(),
short_id: None,
request_id: self.persistence.request_id.clone(),
@@ -599,7 +588,7 @@ impl OpenAiVideoTaskSeed {
format_converted: self.persistence.format_converted,
model: self.model.clone().or_else(|| Some(String::new())),
prompt: self.prompt.clone().or_else(|| Some(String::new())),
original_request_body: Some(self.persistence.original_request_body.clone()),
original_request_body: None,
duration_seconds: request_body_u32(&self.persistence.original_request_body, "seconds"),
resolution: request_body_string(&self.persistence.original_request_body, "resolution"),
aspect_ratio: request_body_string(
@@ -620,19 +609,26 @@ impl OpenAiVideoTaskSeed {
completed_at_unix_secs: self.completed_at_unix_secs,
updated_at_unix_secs: self.completed_at_unix_secs.unwrap_or(now_unix_secs),
error_code: self.error_code.clone(),
error_message: self.error_message.clone(),
video_url: self.video_url.clone(),
request_metadata: Some(json!({
"rust_owner": "async_task",
"rust_local_snapshot": LocalVideoTaskSnapshot::OpenAi(self.clone()),
})),
}
error_message: None,
video_url: None,
request_metadata: None,
};
record.sanitize_for_persistence();
record
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use aether_data_contracts::repository::video_tasks::{StoredVideoTask, VideoTaskStatus};
use serde_json::json;
use crate::{
LocalVideoTaskPersistence, LocalVideoTaskStatus, LocalVideoTaskTransport,
OpenAiVideoTaskSeed,
};
use super::map_openai_stored_task_to_read_response;
@@ -687,10 +683,77 @@ mod tests {
assert_eq!(response.body_json["id"], "task-openai-123");
assert_eq!(response.body_json["status"], "failed");
assert_eq!(response.body_json["completed_at"], 1712345688u64);
assert_eq!(response.body_json["error"]["code"], "upstream_failed");
assert_eq!(response.body_json["error"]["code"], "provider_error");
assert_eq!(
response.body_json["error"]["message"],
"Video generation failed"
);
assert_eq!(
response.body_json["video_url"],
"https://cdn.example.com/video.mp4"
);
}
#[test]
fn builds_minimal_openai_persistence_record_without_sensitive_snapshot() {
let seed = OpenAiVideoTaskSeed {
local_task_id: "task-openai-sensitive".to_string(),
upstream_task_id: "upstream-openai-sensitive".to_string(),
created_at_unix_ms: 1_712_345_678,
user_id: Some("user-1".to_string()),
api_key_id: Some("api-key-1".to_string()),
model: Some("sora-2".to_string()),
prompt: Some("business prompt".to_string()),
size: Some("1280x720".to_string()),
seconds: Some("4".to_string()),
remixed_from_video_id: None,
status: LocalVideoTaskStatus::Failed,
progress_percent: 100,
completed_at_unix_secs: Some(1_712_345_700),
expires_at_unix_secs: None,
error_code: Some("provider-secret-diagnostic".to_string()),
error_message: Some("Bearer sk-sensitive-provider-error".to_string()),
video_url: Some("https://cdn.example/video.mp4?token=sensitive".to_string()),
persistence: LocalVideoTaskPersistence {
request_id: "request-openai-sensitive".to_string(),
username: Some("alice".to_string()),
api_key_name: Some("primary".to_string()),
client_api_format: "openai:video".to_string(),
provider_api_format: "openai:video".to_string(),
original_request_body: json!({
"prompt": "business prompt",
"seconds": "4",
"provider_token": "sk-sensitive"
}),
format_converted: false,
},
transport: LocalVideoTaskTransport {
upstream_base_url: "https://api.example/v1".to_string(),
provider_name: Some("openai".to_string()),
provider_id: "provider-1".to_string(),
endpoint_id: "endpoint-1".to_string(),
key_id: "provider-key-1".to_string(),
headers: BTreeMap::from([(
"authorization".to_string(),
"Bearer sk-sensitive".to_string(),
)]),
content_type: Some("application/json".to_string()),
model_name: Some("sora-2".to_string()),
proxy: None,
transport_profile: None,
timeouts: None,
},
};
let record = seed.to_upsert_record();
assert_eq!(record.error_code.as_deref(), Some("provider_error"));
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!(record.request_metadata.is_none());
assert_eq!(record.duration_seconds, Some(4));
assert_eq!(record.size.as_deref(), Some("1280x720"));
}
}
@@ -13,16 +13,45 @@ pub trait StoredVideoTaskReadSide: Send + Sync {
&self,
key: VideoTaskLookupKey<'_>,
) -> Result<Option<StoredVideoTask>, DataLayerError>;
async fn find_stored_video_task_for_user(
&self,
key: VideoTaskLookupKey<'_>,
user_id: &str,
) -> Result<Option<StoredVideoTask>, DataLayerError>;
}
pub async fn read_data_backed_video_task_response(
state: &impl StoredVideoTaskReadSide,
route_family: Option<&str>,
request_path: &str,
) -> Result<Option<LocalVideoTaskReadResponse>, DataLayerError> {
read_data_backed_video_task_response_inner(state, route_family, request_path, None).await
}
pub async fn read_data_backed_video_task_response_for_user(
state: &impl StoredVideoTaskReadSide,
route_family: Option<&str>,
request_path: &str,
user_id: &str,
) -> Result<Option<LocalVideoTaskReadResponse>, DataLayerError> {
let user_id = user_id.trim();
if user_id.is_empty() {
return Ok(None);
}
read_data_backed_video_task_response_inner(state, route_family, request_path, Some(user_id))
.await
}
async fn read_data_backed_video_task_response_inner(
state: &impl StoredVideoTaskReadSide,
route_family: Option<&str>,
request_path: &str,
user_id: Option<&str>,
) -> Result<Option<LocalVideoTaskReadResponse>, DataLayerError> {
match route_family {
Some("openai") => read_openai_video_task_response(state, request_path).await,
Some("gemini") => read_gemini_video_task_response(state, request_path).await,
Some("openai") => read_openai_video_task_response(state, request_path, user_id).await,
Some("gemini") => read_gemini_video_task_response(state, request_path, user_id).await,
_ => Ok(None),
}
}
@@ -30,12 +59,21 @@ pub async fn read_data_backed_video_task_response(
async fn read_openai_video_task_response(
state: &impl StoredVideoTaskReadSide,
request_path: &str,
user_id: Option<&str>,
) -> Result<Option<LocalVideoTaskReadResponse>, DataLayerError> {
let Some(lookup) = resolve_video_task_read_lookup_key(Some("openai"), request_path) else {
return Ok(None);
};
let Some(task) = state.find_stored_video_task(lookup).await? else {
let task = match user_id {
Some(user_id) => {
state
.find_stored_video_task_for_user(lookup, user_id)
.await?
}
None => state.find_stored_video_task(lookup).await?,
};
let Some(task) = task else {
return Ok(None);
};
@@ -49,12 +87,21 @@ async fn read_openai_video_task_response(
async fn read_gemini_video_task_response(
state: &impl StoredVideoTaskReadSide,
request_path: &str,
user_id: Option<&str>,
) -> Result<Option<LocalVideoTaskReadResponse>, DataLayerError> {
let Some(lookup) = resolve_video_task_read_lookup_key(Some("gemini"), request_path) else {
return Ok(None);
};
let Some(task) = state.find_stored_video_task(lookup).await? else {
let task = match user_id {
Some(user_id) => {
state
.find_stored_video_task_for_user(lookup, user_id)
.await?
}
None => state.find_stored_video_task(lookup).await?,
};
let Some(task) = task else {
return Ok(None);
};
+186 -37
View File
@@ -29,10 +29,11 @@ impl VideoTaskService {
pub fn with_file_store(
mode: VideoTaskTruthSourceMode,
path: impl Into<PathBuf>,
encryption_key: impl Into<String>,
) -> std::io::Result<Self> {
Ok(Self::with_store(
mode,
Arc::new(FileVideoTaskStore::new(path)?),
Arc::new(FileVideoTaskStore::new(path, encryption_key)?),
))
}
@@ -113,6 +114,21 @@ impl VideoTaskService {
}
}
pub fn read_response_for_user(
&self,
route_family: Option<&str>,
request_path: &str,
user_id: &str,
) -> Option<LocalVideoTaskReadResponse> {
if self.truth_source_mode != VideoTaskTruthSourceMode::RustAuthoritative {
return None;
}
let snapshot = self.snapshot_for_route(route_family, request_path)?;
snapshot
.belongs_to_user(user_id)
.then(|| snapshot.read_response())
}
pub fn snapshot_for_route(
&self,
route_family: Option<&str>,
@@ -120,15 +136,29 @@ impl VideoTaskService {
) -> Option<LocalVideoTaskSnapshot> {
match route_family {
Some("openai") => extract_openai_task_id_from_path(request_path)
.or_else(|| extract_openai_task_id_from_cancel_path(request_path))
.or_else(|| extract_openai_task_id_from_remix_path(request_path))
.or_else(|| extract_openai_task_id_from_content_path(request_path))
.and_then(|task_id| self.store.clone_openai(task_id))
.map(LocalVideoTaskSnapshot::OpenAi),
Some("gemini") => extract_gemini_short_id_from_path(request_path)
.or_else(|| extract_gemini_short_id_from_cancel_path(request_path))
.and_then(|short_id| self.store.clone_gemini(short_id))
.map(LocalVideoTaskSnapshot::Gemini),
_ => None,
}
}
pub fn route_belongs_to_user(
&self,
route_family: Option<&str>,
request_path: &str,
user_id: &str,
) -> bool {
self.snapshot_for_route(route_family, request_path)
.is_some_and(|snapshot| snapshot.belongs_to_user(user_id))
}
pub fn prepare_openai_content_stream_action(
&self,
request_path: &str,
@@ -143,6 +173,25 @@ impl VideoTaskService {
seed.build_content_stream_action(query_string, trace_id)
}
pub fn prepare_openai_content_stream_action_for_user(
&self,
request_path: &str,
query_string: Option<&str>,
trace_id: &str,
user_id: &str,
) -> Option<LocalVideoTaskContentAction> {
if self.truth_source_mode != VideoTaskTruthSourceMode::RustAuthoritative {
return None;
}
let task_id = extract_openai_task_id_from_content_path(request_path)?;
let seed = self.store.clone_openai(task_id)?;
let snapshot = LocalVideoTaskSnapshot::OpenAi(seed.clone());
if !snapshot.belongs_to_user(user_id) {
return None;
}
seed.build_content_stream_action(query_string, trace_id)
}
pub fn snapshot_for_refresh_plan(
&self,
refresh_plan: &LocalVideoTaskReadRefreshPlan,
@@ -190,31 +239,63 @@ impl VideoTaskService {
if self.truth_source_mode != VideoTaskTruthSourceMode::RustAuthoritative {
return None;
}
let snapshot = self.snapshot_for_read_refresh_route(route_family, request_path)?;
Self::build_read_refresh_sync_plan_from_snapshot(snapshot, trace_id)
}
pub fn prepare_read_refresh_sync_plan_for_user(
&self,
route_family: Option<&str>,
request_path: &str,
user_id: &str,
trace_id: &str,
) -> Option<LocalVideoTaskReadRefreshPlan> {
if self.truth_source_mode != VideoTaskTruthSourceMode::RustAuthoritative {
return None;
}
let snapshot = self.snapshot_for_read_refresh_route(route_family, request_path)?;
if !snapshot.belongs_to_user(user_id) {
return None;
}
Self::build_read_refresh_sync_plan_from_snapshot(snapshot, trace_id)
}
fn snapshot_for_read_refresh_route(
&self,
route_family: Option<&str>,
request_path: &str,
) -> Option<LocalVideoTaskSnapshot> {
match route_family {
Some("openai") => {
let task_id = extract_openai_task_id_from_path(request_path)?;
let seed = self.store.clone_openai(task_id)?;
Some(LocalVideoTaskReadRefreshPlan {
plan: seed.build_get_follow_up_plan(trace_id)?,
projection_target: LocalVideoTaskProjectionTarget::OpenAi {
task_id: task_id.to_string(),
},
})
}
Some("gemini") => {
let short_id = extract_gemini_short_id_from_path(request_path)?;
let seed = self.store.clone_gemini(short_id)?;
Some(LocalVideoTaskReadRefreshPlan {
plan: seed.build_get_follow_up_plan(trace_id)?,
projection_target: LocalVideoTaskProjectionTarget::Gemini {
short_id: short_id.to_string(),
},
})
}
Some("openai") => extract_openai_task_id_from_path(request_path)
.and_then(|task_id| self.store.clone_openai(task_id))
.map(LocalVideoTaskSnapshot::OpenAi),
Some("gemini") => extract_gemini_short_id_from_path(request_path)
.and_then(|short_id| self.store.clone_gemini(short_id))
.map(LocalVideoTaskSnapshot::Gemini),
_ => None,
}
}
fn build_read_refresh_sync_plan_from_snapshot(
snapshot: LocalVideoTaskSnapshot,
trace_id: &str,
) -> Option<LocalVideoTaskReadRefreshPlan> {
match snapshot {
LocalVideoTaskSnapshot::OpenAi(seed) => Some(LocalVideoTaskReadRefreshPlan {
plan: seed.build_get_follow_up_plan(trace_id)?,
projection_target: LocalVideoTaskProjectionTarget::OpenAi {
task_id: seed.local_task_id.clone(),
},
}),
LocalVideoTaskSnapshot::Gemini(seed) => Some(LocalVideoTaskReadRefreshPlan {
plan: seed.build_get_follow_up_plan(trace_id)?,
projection_target: LocalVideoTaskProjectionTarget::Gemini {
short_id: seed.local_short_id.clone(),
},
}),
}
}
pub fn prepare_poll_refresh_batch(
&self,
limit: usize,
@@ -258,6 +339,18 @@ impl VideoTaskService {
}
let snapshot = LocalVideoTaskSnapshot::from_stored_task(task)?;
self.prepare_poll_refresh_plan_for_snapshot(snapshot, trace_id)
}
pub fn prepare_poll_refresh_plan_for_snapshot(
&self,
snapshot: LocalVideoTaskSnapshot,
trace_id: &str,
) -> Option<LocalVideoTaskReadRefreshPlan> {
if self.truth_source_mode != VideoTaskTruthSourceMode::RustAuthoritative {
return None;
}
match snapshot {
LocalVideoTaskSnapshot::OpenAi(seed) => Some(LocalVideoTaskReadRefreshPlan {
plan: seed.build_get_follow_up_plan(trace_id)?,
@@ -298,33 +391,89 @@ impl VideoTaskService {
fallback_api_key_id: Option<&str>,
trace_id: &str,
) -> Option<LocalVideoTaskFollowUpPlan> {
match plan_kind {
"openai_video_remix_sync" => {
let task_id = extract_openai_task_id_from_remix_path(request_path)?;
let seed = self.store.clone_openai(task_id)?;
seed.build_remix_follow_up_plan(
let snapshot = self.snapshot_for_follow_up_route(plan_kind, request_path)?;
Self::build_follow_up_plan_from_snapshot(
snapshot,
plan_kind,
body_json,
fallback_user_id,
fallback_api_key_id,
trace_id,
)
}
fn build_follow_up_plan_from_snapshot(
snapshot: LocalVideoTaskSnapshot,
plan_kind: &str,
body_json: Option<&Value>,
fallback_user_id: Option<&str>,
fallback_api_key_id: Option<&str>,
trace_id: &str,
) -> Option<LocalVideoTaskFollowUpPlan> {
match (plan_kind, snapshot) {
("openai_video_remix_sync", LocalVideoTaskSnapshot::OpenAi(seed)) => seed
.build_remix_follow_up_plan(
body_json?,
fallback_user_id,
fallback_api_key_id,
trace_id,
)
}
"openai_video_delete_sync" => {
let task_id = extract_openai_task_id_from_path(request_path)?;
let seed = self.store.clone_openai(task_id)?;
),
("openai_video_delete_sync", LocalVideoTaskSnapshot::OpenAi(seed)) => {
seed.build_delete_follow_up_plan(fallback_user_id, fallback_api_key_id, trace_id)
}
"openai_video_cancel_sync" => {
let task_id = extract_openai_task_id_from_cancel_path(request_path)?;
let seed = self.store.clone_openai(task_id)?;
("openai_video_cancel_sync", LocalVideoTaskSnapshot::OpenAi(seed)) => {
seed.build_cancel_follow_up_plan(fallback_user_id, fallback_api_key_id, trace_id)
}
"gemini_video_cancel_sync" => {
let short_id = extract_gemini_short_id_from_cancel_path(request_path)?;
let seed = self.store.clone_gemini(short_id)?;
("gemini_video_cancel_sync", LocalVideoTaskSnapshot::Gemini(seed)) => {
seed.build_cancel_follow_up_plan(fallback_user_id, fallback_api_key_id, trace_id)
}
_ => None,
}
}
pub fn prepare_follow_up_sync_plan_for_user(
&self,
plan_kind: &str,
request_path: &str,
body_json: Option<&Value>,
fallback_user_id: Option<&str>,
fallback_api_key_id: Option<&str>,
trace_id: &str,
) -> Option<LocalVideoTaskFollowUpPlan> {
let snapshot = self.snapshot_for_follow_up_route(plan_kind, request_path)?;
let user_id = fallback_user_id?.trim();
if !snapshot.belongs_to_user(user_id) {
return None;
}
Self::build_follow_up_plan_from_snapshot(
snapshot,
plan_kind,
body_json,
Some(user_id),
fallback_api_key_id,
trace_id,
)
}
fn snapshot_for_follow_up_route(
&self,
plan_kind: &str,
request_path: &str,
) -> Option<LocalVideoTaskSnapshot> {
match plan_kind {
"openai_video_remix_sync" => extract_openai_task_id_from_remix_path(request_path)
.and_then(|task_id| self.store.clone_openai(task_id))
.map(LocalVideoTaskSnapshot::OpenAi),
"openai_video_delete_sync" => extract_openai_task_id_from_path(request_path)
.and_then(|task_id| self.store.clone_openai(task_id))
.map(LocalVideoTaskSnapshot::OpenAi),
"openai_video_cancel_sync" => extract_openai_task_id_from_cancel_path(request_path)
.and_then(|task_id| self.store.clone_openai(task_id))
.map(LocalVideoTaskSnapshot::OpenAi),
"gemini_video_cancel_sync" => extract_gemini_short_id_from_cancel_path(request_path)
.and_then(|short_id| self.store.clone_gemini(short_id))
.map(LocalVideoTaskSnapshot::Gemini),
_ => None,
}
}
}
+70 -2
View File
@@ -1,6 +1,7 @@
use aether_data_contracts::repository::video_tasks::{StoredVideoTask, UpsertVideoTask};
use serde_json::{json, Map, Value};
use crate::types::sanitize_video_task_error_code;
use crate::{
local_status_from_stored, non_empty_owned, request_body_string, GeminiVideoTaskSeed,
LocalVideoTaskPersistence, LocalVideoTaskReadResponse, LocalVideoTaskSnapshot,
@@ -16,11 +17,26 @@ impl LocalVideoTaskSnapshot {
}
pub fn from_stored_task(task: &StoredVideoTask) -> Option<Self> {
task.request_metadata
let mut snapshot = task
.request_metadata
.as_ref()
.and_then(|metadata| metadata.get("rust_local_snapshot"))
.cloned()
.and_then(|value| serde_json::from_value::<LocalVideoTaskSnapshot>(value).ok())
.and_then(|value| serde_json::from_value::<LocalVideoTaskSnapshot>(value).ok())?;
// The row is the ownership source of truth. Older embedded snapshots can
// contain stale identity fields after a task import or repair.
match &mut snapshot {
Self::OpenAi(seed) => {
seed.user_id = task.user_id.clone();
seed.api_key_id = task.api_key_id.clone();
}
Self::Gemini(seed) => {
seed.user_id = task.user_id.clone();
seed.api_key_id = task.api_key_id.clone();
}
}
Some(snapshot)
}
pub fn from_stored_task_with_transport(
@@ -97,6 +113,33 @@ impl LocalVideoTaskSnapshot {
}
}
pub(crate) fn sanitize_persisted_diagnostics(&mut self) -> bool {
match self {
Self::OpenAi(seed) => {
let previous_error_code = seed.error_code.clone();
let error_code =
sanitized_error_code_for_status(seed.status, seed.error_code.take());
let changed = previous_error_code != error_code || seed.error_message.is_some();
seed.error_code = error_code;
seed.error_message = None;
changed
}
Self::Gemini(seed) => {
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 changed = previous_error_code != error_code
|| seed.error_message.is_some()
|| seed.metadata != safe_metadata;
seed.error_code = error_code;
seed.error_message = None;
seed.metadata = safe_metadata;
changed
}
}
}
pub fn read_response(&self) -> LocalVideoTaskReadResponse {
match self {
Self::OpenAi(seed) => match seed.status {
@@ -130,6 +173,18 @@ impl LocalVideoTaskSnapshot {
}
}
pub fn belongs_to_user(&self, user_id: &str) -> bool {
let user_id = user_id.trim();
if user_id.is_empty() {
return false;
}
let owner = match self {
Self::OpenAi(seed) => seed.user_id.as_deref(),
Self::Gemini(seed) => seed.user_id.as_deref(),
};
owner.map(str::trim) == Some(user_id)
}
pub fn is_active_for_refresh(&self) -> bool {
match self {
Self::OpenAi(seed) => matches!(
@@ -161,3 +216,16 @@ impl LocalVideoTaskSnapshot {
}
}
}
fn sanitized_error_code_for_status(
status: LocalVideoTaskStatus,
error_code: Option<String>,
) -> Option<String> {
match status {
LocalVideoTaskStatus::Failed => sanitize_video_task_error_code(error_code)
.or_else(|| Some("provider_error".to_string())),
LocalVideoTaskStatus::Expired => Some("expired".to_string()),
LocalVideoTaskStatus::Cancelled => Some("cancelled".to_string()),
_ => None,
}
}
@@ -1,24 +1,63 @@
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use aether_crypto::{decrypt_python_fernet_ciphertext, encrypt_python_fernet_plaintext};
use serde_json::{Map, Value};
use uuid::Uuid;
use crate::{
GeminiVideoTaskSeed, LocalVideoTaskReadResponse, LocalVideoTaskRegistryMutation,
LocalVideoTaskSnapshot, OpenAiVideoTaskSeed, VideoTaskRegistry, VideoTaskStore,
};
#[derive(Debug, Default)]
#[derive(Default)]
pub struct InMemoryVideoTaskStore {
registry: Mutex<VideoTaskRegistry>,
}
#[derive(Debug)]
impl std::fmt::Debug for InMemoryVideoTaskStore {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("InMemoryVideoTaskStore")
.field("registry", &"[redacted]")
.finish()
}
}
pub struct FileVideoTaskStore {
path: PathBuf,
encryption_key: String,
registry: Mutex<VideoTaskRegistry>,
persisted_file: Mutex<PersistedVideoTaskStore>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum PersistedVideoTaskStore {
Missing,
Bytes(Vec<u8>),
}
struct LoadedVideoTaskRegistry {
registry: VideoTaskRegistry,
persisted_file: PersistedVideoTaskStore,
needs_rewrite: bool,
}
impl std::fmt::Debug for FileVideoTaskStore {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("FileVideoTaskStore")
.field("path", &self.path)
.field("encryption_key", &"[redacted]")
.field("registry", &"[redacted]")
.finish()
}
}
const ENCRYPTED_VIDEO_TASK_STORE_PREFIX: &str = "aether-video-tasks-v2\n";
const LEGACY_ENCRYPTED_VIDEO_TASK_STORE_PREFIX: &str = "aether-video-tasks-v1\n";
const VIDEO_TASK_STORE_PURPOSE: &str = "video-task-file-store";
impl VideoTaskStore for InMemoryVideoTaskStore {
fn insert(&self, snapshot: LocalVideoTaskSnapshot) {
if let Ok(mut registry) = self.registry.lock() {
@@ -75,36 +114,107 @@ impl VideoTaskStore for InMemoryVideoTaskStore {
}
impl FileVideoTaskStore {
pub fn new(path: impl Into<PathBuf>) -> std::io::Result<Self> {
pub fn new(
path: impl Into<PathBuf>,
encryption_key: impl Into<String>,
) -> std::io::Result<Self> {
let path = path.into();
let registry = Self::load_registry(&path)?;
Ok(Self {
let encryption_key = encryption_key.into();
if encryption_key.trim().is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"video task file store encryption key cannot be empty",
));
}
let LoadedVideoTaskRegistry {
registry,
persisted_file,
needs_rewrite,
} = Self::load_registry(&path, &encryption_key)?;
let store = Self {
path,
encryption_key,
registry: Mutex::new(registry),
})
persisted_file: Mutex::new(persisted_file),
};
if needs_rewrite {
let registry = store
.registry
.lock()
.map_err(|_| std::io::Error::other("video task store lock poisoned"))?;
store.persist_registry(&registry)?;
}
Ok(store)
}
fn load_registry(path: &Path) -> std::io::Result<VideoTaskRegistry> {
if !path.exists() {
return Ok(VideoTaskRegistry::default());
}
let bytes = std::fs::read(path)?;
fn load_registry(
path: &Path,
encryption_key: &str,
) -> std::io::Result<LoadedVideoTaskRegistry> {
let persisted_file = read_persisted_video_task_store(path)?;
let PersistedVideoTaskStore::Bytes(bytes) = &persisted_file else {
return Ok(LoadedVideoTaskRegistry {
registry: VideoTaskRegistry::default(),
persisted_file,
needs_rewrite: false,
});
};
if bytes.is_empty() {
return Ok(VideoTaskRegistry::default());
return Err(invalid_store_data("video task store is empty"));
}
serde_json::from_slice(&bytes)
.map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))
if let Some(ciphertext) = bytes.strip_prefix(ENCRYPTED_VIDEO_TASK_STORE_PREFIX.as_bytes()) {
let ciphertext = std::str::from_utf8(ciphertext)
.map_err(|_| invalid_store_data("encrypted video task store is not UTF-8"))?;
let protected = decrypt_python_fernet_ciphertext(encryption_key, ciphertext.trim())
.map_err(|_| invalid_store_data("video task store decryption failed"))?;
let plaintext = protected
.strip_prefix(VIDEO_TASK_STORE_PURPOSE)
.and_then(|value| value.strip_prefix('\0'))
.ok_or_else(|| invalid_store_data("video task store purpose mismatch"))?;
let mut registry: VideoTaskRegistry = serde_json::from_str(&plaintext)
.map_err(|_| invalid_store_data("decrypted video task store is invalid"))?;
let needs_rewrite = registry.sanitize_persisted_diagnostics();
return Ok(LoadedVideoTaskRegistry {
registry,
persisted_file,
needs_rewrite,
});
}
if let Some(ciphertext) =
bytes.strip_prefix(LEGACY_ENCRYPTED_VIDEO_TASK_STORE_PREFIX.as_bytes())
{
let ciphertext = std::str::from_utf8(ciphertext)
.map_err(|_| invalid_store_data("encrypted video task store is not UTF-8"))?;
let plaintext = decrypt_python_fernet_ciphertext(encryption_key, ciphertext.trim())
.map_err(|_| invalid_store_data("video task store decryption failed"))?;
let mut registry: VideoTaskRegistry = serde_json::from_str(&plaintext)
.map_err(|_| invalid_store_data("decrypted video task store is invalid"))?;
registry.sanitize_persisted_diagnostics();
return Ok(LoadedVideoTaskRegistry {
registry,
persisted_file,
needs_rewrite: true,
});
}
if bytes.starts_with(b"aether-") {
return Err(invalid_store_data(
"unsupported encrypted video task store envelope",
));
}
Err(invalid_store_data(
"plaintext video task stores are not accepted",
))
}
fn persist_registry(&self, registry: &VideoTaskRegistry) -> std::io::Result<()> {
if let Some(parent) = self.path.parent() {
std::fs::create_dir_all(parent)?;
}
let bytes = serde_json::to_vec_pretty(registry)
.map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))?;
let temp_path = self.path.with_extension("tmp");
std::fs::write(&temp_path, bytes)?;
std::fs::rename(temp_path, &self.path)?;
let bytes = encrypted_video_task_store_bytes(&self.encryption_key, registry)?;
let mut persisted_file = self
.persisted_file
.lock()
.map_err(|_| std::io::Error::other("video task persisted-file lock poisoned"))?;
replace_video_task_store_if_unchanged(&self.path, &persisted_file, &bytes)?;
*persisted_file = PersistedVideoTaskStore::Bytes(bytes);
Ok(())
}
@@ -112,13 +222,117 @@ impl FileVideoTaskStore {
let Ok(mut registry) = self.registry.lock() else {
return false;
};
if !mutator(&mut registry) {
let mut updated_registry = registry.clone();
if !mutator(&mut updated_registry) {
return false;
}
self.persist_registry(&registry).is_ok()
if self.persist_registry(&updated_registry).is_err() {
return false;
}
*registry = updated_registry;
true
}
}
fn invalid_store_data(message: &'static str) -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::InvalidData, message)
}
fn encrypted_video_task_store_bytes(
encryption_key: &str,
registry: &VideoTaskRegistry,
) -> std::io::Result<Vec<u8>> {
let plaintext = serde_json::to_string(registry)
.map_err(|_| invalid_store_data("video task store serialization failed"))?;
let protected = format!("{VIDEO_TASK_STORE_PURPOSE}\0{plaintext}");
let ciphertext = encrypt_python_fernet_plaintext(encryption_key, &protected)
.map_err(|_| invalid_store_data("video task store encryption failed"))?;
let mut bytes = ENCRYPTED_VIDEO_TASK_STORE_PREFIX.as_bytes().to_vec();
bytes.extend_from_slice(ciphertext.as_bytes());
bytes.push(b'\n');
Ok(bytes)
}
fn read_persisted_video_task_store(path: &Path) -> std::io::Result<PersistedVideoTaskStore> {
match std::fs::read(path) {
Ok(bytes) => Ok(PersistedVideoTaskStore::Bytes(bytes)),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
Ok(PersistedVideoTaskStore::Missing)
}
Err(error) => Err(error),
}
}
fn replace_video_task_store_if_unchanged(
path: &Path,
expected: &PersistedVideoTaskStore,
replacement: &[u8],
) -> std::io::Result<()> {
if let Some(parent) = path
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
{
std::fs::create_dir_all(parent)?;
}
let lock_path = video_task_store_lock_path(path);
let lock_file = open_private_lock_file(&lock_path)?;
// Lock a stable sidecar inode: the store inode itself is replaced by rename.
lock_file.lock()?;
// The bytes captured before parsing/decryption are the migration/write CAS token.
let observed = read_persisted_video_task_store(path)?;
if &observed != expected {
return Err(std::io::Error::new(
std::io::ErrorKind::WouldBlock,
"video task store changed before compare-and-replace",
));
}
let temp_path = path.with_extension(format!("tmp-{}", Uuid::new_v4()));
if let Err(error) = write_private_file(&temp_path, replacement) {
let _ = std::fs::remove_file(&temp_path);
return Err(error);
}
if let Err(error) = std::fs::rename(&temp_path, path) {
let _ = std::fs::remove_file(&temp_path);
return Err(error);
}
Ok(())
}
fn video_task_store_lock_path(path: &Path) -> PathBuf {
let mut lock_path = path.as_os_str().to_os_string();
lock_path.push(".lock");
PathBuf::from(lock_path)
}
fn open_private_lock_file(path: &Path) -> std::io::Result<std::fs::File> {
let mut options = std::fs::OpenOptions::new();
options.read(true).write(true).create(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt as _;
options.mode(0o600);
}
options.open(path)
}
fn write_private_file(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
use std::io::Write as _;
let mut options = std::fs::OpenOptions::new();
options.write(true).create_new(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt as _;
options.mode(0o600);
}
let mut file = options.open(path)?;
file.write_all(bytes)?;
file.sync_all()
}
impl VideoTaskStore for FileVideoTaskStore {
fn insert(&self, snapshot: LocalVideoTaskSnapshot) {
let _ = self.mutate_registry(|registry| {
@@ -169,3 +383,283 @@ impl VideoTaskStore for FileVideoTaskStore {
self.mutate_registry(|registry| registry.project_gemini(short_id, provider_body))
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use aether_crypto::DEVELOPMENT_ENCRYPTION_KEY;
use serde_json::json;
use super::*;
use crate::{LocalVideoTaskPersistence, LocalVideoTaskStatus, LocalVideoTaskTransport};
fn temp_store_path(name: &str) -> PathBuf {
std::env::temp_dir().join(format!(
"aether-video-store-{name}-{}-{}.json",
std::process::id(),
Uuid::new_v4()
))
}
fn cleanup_store_path(path: &Path) {
std::fs::remove_file(path).ok();
std::fs::remove_file(video_task_store_lock_path(path)).ok();
}
fn sensitive_gemini_snapshot() -> LocalVideoTaskSnapshot {
LocalVideoTaskSnapshot::Gemini(GeminiVideoTaskSeed {
local_short_id: "task-sensitive".to_string(),
upstream_operation_name: "operations/upstream-sensitive".to_string(),
user_id: Some("user-1".to_string()),
api_key_id: Some("api-key-1".to_string()),
model: "veo-3".to_string(),
status: LocalVideoTaskStatus::Failed,
progress_percent: 100,
error_code: Some("Bearer code-secret".to_string()),
error_message: Some("Authorization: Bearer error-secret".to_string()),
metadata: json!({
"debug": "metadata-secret",
"url": "https://internal.test/result?token=metadata-query-secret"
}),
persistence: LocalVideoTaskPersistence {
request_id: "request-1".to_string(),
username: Some("alice".to_string()),
api_key_name: Some("primary".to_string()),
client_api_format: "gemini:video".to_string(),
provider_api_format: "gemini:video".to_string(),
original_request_body: json!({"prompt": "create a video"}),
format_converted: false,
},
transport: LocalVideoTaskTransport {
upstream_base_url: "https://generativelanguage.googleapis.com".to_string(),
provider_name: Some("gemini".to_string()),
provider_id: "provider-1".to_string(),
endpoint_id: "endpoint-1".to_string(),
key_id: "key-1".to_string(),
headers: BTreeMap::from([(
"x-goog-api-key".to_string(),
"transport-key-required-for-resume".to_string(),
)]),
content_type: Some("application/json".to_string()),
model_name: Some("veo-3".to_string()),
proxy: None,
transport_profile: None,
timeouts: None,
},
})
}
#[test]
fn loading_encrypted_store_rewrites_legacy_provider_diagnostics() {
let path = temp_store_path("diagnostic-migration");
let legacy_plaintext = serde_json::to_string(&json!({
"openai": {},
"gemini": {
"task-sensitive": sensitive_gemini_snapshot()
}
}))
.expect("legacy registry should serialize");
let ciphertext =
encrypt_python_fernet_plaintext(DEVELOPMENT_ENCRYPTION_KEY, &legacy_plaintext)
.expect("legacy registry should encrypt");
std::fs::write(
&path,
format!("{LEGACY_ENCRYPTED_VIDEO_TASK_STORE_PREFIX}{ciphertext}\n"),
)
.expect("legacy encrypted registry should be written");
let store = FileVideoTaskStore::new(&path, DEVELOPMENT_ENCRYPTION_KEY)
.expect("legacy encrypted registry should load");
let response = store
.read_gemini("task-sensitive")
.expect("migrated task should remain readable");
assert_eq!(response.body_json["error"]["code"], "provider_error");
assert_eq!(
response.body_json["error"]["message"],
"Video generation failed"
);
let bytes = std::fs::read(&path).expect("migrated registry should be readable");
let ciphertext = bytes
.strip_prefix(ENCRYPTED_VIDEO_TASK_STORE_PREFIX.as_bytes())
.expect("migrated registry should stay encrypted");
let ciphertext = std::str::from_utf8(ciphertext)
.expect("ciphertext should be UTF-8")
.trim();
let migrated_plaintext =
decrypt_python_fernet_ciphertext(DEVELOPMENT_ENCRYPTION_KEY, ciphertext)
.expect("migrated registry should decrypt");
for secret in [
"code-secret",
"error-secret",
"metadata-secret",
"metadata-query-secret",
] {
assert!(
!migrated_plaintext.contains(secret),
"migrated registry leaked {secret}"
);
}
assert!(migrated_plaintext.contains("transport-key-required-for-resume"));
assert!(migrated_plaintext.contains("provider_error"));
cleanup_store_path(&path);
}
#[test]
fn rejects_plaintext_registry_without_rewriting_it() {
let path = temp_store_path("plaintext-injection");
let plaintext = serde_json::to_vec(&json!({
"openai": {},
"gemini": {},
}))
.expect("plaintext registry should serialize");
std::fs::write(&path, &plaintext).expect("plaintext registry should be written");
let error = FileVideoTaskStore::new(&path, DEVELOPMENT_ENCRYPTION_KEY)
.expect_err("plaintext registry must fail closed");
assert_eq!(error.kind(), std::io::ErrorKind::InvalidData);
assert!(error.to_string().contains("plaintext"));
assert_eq!(
std::fs::read(&path).expect("rejected registry should remain readable"),
plaintext,
"rejected plaintext must not be rewritten into an authenticated envelope",
);
cleanup_store_path(&path);
}
#[test]
fn rejects_unknown_aether_envelopes_without_rewriting_them() {
for (name, bytes) in [
(
"unknown-video-version",
b"aether-video-tasks-v999\nopaque".as_slice(),
),
(
"foreign-aether-envelope",
b"aether-other-v1\nopaque".as_slice(),
),
] {
let path = temp_store_path(name);
std::fs::write(&path, bytes).expect("unknown envelope should be written");
let error = FileVideoTaskStore::new(&path, DEVELOPMENT_ENCRYPTION_KEY)
.expect_err("unknown aether envelope must fail closed");
assert_eq!(error.kind(), std::io::ErrorKind::InvalidData);
assert!(error.to_string().contains("unsupported"));
assert_eq!(
std::fs::read(&path).expect("rejected envelope should remain readable"),
bytes,
);
cleanup_store_path(&path);
}
}
#[test]
fn rejects_tampered_authenticated_store_without_rewriting_it() {
let path = temp_store_path("tampered-v2");
let mut bytes = encrypted_video_task_store_bytes(
DEVELOPMENT_ENCRYPTION_KEY,
&VideoTaskRegistry::default(),
)
.expect("encrypted registry should serialize");
let tampered_index = ENCRYPTED_VIDEO_TASK_STORE_PREFIX.len() + 12;
bytes[tampered_index] = if bytes[tampered_index] == b'A' {
b'B'
} else {
b'A'
};
std::fs::write(&path, &bytes).expect("tampered registry should be written");
let error = FileVideoTaskStore::new(&path, DEVELOPMENT_ENCRYPTION_KEY)
.expect_err("tampered authenticated registry must fail closed");
assert_eq!(error.kind(), std::io::ErrorKind::InvalidData);
assert!(error.to_string().contains("decryption failed"));
assert_eq!(
std::fs::read(&path).expect("tampered registry should remain readable"),
bytes,
);
cleanup_store_path(&path);
}
#[test]
fn legacy_migration_compare_before_replace_preserves_concurrent_replacement() {
let path = temp_store_path("legacy-migration-race");
let legacy_plaintext = serde_json::to_string(&json!({
"openai": {},
"gemini": {
"task-sensitive": sensitive_gemini_snapshot(),
},
}))
.expect("legacy registry should serialize");
let legacy_ciphertext =
encrypt_python_fernet_plaintext(DEVELOPMENT_ENCRYPTION_KEY, &legacy_plaintext)
.expect("legacy registry should encrypt");
let legacy_bytes =
format!("{LEGACY_ENCRYPTED_VIDEO_TASK_STORE_PREFIX}{legacy_ciphertext}\n").into_bytes();
std::fs::write(&path, &legacy_bytes).expect("legacy registry should be written");
let loaded = FileVideoTaskStore::load_registry(&path, DEVELOPMENT_ENCRYPTION_KEY)
.expect("authenticated legacy registry should load");
assert!(loaded.needs_rewrite);
assert_eq!(
loaded.persisted_file,
PersistedVideoTaskStore::Bytes(legacy_bytes),
);
let store = FileVideoTaskStore {
path: path.clone(),
encryption_key: DEVELOPMENT_ENCRYPTION_KEY.to_string(),
registry: Mutex::new(loaded.registry),
persisted_file: Mutex::new(loaded.persisted_file),
};
let concurrent_replacement = encrypted_video_task_store_bytes(
DEVELOPMENT_ENCRYPTION_KEY,
&VideoTaskRegistry::default(),
)
.expect("replacement registry should encrypt");
std::fs::write(&path, &concurrent_replacement)
.expect("concurrent replacement should be written");
let registry = store.registry.lock().expect("registry lock should succeed");
let error = store
.persist_registry(&registry)
.expect_err("stale legacy migration must report a conflict");
drop(registry);
assert_eq!(error.kind(), std::io::ErrorKind::WouldBlock);
assert_eq!(
std::fs::read(&path).expect("concurrent replacement should remain readable"),
concurrent_replacement,
"stale migration must not overwrite a newer exact byte snapshot",
);
cleanup_store_path(&path);
}
#[test]
fn rejects_valid_fernet_ciphertext_from_another_purpose() {
let path = temp_store_path("purpose-mismatch");
let protected = format!(
"another-purpose\0{}",
serde_json::to_string(&VideoTaskRegistry::default())
.expect("empty registry should serialize")
);
let ciphertext = encrypt_python_fernet_plaintext(DEVELOPMENT_ENCRYPTION_KEY, &protected)
.expect("foreign payload should encrypt");
std::fs::write(
&path,
format!("{ENCRYPTED_VIDEO_TASK_STORE_PREFIX}{ciphertext}\n"),
)
.expect("foreign ciphertext should be written");
let error = FileVideoTaskStore::new(&path, DEVELOPMENT_ENCRYPTION_KEY)
.expect_err("foreign-purpose ciphertext must fail closed");
assert_eq!(error.kind(), std::io::ErrorKind::InvalidData);
cleanup_store_path(&path);
}
}
@@ -15,7 +15,8 @@ pub struct VideoTaskRegistry {
}
impl VideoTaskRegistry {
pub fn insert(&mut self, snapshot: LocalVideoTaskSnapshot) {
pub fn insert(&mut self, mut snapshot: LocalVideoTaskSnapshot) {
snapshot.sanitize_persisted_diagnostics();
match &snapshot {
LocalVideoTaskSnapshot::OpenAi(seed) => {
self.openai.insert(seed.local_task_id.clone(), snapshot);
@@ -97,4 +98,12 @@ impl VideoTaskRegistry {
seed.apply_provider_body(provider_body);
true
}
pub(crate) fn sanitize_persisted_diagnostics(&mut self) -> bool {
let mut changed = false;
for snapshot in self.openai.values_mut().chain(self.gemini.values_mut()) {
changed = snapshot.sanitize_persisted_diagnostics() || changed;
}
changed
}
}
+70 -2
View File
@@ -89,7 +89,7 @@ pub enum LocalVideoTaskSeed {
GeminiCreate(GeminiVideoTaskSeed),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct LocalVideoTaskTransport {
pub upstream_base_url: String,
pub provider_name: Option<String>,
@@ -104,7 +104,52 @@ pub struct LocalVideoTaskTransport {
pub timeouts: Option<ExecutionTimeouts>,
}
#[derive(Debug, Clone, PartialEq)]
impl std::fmt::Debug for LocalVideoTaskTransport {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("LocalVideoTaskTransport")
.field("upstream_base_url", &"[redacted]")
.field("provider_name", &self.provider_name)
.field("provider_id", &self.provider_id)
.field("endpoint_id", &self.endpoint_id)
.field("key_id", &self.key_id)
.field("headers", &"[redacted]")
.field("content_type", &self.content_type)
.field("model_name", &self.model_name)
.field("proxy", &self.proxy.as_ref().map(|_| "[redacted]"))
.field(
"transport_profile",
&self.transport_profile.as_ref().map(|_| "[redacted]"),
)
.field("timeouts", &self.timeouts)
.finish()
}
}
pub(crate) fn sanitize_video_task_error_code(value: Option<String>) -> Option<String> {
let value = value?.trim().to_ascii_lowercase();
if value.is_empty() {
return None;
}
Some(match value.as_str() {
"authentication_error"
| "cancelled"
| "content_policy_violation"
| "expired"
| "invalid_request"
| "not_found"
| "permission_denied"
| "poll_permanent_error"
| "poll_timeout"
| "provider_error"
| "rate_limit_exceeded"
| "server_error"
| "unknown" => value,
_ => "provider_error".to_string(),
})
}
#[derive(Clone, PartialEq)]
pub struct LocalVideoTaskTransportBridgeInput {
pub upstream_base_url: String,
pub provider_name: Option<String>,
@@ -120,6 +165,29 @@ pub struct LocalVideoTaskTransportBridgeInput {
pub timeouts: Option<ExecutionTimeouts>,
}
impl std::fmt::Debug for LocalVideoTaskTransportBridgeInput {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("LocalVideoTaskTransportBridgeInput")
.field("upstream_base_url", &"[redacted]")
.field("provider_name", &self.provider_name)
.field("provider_id", &self.provider_id)
.field("endpoint_id", &self.endpoint_id)
.field("key_id", &self.key_id)
.field("auth_header", &"[redacted]")
.field("auth_value", &"[redacted]")
.field("content_type", &self.content_type)
.field("model_name", &self.model_name)
.field("proxy", &self.proxy.as_ref().map(|_| "[redacted]"))
.field(
"transport_profile",
&self.transport_profile.as_ref().map(|_| "[redacted]"),
)
.field("timeouts", &self.timeouts)
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LocalVideoTaskPersistence {
pub request_id: String,