refactor(code-runner): default CODE_RUNNER_LANGUAGES to all-open

CODE_RUNNER_LANGUAGES 未设置时,白名单不再默认为 python,node,
而是放行 LANGUAGES 注册表里的全部语言。该环境变量变为「可选的收窄开关」:
新增语言到注册表即可默认启用,无需再同步运维白名单(避免两边脱节,
如本次 go/rust 就因 .env 未同步白名单导致 is_supported_lang 拒绝)。

- RunnerConfig.languages: Vec<String> → Option<Vec<String>>,
  None 表示不限制
- is_supported_lang: Option 为 None 时直接放行注册表语言
- 测试 is_supported_lang_default_whitelist → is_supported_lang_default_all_open,
  断言 go/rust 在默认(未设白名单)下可用
This commit is contained in:
xfy 2026-07-09 11:02:04 +08:00
parent 75767661ac
commit 2f6f8e549d
4 changed files with 30 additions and 15 deletions

View File

@ -140,7 +140,8 @@ CODE_RUNNER_MAX_CPU_CORES=2.0
CODE_RUNNER_MAX_MEMORY_MB=1024 CODE_RUNNER_MAX_MEMORY_MB=1024
CODE_RUNNER_MAX_TIMEOUT_SECS=30 CODE_RUNNER_MAX_TIMEOUT_SECS=30
CODE_RUNNER_MAX_OUTPUT_BYTES=1048576 CODE_RUNNER_MAX_OUTPUT_BYTES=1048576
CODE_RUNNER_LANGUAGES=python,node,go,rust # 默认全开:注册表里的语言均可用。设置则收窄到逗号分隔列表(如 python,node
# CODE_RUNNER_LANGUAGES=python,node
DOCKER_SOCKET_PATH=/var/run/docker.sock DOCKER_SOCKET_PATH=/var/run/docker.sock
# Rate limits (governor keyed limiters) # Rate limits (governor keyed limiters)

View File

@ -97,7 +97,7 @@ CODE_RUNNER_MAX_OUTPUT_BYTES=1048576 # hard cap on stdout+stderr captured
CODE_RUNNER_MAX_SOURCE_BYTES=65536 # max source size accepted by StartExec CODE_RUNNER_MAX_SOURCE_BYTES=65536 # max source size accepted by StartExec
CODE_RUNNER_QUEUE_TIMEOUT_SECS=30 # how long a task waits for a container slot before failing CODE_RUNNER_QUEUE_TIMEOUT_SECS=30 # how long a task waits for a container slot before failing
CODE_RUNNER_TASK_TTL_SECS=300 # DashMap task entry lifetime (gc_old_tasks) CODE_RUNNER_TASK_TTL_SECS=300 # DashMap task entry lifetime (gc_old_tasks)
CODE_RUNNER_LANGUAGES=python,node # ops whitelist (must also exist in LANGUAGES registry) CODE_RUNNER_LANGUAGES= # unset = all registered langs; set a comma-list to narrow (e.g. python,node)
DOCKER_SOCKET_PATH=/var/run/docker.sock DOCKER_SOCKET_PATH=/var/run/docker.sock
``` ```

View File

@ -4,8 +4,8 @@
//! [`parse_fence_info`] 解析 markdown 围栏代码块的信息串,识别 `runnable` 标记与 //! [`parse_fence_info`] 解析 markdown 围栏代码块的信息串,识别 `runnable` 标记与
//! 可选的 JSON 资源覆盖(如 `python runnable {"timeout_secs":10}`)。 //! 可选的 JSON 资源覆盖(如 `python runnable {"timeout_secs":10}`)。
//! //!
//! 实际可用语言还受 `RUNNER_CONFIG.languages`(环境变量 `CODE_RUNNER_LANGUAGES`)双重约束: //! 实际可用语言默认即注册表里的全部;若设置了 `CODE_RUNNER_LANGUAGES`
//! 既要在注册表中存在,也要在运维白名单中启用,才视为支持[`is_supported_lang`])。 //! 环境变量,则进一步收窄到该白名单内[`is_supported_lang`])。
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::LazyLock; use std::sync::LazyLock;
@ -24,8 +24,8 @@ pub struct LanguageDef {
pub allow_network: bool, pub allow_network: bool,
} }
/// 内置语言注册表。新增语言时在此 `insert`,并在 `.env.example` / `CODE_RUNNER_LANGUAGES` /// 内置语言注册表。新增语言时在此 `insert` 即可默认启用;
/// 中默认启用 /// 若运维需要收窄,设置 `CODE_RUNNER_LANGUAGES` 为逗号分隔列表
pub static LANGUAGES: LazyLock<HashMap<String, LanguageDef>> = LazyLock::new(|| { pub static LANGUAGES: LazyLock<HashMap<String, LanguageDef>> = LazyLock::new(|| {
let mut m = HashMap::new(); let mut m = HashMap::new();
@ -106,10 +106,16 @@ pub static LANGUAGES: LazyLock<HashMap<String, LanguageDef>> = LazyLock::new(||
m m
}); });
/// 同时存在于注册表与运维白名单 (`RUNNER_CONFIG.languages`) 才视为支持。 /// 是否支持该语言:必须在 LANGUAGES 注册表中存在。
/// 若设置了 `CODE_RUNNER_LANGUAGES`,还需同时在该白名单内(用于收窄可用语言);
/// 未设置则注册表里的语言全部放行。
pub fn is_supported_lang(lang: &str) -> bool { pub fn is_supported_lang(lang: &str) -> bool {
let clean = lang.trim().to_lowercase(); let clean = lang.trim().to_lowercase();
RUNNER_CONFIG.languages.iter().any(|l| l == &clean) && LANGUAGES.contains_key(&clean) LANGUAGES.contains_key(&clean)
&& RUNNER_CONFIG
.languages
.as_ref()
.is_none_or(|list| list.iter().any(|l| l == &clean))
} }
/// 解析围栏代码块的 info string。 /// 解析围栏代码块的 info string。
@ -202,11 +208,14 @@ mod tests {
} }
#[test] #[test]
fn is_supported_lang_default_whitelist() { fn is_supported_lang_default_all_open() {
// 默认 CODE_RUNNER_LANGUAGES=python,node // 默认未设 CODE_RUNNER_LANGUAGES注册表里的语言全部放行。
assert!(is_supported_lang("python")); assert!(is_supported_lang("python"));
assert!(is_supported_lang("node")); assert!(is_supported_lang("node"));
assert!(!is_supported_lang("rust")); assert!(is_supported_lang("go"));
assert!(is_supported_lang("rust"));
// 未注册的语言仍不支持。
assert!(!is_supported_lang("ruby"));
assert!(!is_supported_lang("")); assert!(!is_supported_lang(""));
} }

View File

@ -25,7 +25,9 @@ pub struct RunnerConfig {
pub queue_timeout_secs: u64, pub queue_timeout_secs: u64,
pub task_ttl_secs: u64, pub task_ttl_secs: u64,
pub docker_socket_path: String, pub docker_socket_path: String,
pub languages: Vec<String>, /// 语言白名单。`None` 表示不限制——注册表里的所有语言均视为支持;
/// `Some(list)` 表示收窄到列表内(仍须同时在 LANGUAGES 注册表存在)。
pub languages: Option<Vec<String>>,
} }
#[cfg(feature = "server")] #[cfg(feature = "server")]
@ -36,8 +38,11 @@ fn parse_allow_network(v: &str) -> bool {
#[cfg(feature = "server")] #[cfg(feature = "server")]
pub static RUNNER_CONFIG: LazyLock<RunnerConfig> = LazyLock::new(|| { pub static RUNNER_CONFIG: LazyLock<RunnerConfig> = LazyLock::new(|| {
let languages_str = env::var("CODE_RUNNER_LANGUAGES").unwrap_or_else(|_| "python,node".to_string()); // CODE_RUNNER_LANGUAGES 未设置时默认全开None注册表里的语言均可用
let languages = languages_str.split(',').map(|s| s.trim().to_lowercase()).collect(); // 新增语言无需同步白名单。设置为逗号分隔列表则收窄到这些语言。
let languages = env::var("CODE_RUNNER_LANGUAGES").ok().map(|s| {
s.split(',').map(|t| t.trim().to_lowercase()).filter(|t| !t.is_empty()).collect()
});
RunnerConfig { RunnerConfig {
max_cpu_cores: env::var("CODE_RUNNER_MAX_CPU_CORES").ok().and_then(|v| v.parse().ok()).unwrap_or(2.0), max_cpu_cores: env::var("CODE_RUNNER_MAX_CPU_CORES").ok().and_then(|v| v.parse().ok()).unwrap_or(2.0),
@ -118,7 +123,7 @@ mod tests {
queue_timeout_secs: 1, queue_timeout_secs: 1,
task_ttl_secs: 1, task_ttl_secs: 1,
docker_socket_path: "".to_string(), docker_socket_path: "".to_string(),
languages: vec![], languages: None,
}; };
let raw = ResourceLimits { let raw = ResourceLimits {
cpu_cores: 1.0, cpu_cores: 1.0,