Use Landlock to restrict bash calls. (#5)
https://docs.kernel.org/userspace-api/landlock.html Reviewed-on: #5 Co-authored-by: Drew Galbraith <drew@tiramisu.one> Co-committed-by: Drew Galbraith <drew@tiramisu.one>
This commit is contained in:
parent
797d7564b7
commit
7efc6705d3
19 changed files with 1315 additions and 238 deletions
|
|
@ -7,6 +7,7 @@ use crate::core::types::{
|
|||
ContentBlock, ConversationMessage, Role, StreamEvent, ToolDefinition, UIEvent, UserAction,
|
||||
};
|
||||
use crate::provider::ModelProvider;
|
||||
use crate::sandbox::Sandbox;
|
||||
use crate::tools::{RiskLevel, ToolOutput, ToolRegistry};
|
||||
|
||||
/// Accumulates data for a single tool-use block while it is being streamed.
|
||||
|
|
@ -106,7 +107,7 @@ pub struct Orchestrator<P> {
|
|||
history: ConversationHistory,
|
||||
provider: P,
|
||||
tool_registry: ToolRegistry,
|
||||
working_dir: std::path::PathBuf,
|
||||
sandbox: Sandbox,
|
||||
action_rx: mpsc::Receiver<UserAction>,
|
||||
event_tx: mpsc::Sender<UIEvent>,
|
||||
/// Messages typed by the user while an approval prompt is open. They are
|
||||
|
|
@ -119,7 +120,7 @@ impl<P: ModelProvider> Orchestrator<P> {
|
|||
pub fn new(
|
||||
provider: P,
|
||||
tool_registry: ToolRegistry,
|
||||
working_dir: std::path::PathBuf,
|
||||
sandbox: Sandbox,
|
||||
action_rx: mpsc::Receiver<UserAction>,
|
||||
event_tx: mpsc::Sender<UIEvent>,
|
||||
) -> Self {
|
||||
|
|
@ -127,7 +128,7 @@ impl<P: ModelProvider> Orchestrator<P> {
|
|||
history: ConversationHistory::new(),
|
||||
provider,
|
||||
tool_registry,
|
||||
working_dir,
|
||||
sandbox,
|
||||
action_rx,
|
||||
event_tx,
|
||||
queued_messages: Vec::new(),
|
||||
|
|
@ -340,7 +341,7 @@ impl<P: ModelProvider> Orchestrator<P> {
|
|||
|
||||
// Re-fetch tool for execution (borrow was released above).
|
||||
let tool = self.tool_registry.get(tool_name).unwrap();
|
||||
match tool.execute(input, &self.working_dir).await {
|
||||
match tool.execute(input, &self.sandbox).await {
|
||||
Ok(output) => {
|
||||
let _ = self
|
||||
.event_tx
|
||||
|
|
@ -409,6 +410,14 @@ impl<P: ModelProvider> Orchestrator<P> {
|
|||
// not in the main action loop. If one arrives here it's stale.
|
||||
UserAction::ToolApprovalResponse { .. } => {}
|
||||
|
||||
UserAction::SetNetworkPolicy(allowed) => {
|
||||
self.sandbox.set_network_allowed(allowed);
|
||||
let _ = self
|
||||
.event_tx
|
||||
.send(UIEvent::NetworkPolicyChanged(allowed))
|
||||
.await;
|
||||
}
|
||||
|
||||
UserAction::SendMessage(text) => {
|
||||
self.history
|
||||
.push(ConversationMessage::text(Role::User, text));
|
||||
|
|
@ -467,10 +476,17 @@ mod tests {
|
|||
action_rx: mpsc::Receiver<UserAction>,
|
||||
event_tx: mpsc::Sender<UIEvent>,
|
||||
) -> Orchestrator<P> {
|
||||
use crate::sandbox::policy::SandboxPolicy;
|
||||
use crate::sandbox::{EnforcementMode, Sandbox};
|
||||
let sandbox = Sandbox::new(
|
||||
SandboxPolicy::for_project(std::path::PathBuf::from("/tmp")),
|
||||
std::path::PathBuf::from("/tmp"),
|
||||
EnforcementMode::Yolo,
|
||||
);
|
||||
Orchestrator::new(
|
||||
provider,
|
||||
ToolRegistry::empty(),
|
||||
std::path::PathBuf::from("/tmp"),
|
||||
sandbox,
|
||||
action_rx,
|
||||
event_tx,
|
||||
)
|
||||
|
|
@ -717,12 +733,19 @@ mod tests {
|
|||
let (event_tx, mut event_rx) = mpsc::channel::<UIEvent>(32);
|
||||
|
||||
// Use a real ToolRegistry so read_file works.
|
||||
use crate::sandbox::policy::SandboxPolicy;
|
||||
use crate::sandbox::{EnforcementMode, Sandbox};
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
std::fs::write(dir.path().join("Cargo.toml"), "[package]\nname = \"test\"").unwrap();
|
||||
let sandbox = Sandbox::new(
|
||||
SandboxPolicy::for_project(dir.path().to_path_buf()),
|
||||
dir.path().to_path_buf(),
|
||||
EnforcementMode::Yolo,
|
||||
);
|
||||
let orch = Orchestrator::new(
|
||||
MultiCallMock { turns },
|
||||
ToolRegistry::default_tools(),
|
||||
dir.path().to_path_buf(),
|
||||
sandbox,
|
||||
action_rx,
|
||||
event_tx,
|
||||
);
|
||||
|
|
@ -873,14 +896,7 @@ mod tests {
|
|||
let (action_tx, action_rx) = mpsc::channel::<UserAction>(16);
|
||||
let (event_tx, mut event_rx) = mpsc::channel::<UIEvent>(64);
|
||||
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
let orch = Orchestrator::new(
|
||||
MultiCallMock { turns },
|
||||
ToolRegistry::default_tools(),
|
||||
dir.path().to_path_buf(),
|
||||
action_rx,
|
||||
event_tx,
|
||||
);
|
||||
let orch = test_orchestrator(MultiCallMock { turns }, action_rx, event_tx);
|
||||
let handle = tokio::spawn(orch.run());
|
||||
|
||||
// Start turn 1 -- orchestrator will block on approval.
|
||||
|
|
@ -927,4 +943,45 @@ mod tests {
|
|||
action_tx.send(UserAction::Quit).await.unwrap();
|
||||
handle.await.unwrap();
|
||||
}
|
||||
|
||||
// -- network policy toggle ------------------------------------------------
|
||||
|
||||
/// SetNetworkPolicy sends a NetworkPolicyChanged event back to the TUI.
|
||||
#[tokio::test]
|
||||
async fn set_network_policy_sends_event() {
|
||||
// Provider that returns immediately to avoid blocking.
|
||||
struct NeverCalledProvider;
|
||||
impl ModelProvider for NeverCalledProvider {
|
||||
fn stream<'a>(
|
||||
&'a self,
|
||||
_messages: &'a [ConversationMessage],
|
||||
_tools: &'a [ToolDefinition],
|
||||
) -> impl Stream<Item = StreamEvent> + Send + 'a {
|
||||
futures::stream::empty()
|
||||
}
|
||||
}
|
||||
|
||||
let (action_tx, action_rx) = mpsc::channel::<UserAction>(8);
|
||||
let (event_tx, mut event_rx) = mpsc::channel::<UIEvent>(8);
|
||||
|
||||
let orch = test_orchestrator(NeverCalledProvider, action_rx, event_tx);
|
||||
let handle = tokio::spawn(orch.run());
|
||||
|
||||
action_tx
|
||||
.send(UserAction::SetNetworkPolicy(true))
|
||||
.await
|
||||
.unwrap();
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
|
||||
let mut found = false;
|
||||
while let Ok(ev) = event_rx.try_recv() {
|
||||
if matches!(ev, UIEvent::NetworkPolicyChanged(true)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
assert!(found, "expected NetworkPolicyChanged(true) event");
|
||||
|
||||
action_tx.send(UserAction::Quit).await.unwrap();
|
||||
handle.await.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue