#[cfg(feature = "telemetry")]
mod inner {
use hmac::Hmac;
use once_cell::sync::Lazy;
use pbkdf2::pbkdf2;
use sha2::Sha256;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use uuid::Uuid;
const DEFAULT_SALT: &[u8] = b"cognee.telemetry.api-key-tracking.v1";
const ITERATIONS: u32 = 100_000;
const DKLEN: usize = 16;
static ANON_ID: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mutex::new(None));
static PERSISTENT_ID: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mutex::new(None));
#[cfg(test)]
#[allow(
clippy::unwrap_used,
reason = "Mutex lock poison is unrecoverable — no meaningful recovery possible"
)]
pub(crate) fn reset_caches_for_test() {
*ANON_ID.lock().unwrap() = None;
*PERSISTENT_ID.lock().unwrap() = None;
}
#[cfg(any(test, debug_assertions))]
#[doc(hidden)]
#[allow(
clippy::unwrap_used,
reason = "Mutex lock poison is unrecoverable — no meaningful recovery possible"
)]
pub fn __test_only_reset_caches() {
*ANON_ID.lock().unwrap() = None;
*PERSISTENT_ID.lock().unwrap() = None;
}
#[allow(
clippy::unwrap_used,
reason = "Mutex lock poison is unrecoverable — no meaningful recovery possible"
)]
pub fn get_anonymous_id() -> String {
if let Ok(v) = std::env::var("TRACKING_ID")
&& !v.is_empty()
{
return v;
}
let mut cached = ANON_ID.lock().unwrap();
if let Some(v) = cached.as_ref() {
return v.clone();
}
let computed = compute_anon_id();
*cached = Some(computed.clone());
computed
}
fn compute_anon_id() -> String {
let dir = match find_project_root() {
Some(p) => p,
None => match std::env::current_dir() {
Ok(p) => p,
Err(e) => {
tracing::debug!(
target: "cognee.telemetry",
error = %e,
"could not resolve project root for .anon_id"
);
return "unknown-anonymous-id".to_string();
}
},
};
let path = dir.join(".anon_id");
match std::fs::read_to_string(&path) {
Ok(s) => s.trim().to_string(),
Err(_) => {
let new_id = Uuid::new_v4().to_string();
if let Err(e) = std::fs::create_dir_all(&dir) {
tracing::debug!(
target: "cognee.telemetry",
error = %e,
path = %dir.display(),
"could not create dir for .anon_id"
);
return "unknown-anonymous-id".to_string();
}
if let Err(e) = std::fs::write(&path, &new_id) {
tracing::debug!(
target: "cognee.telemetry",
error = %e,
path = %path.display(),
"could not write .anon_id"
);
return "unknown-anonymous-id".to_string();
}
new_id
}
}
}
fn find_project_root() -> Option<PathBuf> {
let start = std::env::current_dir().ok()?;
let mut here: &Path = &start;
loop {
if here.join("Cargo.toml").is_file() {
return Some(here.to_path_buf());
}
here = here.parent()?;
}
}
#[allow(
clippy::unwrap_used,
reason = "Mutex lock poison is unrecoverable — no meaningful recovery possible"
)]
pub fn get_persistent_id() -> String {
let mut cached = PERSISTENT_ID.lock().unwrap();
if let Some(v) = cached.as_ref() {
return v.clone();
}
let computed = compute_persistent_id();
*cached = Some(computed.clone());
computed
}
fn compute_persistent_id() -> String {
let dir = match dirs::home_dir() {
Some(p) => p.join(".cognee"),
None => {
tracing::debug!(
target: "cognee.telemetry",
"no home directory; falling back to anonymous id"
);
return get_anonymous_id();
}
};
let path = dir.join(".persistent_id");
if let Ok(s) = std::fs::read_to_string(&path) {
return s.trim().to_string();
}
let mut new_id = get_anonymous_id();
if new_id == "unknown-anonymous-id" {
new_id = Uuid::new_v4().to_string();
}
if let Err(e) = std::fs::create_dir_all(&dir) {
tracing::debug!(
target: "cognee.telemetry",
error = %e,
path = %dir.display(),
"could not create ~/.cognee for persistent id"
);
return get_anonymous_id();
}
if let Err(e) = std::fs::write(&path, &new_id) {
tracing::debug!(
target: "cognee.telemetry",
error = %e,
path = %path.display(),
"could not write persistent id"
);
return get_anonymous_id();
}
new_id
}
#[allow(
clippy::expect_used,
reason = "PBKDF2 with dklen=16 ≤ SHA256 output=32 is mathematically guaranteed to succeed"
)]
pub fn get_api_key_tracking_id() -> String {
let key = std::env::var("LLM_API_KEY").unwrap_or_default();
if key.is_empty() {
return String::new();
}
let salt: Vec<u8> = std::env::var("TELEMETRY_API_KEY_TRACKING_SALT")
.map(|s| s.into_bytes())
.unwrap_or_else(|_| DEFAULT_SALT.to_vec());
let mut out = [0u8; DKLEN];
pbkdf2::<Hmac<Sha256>>(key.as_bytes(), &salt, ITERATIONS, &mut out)
.expect("dklen 16 ≤ Sha256 output 32 — invariant holds");
format!("ak_{}", hex::encode(out))
}
}
#[cfg(not(feature = "telemetry"))]
mod inner {
pub fn get_anonymous_id() -> String {
String::new()
}
pub fn get_persistent_id() -> String {
String::new()
}
pub fn get_api_key_tracking_id() -> String {
String::new()
}
}
pub use inner::get_anonymous_id;
pub use inner::get_api_key_tracking_id;
pub use inner::get_persistent_id;
#[cfg(all(any(test, debug_assertions), feature = "telemetry"))]
#[doc(hidden)]
pub use inner::__test_only_reset_caches;
#[cfg(all(test, feature = "telemetry"))]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "test code — panics are acceptable failures"
)]
mod ids_tests {
use super::*;
use serial_test::serial;
use tempfile::TempDir;
struct EnvGuard {
home: Option<String>,
tracking_id: Option<String>,
llm_api_key: Option<String>,
salt: Option<String>,
}
impl EnvGuard {
fn snapshot() -> Self {
Self {
home: std::env::var("HOME").ok(),
tracking_id: std::env::var("TRACKING_ID").ok(),
llm_api_key: std::env::var("LLM_API_KEY").ok(),
salt: std::env::var("TELEMETRY_API_KEY_TRACKING_SALT").ok(),
}
}
}
impl Drop for EnvGuard {
fn drop(&mut self) {
for (k, v) in [
("HOME", &self.home),
("TRACKING_ID", &self.tracking_id),
("LLM_API_KEY", &self.llm_api_key),
("TELEMETRY_API_KEY_TRACKING_SALT", &self.salt),
] {
unsafe {
match v {
Some(v) => std::env::set_var(k, v),
None => std::env::remove_var(k),
}
}
}
}
}
#[test]
#[serial]
fn ak_format_invariants() {
let _g = EnvGuard::snapshot();
unsafe {
std::env::set_var("LLM_API_KEY", "sk-test");
std::env::remove_var("TELEMETRY_API_KEY_TRACKING_SALT");
}
let id = get_api_key_tracking_id();
assert!(id.starts_with("ak_"));
assert_eq!(id.len(), 35);
assert!(
id[3..]
.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
);
}
#[test]
#[serial]
fn empty_llm_api_key_returns_empty() {
let _g = EnvGuard::snapshot();
unsafe {
std::env::remove_var("LLM_API_KEY");
}
assert_eq!(get_api_key_tracking_id(), "");
}
#[test]
#[serial]
fn full_key_used_not_visible_tail() {
let _g = EnvGuard::snapshot();
unsafe {
std::env::remove_var("TELEMETRY_API_KEY_TRACKING_SALT");
std::env::set_var("LLM_API_KEY", "sk-aaaaaaaaaaaaaa1234");
}
let id_a = get_api_key_tracking_id();
unsafe {
std::env::set_var("LLM_API_KEY", "sk-bbbbbbbbbbbbbb1234");
}
let id_b = get_api_key_tracking_id();
assert_ne!(id_a, id_b);
}
#[test]
#[serial]
fn deployment_salt_changes_id() {
let _g = EnvGuard::snapshot();
unsafe {
std::env::set_var("LLM_API_KEY", "sk-test");
std::env::remove_var("TELEMETRY_API_KEY_TRACKING_SALT");
}
let default_id = get_api_key_tracking_id();
unsafe {
std::env::set_var("TELEMETRY_API_KEY_TRACKING_SALT", "private-salt-2026");
}
let custom_id = get_api_key_tracking_id();
assert_ne!(default_id, custom_id);
}
#[test]
#[serial]
fn persistent_id_create_then_stable() {
let _g = EnvGuard::snapshot();
let dir = TempDir::new().expect("tempdir");
unsafe {
std::env::set_var("HOME", dir.path());
}
super::inner::reset_caches_for_test();
let first = get_persistent_id();
assert!(!first.is_empty());
assert!(uuid::Uuid::parse_str(&first).is_ok());
let path = dir.path().join(".cognee").join(".persistent_id");
assert!(path.exists());
let on_disk = std::fs::read_to_string(&path).expect("persistent_id file readable");
assert_eq!(on_disk.trim(), first);
super::inner::reset_caches_for_test();
let second = get_persistent_id();
assert_eq!(first, second);
}
#[test]
#[serial]
fn tracking_id_env_overrides_anon_id() {
let _g = EnvGuard::snapshot();
unsafe {
std::env::set_var("TRACKING_ID", "fixed-anon-12345");
}
super::inner::reset_caches_for_test();
assert_eq!(get_anonymous_id(), "fixed-anon-12345");
}
#[test]
#[serial]
fn tracking_id_empty_env_does_not_override() {
let _g = EnvGuard::snapshot();
let dir = TempDir::new().expect("tempdir");
unsafe {
std::env::set_var("HOME", dir.path());
std::env::set_var("TRACKING_ID", "");
}
super::inner::reset_caches_for_test();
let id = get_anonymous_id();
assert_ne!(id, ""); }
}