Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/isolation.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ A pane starts with your access: its program, an agent included, can read and wri
| Only these providers | Only the hosts and ports that the agents you choose name in their manifests, through a proxy nerd runs for that pane alone. The agent must honour `HTTPS_PROXY`; one that does not gets no network. Agents whose manifests name different ports cannot share a pane. |
| None | No internet and no name lookups. Services on this Mac (`localhost`) stay reachable. |

The hosts in the agents' `plugin.json` are a starting point: check them against what your agent contacts.
The hosts in the agents' `plugin.json` are a starting point: check them against what your agent contacts. pi's names every provider pi ships with; Bedrock outside us-east-1 and eu-central-1, Vertex and Azure use addresses of your own account and are not in it.

**This folder only** also keeps one session from planting code or instructions for another. Inside the places it may write, these stay read-only: shell, git and editor startup files, `~/.local/bin` and other folders ahead on `PATH`, agents' settings and memory, Claude Code's task list, Codex's shared databases, Qwen Code's workflow scripts, what agents source or run later from their own folders, and the caches package managers run code from (pnpm, uv, deno, Go, Playwright, Poetry, Yarn, node-gyp, pip, Cypress, SwiftPM, Copilot). When the pane's folder is your home folder, its dot files, `~/Library` and `~/bin` stay read-only too, except `~/Library/Caches` and the agents' session folders; choose a project folder for a real limit. The **Isolation** window lists what stops working.

Expand Down
2 changes: 1 addition & 1 deletion plugins/pi/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
"flag": ["--continue", "-c", "--resume", "-r", "--no-session"]
},
"data": ["~/.pi/agent/sessions"],
"network": { "hosts": ["api.anthropic.com", "console.anthropic.com", "claude.ai", "api.openai.com", "auth.openai.com", "chatgpt.com", "generativelanguage.googleapis.com"], "ports": [443] }
"network": { "hosts": ["ai-gateway.vercel.sh", "api.ant-ling.com", "api.anthropic.com", "api.business.githubcopilot.com", "api.cerebras.ai", "api.cloudflare.com", "api.deepseek.com", "api.enterprise.githubcopilot.com", "api.fireworks.ai", "api.github.com", "api.githubcopilot.com", "api.groq.com", "api.individual.githubcopilot.com", "api.kimi.com", "api.meta.ai", "api.minimax.io", "api.minimaxi.com", "api.mistral.ai", "api.moonshot.ai", "api.moonshot.cn", "api.openai.com", "api.together.ai", "api.typesafe.ai", "api.x.ai", "api.xiaomimimo.com", "api.z.ai", "auth.kimi.com", "auth.meta.com", "auth.openai.com", "auth.x.ai", "bedrock-runtime.eu-central-1.amazonaws.com", "bedrock-runtime.us-east-1.amazonaws.com", "chatgpt.com", "claude.ai", "console.anthropic.com", "gateway.ai.cloudflare.com", "generativelanguage.googleapis.com", "github.com", "inference.baseten.co", "integrate.api.nvidia.com", "open.bigmodel.cn", "opencode.ai", "openrouter.ai", "platform.claude.com", "radius.pi.dev", "router.huggingface.co", "token-plan-ams.xiaomimimo.com", "token-plan-cn.xiaomimimo.com", "token-plan-sgp.xiaomimimo.com", "token-plan.ap-southeast-1.maas.aliyuncs.com", "token-plan.cn-beijing.maas.aliyuncs.com", "us.openrouter.ai"], "ports": [443] }
}
}
16 changes: 14 additions & 2 deletions src/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ pub enum Network {
Internet { hosts: Vec<String>, ports: Vec<u16> },
}

/// A plugin's or a user's manifest; a built-in agent may name every host of its providers (pi has dozens).
const HOSTS: usize = 16;
const BUILTIN_HOSTS: usize = 64;

const NETWORK_FORMS: &str = r#"permissions.network is "localhost" or {"hosts": [...], "ports": [...]}"#;

impl<'de> Deserialize<'de> for Network {
Expand All @@ -102,8 +106,8 @@ fn network(v: &serde_json::Value) -> Result<Network, String> {
hosts.dedup();
ports.sort_unstable();
ports.dedup();
if hosts.len() > 16 || ports.len() > 8 {
return Err(format!("{NETWORK_FORMS}; at most 16 hosts and 8 ports"));
if hosts.len() > BUILTIN_HOSTS || ports.len() > 8 {
return Err(format!("{NETWORK_FORMS}; at most {HOSTS} hosts and 8 ports"));
}
Ok(Network::Internet { hosts, ports })
}
Expand Down Expand Up @@ -578,6 +582,10 @@ pub fn validate(m: &Manifest, others: &[Manifest]) -> Result<(), String> {
if crate::slots::BUILTINS.contains(&m.name.as_str()) {
return Err(format!("{} is a built-in's name", m.name));
}
let many = |n: &Network| matches!(n, Network::Internet { hosts, .. } if hosts.len() > HOSTS);
if !m.builtin && (many(&m.permissions.network) || many(&a.network)) {
return Err(format!("{NETWORK_FORMS}; at most {HOSTS} hosts and 8 ports"));
}
if let Some(o) = others.iter().find(|o| o.name == m.name) {
return Err(format!("{} is already taken by {}", m.name, if o.builtin { "a built-in" } else { "another plugin" }));
}
Expand Down Expand Up @@ -1037,11 +1045,15 @@ mod tests {
(json!({ "data": ["~/.fake/*/x"] }), "a * only"),
(json!({ "data": ["~/.fake/a\"b"] }), "no quote"),
(json!({ "network": "localhost" }), "names its hosts"),
(json!({ "network": { "hosts": (0..17).map(|i| format!("h{i}.fake.dev")).collect::<Vec<_>>(), "ports": [443] } }), "at most 16 hosts"),
] {
let e = refused(bad.clone());
assert!(e.contains(says), "{bad}: {e}");
}
assert!(WORDS.contains(&"mail"));
let mut wide = fake(json!({ "network": { "hosts": (0..64).map(|i| format!("h{i}.fake.dev")).collect::<Vec<_>>(), "ports": [443] } }));
wide.builtin = true;
assert!(validate(&wide, &builtins()).is_ok(), "a built-in agent may name all its providers' hosts");
}

#[test]
Expand Down
Loading