bordumb/capsec

GitHub: bordumb/capsec

为 Rust 提供编译时能力安全的三层防护框架,解决依赖代码实际行为不可见、权限边界不可控的问题。

Stars: 3 | Forks: 0

# capsec [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/03/b504e90fff172614.svg)](https://github.com/bordumb/capsec/actions/workflows/ci.yml) [![crates.io](https://img.shields.io/crates/v/capsec.svg)](https://crates.io/crates/capsec) [![docs.rs](https://docs.rs/capsec/badge.svg)](https://docs.rs/capsec) Rust 保证内存安全 —— capsec 保证行为安全。 ### 快速开始 ``` # 查看您的代码和依赖项的实际行为(零配置,零代码更改): cargo install cargo-capsec cargo capsec audit ``` `cargo audit` 检查 CVE。`cargo vet` 检查信任度。两者都无法告诉你代码实际上*做*了什么。没有什么能阻止你的 CSV 解析器打开一个 TCP socket 来回传遥测数据。 capsec 通过三层机制填补了这一空白: 1. **`cargo capsec audit`** —— 一个静态审计工具,扫描你的代码并报告每一个 I/O 调用。将其放入 CI 中,就能确切了解你的依赖项在做什么。 2. **编译时类型系统** —— 函数通过 `Has

` trait 边界声明其 I/O 权限,编译器会拒绝任何超出权限的代码。零运行时成本。 3. **运行时能力控制** —— `RuntimeCap`(可撤销)和 `TimedCap`(可过期)将静态能力与运行时有效性检查包装在一起,适用于服务器初始化或迁移窗口等动态场景。 审计工具负责发现问题。类型系统在编译时阻止问题。运行时 caps 处理权限需要动态变更的情况。 ## cargo-capsec —— 静态能力审计 扫描 Rust 源代码中的环境权限(filesystem, network, env, process)并报告你的代码 —— 以及你的依赖项 —— 能对外部世界做什么。零配置,零代码修改。 ### 安装 ``` cargo install cargo-capsec # 或从源码: cargo install --path crates/cargo-capsec ``` ### 运行 ``` cargo capsec audit ``` ``` my-app v0.1.0 ───────────── FS src/config.rs:8:5 fs::read_to_string load_config() NET src/api.rs:15:9 TcpStream::connect fetch_data() PROC src/deploy.rs:42:17 Command::new run_migration() Summary ─────── Crates with findings: 1 Total findings: 3 Categories: FS: 1 NET: 1 ENV: 0 PROC: 1 1 critical-risk findings ``` ### 添加到 CI ``` # .github/workflows/capsec.yml name: Capability Audit on: [pull_request] jobs: audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo install cargo-capsec - run: cargo capsec audit --fail-on high --quiet ``` PR 中出现了新的高风险 I/O?CI 失败。没有新的 I/O?CI 通过。团队可以使用 `--baseline` 和 `--diff` 逐步采用,仅标记*新发现*的问题。 要查看实际效果,可以参考以下内容: * [CI/CD](https://github.com/bordumb/capsec/blob/main/.github/workflows/ci.yml#L57) * [Pre-Commit Hook](https://github.com/bordumb/capsec/blob/main/.pre-commit-config.yaml#L32) ## capsec —— 编译时能力强制执行 审计工具告诉你代码做了什么。类型系统控制代码*被允许*做什么。 ### 使用 capsec 之前 任何函数都可以做任何事。函数签名具有误导性 —— 它看起来是纯函数,但它读取文件并打开 socket: ``` // This function signature says nothing about I/O. // But inside, it reads from disk and phones home over the network. pub fn process_csv(input: &[u8]) -> Vec> { let config = std::fs::read_to_string("/etc/app/config.toml") .unwrap_or_default(); if let Ok(mut stream) = std::net::TcpStream::connect("telemetry.example.com:8080") { stream.write_all(input).ok(); } parse(input, &config) } ``` Rust 编译器对此完全满意。Clippy 不会标记它。没有什么能阻止它。 ### 使用 capsec 之后 函数在类型签名中声明其 I/O 需求。编译器强制执行这些需求: ``` use capsec::prelude::*; // Define a context with exactly the permissions your app needs. // The macro generates Cap fields, constructor, and Has

impls. #[capsec::context] struct AppCtx { fs: FsRead, net: NetConnect, } // Leaf functions take &impl Has

— works with raw caps AND context structs. pub fn load_config(path: &str, cap: &impl Has) -> Result { capsec::fs::read_to_string(path, cap) } // Intermediate functions take a single context reference — not N separate caps. pub fn app_logic(ctx: &AppCtx) -> Result { load_config("/etc/app/config.toml", ctx) // ctx satisfies Has } // #[capsec::main] injects the capability root automatically. #[capsec::main] fn main(root: CapRoot) { let ctx = AppCtx::new(&root); let config = app_logic(&ctx).unwrap(); } ``` 每个能力都可追溯至 `CapRoot`。如果函数在没有被授予 `Cap` 的情况下使用 capsec 包装器(如 `capsec::fs::read_to_string`),代码将无法编译。审计工具会捕获完全绕过 capsec 包装器的代码 —— 直接调用 `std::fs`、使用 FFI 或将 I/O 隐藏在重导出之后。 ### 编译器实际上会说什么 **错误的能力类型** —— 在需要 `Cap` 的地方传递了 `Cap`: ``` let net_cap = root.grant::(); let _ = capsec::fs::read_to_string("/etc/passwd", &net_cap); ``` ``` error[E0277]: the trait bound `Cap: Has` is not satisfied --> src/main.rs:4:55 | 4 | let _ = capsec::fs::read_to_string("/etc/passwd", &net_cap); | -------------------------- ^^^^^^^^ the trait `Has` is not implemented for `Cap` | | | required by a bound introduced by this call ``` **缺少能力** —— 调用 capsec 函数时根本没有提供 token: ``` let _ = capsec::fs::read_to_string("/etc/passwd"); ``` ``` error[E0061]: this function takes 2 arguments but 1 argument was supplied --> src/main.rs:2:13 | 2 | let _ = capsec::fs::read_to_string("/etc/passwd"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^--------------- argument #2 of type `&_` is missing | help: provide the argument | 2 | let _ = capsec::fs::read_to_string("/etc/passwd", /* cap */); | +++++++++++ ``` **跨类别违规** —— `FsAll` 包含 `FsRead` 和 `FsWrite`,但不包含 `NetConnect`: ``` let fs_all = root.grant::(); needs_net(&fs_all); // fn needs_net(_: &impl Has) {} ``` ``` error[E0277]: the trait bound `Cap: Has` is not satisfied --> src/main.rs:3:15 | 3 | needs_net(&fs_all); | --------- ^^^^^^^ the trait `Has` is not implemented for `Cap` | | | required by a bound introduced by this call ``` 这些是真实的 `rustc` 错误 —— 没有自定义错误框架,没有运行时 panic。Rust 编译器负责执行。 ### 这为你带来了什么 | | 之前 | 之后 | |--|--------|-------| | 任何函数都能读文件吗? | 是 | 只有拥有 `Cap` 的函数才能 | | 任何函数都能打开 socket 吗? | 是 | 只有拥有 `Cap` 的函数才能 | | 你能审计谁拥有什么权限吗? | 只能靠 Grep 碰运气 | Grep `Has` 即可 | | 运行时成本? | 不适用 | 零 —— 所有类型在编译时被擦除 | ### 安全模型 capsec 针对的是**协作式安全 Rust** —— 使用 capsec 包装器的代码无法超出其声明的权限,编译器以零运行时成本强制执行这一点。 `Has

` trait 是开放实现的 —— 自定义上下文结构体可以实现它以委托能力访问。安全性得以维持是因为 `Cap::new()` 是 `pub(crate)` 的:外部代码无法在 safe Rust 中伪造 `Cap

`。`Permission` trait 保持封印 —— 外部 crate 无法发明新的权限类型。 capsec **不**防范的内容: - **`unsafe` 代码**,通过 `transmute`、`MaybeUninit` 或指针技巧伪造能力 token。类型系统仅在 safe Rust 内部是健全的。(`capsec-tests/tests/type_system.rs` 中的对抗性测试套件记录了这些攻击,并确认它们需要 `unsafe`。) - **直接 `std` 调用**,绕过 capsec 包装器。函数总是可以在没有能力 token 的情况下调用 `std::fs::read()` —— 编译器不会阻止它。这就是 `cargo capsec audit` 的用武之地:它静态检测这些调用。 - **FFI 和内联汇编**,直接与 OS 交互。审计工具会标记 `extern` 块,但无法推断外部代码的行为。 这三层是互补的:**类型系统**在选择了加入的代码内强制执行边界,**审计工具**发现尚未选择加入的代码,**运行时 caps** 处理动态权限生命周期。单独一层都不完整 —— 它们一起提供了纵深防御。 capsec 附带一个包含 74 个测试的对抗性安全套件,记录了每个已知的规避向量和攻击面 —— unsafe 伪造、`std` 绕过、FFI 逃生舱口、上下文委托攻击等。大多数安全工具不会编目其自身的弱点。capsec 做到了,所以你确切知道什么被覆盖了,什么没有。参见 [`capsec-tests/tests/type_system.rs`](crates/capsec-tests/tests/type_system.rs) 和 [`capsec-tests/tests/audit_evasion.rs`](crates/capsec-tests/tests/audit_evasion.rs)。 ## 运行时能力控制 静态 `Cap

` token 是永久的 —— 一旦授予,它们永远有效。对于权限应该是临时或可撤销的场景,capsec 提供了运行时能力包装器。 ### 可撤销能力 为服务器启动授予网络访问权限,然后撤销它,以便在运行时无法建立新连接: ``` use capsec::prelude::*; #[capsec::main] fn main(root: CapRoot) -> Result<(), Box> { // Wrap a capability with a revocation handle let (runtime_cap, revoker) = RuntimeCap::new(root.net_connect()); // During startup: try_cap() returns a real Cap let cap = runtime_cap.try_cap()?; init_connection_pool(&cap); // After init: revoke — no new connections possible revoker.revoke(); // Now try_cap() returns Err(CapSecError::Revoked) assert!(runtime_cap.try_cap().is_err()); Ok(()) } ``` 运行时 caps 与上下文模式组合 —— 包装单个能力以进行撤销,同时保持上下文的人体工程学: ``` #[capsec::context] struct ServerCtx { fs: FsRead, net: NetConnect, } // Grant static fs access + revocable net access let ctx = ServerCtx::new(&root); // static caps for the context let (net_rt, revoker) = RuntimeCap::new(root.net_connect()); // revocable net cap // Use ctx for Has bounds, net_rt.try_cap()? for revocable net access ``` ### 有时间限制的能力 为迁移窗口授予临时写入权限: ``` use capsec::prelude::*; use std::time::Duration; #[capsec::main] fn main(root: CapRoot) -> Result<(), Box> { let timed_cap = TimedCap::new(root.fs_write(), Duration::from_secs(30)); // Within the window: try_cap() succeeds let cap = timed_cap.try_cap()?; capsec::fs::write("/tmp/migration.txt", "data", &cap)?; // After TTL: try_cap() returns Err(CapSecError::Expired) // timed_cap.remaining() returns Duration::ZERO Ok(()) } ``` ### 关键属性 - `RuntimeCap` 和 `TimedCap` **不**实现 `Has

` —— 在每个调用点通过 `try_cap()` 显式表达可失败性 - 两者默认都是 `!Send` 的 —— 使用 `make_send()` 以选择加入跨线程传输 - 克隆 `RuntimeCap` 会共享撤销标志 —— 撤销一个即撤销所有克隆 - `Revoker` 是 `Send + Sync + Clone` 的 —— 可以从任何线程撤销 ### capsec 与其他工具的对比 | 工具 | 方法 | 层级 | |------|----------|-------| | **capsec** | 编译时类型(`Has

` 边界)+ 运行时 caps(`RuntimeCap`,`TimedCap`)+ 静态审计 | 源码级,协作式 | | **[cap-std](https://github.com/bytecodealliance/cap-std)** | 运行时能力句柄(移除环境权限) | OS 级,面向 WASI | | **[cargo-scan](https://github.com/AlfredoSystems/cargo-scan)** | 危险 API 使用的静态分析 | 源码级,研究原型 | | **[cargo-cgsec](https://github.com/nicholasgasior/cargo-cgsec)** | 调用图能力分析(Capslock 移植) | 源码级,仅审计 | `cap-std` 在不同的层级运作 —— 它在运行时将 OS 级文件描述符替换为能力句柄,目标是 WASI 沙箱。capsec 在类型级别工作,零运行时成本,不需要 OS 支持。两者是互补的:你可以在 capsec 把关的函数内使用 `cap-std` 句柄。 `cargo-scan`(来自 UC San Diego)执行与 `cargo capsec audit` 类似的静态分析。capsec 增加了类型系统强制层,并作为包含这两个工具的单个工作区发布。 `cargo-cgsec` 是 Google Capslock 的 Rust 移植版,由 Rust Foundation 资助。它执行调用图分析以识别能力使用 —— 仅审计,无强制或运行时层。capsec 通过 `cargo capsec audit` 覆盖相同的审计面,并增加了编译时类型强制和运行时能力控制。 ## 许可证 Apache-2.0

标签:Capability-based Security, Cargo 插件, FS/NET 权限, I/O 监控, JSONLines, Runtime Control, Rust, Security Audit, Streamlit, 云安全监控, 依赖审计, 内存安全, 可视化界面, 文档安全, 最小权限原则, 权限控制, 类型系统, 编译时安全, 网络信息收集, 网络流量审计, 行为安全, 访问控制, 通知系统, 防御 telemetry, 零信任, 静态分析