Support remote credential masking with Zsh shell snapshots (#54432)

## Why

Remote credential masking previously supported only Bash commands without login startup or shell snapshots, leaving Zsh commands with unmasked credentials.

## What changed

- Enable masking for Zsh login commands using shell snapshots on Linux and macOS.
- Capture trusted startup profiles per launch, bind the credential broker to the resulting environment, and replace credentials and supported aliases with placeholders before snapshot replay.
- Preserve explicit environment overrides, credential unsets, and launcher-managed proxy settings during replay.
- Warn and fall back to unmasked startup when a snapshot cannot safely replay credentials; stop execution if protected snapshot restoration fails.

## Testing

Extend remote execution tests for inherited and profile-defined credentials, overrides, destination changes, and unsupported-profile fallback. Add a Zsh scenario snapshot showing a credential placeholder in command output.

GitOrigin-RevId: 83193d6cbc05f3deb0d6d0b9ae80974b353ce900
This commit is contained in:
viyatb-oai
2026-10-15 22:25:58 +00:00
committed by copyberry
parent aff92080f2
commit 1c5ca2a979
18 changed files with 895 additions and 177 deletions
+26 -1
View File
@@ -927,6 +927,18 @@ impl NetworkProxy {
self.state.current_cfg().await
}
/// Selects cached credentials for the startup environment without changing listeners or policy.
/// Restore any existing placeholders before binding, then prepare the environment for execution.
pub async fn bind_credential_broker_environment(
&self,
credential_brokers: &crate::RemoteCredentialBrokerCache,
environment: &HashMap<String, String>,
) -> Result<()> {
self.state
.bind_credential_broker_environment(credential_brokers, environment)
.await
}
/// Revision of credential configuration, excluding ordinary network policy changes.
pub fn credential_broker_config_revision(&self) -> u64 {
self.state.credential_broker_config_revision()
@@ -1253,7 +1265,8 @@ impl NetworkProxy {
let environment_id = self
.execution_scope
.as_ref()
.map(|scope| scope.environment_id.as_str());
.map(|scope| scope.environment_id.as_str())
.or(self.state.environment_id());
self.state
.virtualize_snapshot_credentials(env, environment_id);
}
@@ -1646,6 +1659,7 @@ impl NetworkProxy {
*active_route = Some(Arc::clone(&route));
drop(active_route);
return Ok(NetworkProxyHandle::windows_shared(
self.clone(),
route,
Arc::clone(&windows_runtime.active_route),
Arc::clone(&self.environment_proxies),
@@ -1720,6 +1734,7 @@ impl NetworkProxy {
Ok(NetworkProxyHandle {
runtime: Some(ProxyRuntime::Listeners(listeners)),
proxy: Some(self.clone()),
environment_proxies: Some(Arc::clone(&self.environment_proxies)),
#[cfg(target_os = "windows")]
windows_active_route: None,
@@ -1729,6 +1744,7 @@ impl NetworkProxy {
pub struct NetworkProxyHandle {
runtime: Option<ProxyRuntime>,
proxy: Option<NetworkProxy>,
environment_proxies: Option<Arc<Mutex<HashMap<String, EnvironmentProxy>>>>,
#[cfg(target_os = "windows")]
windows_active_route: Option<Arc<Mutex<Option<Arc<WindowsProxyRoute>>>>>,
@@ -1738,6 +1754,7 @@ impl NetworkProxyHandle {
fn noop() -> Self {
Self {
runtime: None,
proxy: None,
environment_proxies: None,
#[cfg(target_os = "windows")]
windows_active_route: None,
@@ -1746,17 +1763,24 @@ impl NetworkProxyHandle {
#[cfg(target_os = "windows")]
fn windows_shared(
proxy: NetworkProxy,
route: Arc<WindowsProxyRoute>,
active_route: Arc<Mutex<Option<Arc<WindowsProxyRoute>>>>,
environment_proxies: Arc<Mutex<HashMap<String, EnvironmentProxy>>>,
) -> Self {
Self {
runtime: Some(ProxyRuntime::SharedIngress { route }),
proxy: Some(proxy),
environment_proxies: Some(environment_proxies),
windows_active_route: Some(active_route),
}
}
/// Returns the running proxy. Disabled proxy handles return `None`.
pub fn proxy(&self) -> Option<&NetworkProxy> {
self.proxy.as_ref()
}
pub async fn wait(mut self) -> Result<()> {
let result = match self.runtime.as_mut() {
Some(runtime) => runtime.wait().await,
@@ -1857,6 +1881,7 @@ mod tests {
});
let handle = NetworkProxyHandle {
runtime: Some(ProxyRuntime::Listeners(listeners)),
proxy: None,
environment_proxies: Some(Arc::new(Mutex::new(HashMap::new()))),
#[cfg(target_os = "windows")]
windows_active_route: None,