Skip to content

Commit 7f6f441

Browse files
committed
fix(stargate): preserve safe registration token error causes
Keep the source error chain during token resolution and redact parser excerpts and sensitive HTTP URLs when logging. Verify file and transport causes through recorded diagnostics. Refs: #1817
1 parent 63c75bd commit 7f6f441

3 files changed

Lines changed: 75 additions & 8 deletions

File tree

‎src/libraries/rust/stargate/crates/pylon-lib/src/registration/grpc_endpoint.rs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,16 @@ fn grpc_error_chain(mut error: &(dyn Error + 'static)) -> String {
383383
} else if let Some(status) = error.downcast_ref::<tonic::Status>() {
384384
// Metadata and binary details are not needed to diagnose the RPC.
385385
format!("gRPC {:?}: {}", status.code(), status.message())
386+
} else if let Some(error) = error.downcast_ref::<reqwest::Error>() {
387+
// Token-issuer URLs can contain credentials or sensitive queries.
388+
// Keep the failure category and its sources without displaying the URL.
389+
if error.is_timeout() {
390+
"HTTP request timed out".into()
391+
} else if error.is_connect() {
392+
"HTTP connection failed".into()
393+
} else {
394+
"HTTP request failed".into()
395+
}
386396
} else {
387397
error.to_string()
388398
};

‎src/libraries/rust/stargate/crates/pylon-lib/src/registration/router_stream.rs‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -330,10 +330,8 @@ pub(super) async fn open_registration_stream(
330330
pub(super) async fn resolve_registration_token(
331331
provider: &AuthTokenProvider,
332332
) -> anyhow::Result<String> {
333-
// The top-level context identifies token-file and issuer failures without
334-
// exposing a parser's secret-file excerpt through the source chain.
335333
provider
336334
.resolve_token()
337335
.await
338-
.map_err(|error| anyhow::anyhow!("failed to resolve registration token: {error}"))
336+
.context("failed to resolve registration token")
339337
}

‎src/libraries/rust/stargate/crates/pylon-lib/src/registration/tests.rs‎

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,16 +1524,75 @@ fn registration_failures_keep_causes_and_suppress_repeated_errors_until_recovery
15241524
#[tokio::test]
15251525
async fn registration_token_errors_do_not_expose_secret_file_excerpts() {
15261526
let file = tempfile::NamedTempFile::new().unwrap();
1527-
std::fs::write(file.path(), br#"{"secret":"do-not-log-this-secret"}"#).unwrap();
15281527
let provider = stargate_auth::AuthTokenProvider::JsonFile {
15291528
path: file.path().to_owned(),
15301529
key: vec!["missing".into()],
15311530
};
1531+
for contents in [
1532+
r#"{"secret":"do-not-log-this-secret"}"#,
1533+
r#"{"secret":"do-not-log-this-secret","missing":invalid}"#,
1534+
] {
1535+
std::fs::write(file.path(), contents).unwrap();
1536+
let error = super::router_stream::resolve_registration_token(&provider)
1537+
.await
1538+
.unwrap_err();
1539+
let detail = recorded_registration_error(error.as_ref());
1540+
assert!(detail.contains("failed to resolve registration token"));
1541+
assert!(detail.contains("failed to extract key"));
1542+
assert!(!detail.contains("do-not-log-this-secret"));
1543+
}
1544+
}
1545+
1546+
#[tokio::test]
1547+
async fn registration_token_errors_preserve_io_causes() {
1548+
let directory = tempfile::tempdir().unwrap();
1549+
let provider = stargate_auth::AuthTokenProvider::File(directory.path().join("missing-token"));
15321550
let error = super::router_stream::resolve_registration_token(&provider)
15331551
.await
15341552
.unwrap_err();
1535-
let detail = format!("{error:#}");
1536-
assert!(detail.contains("failed to resolve registration token"));
1537-
assert!(detail.contains("failed to extract key"));
1538-
assert!(!detail.contains("do-not-log-this-secret"));
1553+
let cause = error.downcast_ref::<std::io::Error>().unwrap();
1554+
assert_eq!(cause.kind(), std::io::ErrorKind::NotFound);
1555+
let detail = recorded_registration_error(error.as_ref());
1556+
assert!(detail.contains("failed to read"));
1557+
assert!(detail.contains(&cause.to_string()));
1558+
}
1559+
1560+
#[tokio::test]
1561+
async fn registration_http_errors_keep_transport_causes_without_sensitive_urls() {
1562+
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1563+
let address = listener.local_addr().unwrap();
1564+
// An HTTP peer that closes before responding produces a local client error.
1565+
let server = tokio::spawn(async move {
1566+
let (socket, _) = listener.accept().await.unwrap();
1567+
drop(socket);
1568+
});
1569+
let error = reqwest::Client::new()
1570+
.get(format!(
1571+
"http://test:private-password@{address}/token?key=private-query"
1572+
))
1573+
.send()
1574+
.await
1575+
.unwrap_err();
1576+
server.await.unwrap();
1577+
let detail = recorded_registration_error(&error);
1578+
assert!(detail.starts_with("HTTP "), "{detail}");
1579+
assert!(detail.contains(&std::error::Error::source(&error).unwrap().to_string()));
1580+
assert!(!detail.contains("private-password"));
1581+
assert!(!detail.contains("private-query"));
1582+
}
1583+
1584+
fn recorded_registration_error(error: &(dyn std::error::Error + 'static)) -> String {
1585+
let subscriber = RecordingTracingSubscriber::default();
1586+
let dispatch = tracing::Dispatch::new(subscriber.clone());
1587+
let _guard = tracing::dispatcher::set_default(&dispatch);
1588+
super::grpc_endpoint::RegistrationFailureLog::default().report(
1589+
&grpc_endpoint("router.example.test:50071"),
1590+
"register_inference_server",
1591+
error,
1592+
);
1593+
subscriber
1594+
.events()
1595+
.into_iter()
1596+
.find_map(|event| event.fields.get("error").cloned())
1597+
.unwrap()
15391598
}

0 commit comments

Comments
 (0)