Files
everything-claude-code/docs/zh-CN/rules/rust/security.md

143 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
paths:
- "**/*.rs"
---
# Rust 安全
> 本文档在 [common/security.md](../common/security.md) 的基础上扩展了 Rust 相关的内容。
## 密钥管理
* 切勿在源代码中硬编码 API 密钥、令牌或凭证
* 使用环境变量:`std::env::var("API_KEY")`
* 如果启动时缺少必需的密钥,应快速失败
*`.env` 文件保存在 `.gitignore`
```rust
// BAD
const API_KEY: &str = "sk-abc123...";
// GOOD — environment variable with early validation
fn load_api_key() -> anyhow::Result<String> {
std::env::var("PAYMENT_API_KEY")
.context("PAYMENT_API_KEY must be set")
}
```
## SQL 注入防护
* 始终使用参数化查询 —— 切勿将用户输入格式化到 SQL 字符串中
* 使用支持绑定参数的查询构建器或 ORMsqlx, diesel, sea-orm
```rust
// BAD — SQL injection via format string
let query = format!("SELECT * FROM users WHERE name = '{name}'");
sqlx::query(&query).fetch_one(&pool).await?;
// GOOD — parameterized query with sqlx
// Placeholder syntax varies by backend: Postgres: $1 | MySQL: ? | SQLite: $1
sqlx::query("SELECT * FROM users WHERE name = $1")
.bind(&name)
.fetch_one(&pool)
.await?;
```
## 输入验证
* 在处理之前,在系统边界处验证所有用户输入
* 利用类型系统来强制约束newtype 模式)
* 进行解析,而非验证 —— 在边界处将非结构化数据转换为有类型的结构体
* 以清晰的错误信息拒绝无效输入
```rust
// Parse, don't validate — invalid states are unrepresentable
pub struct Email(String);
impl Email {
pub fn parse(input: &str) -> Result<Self, ValidationError> {
let trimmed = input.trim();
let at_pos = trimmed.find('@')
.filter(|&p| p > 0 && p < trimmed.len() - 1)
.ok_or_else(|| ValidationError::InvalidEmail(input.to_string()))?;
let domain = &trimmed[at_pos + 1..];
if trimmed.len() > 254 || !domain.contains('.') {
return Err(ValidationError::InvalidEmail(input.to_string()));
}
// For production use, prefer a validated email crate (e.g., `email_address`)
Ok(Self(trimmed.to_string()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
```
## 不安全代码
* 尽量减少 `unsafe` 块 —— 优先使用安全的抽象
* 每个 `unsafe` 块必须附带一个 `// SAFETY:` 注释来解释其不变量
* 切勿为了方便而使用 `unsafe` 来绕过借用检查器
* 在代码审查时审核所有 `unsafe` 代码 —— 若无合理解释,应视为危险信号
* 优先使用 `safe` 作为 C 库的 FFI 包装器
```rust
// GOOD — safety comment documents ALL required invariants
let widget: &Widget = {
// SAFETY: `ptr` is non-null, aligned, points to an initialized Widget,
// and no mutable references or mutations exist for its lifetime.
unsafe { &*ptr }
};
// BAD — no safety justification
unsafe { &*ptr }
```
## 依赖项安全
* 运行 `cargo audit` 以扫描依赖项中已知的 CVE
* 运行 `cargo deny check` 以确保许可证和公告合规
* 使用 `cargo tree` 来审计传递依赖项
* 保持依赖项更新 —— 设置 Dependabot 或 Renovate
* 最小化依赖项数量 —— 添加新 crate 前进行评估
```bash
# Security audit
cargo audit
# Deny advisories, duplicate versions, and restricted licenses
cargo deny check
# Inspect dependency tree
cargo tree
cargo tree -d # Show duplicates only
```
## 错误信息
* 切勿在 API 响应中暴露内部路径、堆栈跟踪或数据库错误
* 在服务器端记录详细错误;向客户端返回通用消息
* 使用 `tracing``log` 进行结构化的服务器端日志记录
```rust
// Map errors to appropriate status codes and generic messages
// (Example uses axum; adapt the response type to your framework)
match order_service.find_by_id(id) {
Ok(order) => Ok((StatusCode::OK, Json(order))),
Err(ServiceError::NotFound(_)) => {
tracing::info!(order_id = id, "order not found");
Err((StatusCode::NOT_FOUND, "Resource not found"))
}
Err(e) => {
tracing::error!(order_id = id, error = %e, "unexpected error");
Err((StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"))
}
}
```
## 参考资料
关于不安全代码指南和所有权模式,请参见技能:`rust-patterns`
关于通用安全检查清单,请参见技能:`security-review`