Synced from monorepo
Synced from monorepo Changes: - Report invalid MCP server config instead of failing startup - Keep completed terminal output when the gateway connection is lost - Show a duration-only detail view for single-task task output - Don't let a stale registry turn counter hide local sessions - Raise the file-descriptor soft limit on Linux and log effective limits at startup - Stop aborting when HTTP client construction fails - Make session thread and runtime spawn failures recoverable - Fix main-prompt paste parity in the question freeform input - Fire SessionEnd hooks on /exit and headless quit - Embed the deployment-config signing public key - Repaint paste-chip background on inline panel inputs - Security: prevent acceptEdits from auto-approving agent writes into the always-trusted global hook root - Fix stacked "Worked for" markers so parks render as status and turns close with exactly one marker - Parse hooks from config files - Add a remote kill-switch for managed-config signature verification - Security: fix workspace file-reference resolution bypassing workspace filesystem confinement Source-Revision: d02693a856a54f1030695b36b91d276e96b30b23
This commit is contained in:
parent
6e38642082
commit
47348d13ec
138 changed files with 7283 additions and 5796 deletions
|
|
@ -526,11 +526,28 @@ impl WorkspaceRpcHandler {
|
|||
let cwd = self.workspace.root_cwd()?;
|
||||
let mut results = Vec::new();
|
||||
for ref_path in &refs {
|
||||
let full_path = if std::path::Path::new(ref_path).is_absolute() {
|
||||
let requested_path = if std::path::Path::new(ref_path).is_absolute() {
|
||||
std::path::PathBuf::from(ref_path)
|
||||
} else {
|
||||
cwd.join(ref_path)
|
||||
};
|
||||
let full_path = match self
|
||||
.workspace
|
||||
.confine_to_workspace_root(&requested_path)
|
||||
.await
|
||||
{
|
||||
Ok((confined, _)) => confined,
|
||||
Err(e) => {
|
||||
results.push(serde_json::json!({
|
||||
"path": requested_path.to_string_lossy(),
|
||||
"ref": ref_path,
|
||||
"exists": false,
|
||||
"content": Value::Null,
|
||||
"error": e.to_string(),
|
||||
}));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let exists = full_path.exists();
|
||||
let content = if exists {
|
||||
tokio::fs::read_to_string(&full_path).await.ok()
|
||||
|
|
@ -601,6 +618,9 @@ impl WorkspaceRpcHandler {
|
|||
);
|
||||
Ok(Value::Array(plugins))
|
||||
}
|
||||
<ExportGithubReq as WorkspaceRpc>::METHOD => {
|
||||
dispatch_op::<ExportGithubReq>(params, &self.workspace, None).await
|
||||
}
|
||||
<HookRegistryReq as WorkspaceRpc>::METHOD => {
|
||||
dispatch_op::<HookRegistryReq>(params, &self.workspace, None).await
|
||||
}
|
||||
|
|
@ -1169,7 +1189,9 @@ impl ToolServerHandler for WorkspaceRpcHandler {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::capability::CapabilityMode;
|
||||
use crate::handle::tests::{background_capable_cfg, make_handle, start_background_sleep};
|
||||
use crate::handle::tests::{
|
||||
background_capable_cfg, make_confining_handle, make_handle, start_background_sleep,
|
||||
};
|
||||
use xai_grok_tools::implementations::grok_build::scheduler::types::{
|
||||
ScheduledTask, SchedulerState,
|
||||
};
|
||||
|
|
@ -2498,6 +2520,34 @@ mod tests {
|
|||
);
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn dispatch_resolve_file_references_rejects_outside_root_when_confined() {
|
||||
let handle = make_confining_handle();
|
||||
let handler = WorkspaceRpcHandler::new(handle);
|
||||
let secret = std::env::temp_dir().join("h1_3885911_outside_secret.txt");
|
||||
std::fs::write(&secret, "OUTSIDE_SECRET").unwrap();
|
||||
let params = serde_json::json!({
|
||||
"refs": [secret.to_string_lossy(), "../escape.txt"]
|
||||
});
|
||||
let result = handler
|
||||
.dispatch("workspace.resolve_file_references", params, None)
|
||||
.await
|
||||
.expect("dispatch itself should succeed");
|
||||
let arr = result.as_array().expect("results array");
|
||||
assert_eq!(arr.len(), 2);
|
||||
for entry in arr {
|
||||
assert_eq!(entry["exists"], serde_json::Value::Bool(false));
|
||||
assert_eq!(entry["content"], serde_json::Value::Null);
|
||||
assert!(
|
||||
entry["error"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.contains("escapes workspace root"),
|
||||
"escape should be rejected, not read: {entry:?}"
|
||||
);
|
||||
}
|
||||
std::fs::remove_file(&secret).ok();
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn handle_hook_pause_resume_are_noops() {
|
||||
let handle = make_handle();
|
||||
let handler = WorkspaceRpcHandler::new(handle);
|
||||
|
|
@ -3057,6 +3107,7 @@ mod tests {
|
|||
<InstallPluginReq as WorkspaceRpc>::METHOD,
|
||||
<RefreshPluginsReq as WorkspaceRpc>::METHOD,
|
||||
<DiscoverPluginsReq as WorkspaceRpc>::METHOD,
|
||||
<ExportGithubReq as WorkspaceRpc>::METHOD,
|
||||
];
|
||||
let skipped_global_db_mutators = [
|
||||
<WorktreeGcReq as WorkspaceRpc>::METHOD,
|
||||
|
|
|
|||
Loading…
Reference in a new issue