Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
1016d08
add example center v0.1
Jun 9, 2026
28b7e04
mod example-center copy func
Jun 10, 2026
c3146d7
add more examples for center
Jun 10, 2026
8fe0351
preinstall for example case
Jun 10, 2026
0d69f17
replace e2b sdk to cubesandbox sdk for examples
Jun 10, 2026
066272b
mod example category font size
Jun 10, 2026
d43818f
remove timeline && add cost time
Jun 11, 2026
5aa71ec
remove step mock data
Jun 11, 2026
db1bd84
modify tuopu graph
Jun 11, 2026
8d4f2ae
add store template db&&crud
Jun 11, 2026
2355d65
add sandbox-nginx image to store template
Jun 11, 2026
c3d72ef
match demo with templateid
Jun 11, 2026
bdd54ce
mod browser.py
Jun 12, 2026
498ab2f
mod(web) store template sandbo-browser install info
Jun 12, 2026
700ae7d
mod codeedit space input r bug
Jun 15, 2026
91ba368
mod code-edit bug
Jun 15, 2026
0d38305
add vnc to browser sanbox case
Jun 15, 2026
a2581a9
add env settings for demo
Jun 15, 2026
5fd56e6
mod setting style
Jun 16, 2026
3dddd16
mod cubeapi remove warning
Jun 16, 2026
773e58e
mod web graph despcription
Jun 16, 2026
9c5f490
add sandboxdetail terminal for run code
Jun 16, 2026
60711d7
add storage for settings in demo case
Jun 16, 2026
e2fa455
change place for sandboxdetail terminal
Jun 16, 2026
3f3ce4c
mod sandboxdetail style
Jun 16, 2026
319ee1f
mod web dashboard style
Jun 17, 2026
02e537b
chore: apply code formatting
Jun 17, 2026
78cd722
add download for sandboxdetail
Jun 18, 2026
f805f29
mod menu style
Jun 18, 2026
c42d21f
add sandboxdetail event download with txt and csv
Jun 18, 2026
11ae1b2
fix(web): resolve tsc errors and remove dead code in TemplateStore & …
Jun 18, 2026
94457ef
feat(templatestore):rely on cubemaster migration 0010
Jun 22, 2026
21a0404
chore: apply cargo fmt formatting
Jun 22, 2026
b365870
mod(templatestore)add agent template store sql
Jun 22, 2026
9dca70b
refactor(cubeapi): move exec-code endpoint from e2b surface to /cube/…
Jun 22, 2026
f9750fa
refactor(CubeAPI): extract exec-code service layer, sanitize rich out…
Jun 22, 2026
971a0ab
refactor(cubeapi): extract ExampleService and modularise example regi…
Jun 22, 2026
8666604
chore: apply cargo fmt formatting
Jun 22, 2026
9ad7669
chore(web): untrack auto-generated tsconfig.tsbuildinfo
Jun 25, 2026
e9f00e1
fix(migrate): rename 0011 to UTC timestamp per immutable migration po…
Jun 25, 2026
43126b0
cubeapi/store: limit concurrent docker pulls with a semaphore
Jun 25, 2026
3c67584
mod cubeapi/web :Remove Incomplete Step Timeline Feature;Memory Manag…
Jun 25, 2026
2c127c5
feat(cubeapi): add per-request timeout for POST /cube/sandboxes/:id/e…
Jun 25, 2026
de06ed9
chore: apply code formatting
Jun 25, 2026
841cc03
feat(cubeapi/web):resolve envd_auth credential leaked into example su…
Jun 26, 2026
ac10da4
feat(web):mod svg and description
Jun 26, 2026
a22425d
feat(web): remove cube-bench from SandboxCases page
Jun 30, 2026
31104ce
fix(cubeapi): robust examples-root resolution with startup fail-fast
Jun 30, 2026
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
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ examples/openai-agents-example/.scratch/
examples/openai-agents-code-interpreter/__pycache__/
examples/openai-agents-code-interpreter/output/
examples/openai-agents-code-interpreter/.scratch/
examples/code-sandbox-quickstart/__pycache__/
examples/browser-sandbox/__pycache__/
examples/host-mount/__pycache__/
examples/network-policy/__pycache__/
examples/snapshot-rollback-clone/__pycache__/

# Auto-generated pip install fingerprints (local cache, do not commit)
examples/*/.requirements_installed

pvm-guest-build/
pvm-host-build/
Expand Down
82 changes: 82 additions & 0 deletions CubeAPI/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,52 @@ pub struct ServerConfig {
/// Example: mysql://cube:cube_pass@127.0.0.1:3306/cube_mvp
#[serde(default = "default_database_url")]
pub database_url: Option<String>,

/// Default template ID used by the Examples runner.
/// Env var: CUBE_TEMPLATE_ID
#[serde(default = "default_template_id")]
pub default_template_id: Option<String>,

/// CubeAPI URL used by the Examples runner (passed as CUBE_API_URL to scripts).
/// Env var: CUBE_API_URL (default "http://127.0.0.1:3000")
#[serde(default = "default_cube_api_url")]
pub cube_api_url: Option<String>,

/// CubeProxy node IP for bypassing DNS resolution (passed as CUBE_PROXY_NODE_IP).
/// Env var: CUBE_PROXY_NODE_IP
#[serde(default)]
pub cube_proxy_node_ip: Option<String>,

/// CubeProxy HTTP port (passed as CUBE_PROXY_PORT_HTTP).
/// Env var: CUBE_PROXY_PORT_HTTP (no default; omitted when unset)
#[serde(default = "default_cube_proxy_port_http")]
pub cube_proxy_port_http: Option<u16>,

/// Base URL of the sandbox proxy used to reach in-sandbox services
/// (envd / Jupyter). Env var: AGENTHUB_SANDBOX_PROXY_URL (default
/// "http://127.0.0.1").
#[serde(default = "default_sandbox_proxy_url")]
pub sandbox_proxy_url: String,

/// `Authorization` header value used for internal service-to-service auth
/// with the in-sandbox envd / Jupyter endpoints.
///
/// **Security**: this is a credential and must never be hardcoded in
/// business logic. It is sourced from the environment so deployments can
/// rotate it without code changes. Env var: CUBE_API_ENVD_AUTH (default
/// `Basic cm9vdDo=`, i.e. the envd built-in `root:` with an empty
/// password — override it in any non-local environment).
#[serde(default = "default_envd_auth")]
pub envd_auth: String,

/// Fallback CUBE_API_KEY injected into example subprocesses when the
/// parent process does not export CUBE_API_KEY.
///
/// Intended for sandbox/demo deployments to provide an out-of-the-box
/// experience. In production, leave this unset and export CUBE_API_KEY
/// directly. Env var: CUBE_API_DEFAULT_KEY
#[serde(default = "default_api_key")]
pub default_api_key: Option<String>,
}

fn default_bind() -> String {
Expand Down Expand Up @@ -109,6 +155,35 @@ fn default_database_url() -> Option<String> {
.ok()
.or_else(default_cube_sandbox_mysql_url)
}
fn default_template_id() -> Option<String> {
std::env::var("CUBE_TEMPLATE_ID")
.ok()
.filter(|s| !s.is_empty())
}
fn default_cube_api_url() -> Option<String> {
std::env::var("CUBE_API_URL")
.ok()
.filter(|s| !s.is_empty())
.or_else(|| Some("http://127.0.0.1:3000".to_string()))
}

fn default_cube_proxy_port_http() -> Option<u16> {
std::env::var("CUBE_PROXY_PORT_HTTP")
.ok()
.and_then(|s| s.parse().ok())
}
fn default_sandbox_proxy_url() -> String {
std::env::var("AGENTHUB_SANDBOX_PROXY_URL").unwrap_or_else(|_| "http://127.0.0.1".to_string())
}
fn default_envd_auth() -> String {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security: Default envd_auth is a well-known base64 value

The default Basic cm9vdDo= decodes to root: with an empty password — the envd built-in default. This shared credential is sent to every sandbox's Jupyter/envd endpoint. Anyone who learns it (including by running code in their own sandbox and observing request headers) can access the in-sandbox services of other sandboxes. Use per-sandbox credentials (the sandbox detail already includes an envd_access_token field) instead of a cluster-wide shared secret.

std::env::var("CUBE_API_ENVD_AUTH").unwrap_or_else(|_| "Basic cm9vdDo=".to_string())
}
fn default_api_key() -> Option<String> {
std::env::var("CUBE_API_DEFAULT_KEY")
.ok()
.filter(|s| !s.is_empty())
.or_else(|| Some("cube_0000000000000000000000000000000000000000".to_string()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security: Hardcoded fallback API key

When CUBE_API_DEFAULT_KEY is not set, this fallback value cube_0000000000000000000000000000000000000000 is used as an API key and injected into every example subprocess. This is a known static key shared across all deployments that don't explicitly configure it — effectively a backdoor credential.

Suggestion: Remove the hardcoded fallback and require explicit configuration. Log a loud warning at startup if the env var is unset.

}

fn default_cube_sandbox_mysql_url() -> Option<String> {
let host = std::env::var("CUBE_SANDBOX_MYSQL_HOST").ok()?;
Expand Down Expand Up @@ -148,6 +223,13 @@ impl Default for ServerConfig {
log_prefix: default_log_prefix(),
auth_callback_url: None,
database_url: default_database_url(),
default_template_id: default_template_id(),
cube_api_url: default_cube_api_url(),
cube_proxy_node_ip: None,
cube_proxy_port_http: default_cube_proxy_port_http(),
sandbox_proxy_url: default_sandbox_proxy_url(),
envd_auth: default_envd_auth(),
default_api_key: default_api_key(),
}
}
}
171 changes: 171 additions & 0 deletions CubeAPI/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,27 @@ pub struct AgentHubOperationRecord {
pub updated_at: Option<String>,
}

// ── Store Template records ──────────────────────────────────────────────────

pub struct StoreTemplateRecord {
pub item_id: String,
pub name_key: String,
pub description_key: String,
pub image_cn: String,
pub image_intl: String,
pub digest: Option<String>,
pub tags: Vec<String>,
pub category: String,
pub size_mb: i32,
pub expose_ports: Vec<i32>,
pub probe_port: i32,
pub probe_path: String,
pub writable_layer_size: String,
pub official: bool,
pub dns: Vec<String>,
pub sort_order: i32,
}

impl AgentHubStore {
pub async fn connect(database_url: &str) -> anyhow::Result<Self> {
let pool = MySqlPoolOptions::new()
Expand Down Expand Up @@ -1248,6 +1269,156 @@ LIMIT 1
.await?;
Ok(())
}

// ── Store Template CRUD ──────────────────────────────────────────────────

pub async fn list_store_templates(&self) -> anyhow::Result<Vec<StoreTemplateRecord>> {
let rows = sqlx::query(
r#"
SELECT item_id, name_key, description_key, image_cn, image_intl, digest,
tags, category, size_mb, expose_ports, probe_port, probe_path,
writable_layer_size, official, dns, sort_order
FROM t_store_template
WHERE deleted_at IS NULL
ORDER BY sort_order, id
"#,
)
.fetch_all(&self.pool)
.await?;

rows.into_iter()
.map(|row| {
let tags_value: Option<Value> = row.try_get("tags")?;
let ports_value: Option<Value> = row.try_get("expose_ports")?;
let dns_value: Option<Value> = row.try_get("dns")?;
Ok::<StoreTemplateRecord, sqlx::Error>(StoreTemplateRecord {
item_id: row.try_get("item_id")?,
name_key: row.try_get("name_key")?,
description_key: row.try_get("description_key")?,
image_cn: row.try_get("image_cn")?,
image_intl: row.try_get("image_intl")?,
digest: row.try_get("digest")?,
tags: tags_value
.and_then(|v| serde_json::from_value(v).ok())
.unwrap_or_default(),
category: row.try_get("category")?,
size_mb: row.try_get("size_mb")?,
expose_ports: ports_value
.and_then(|v| serde_json::from_value(v).ok())
.unwrap_or_default(),
probe_port: row.try_get("probe_port")?,
probe_path: row.try_get("probe_path")?,
writable_layer_size: row.try_get("writable_layer_size")?,
official: row.try_get::<i8, _>("official")? != 0,
dns: dns_value
.and_then(|v| serde_json::from_value(v).ok())
.unwrap_or_default(),
sort_order: row.try_get("sort_order")?,
})
})
.collect::<Result<Vec<_>, sqlx::Error>>()
.map_err(anyhow::Error::from)
}

pub async fn create_store_template(&self, record: &StoreTemplateRecord) -> anyhow::Result<()> {
let tags = serde_json::to_value(&record.tags)?;
let ports = serde_json::to_value(&record.expose_ports)?;
let dns = serde_json::to_value(&record.dns)?;
sqlx::query(
r#"
INSERT INTO t_store_template (
item_id, name_key, description_key, image_cn, image_intl, digest,
tags, category, size_mb, expose_ports, probe_port, probe_path,
writable_layer_size, official, dns, sort_order, deleted_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)
ON DUPLICATE KEY UPDATE
name_key = VALUES(name_key),
description_key = VALUES(description_key),
image_cn = VALUES(image_cn),
image_intl = VALUES(image_intl),
digest = VALUES(digest),
tags = VALUES(tags),
category = VALUES(category),
size_mb = VALUES(size_mb),
expose_ports = VALUES(expose_ports),
probe_port = VALUES(probe_port),
probe_path = VALUES(probe_path),
writable_layer_size = VALUES(writable_layer_size),
official = VALUES(official),
dns = VALUES(dns),
sort_order = VALUES(sort_order),
deleted_at = NULL
"#,
)
.bind(&record.item_id)
.bind(&record.name_key)
.bind(&record.description_key)
.bind(&record.image_cn)
.bind(&record.image_intl)
.bind(&record.digest)
.bind(tags)
.bind(&record.category)
.bind(record.size_mb)
.bind(ports)
.bind(record.probe_port)
.bind(&record.probe_path)
.bind(&record.writable_layer_size)
.bind(record.official)
.bind(dns)
.bind(record.sort_order)
.execute(&self.pool)
.await?;
Ok(())
}

pub async fn update_store_template(&self, record: &StoreTemplateRecord) -> anyhow::Result<()> {
let tags = serde_json::to_value(&record.tags)?;
let ports = serde_json::to_value(&record.expose_ports)?;
let dns = serde_json::to_value(&record.dns)?;
sqlx::query(
r#"
UPDATE t_store_template SET
name_key = ?, description_key = ?, image_cn = ?, image_intl = ?,
digest = ?, tags = ?, category = ?, size_mb = ?, expose_ports = ?,
probe_port = ?, probe_path = ?, writable_layer_size = ?,
official = ?, dns = ?, sort_order = ?
WHERE item_id = ? AND deleted_at IS NULL
"#,
)
.bind(&record.name_key)
.bind(&record.description_key)
.bind(&record.image_cn)
.bind(&record.image_intl)
.bind(&record.digest)
.bind(tags)
.bind(&record.category)
.bind(record.size_mb)
.bind(ports)
.bind(record.probe_port)
.bind(&record.probe_path)
.bind(&record.writable_layer_size)
.bind(record.official)
.bind(dns)
.bind(record.sort_order)
.bind(&record.item_id)
.execute(&self.pool)
.await?;
Ok(())
}

pub async fn soft_delete_store_template(&self, item_id: &str) -> anyhow::Result<()> {
sqlx::query(
r#"
UPDATE t_store_template
SET deleted_at = CURRENT_TIMESTAMP
WHERE item_id = ? AND deleted_at IS NULL
"#,
)
.bind(item_id)
.execute(&self.pool)
.await?;
Ok(())
}
}

impl AgentHubInstanceRecord {
Expand Down
13 changes: 13 additions & 0 deletions CubeAPI/src/examples/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) 2024 Tencent Inc.
// SPDX-License-Identifier: Apache-2.0
//
//! Example registry and topology templates.
//!
//! Pure-data modules with no async dependencies; they are consumed by
//! [`crate::services::examples::ExampleService`].

pub mod registry;
pub mod topology;

pub use registry::{file_languages, scenario_registry, FileSpec, ScenarioSpec};
pub use topology::{topology_with_status, TopologyGraph};
Loading
Loading