Files
bsodfather 0e8f1ca03a
CI / Rust Checks (push) Failing after 4m28s
CI / UI Checks (push) Has been skipped
CI / Frontend E2E (push) Has been skipped
CI / Community Image Smoke (push) Has been skipped
CI / Deploy (push) Has been skipped
наблюдаемость: завершить базовый контур Community
Добавить структурированные журналы, метрики, трассировку и безопасный канал критических ошибок. Усилить границы рантайма, тесты, проверку зависимостей и сценарии развёртывания.
2026-07-31 01:01:14 +03:00

207 lines
5.8 KiB
Rust

use crank_observability::{RedactionLimits, redact_value};
use serde_json::{Value, json};
const REDACTED: &str = "[REDACTED]";
const TRUNCATED: &str = "[TRUNCATED]";
#[test]
fn sensitive_keys_are_redacted_case_insensitively() {
let keys = [
"password",
"PassWord",
"api_key",
"access-token",
"authorization",
"Proxy.Authorization",
"cookie",
"set_cookie",
"payload",
"request_body",
"arguments",
"result",
"response",
];
for key in keys {
let cleaned = redact_value(&json!({ key: "canary-secret" }), RedactionLimits::default());
assert_eq!(cleaned[key], REDACTED, "key {key} was not redacted");
assert!(!cleaned.to_string().contains("canary-secret"));
}
}
#[test]
fn generated_sensitive_key_variants_are_redacted() {
for canonical in [
"password",
"api_key",
"access_key",
"secret_key",
"proxy_authorization",
"set_cookie",
"query_string",
"request_body",
] {
for separator in ["_", "-", "."] {
let variant = canonical
.split('_')
.collect::<Vec<_>>()
.join(separator)
.to_ascii_uppercase();
let cleaned = redact_value(
&json!({ variant.clone(): "canary-secret" }),
RedactionLimits::default(),
);
assert_eq!(
cleaned[&variant], REDACTED,
"key {variant} was not redacted"
);
assert!(!cleaned.to_string().contains("canary-secret"));
}
}
}
#[test]
fn compound_sensitive_key_names_are_redacted() {
for key in [
"client_api_key",
"aws_access_key_id",
"http_authorization_header",
"response_body",
"tool_arguments",
"query_params",
"secret_value",
] {
let cleaned = redact_value(&json!({ key: "canary-secret" }), RedactionLimits::default());
assert_eq!(cleaned[key], REDACTED, "key {key} was not redacted");
assert!(!cleaned.to_string().contains("canary-secret"));
}
}
#[test]
fn url_query_and_fragment_are_removed_at_every_depth() {
let input = json!({
"url": "https://example.test/path?token=canary-secret#fragment",
"nested": [{
"endpoint_uri": "https://example.test/other?q=canary-secret"
}]
});
let cleaned = redact_value(&input, RedactionLimits::default());
assert_eq!(cleaned["url"], "https://example.test/path");
assert_eq!(
cleaned["nested"][0]["endpoint_uri"],
"https://example.test/other"
);
assert!(!cleaned.to_string().contains("canary-secret"));
}
#[test]
fn url_credentials_and_compound_endpoint_fields_are_removed() {
let input = json!({
"upstream_endpoint": "https://user:canary-secret@example.test/path?token=canary-secret",
});
let cleaned = redact_value(&input, RedactionLimits::default());
assert_eq!(cleaned["upstream_endpoint"], "https://example.test/path");
assert!(!cleaned.to_string().contains("user"));
assert!(!cleaned.to_string().contains("canary-secret"));
}
#[test]
fn nested_values_and_collections_respect_all_limits() {
let limits = RedactionLimits {
max_string_bytes: 16,
max_array_items: 3,
max_object_fields: 3,
max_depth: 2,
max_event_bytes: 256,
};
let input = json!({
"long": "абвгдежзийклмнопрсту",
"array": [1, 2, 3, 4, 5],
"object": {"a": 1, "b": 2, "c": 3, "d": 4},
"nested": {"level2": {"level3": "must not survive"}}
});
let cleaned = redact_value(&input, limits);
let object = cleaned
.as_object()
.expect("cleaned root must remain an object");
assert!(object.len() <= limits.max_object_fields);
assert!(
cleaned["long"]
.as_str()
.map(|value| value.len() <= limits.max_string_bytes)
.unwrap_or(true)
);
assert!(
cleaned["array"]
.as_array()
.map(|value| value.len() <= limits.max_array_items)
.unwrap_or(true)
);
assert!(!cleaned.to_string().contains("must not survive"));
assert!(cleaned.to_string().contains(TRUNCATED));
}
#[test]
fn truncation_preserves_utf8_and_never_reveals_secret_fragments() {
let input = json!({
"secret_key": "секретное-значение",
"description": "я".repeat(2048),
});
let cleaned = redact_value(&input, RedactionLimits::default());
let serialized = serde_json::to_string(&cleaned).expect("cleaned value must be valid JSON");
assert_eq!(cleaned["secret_key"], REDACTED);
assert!(!serialized.contains("секретное"));
assert!(cleaned["description"].as_str().is_some());
}
#[test]
fn redacted_value_does_not_mutate_input() {
let input = json!({"password": "canary-secret"});
let original = input.clone();
let _ = redact_value(&input, RedactionLimits::default());
assert_eq!(input, original);
}
#[test]
fn object_keys_respect_the_string_limit() {
let limits = RedactionLimits {
max_string_bytes: 16,
..RedactionLimits::default()
};
let long_key = format!("field-{}", "x".repeat(128));
let cleaned = redact_value(&json!({ long_key: "value" }), limits);
assert!(
cleaned
.as_object()
.expect("cleaned object")
.keys()
.all(|key| key.len() <= limits.max_string_bytes)
);
}
#[test]
fn limits_have_finite_safe_defaults() {
let limits = RedactionLimits::default();
assert_eq!(limits.max_string_bytes, 1024);
assert_eq!(limits.max_array_items, 32);
assert_eq!(limits.max_object_fields, 64);
assert_eq!(limits.max_depth, 8);
assert_eq!(limits.max_event_bytes, 16 * 1024);
assert!(Value::Null.is_null());
}