Skip to content

Commit 7c54994

Browse files
benthecarmanclaude
andcommitted
Reject non-UTF-8 MCP credential env vars
A set but non-UTF-8 LDK_MACAROON was treated as unset, so the bridge silently loaded the admin macaroon from disk instead. LDK_BASE_URL and LDK_TLS_CERT_PATH fell back to the config in the same way. Return an error that names the variable so a malformed override fails rather than selecting a different credential or endpoint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 08316de commit 7c54994

1 file changed

Lines changed: 80 additions & 3 deletions

File tree

‎ldk-server-mcp/src/config.rs‎

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
// You may not use this file except in accordance with one or both of these
88
// licenses.
99

10+
use std::env::VarError;
1011
use std::path::PathBuf;
1112

1213
use ldk_server_client::config::{
@@ -20,10 +21,20 @@ pub struct ResolvedConfig {
2021
pub tls_cert_pem: Vec<u8>,
2122
}
2223

24+
/// Reads an environment variable, treating a set but non-UTF-8 value as an
25+
/// error rather than as unset so it cannot trigger a fallback.
26+
fn read_env_var(name: &str) -> Result<Option<String>, String> {
27+
match std::env::var(name) {
28+
Ok(value) => Ok(Some(value)),
29+
Err(VarError::NotPresent) => Ok(None),
30+
Err(VarError::NotUnicode(_)) => Err(format!("{name} is set but is not valid UTF-8")),
31+
}
32+
}
33+
2334
pub fn resolve_config(config_path: Option<String>) -> Result<ResolvedConfig, String> {
24-
let env_base_url = std::env::var("LDK_BASE_URL").ok();
25-
let env_macaroon = std::env::var("LDK_MACAROON").ok();
26-
let env_tls_cert_path = std::env::var("LDK_TLS_CERT_PATH").ok().map(PathBuf::from);
35+
let env_base_url = read_env_var("LDK_BASE_URL")?;
36+
let env_macaroon = read_env_var("LDK_MACAROON")?;
37+
let env_tls_cert_path = read_env_var("LDK_TLS_CERT_PATH")?.map(PathBuf::from);
2738
let env_overrides_complete =
2839
env_base_url.is_some() && env_macaroon.is_some() && env_tls_cert_path.is_some();
2940

@@ -335,4 +346,70 @@ mod tests {
335346

336347
std::fs::remove_dir_all(temp_dir).unwrap();
337348
}
349+
350+
#[cfg(unix)]
351+
fn assert_non_utf8_env_var_rejected(name: &str, test_name: &str) {
352+
use std::ffi::OsStr;
353+
use std::os::unix::ffi::OsStrExt;
354+
355+
let _lock = ENV_LOCK.lock().unwrap();
356+
357+
let temp_dir = std::env::temp_dir().join(format!(
358+
"ldk-server-mcp-non-utf8-{}-{}",
359+
test_name,
360+
std::process::id()
361+
));
362+
let storage_dir = temp_dir.join("storage");
363+
let macaroons_dir = storage_dir.join("regtest").join("macaroons");
364+
std::fs::create_dir_all(&macaroons_dir).unwrap();
365+
366+
// Valid credentials exist on disk, so any fallback would succeed.
367+
let cert_path = storage_dir.join("tls.crt");
368+
std::fs::write(&cert_path, b"storage-cert").unwrap();
369+
std::fs::write(macaroons_dir.join("admin.macaroon"), "0201000207746573742d6964000006203846eea2ed59d53650493222c2380a3540668ade20044e28f9cb1e0b5b4e4429").unwrap();
370+
371+
let config_path = temp_dir.join("config.toml");
372+
std::fs::write(
373+
&config_path,
374+
format!(
375+
r#"
376+
[node]
377+
network = "regtest"
378+
379+
[storage.disk]
380+
dir_path = "{}"
381+
"#,
382+
storage_dir.display()
383+
),
384+
)
385+
.unwrap();
386+
387+
std::env::remove_var("LDK_MACAROON");
388+
std::env::remove_var("LDK_TLS_CERT_PATH");
389+
std::env::remove_var("LDK_BASE_URL");
390+
std::env::set_var(name, OsStr::from_bytes(b"\xff\xfe"));
391+
let result = resolve_config(Some(config_path.display().to_string()));
392+
std::env::remove_var(name);
393+
394+
std::fs::remove_dir_all(temp_dir).unwrap();
395+
assert_eq!(result.err().unwrap(), format!("{name} is set but is not valid UTF-8"));
396+
}
397+
398+
#[test]
399+
#[cfg(unix)]
400+
fn resolve_config_rejects_non_utf8_macaroon() {
401+
assert_non_utf8_env_var_rejected("LDK_MACAROON", "macaroon");
402+
}
403+
404+
#[test]
405+
#[cfg(unix)]
406+
fn resolve_config_rejects_non_utf8_base_url() {
407+
assert_non_utf8_env_var_rejected("LDK_BASE_URL", "base-url");
408+
}
409+
410+
#[test]
411+
#[cfg(unix)]
412+
fn resolve_config_rejects_non_utf8_tls_cert_path() {
413+
assert_non_utf8_env_var_rejected("LDK_TLS_CERT_PATH", "tls-cert-path");
414+
}
338415
}

0 commit comments

Comments
 (0)