mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
fix(gateway): mask catalog secrets on char boundaries
This commit is contained in:
@@ -3,7 +3,10 @@ use crate::handlers::admin::provider::shared::support::{
|
||||
ADMIN_PROVIDER_MAPPING_PREVIEW_MAX_KEYS, ADMIN_PROVIDER_MAPPING_PREVIEW_MAX_MODELS,
|
||||
};
|
||||
use crate::handlers::admin::request::AdminAppState;
|
||||
use crate::handlers::admin::shared::{decrypt_catalog_secret_with_fallbacks, json_string_list};
|
||||
use crate::handlers::admin::shared::{
|
||||
decrypt_catalog_secret_with_fallbacks, json_string_list, take_secret_prefix,
|
||||
take_secret_suffix,
|
||||
};
|
||||
use crate::handlers::public::matches_model_mapping_for_models;
|
||||
use crate::{GatewayError, LocalProviderDeleteTaskState};
|
||||
use aether_data_contracts::repository::global_models::{
|
||||
@@ -175,14 +178,15 @@ pub(crate) fn mapping_preview_masked_catalog_api_key(
|
||||
|
||||
decrypt_catalog_secret_with_fallbacks(state.encryption_key(), ciphertext)
|
||||
.map(|value| {
|
||||
if value.len() > 8 {
|
||||
let char_count = value.chars().count();
|
||||
if char_count > 8 {
|
||||
format!(
|
||||
"{}***{}",
|
||||
&value[..4],
|
||||
&value[value.len().saturating_sub(4)..]
|
||||
take_secret_prefix(&value, 4),
|
||||
take_secret_suffix(&value, 4)
|
||||
)
|
||||
} else if value.len() >= 2 {
|
||||
format!("{}***", &value[..2])
|
||||
} else if char_count >= 2 {
|
||||
format!("{}***", take_secret_prefix(&value, 2))
|
||||
} else {
|
||||
"***".to_string()
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@ pub(crate) use crate::handlers::shared::{
|
||||
masked_catalog_api_key, normalize_json_array, normalize_json_object, normalize_string_list,
|
||||
parse_catalog_auth_config_json, provider_catalog_key_supports_format,
|
||||
provider_key_health_summary, provider_key_status_snapshot_payload, query_param_bool,
|
||||
query_param_optional_bool, query_param_value, unix_secs_to_rfc3339,
|
||||
OFFICIAL_EXTERNAL_MODEL_PROVIDERS,
|
||||
query_param_optional_bool, query_param_value, take_secret_prefix, take_secret_suffix,
|
||||
unix_secs_to_rfc3339, OFFICIAL_EXTERNAL_MODEL_PROVIDERS,
|
||||
};
|
||||
|
||||
@@ -102,6 +102,29 @@ pub(crate) fn encrypt_catalog_secret_with_fallbacks(
|
||||
encrypt_python_fernet_plaintext(encryption_key.as_ref(), plaintext).ok()
|
||||
}
|
||||
|
||||
pub(crate) fn take_secret_prefix(value: &str, prefix_chars: usize) -> &str {
|
||||
let end = value
|
||||
.char_indices()
|
||||
.nth(prefix_chars)
|
||||
.map(|(index, _)| index)
|
||||
.unwrap_or(value.len());
|
||||
&value[..end]
|
||||
}
|
||||
|
||||
pub(crate) fn take_secret_suffix(value: &str, suffix_chars: usize) -> &str {
|
||||
if suffix_chars == 0 {
|
||||
return &value[value.len()..];
|
||||
}
|
||||
|
||||
let start = value
|
||||
.char_indices()
|
||||
.rev()
|
||||
.nth(suffix_chars - 1)
|
||||
.map(|(index, _)| index)
|
||||
.unwrap_or(0);
|
||||
&value[start..]
|
||||
}
|
||||
|
||||
pub(crate) fn masked_catalog_api_key(state: &AppState, key: &StoredProviderCatalogKey) -> String {
|
||||
match key.auth_type.trim() {
|
||||
"service_account" | "vertex_ai" => "[Service Account]".to_string(),
|
||||
@@ -117,13 +140,13 @@ pub(crate) fn masked_catalog_api_key(state: &AppState, key: &StoredProviderCatal
|
||||
};
|
||||
decrypt_catalog_secret_with_fallbacks(state.encryption_key(), ciphertext)
|
||||
.map(|value| {
|
||||
if value.len() <= 12 {
|
||||
if value.chars().count() <= 12 {
|
||||
format!("{value}***")
|
||||
} else {
|
||||
format!(
|
||||
"{}***{}",
|
||||
&value[..8],
|
||||
&value[value.len().saturating_sub(4)..]
|
||||
take_secret_prefix(&value, 8),
|
||||
take_secret_suffix(&value, 4)
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1621,6 +1644,41 @@ mod tests {
|
||||
.expect("key transport should build")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn masked_catalog_api_key_handles_unicode_plaintext_without_panicking() {
|
||||
let state = AppState::new().expect("gateway should build");
|
||||
let encrypted_api_key = encrypt_python_fernet_plaintext(
|
||||
DEVELOPMENT_ENCRYPTION_KEY,
|
||||
"测试-密钥-1234567890",
|
||||
)
|
||||
.expect("api key ciphertext should build");
|
||||
let key = StoredProviderCatalogKey::new(
|
||||
"key-unicode".to_string(),
|
||||
"provider-test".to_string(),
|
||||
"default".to_string(),
|
||||
"api_key".to_string(),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.expect("key should build")
|
||||
.with_transport_fields(
|
||||
Some(json!(["openai:chat"])),
|
||||
encrypted_api_key,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.expect("key transport should build");
|
||||
|
||||
let masked = masked_catalog_api_key(&state, &key);
|
||||
assert!(masked.contains("***"));
|
||||
assert_ne!(masked, "***ERROR***");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_key_status_snapshot_payload_backfills_missing_quota_from_upstream_metadata() {
|
||||
let mut key = sample_catalog_key();
|
||||
|
||||
@@ -25,7 +25,7 @@ pub(crate) use self::catalog::{
|
||||
encrypt_catalog_secret_with_fallbacks, masked_catalog_api_key, parse_catalog_auth_config_json,
|
||||
provider_catalog_key_supports_format, provider_key_health_summary,
|
||||
provider_key_status_snapshot_payload, sync_provider_key_oauth_status_snapshot,
|
||||
sync_provider_key_quota_status_snapshot,
|
||||
sync_provider_key_quota_status_snapshot, take_secret_prefix, take_secret_suffix,
|
||||
};
|
||||
pub(crate) use self::email_templates::{
|
||||
admin_email_template_definition, admin_email_template_html_key,
|
||||
|
||||
Reference in New Issue
Block a user