feat: 新增 stardomain 沙箱子项目脚手架(Docker + Rust 过滤层)
提供统一沙箱运行时,支持 local/sandbox 两种模式切换。Rust 层负责命令和代码的策略过滤, Docker 层负责实际的进程隔离。包含三种预设策略:agent_exec / tool_run / untrusted。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
/target
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
Generated
+1547
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "stardomain"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["zhaoxi826"]
|
||||||
|
description = "Sandbox runtime for KiloStar — Docker-based isolation with Rust filtering layer."
|
||||||
|
license = "Apache-2.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "stardomain"
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "stardomain"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pyo3 = { version = "0.28", features = ["extension-module"] }
|
||||||
|
pyo3-async-runtimes = { version = "0.28", features = ["tokio-runtime"] }
|
||||||
|
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "process", "time"] }
|
||||||
|
bollard = "0.18"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde_yaml = "0.9"
|
||||||
|
anyhow = "1.0"
|
||||||
|
regex = "1"
|
||||||
|
clap = { version = "4", features = ["derive"] }
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["maturin>=1.0,<2.0"]
|
||||||
|
build-backend = "maturin"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "stardomain"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Sandbox runtime for KiloStar — Docker-based isolation with Rust filtering layer."
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
license = { text = "Apache-2.0" }
|
||||||
|
authors = [{ name = "zhaoxi826" }]
|
||||||
|
|
||||||
|
[tool.maturin]
|
||||||
|
features = ["pyo3/extension-module"]
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 zhaoxi826
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Docker integration layer.
|
||||||
|
/// Uses bollard to manage container lifecycle for sandbox execution.
|
||||||
|
|
||||||
|
pub struct DockerBackend {
|
||||||
|
pub image: String,
|
||||||
|
pub network_disabled: bool,
|
||||||
|
pub memory_limit: u64,
|
||||||
|
pub cpu_period: u64,
|
||||||
|
pub cpu_quota: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DockerBackend {
|
||||||
|
fn default() -> Self {
|
||||||
|
DockerBackend {
|
||||||
|
image: "stardomain-runtime:latest".to_string(),
|
||||||
|
network_disabled: false,
|
||||||
|
memory_limit: 512 * 1024 * 1024, // 512MB
|
||||||
|
cpu_period: 100_000,
|
||||||
|
cpu_quota: 50_000, // 50% of one core
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DockerBackend {
|
||||||
|
pub fn with_policy(policy_name: &str) -> Self {
|
||||||
|
let mut backend = Self::default();
|
||||||
|
if policy_name == "untrusted" {
|
||||||
|
backend.network_disabled = true;
|
||||||
|
backend.memory_limit = 128 * 1024 * 1024;
|
||||||
|
backend.cpu_quota = 25_000;
|
||||||
|
}
|
||||||
|
backend
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 zhaoxi826
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub mod docker;
|
||||||
|
pub mod policy;
|
||||||
|
pub mod sandbox;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
use sandbox::SandboxResult;
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
struct Sandbox {
|
||||||
|
mode: String,
|
||||||
|
workspace: String,
|
||||||
|
timeout: u64,
|
||||||
|
memory_limit_mb: u64,
|
||||||
|
policy_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl Sandbox {
|
||||||
|
#[new]
|
||||||
|
#[pyo3(signature = (mode="sandbox", workspace="/tmp/stardomain_ws", timeout=30, memory_limit_mb=512, policy="agent_exec"))]
|
||||||
|
fn new(mode: &str, workspace: &str, timeout: u64, memory_limit_mb: u64, policy: &str) -> Self {
|
||||||
|
Sandbox {
|
||||||
|
mode: mode.to_string(),
|
||||||
|
workspace: workspace.to_string(),
|
||||||
|
timeout,
|
||||||
|
memory_limit_mb,
|
||||||
|
policy_name: policy.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_command(&self, command: &str) -> PyResult<SandboxResult> {
|
||||||
|
let policy = policy::get_policy(&self.policy_name);
|
||||||
|
policy::filter::validate_command(command, &policy)?;
|
||||||
|
Ok(SandboxResult::stub(command))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_python(&self, code: &str) -> PyResult<SandboxResult> {
|
||||||
|
let policy = policy::get_policy(&self.policy_name);
|
||||||
|
policy::filter::validate_python(code, &policy)?;
|
||||||
|
Ok(SandboxResult::stub(code))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 zhaoxi826
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "stardomain", version, about = "KiloStar sandbox runtime")]
|
||||||
|
struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum Commands {
|
||||||
|
/// Run a command inside the sandbox
|
||||||
|
Run {
|
||||||
|
/// Execution policy: agent_exec, tool_run, untrusted
|
||||||
|
#[arg(short, long, default_value = "agent_exec")]
|
||||||
|
policy: String,
|
||||||
|
|
||||||
|
/// Working directory inside sandbox
|
||||||
|
#[arg(short, long, default_value = "/tmp/stardomain_ws")]
|
||||||
|
workspace: String,
|
||||||
|
|
||||||
|
/// Timeout in seconds
|
||||||
|
#[arg(short, long, default_value_t = 30)]
|
||||||
|
timeout: u64,
|
||||||
|
|
||||||
|
/// The command to execute
|
||||||
|
#[arg(trailing_var_arg = true)]
|
||||||
|
cmd: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
match cli.command {
|
||||||
|
Commands::Run { policy, workspace, timeout, cmd } => {
|
||||||
|
println!("[stardomain] policy={policy}, workspace={workspace}, timeout={timeout}s");
|
||||||
|
println!("[stardomain] cmd: {:?}", cmd);
|
||||||
|
println!("[stardomain] (stub: execution not yet implemented)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 zhaoxi826
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use regex::Regex;
|
||||||
|
use super::Policy;
|
||||||
|
|
||||||
|
pub fn validate_command(command: &str, policy: &Policy) -> PyResult<()> {
|
||||||
|
let cmd_lower = command.trim().to_lowercase();
|
||||||
|
|
||||||
|
for blocked in &policy.blocked_commands {
|
||||||
|
if blocked == "*" {
|
||||||
|
return Err(PyErr::new::<pyo3::exceptions::PyPermissionError, _>(
|
||||||
|
"当前策略禁止执行任何 shell 命令"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if cmd_lower.starts_with(&blocked.to_lowercase()) {
|
||||||
|
return Err(PyErr::new::<pyo3::exceptions::PyPermissionError, _>(
|
||||||
|
format!("命令被策略禁止: {}", blocked)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for op in &policy.blocked_operators {
|
||||||
|
if command.contains(op.as_str()) {
|
||||||
|
return Err(PyErr::new::<pyo3::exceptions::PyPermissionError, _>(
|
||||||
|
format!("命令包含被禁止的操作符: '{}'", op)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_python(code: &str, policy: &Policy) -> PyResult<()> {
|
||||||
|
for module in &policy.blocked_imports {
|
||||||
|
let pattern = format!(r"(?m)^\s*(?:import\s+{}|from\s+{})\b", regex::escape(module), regex::escape(module));
|
||||||
|
if let Ok(re) = Regex::new(&pattern) {
|
||||||
|
if re.is_match(code) {
|
||||||
|
return Err(PyErr::new::<pyo3::exceptions::PyPermissionError, _>(
|
||||||
|
format!("禁止导入模块: {}", module)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for builtin in &policy.blocked_builtins {
|
||||||
|
let pattern = format!(r"\b{}\s*\(", regex::escape(builtin));
|
||||||
|
if let Ok(re) = Regex::new(&pattern) {
|
||||||
|
if re.is_match(code) {
|
||||||
|
return Err(PyErr::new::<pyo3::exceptions::PyPermissionError, _>(
|
||||||
|
format!("禁止使用: {}()", builtin)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 zhaoxi826
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub mod filter;
|
||||||
|
pub mod preset;
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct Policy {
|
||||||
|
pub name: String,
|
||||||
|
pub blocked_commands: Vec<String>,
|
||||||
|
pub blocked_operators: Vec<String>,
|
||||||
|
pub blocked_imports: Vec<String>,
|
||||||
|
pub blocked_builtins: Vec<String>,
|
||||||
|
pub allow_network: bool,
|
||||||
|
pub max_timeout: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_policy(name: &str) -> Policy {
|
||||||
|
match name {
|
||||||
|
"tool_run" => preset::tool_run(),
|
||||||
|
"untrusted" => preset::untrusted(),
|
||||||
|
_ => preset::agent_exec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 zhaoxi826
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
use super::Policy;
|
||||||
|
|
||||||
|
pub fn agent_exec() -> Policy {
|
||||||
|
Policy {
|
||||||
|
name: "agent_exec".to_string(),
|
||||||
|
blocked_commands: vec![
|
||||||
|
"rm -rf /".to_string(),
|
||||||
|
"mkfs".to_string(),
|
||||||
|
"dd ".to_string(),
|
||||||
|
"shutdown".to_string(),
|
||||||
|
"reboot".to_string(),
|
||||||
|
],
|
||||||
|
blocked_operators: vec![
|
||||||
|
"&&".to_string(),
|
||||||
|
"||".to_string(),
|
||||||
|
";".to_string(),
|
||||||
|
"`".to_string(),
|
||||||
|
"$(".to_string(),
|
||||||
|
],
|
||||||
|
blocked_imports: vec![
|
||||||
|
"ctypes".to_string(),
|
||||||
|
"socket".to_string(),
|
||||||
|
],
|
||||||
|
blocked_builtins: vec![
|
||||||
|
"exec".to_string(),
|
||||||
|
"eval".to_string(),
|
||||||
|
"compile".to_string(),
|
||||||
|
"__import__".to_string(),
|
||||||
|
],
|
||||||
|
allow_network: true,
|
||||||
|
max_timeout: 60,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tool_run() -> Policy {
|
||||||
|
Policy {
|
||||||
|
name: "tool_run".to_string(),
|
||||||
|
blocked_commands: vec![
|
||||||
|
"rm -rf /".to_string(),
|
||||||
|
"mkfs".to_string(),
|
||||||
|
"shutdown".to_string(),
|
||||||
|
"reboot".to_string(),
|
||||||
|
],
|
||||||
|
blocked_operators: vec![],
|
||||||
|
blocked_imports: vec!["ctypes".to_string()],
|
||||||
|
blocked_builtins: vec!["exec".to_string(), "eval".to_string()],
|
||||||
|
allow_network: true,
|
||||||
|
max_timeout: 120,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn untrusted() -> Policy {
|
||||||
|
Policy {
|
||||||
|
name: "untrusted".to_string(),
|
||||||
|
blocked_commands: vec!["*".to_string()], // all blocked
|
||||||
|
blocked_operators: vec![
|
||||||
|
"&&".to_string(), "||".to_string(), ";".to_string(),
|
||||||
|
"`".to_string(), "$(".to_string(), ">".to_string(), "<".to_string(),
|
||||||
|
],
|
||||||
|
blocked_imports: vec![
|
||||||
|
"os".to_string(), "subprocess".to_string(), "shutil".to_string(),
|
||||||
|
"socket".to_string(), "ctypes".to_string(), "importlib".to_string(),
|
||||||
|
],
|
||||||
|
blocked_builtins: vec![
|
||||||
|
"exec".to_string(), "eval".to_string(), "compile".to_string(),
|
||||||
|
"__import__".to_string(), "open".to_string(),
|
||||||
|
],
|
||||||
|
allow_network: false,
|
||||||
|
max_timeout: 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 zhaoxi826
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub struct SandboxConfig {
|
||||||
|
pub mode: SandboxMode,
|
||||||
|
pub workspace: String,
|
||||||
|
pub timeout_secs: u64,
|
||||||
|
pub memory_limit_mb: u64,
|
||||||
|
pub policy_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SandboxMode {
|
||||||
|
Local,
|
||||||
|
Sandbox,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SandboxConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
SandboxConfig {
|
||||||
|
mode: SandboxMode::Sandbox,
|
||||||
|
workspace: "/tmp/stardomain_ws".to_string(),
|
||||||
|
timeout_secs: 30,
|
||||||
|
memory_limit_mb: 512,
|
||||||
|
policy_name: "agent_exec".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 zhaoxi826
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Executor: responsible for running commands either locally or in Docker.
|
||||||
|
/// This is a stub — actual Docker execution will be implemented later.
|
||||||
|
pub struct Executor;
|
||||||
|
|
||||||
|
impl Executor {
|
||||||
|
pub fn run_local(_command: &str) -> (String, String, i32) {
|
||||||
|
("".to_string(), "".to_string(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_docker(_command: &str) -> (String, String, i32) {
|
||||||
|
("".to_string(), "[stardomain] Docker execution not yet implemented".to_string(), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 zhaoxi826
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
pub mod executor;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SandboxResult {
|
||||||
|
#[pyo3(get)]
|
||||||
|
pub stdout: String,
|
||||||
|
#[pyo3(get)]
|
||||||
|
pub stderr: String,
|
||||||
|
#[pyo3(get)]
|
||||||
|
pub exit_code: i32,
|
||||||
|
#[pyo3(get)]
|
||||||
|
pub killed_by_timeout: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SandboxResult {
|
||||||
|
pub fn stub(input: &str) -> Self {
|
||||||
|
SandboxResult {
|
||||||
|
stdout: format!("[stardomain stub] would execute: {}", input),
|
||||||
|
stderr: String::new(),
|
||||||
|
exit_code: 0,
|
||||||
|
killed_by_timeout: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user