fix: 安全漏洞修复 — CLI密码泄露 + margin上限 + 路径检查 + CSP/限速

CRITICAL:
- CLI --password 未提供时自动从 QRGEN_WIFI_PASSWORD 环境变量读取
  避免密码出现在 ps aux / /proc/pid/cmdline 进程列表中

HIGH:
- unsafe from_utf8_unchecked SAFETY 注释增强, 详细解释为何有效
- CLI --margin 添加 value_parser range(0..=100), 防止 u8=255 导致内存爆炸
- Web CORS permissive 添加注释说明公开 API 设计理由
- GUI FS 权限收紧: 写权限从 /c/Users/33644/** 限制为 Downloads/Documents/Desktop/AppData

MEDIUM:
- 新增 QrError::InvalidEcLevel(String), 不再复用 InvalidVersion(0)
- --logo/--batch/--output_dir 均添加 check_path() 路径遍历检查
- Web 添加 tower::limit::ConcurrencyLimitLayer(10) 并发限制防 CPU 耗尽
- decode_image 添加 4096x4096 图片尺寸上限防解压炸弹

LOW:
- Web 添加 Content-Security-Policy 响应头
This commit is contained in:
2026-06-21 16:09:08 +08:00
parent bd4ca718ac
commit b053272825
10 changed files with 68 additions and 12 deletions
+17 -3
View File
@@ -56,7 +56,7 @@ struct EncodeOpts {
version: Option<u8>,
#[arg(short = 's', long, default_value = "4", value_parser = clap::value_parser!(u8).range(1..=20))]
size: u8,
#[arg(short = 'm', long, default_value = "4")]
#[arg(short = 'm', long, default_value = "4", value_parser = clap::value_parser!(u8).range(0..=100))]
margin: u8,
#[arg(long)] fg: Option<String>,
#[arg(long)] bg: Option<String>,
@@ -64,7 +64,7 @@ struct EncodeOpts {
#[arg(short = 'f', long, default_value = "png")]
format: String,
#[arg(long)] mode: Option<String>,
// WiFi
// WiFi — 密码优先从环境变量 QRGEN_WIFI_PASSWORD 读取,避免命令行泄露
#[arg(long)] ssid: Option<String>,
#[arg(long)] password: Option<String>,
#[arg(long, default_value = "WPA")]
@@ -174,12 +174,20 @@ fn cmd_encode(content: &str, output: &Option<String>, opts: &EncodeOpts) -> Resu
let final_text = if let Some(m) = &opts.mode {
build_mode(m, opts, &text)?
} else if let Some(ref bf) = opts.batch {
check_path(bf)?; // 批量文件路径检查
if let Some(od) = &opts.output_dir {
check_path(od)?; // 输出目录路径检查
}
return do_batch(bf, opts);
} else {
text
};
let level: EcLevel = opts.level.parse().map_err(|e: String| anyhow::anyhow!(e))?;
// --logo 文件路径也需安全检查
if let Some(logo_path) = &opts.logo {
check_path(logo_path)?;
}
let logo = opts
.logo
.as_ref()
@@ -252,9 +260,15 @@ fn build_mode(mode: &str, opts: &EncodeOpts, fb: &str) -> Result<String> {
.ssid
.as_deref()
.ok_or_else(|| anyhow::anyhow!("WiFi 模式需要 --ssid"))?;
// 密码优先从 --password 读取,未提供时尝试环境变量 QRGEN_WIFI_PASSWORD
let pwd = opts
.password
.as_deref()
.or_else(|| std::env::var("QRGEN_WIFI_PASSWORD").ok().as_deref())
.unwrap_or("");
Ok(text_builder::build_wifi_text(
s,
opts.password.as_deref().unwrap_or(""),
pwd,
&opts.encryption,
opts.hidden,
))