LibreFang 安全架构

安全联系方式: 请通过 GitHub 私密漏洞报告功能提交:https://github.com/librefang/librefang/security/advisories/new

本文档为 LibreFang Agent 操作系统中的每个安全子系统提供完整的技术参考。 所有结构体名称、函数签名、常量值和算法描述均直接来源于源代码。


目录

  1. 安全概述
  2. 基于能力的安全模型
  3. WASM 双重计量
  4. Merkle 哈希链审计追踪
  5. 信息流污点追踪
  6. Ed25519 清单签名
  7. SSRF 防护
  8. 密钥零化
  9. OFP 双向认证
  10. 安全响应头
  11. GCRA 速率限制器
  12. 路径遍历防护
  13. 子进程沙箱
  14. 提示注入扫描器
  15. 循环守卫
  16. 会话修复
  17. 健康端点信息脱敏
  18. Dashboard 认证
  19. TOTP 二次验证
  20. 安全配置
  21. 安全相关依赖

1. 安全概述

LibreFang 采用纵深防御安全策略。系统不依赖任何单一机制作为唯一 保护手段,而是由 16 个独立的安全子系统形成相互重叠的防护层,确保任何 单层失效都能被其他层捕获。

#系统所属 Crate防护目标
1基于能力的安全模型librefang-typesAgent 未授权操作
2WASM 双重计量librefang-runtime无限循环、CPU 拒绝服务
3Merkle 审计追踪librefang-runtime审计日志篡改
4污点追踪librefang-types提示注入、数据外泄
5Ed25519 清单签名librefang-types供应链攻击
6SSRF 防护librefang-runtime服务端请求伪造
7密钥零化librefang-runtimelibrefang-channels内存取证、密钥泄露
8OFP 双向认证librefang-wire未授权的对等节点连接
9安全响应头librefang-apiXSS、点击劫持、MIME 嗅探
10GCRA 速率限制器librefang-apiAPI 滥用、拒绝服务
11路径遍历防护librefang-runtime目录遍历攻击
12子进程沙箱librefang-runtime子进程泄露密钥
13提示注入扫描器librefang-skills恶意技能提示词
14循环守卫librefang-runtimeAgent 工具调用死循环
15会话修复librefang-runtimeLLM 对话历史损坏
16健康端点信息脱敏librefang-api信息泄露
17TOTP 二次验证librefang-kernel被攻破的渠道自我审批

2. 基于能力的安全模型

源码: librefang-types/src/capability.rs

LibreFang 采用基于能力的安全模型。Agent 只能执行被明确授权的操作。 能力在 Agent 创建后不可变,并在内核层面强制执行。

2.1 能力变体

Capability 枚举定义了所有权限类型:

pub enum Capability {
    // Filesystem
    FileRead(String),       // Glob pattern, e.g. "/data/*"
    FileWrite(String),

    // Network
    NetConnect(String),     // Host:port pattern, e.g. "*.openai.com:443"
    NetListen(u16),

    // Tools
    ToolInvoke(String),     // Specific tool ID
    ToolAll,                // All tools (dangerous)

    // LLM
    LlmQuery(String),
    LlmMaxTokens(u64),

    // Agent interaction
    AgentSpawn,
    AgentMessage(String),
    AgentKill(String),

    // Memory
    MemoryRead(String),
    MemoryWrite(String),

    // Shell
    ShellExec(String),
    EnvRead(String),

    // OFP Wire Protocol
    OfpDiscover,
    OfpConnect(String),
    OfpAdvertise,

    // Economic
    EconSpend(f64),
    EconEarn,
    EconTransfer(String),
}

2.2 模式匹配

capability_matches(granted, required) 函数实现了 glob 风格的匹配:

  • 精确匹配: "api.openai.com:443" 匹配 "api.openai.com:443"
  • 全通配符: "*" 匹配任意值
  • 前缀通配符: "*.openai.com:443" 匹配 "api.openai.com:443"
  • 后缀通配符: "api.*" 匹配 "api.openai.com"
  • 中间通配符: "api.*.com" 匹配 "api.openai.com"
  • ToolAll 特殊情况: ToolAll 可授权任意 ToolInvoke(_)
  • 数值边界: LlmMaxTokens(10000) 可授权 LlmMaxTokens(5000)(授予值 >= 请求值)

2.3 执行检查点

在 WASM 沙箱中,每个宿主调用在执行之前都会通过 host_functions.rs 中的 check_capability() 进行检查:

fn check_capability(
    capabilities: &[Capability],
    required: &Capability,
) -> Result<(), serde_json::Value> {
    for granted in capabilities {
        if capability_matches(granted, required) {
            return Ok(());
        }
    }
    Err(json!({"error": format!("Capability denied: {required:?}")}))
}

如果没有任何已授予的能力与所需能力匹配,操作会立即返回 JSON 错误——工具永远不会被调用。

2.4 能力继承

当 Agent 生成子 Agent 时,validate_capability_inheritance() 会确保 子 Agent 的能力是父 Agent 能力的子集。这可以防止权限提升:

pub fn validate_capability_inheritance(
    parent_caps: &[Capability],
    child_caps: &[Capability],
) -> Result<(), String> {
    for child_cap in child_caps {
        let is_covered = parent_caps
            .iter()
            .any(|parent_cap| capability_matches(parent_cap, child_cap));
        if !is_covered {
            return Err(format!(
                "Privilege escalation denied: child requests {:?} \
                 but parent does not have a matching grant",
                child_cap
            ));
        }
    }
    Ok(())
}

host_functions.rs 中的 host_agent_spawn() 函数调用 kernel.spawn_agent_checked(manifest_toml, Some(&state.agent_id), &state.capabilities), 在子 Agent 创建之前执行此验证。


3. WASM 双重计量

源码: librefang-runtime/src/sandbox.rs

不受信任的 WASM 模块在 Wasmtime 沙箱中运行,同时启用两个独立的 计量机制。

3.1 燃料计量(确定性)

燃料计量统计 WASM 指令数。引擎为每条执行的指令扣除燃料。当预算耗尽时, 执行会以 Trap::OutOfFuel 中断。

// SandboxConfig defaults
pub fuel_limit: u64,  // Default: 1_000_000

// Applied at execution time
if config.fuel_limit > 0 {
    store.set_fuel(config.fuel_limit)?;
}

执行结束后会报告燃料消耗量:

let fuel_remaining = store.get_fuel().unwrap_or(0);
let fuel_consumed = config.fuel_limit.saturating_sub(fuel_remaining);

3.2 纪元中断(墙钟时间)

看门狗线程休眠指定的超时时长,然后递增引擎纪元。当纪元超过 store 的 截止期限时,执行会以 Trap::Interrupt 中断。

store.set_epoch_deadline(1);
let engine_clone = engine.clone();
let timeout = config.timeout_secs.unwrap_or(30);
let _watchdog = std::thread::spawn(move || {
    std::thread::sleep(std::time::Duration::from_secs(timeout));
    engine_clone.increment_epoch();
});

3.3 为什么需要两种机制?

属性燃料纪元
度量指标指令数墙钟时间
精确度确定性,可复现非确定性
可捕获CPU 密集型循环宿主调用阻塞、I/O 等待
可规避可在宿主调用中浪费时间可廉价地忙等循环

两者组合形成完整防御:燃料捕获计算密集型循环,纪元捕获宿主调用滥用或 环境延迟。

3.4 SandboxConfig

pub struct SandboxConfig {
    pub fuel_limit: u64,           // Default: 1_000_000
    pub max_memory_bytes: usize,   // Default: 16 MB
    pub capabilities: Vec<Capability>,
    pub timeout_secs: Option<u64>, // Default: 30 seconds
}

3.5 错误类型

pub enum SandboxError {
    Compilation(String),
    Instantiation(String),
    Execution(String),
    FuelExhausted,         // Trap::OutOfFuel
    AbiError(String),
}

4. Merkle 哈希链审计追踪

源码: librefang-runtime/src/audit.rs

每个安全关键操作都会追加到一条防篡改的 Merkle 哈希链中,类似于区块链。 每个条目包含其自身内容与前一个条目哈希值拼接后的 SHA-256 哈希。

4.1 可审计的操作

pub enum AuditAction {
    ToolInvoke,
    CapabilityCheck,
    AgentSpawn,
    AgentKill,
    AgentMessage,
    MemoryAccess,
    FileAccess,
    NetworkAccess,
    ShellExec,
    AuthAttempt,
    WireConnect,
    ConfigChange,
}

4.2 条目结构

pub struct AuditEntry {
    pub seq: u64,          // Monotonically increasing sequence number
    pub timestamp: String, // ISO-8601
    pub agent_id: String,
    pub action: AuditAction,
    pub detail: String,    // e.g. tool name, file path
    pub outcome: String,   // "ok", "denied", error message
    pub prev_hash: String, // SHA-256 of previous entry (or 64 zeros)
    pub hash: String,      // SHA-256 of this entry + prev_hash
}

4.3 哈希计算

每个条目的哈希值由其所有字段与前一条目的哈希值拼接后计算得出:

fn compute_entry_hash(
    seq: u64, timestamp: &str, agent_id: &str,
    action: &AuditAction, detail: &str,
    outcome: &str, prev_hash: &str,
) -> String {
    let mut hasher = Sha256::new();
    hasher.update(seq.to_string().as_bytes());
    hasher.update(timestamp.as_bytes());
    hasher.update(agent_id.as_bytes());
    hasher.update(action.to_string().as_bytes());
    hasher.update(detail.as_bytes());
    hasher.update(outcome.as_bytes());
    hasher.update(prev_hash.as_bytes());
    hex::encode(hasher.finalize())
}

4.4 链完整性验证

AuditLog::verify_integrity() 遍历整条链并重新计算每个哈希值。如果 任何条目被篡改,重新计算的哈希值将与存储的哈希值不匹配,或者 prev_hash 链接将断裂:

pub fn verify_integrity(&self) -> Result<(), String> {
    let entries = self.entries.lock().unwrap_or_else(|e| e.into_inner());
    let mut expected_prev = "0".repeat(64);  // Genesis sentinel

    for entry in entries.iter() {
        if entry.prev_hash != expected_prev {
            return Err(format!(
                "chain break at seq {}: expected prev_hash {} but found {}",
                entry.seq, expected_prev, entry.prev_hash
            ));
        }
        let recomputed = compute_entry_hash(/* ... */);
        if recomputed != entry.hash {
            return Err(format!(
                "hash mismatch at seq {}: expected {} but found {}",
                entry.seq, recomputed, entry.hash
            ));
        }
        expected_prev = entry.hash.clone();
    }
    Ok(())
}

4.5 线程安全

AuditLog 使用 Mutex<Vec<AuditEntry>>Mutex<String> 来保护 链尾哈希。两个锁都使用 unwrap_or_else(|e| e.into_inner()) 从中毒的 互斥锁中恢复,确保审计日志在 panic 之后仍然可用。

4.6 API

方法描述
AuditLog::new()创建空日志,初始哨兵为 "0" * 64
record(agent_id, action, detail, outcome)追加条目,返回其哈希值
verify_integrity()验证整条链的完整性
tip_hash()返回最新条目的哈希值
len() / is_empty()条目计数
recent(n)返回最近 n 个条目(克隆)

5. 信息流污点追踪

源码: librefang-types/src/taint.rs

LibreFang 实现了基于格的污点传播模型,防止被污染的值在未经显式解密的 情况下流入敏感汇聚点。此机制可防范提示注入、数据外泄和混淆代理攻击。

5.1 污点标签

pub enum TaintLabel {
    ExternalNetwork,  // Data from external network requests
    UserInput,        // Direct user input
    Pii,              // Personally identifiable information
    Secret,           // API keys, tokens, passwords
    UntrustedAgent,   // Data from sandboxed/untrusted agents
}

5.2 被污染的值

pub struct TaintedValue {
    pub value: String,              // The payload
    pub labels: HashSet<TaintLabel>, // Attached taint labels
    pub source: String,             // Human-readable origin
}

关键方法:

方法描述
TaintedValue::new(value, labels, source)创建带标签的值
TaintedValue::clean(value, source)创建无标签的值(未污染)
merge_taint(&mut self, other)标签取并集(用于拼接场景)
check_sink(&self, sink)检查值是否可以流入指定汇聚点
declassify(&mut self, label)移除特定标签(显式安全决策)
is_tainted(&self) -> bool如果存在任何标签则返回 true

5.3 污点汇聚点

TaintSink 定义了哪些标签被阻止到达该汇聚点:

汇聚点被阻止的标签原因
TaintSink::shell_exec()ExternalNetworkUntrustedAgentUserInput防止命令注入
TaintSink::net_fetch()SecretPii防止数据外泄
TaintSink::agent_message()Secret防止密钥泄露给其他 Agent

5.4 违规处理

check_sink() 发现被阻止的标签时,会返回 TaintViolation

pub struct TaintViolation {
    pub label: TaintLabel,    // The offending label
    pub sink_name: String,    // "shell_exec", "net_fetch", etc.
    pub source: String,       // Where the tainted value came from
}

输出示例:taint violation: label 'Secret' from source 'env_var' is not allowed to reach sink 'net_fetch'

5.5 解密

解密是一个显式的安全决策。调用方声明该值已经过净化处理:

tainted.declassify(&TaintLabel::ExternalNetwork);
tainted.declassify(&TaintLabel::UserInput);
// After declassification, value can flow to shell_exec
assert!(tainted.check_sink(&TaintSink::shell_exec()).is_ok());

5.6 污点传播

当两个值被组合(拼接、插值)时,结果必须携带两者标签集的并集:

let mut combined = TaintedValue::new(/* ... */);
combined.merge_taint(&other_value);
// combined.labels is now the union of both

6. Ed25519 清单签名

源码: librefang-types/src/manifest_signing.rs

Agent 清单定义了 Agent 的能力、工具和配置。被篡改的清单可以授予 提升的权限。本模块提供基于 Ed25519 的加密签名。

6.1 签名方案

  1. 计算清单内容(原始 TOML 文本)的 SHA-256 哈希。
  2. 使用 Ed25519(通过 ed25519-dalek)对哈希进行签名。
  3. 将签名、公钥和内容哈希打包到 SignedManifest 信封中。

6.2 SignedManifest 结构

pub struct SignedManifest {
    pub manifest: String,           // Raw TOML content
    pub content_hash: String,       // Hex SHA-256 of manifest
    pub signature: Vec<u8>,         // Ed25519 signature (64 bytes)
    pub signer_public_key: Vec<u8>, // Ed25519 public key (32 bytes)
    pub signer_id: String,          // Human-readable signer ID
}

6.3 签名过程

let signing_key = SigningKey::generate(&mut OsRng);
let signed = SignedManifest::sign(manifest_toml, &signing_key, "admin@org.com");

内部实现:

pub fn sign(manifest: impl Into<String>, signing_key: &SigningKey, signer_id: impl Into<String>) -> Self {
    let manifest = manifest.into();
    let content_hash = hash_manifest(&manifest);  // SHA-256
    let signature = signing_key.sign(content_hash.as_bytes());
    let verifying_key = signing_key.verifying_key();
    Self {
        manifest,
        content_hash,
        signature: signature.to_bytes().to_vec(),
        signer_public_key: verifying_key.to_bytes().to_vec(),
        signer_id: signer_id.into(),
    }
}

6.4 验证过程

两阶段验证:

  1. 哈希校验: 重新计算 manifest 的 SHA-256 并与 content_hash 比较。
  2. 签名校验: 使用 signer_public_key 验证 content_hash 上的 Ed25519 签名。
pub fn verify(&self) -> Result<(), String> {
    let recomputed = hash_manifest(&self.manifest);
    if recomputed != self.content_hash {
        return Err("content hash mismatch: ...");
    }
    let verifying_key = VerifyingKey::from_bytes(&pk_bytes)?;
    let signature = Signature::from_bytes(&sig_bytes);
    verifying_key.verify(self.content_hash.as_bytes(), &signature)
        .map_err(|e| format!("signature verification failed: {}", e))
}

6.5 篡改检测

  • 签名后修改清单内容会导致内容哈希不匹配
  • 替换公钥为不同的密钥会导致签名验证失败
  • 两种攻击都会被 verify() 捕获。

7. SSRF 防护

源码: librefang-runtime/src/host_functions.rs

host_net_fetch 函数(用于网络请求的 WASM 宿主调用)包含全面的 服务端请求伪造防护。

7.1 协议验证

仅允许 http://https:// 协议。所有其他协议(file://gopher://ftp://)会被立即阻止:

if !url.starts_with("http://") && !url.starts_with("https://") {
    return Err(json!({"error": "Only http:// and https:// URLs are allowed"}));
}

7.2 主机名黑名单

在 DNS 解析之前,以下主机名会被阻止:

  • localhost
  • metadata.google.internal
  • metadata.aws.internal
  • instance-data
  • 169.254.169.254(AWS/GCP 元数据端点)

7.3 DNS 解析检查

在主机名黑名单检查之后,函数会将主机名解析为 IP 地址,并检查每个 解析出的 IP 是否属于私有地址范围。这可以防御 DNS 重绑定攻击:

let socket_addr = format!("{hostname}:{port}");
if let Ok(addrs) = socket_addr.to_socket_addrs() {
    for addr in addrs {
        let ip = addr.ip();
        if ip.is_loopback() || ip.is_unspecified() || is_private_ip(&ip) {
            return Err(json!({"error": format!(
                "SSRF blocked: {hostname} resolves to private IP {ip}"
            )}));
        }
    }
}

7.4 私有 IP 检测

is_private_ip() 函数覆盖以下范围:

IPv4:

  • 10.0.0.0/8 -- RFC 1918
  • 172.16.0.0/12 -- RFC 1918
  • 192.168.0.0/16 -- RFC 1918
  • 169.254.0.0/16 -- 链路本地(AWS 元数据)

IPv6:

  • fc00::/7 -- 唯一本地地址
  • fe80::/10 -- 链路本地
fn is_private_ip(ip: &std::net::IpAddr) -> bool {
    match ip {
        IpAddr::V4(v4) => {
            let octets = v4.octets();
            matches!(
                octets,
                [10, ..] | [172, 16..=31, ..] | [192, 168, ..] | [169, 254, ..]
            )
        }
        IpAddr::V6(v6) => {
            let segments = v6.segments();
            (segments[0] & 0xfe00) == 0xfc00 || (segments[0] & 0xffc0) == 0xfe80
        }
    }
}

7.5 主机提取

extract_host_from_url() 解析 URL 以提取 host:port,同时用于 SSRF 检查和能力匹配:

https://api.openai.com/v1/chat  ->  api.openai.com:443
http://localhost:8080/api       ->  localhost:8080
http://example.com              ->  example.com:80

7.6 SSRF 白名单(自托管 / K8s)

自托管和 Kubernetes 部署通常需要 Agent 访问内部服务(例如企业代理后面的 私有 API)。[tools.web_fetch] ssrf_allowed_hosts 配置项允许运维人员 显式豁免原本会被私有 IP 检查拦截的 CIDR 或主机名。

云元数据地址段(169.254.0.0/16100.64.0.0/10)无条件拦截, 无法通过白名单豁免。

配置示例

[tools.web_fetch]
ssrf_allowed_hosts = [
  "10.0.0.0/8",                # CIDR — 整个 RFC-1918 /8 段
  "172.16.0.0/12",             # CIDR — RFC-1918 /12 段
  "*.internal.example.com",    # glob 前缀通配符
  "svc.cluster.local",         # 字面主机名
  "192.168.1.100",             # 字面 IP
]

匹配规则

条目格式示例匹配对象
CIDR"10.0.0.0/8"目标主机的解析 IP
glob 前缀"*.internal.example.com"URL 中的主机名(后缀匹配)
字面 IP"192.168.1.100"目标主机的解析 IP
字面主机名"svc.cluster.local"URL 中的主机名(大小写不敏感)

仍然会被拦截的地址

即使白名单中有匹配条目,以下地址始终会被拒绝:

  • 169.254.0.0/16 — 链路本地 / AWS EC2 实例元数据
  • 100.64.0.0/10 — CGNAT(包括阿里云 IMDS 100.100.100.200
  • 主机名黑名单中的所有条目(localhostmetadata.google.internal 等)

8. 密钥零化

源码: 所有 LLM 驱动模块、通道适配器和网络搜索模块。

LibreFang 在所有持有密钥材料的字段上使用 zeroize crate 提供的 Zeroizing<String>。当值被销毁时,其内存会被零覆盖,防止密钥在 内存中残留。

8.1 工作原理

Zeroizing<T>zeroize crate 提供的智能指针包装器。它实现了 Deref<Target=T> 以实现透明使用,以及 Drop 以实现自动零化:

// On Drop, the inner String's buffer is overwritten with zeros
let key = Zeroizing::new("sk-secret-key".to_string());
// Use key transparently via Deref
client.post(url).header("authorization", format!("Bearer {}", &*key));
// When key goes out of scope, memory is zeroed

8.2 使用零化的字段

LLM 驱动 (librefang-runtime/src/drivers/):

驱动字段
AnthropicDriverapi_key: Zeroizing<String>
GeminiDriverapi_key: Zeroizing<String>
OpenAiCompatDriverapi_key: Zeroizing<String>

通道适配器 (librefang-channels/src/):

适配器字段
DiscordAdaptertoken: Zeroizing<String>
EmailAdapterpassword: Zeroizing<String>
BlueskyAdapterapp_password: Zeroizing<String>
DingTalkAdapteraccess_token: Zeroizing<String>secret: Zeroizing<String>client_id: Zeroizing<String>client_secret: Zeroizing<String>
FeishuAdapterapp_secret: Zeroizing<String>
FlockAdapterbot_token: Zeroizing<String>
GitterAdaptertoken: Zeroizing<String>
GotifyAdapterapp_token: Zeroizing<String>client_token: Zeroizing<String>

网络搜索 (librefang-runtime/src/web_search.rs):

fn resolve_api_key(env_var: &str) -> Option<Zeroizing<String>> {
    std::env::var(env_var).ok().filter(|k| !k.is_empty()).map(Zeroizing::new)
}

嵌入 (librefang-runtime/src/embedding.rs):

结构体字段
EmbeddingClientapi_key: Zeroizing<String>

8.3 为什么重要

如果不进行零化,密钥在使用后会一直留在内存中,直到操作系统回收该页面。 具有核心转储、交换文件或内存取证工具访问权限的攻击者可以恢复 API 密钥。 Zeroizing<String> 确保密钥在不再需要时立即被覆盖。


9. OFP 双向认证

源码: librefang-wire/src/peer.rs

LibreFang 线路协议(OFP)在 TCP 连接上使用基于 HMAC-SHA256 的 nonce 双向认证。

9.1 预共享密钥要求

OFP 在没有 shared_secret 的情况下拒绝启动:

if config.shared_secret.is_empty() {
    return Err(WireError::HandshakeFailed(
        "OFP requires shared_secret. Set [network] shared_secret in config.toml".into(),
    ));
}

9.2 HMAC 函数

type HmacSha256 = Hmac<Sha256>;

fn hmac_sign(secret: &str, data: &[u8]) -> String {
    let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
        .expect("HMAC accepts any key size");
    mac.update(data);
    hex::encode(mac.finalize().into_bytes())
}

fn hmac_verify(secret: &str, data: &[u8], signature: &str) -> bool {
    let expected = hmac_sign(secret, data);
    subtle::ConstantTimeEq::ct_eq(expected.as_bytes(), signature.as_bytes()).into()
}

常数时间比较subtle::ConstantTimeEq)防止时序侧信道攻击。

9.3 握手协议

发起方(客户端):

  1. 生成随机 UUID nonce。
  2. 计算 auth_data = nonce + node_id
  3. 计算 auth_hmac = hmac_sign(shared_secret, auth_data)
  4. 发送 Handshake { node_id, node_name, protocol_version, agents, nonce, auth_hmac }

响应方(服务端):

  1. 接收 Handshake 消息。
  2. 验证传入的 HMAC:hmac_verify(shared_secret, nonce + node_id, auth_hmac)
  3. 如果验证失败,返回错误码 403。
  4. 为确认消息生成新的 UUID nonce。
  5. 计算 ack_auth_data = ack_nonce + self.node_id
  6. 计算 ack_hmac = hmac_sign(shared_secret, ack_auth_data)
  7. 发送 HandshakeAck { node_id, node_name, protocol_version, agents, nonce: ack_nonce, auth_hmac: ack_hmac }

发起方(验证):

  1. 接收 HandshakeAck
  2. 验证:hmac_verify(shared_secret, ack_nonce + node_id, ack_hmac)
  3. 如果验证失败,返回 WireError::HandshakeFailed

9.4 安全属性

属性实现方式
双向认证双方都证明拥有共享密钥
重放保护每次握手使用随机 UUID nonce
时序攻击抵抗HMAC 比较使用 subtle::ConstantTimeEq
强制密钥shared_secret 时 OFP 拒绝启动
消息大小限制MAX_MESSAGE_SIZE = 16 MB 防止内存 DoS
协议版本检查PROTOCOL_VERSION 不匹配时返回 WireError::VersionMismatch

9.5 链路保密性

OFP 在线路上传输的帧是明文的。上述 HMAC 帧机制证明对端身份、防止 篡改与重放,但不加密有效载荷。保密性由部署层提供,不在 librefang-wire 内部重新实现。完整部署建议(WireGuard / Tailscale / SSH 隧道 / 服务网格 mTLS)、HMAC 已覆盖的威胁、以及重新评估的条件, 见 OFP 链路加密 页面。


10. 安全响应头

源码: librefang-api/src/middleware.rs

security_headers 中间件应用于所有 API 响应:

pub async fn security_headers(request: Request<Body>, next: Next) -> Response<Body> {
    let mut response = next.run(request).await;
    let headers = response.headers_mut();
    headers.insert("x-content-type-options", "nosniff".parse().unwrap());
    headers.insert("x-frame-options", "DENY".parse().unwrap());
    headers.insert("x-xss-protection", "1; mode=block".parse().unwrap());
    headers.insert("content-security-policy", /* CSP policy */);
    headers.insert("referrer-policy", "strict-origin-when-cross-origin".parse().unwrap());
    headers.insert("cache-control", "no-store, no-cache, must-revalidate".parse().unwrap());
    response
}
响应头防护目标
X-Content-Type-OptionsnosniffMIME 类型嗅探攻击
X-Frame-OptionsDENY通过 iframe 进行的点击劫持
X-XSS-Protection1; mode=block反射型 XSS(旧版浏览器)
Content-Security-Policy见下文XSS、代码注入、数据外泄
Referrer-Policystrict-origin-when-cross-originReferrer 泄露
Cache-Controlno-store, no-cache, must-revalidate敏感数据缓存

10.1 CSP 详细说明

指令用途
default-src'self'默认拒绝所有外部资源
script-src'self' 'unsafe-inline' 'unsafe-eval' cdn.jsdelivr.net允许来自自身和 CDN 的脚本
style-src'self' 'unsafe-inline' cdn.jsdelivr.net fonts.googleapis.com允许来自自身、CDN、Google Fonts 的样式
img-src'self' data:允许来自自身和 data URI 的图片
connect-src'self' ws: wss:允许 WebSocket 连接
font-src'self' cdn.jsdelivr.net fonts.gstatic.com允许来自 CDN 的字体
object-src'none'阻止所有插件(Flash、Java 等)
base-uri'self'防止 base 标签劫持
form-action'self'限制表单提交目标

11. GCRA 速率限制器

源码: librefang-api/src/rate_limiter.rs

LibreFang 通过 governor crate 使用通用信元速率算法(GCRA)实现 成本感知的 API 速率限制。

11.1 算法

GCRA 是漏桶算法的一种变体,为每个键追踪一个"虚拟调度时间" (TAT -- Theoretical Arrival Time)。每个请求消耗的令牌数与其成本 成正比。桶以恒定速率补充。

预算: 每个 IP 地址每分钟 500 个令牌。

pub fn create_rate_limiter() -> Arc<KeyedRateLimiter> {
    Arc::new(RateLimiter::keyed(Quota::per_minute(NonZeroU32::new(500).unwrap())))
}

11.2 操作成本

每个 API 操作都有可配置的令牌成本:

pub fn operation_cost(method: &str, path: &str) -> NonZeroU32 {
    match (method, path) {
        (_, "/api/health")                            => 1,
        ("GET", "/api/status")                        => 1,
        ("GET", "/api/version")                       => 1,
        ("GET", "/api/tools")                         => 1,
        ("GET", "/api/agents")                        => 2,
        ("GET", "/api/skills")                        => 2,
        ("GET", "/api/peers")                         => 2,
        ("GET", "/api/config")                        => 2,
        ("GET", "/api/usage")                         => 3,
        ("GET", p) if p.starts_with("/api/audit")     => 5,
        ("GET", p) if p.starts_with("/api/marketplace")=> 10,
        ("POST", "/api/agents")                       => 50,
        ("POST", p) if p.contains("/message")         => 30,
        ("POST", p) if p.contains("/run")             => 100,
        ("POST", "/api/skills/install")               => 50,
        ("POST", "/api/skills/uninstall")             => 10,
        ("POST", "/api/migrate")                      => 100,
        ("PUT", p) if p.contains("/update")           => 10,
        _                                             => 5,
    }
}

成本分层是有意设计的:只读的健康检查消耗 1 个令牌,而昂贵的工作流运行 消耗 100 个令牌,这意味着客户端每分钟可以执行 500 次健康检查,但只能 执行 5 次工作流运行。

11.3 中间件

pub async fn gcra_rate_limit(
    State(limiter): State<Arc<KeyedRateLimiter>>,
    request: Request<Body>,
    next: Next,
) -> Response<Body> {
    let ip = /* extract from ConnectInfo, default 127.0.0.1 */;
    let cost = operation_cost(&method, &path);

    if limiter.check_key_n(&ip, cost).is_err() {
        tracing::warn!(ip, cost, path, "GCRA rate limit exceeded");
        return Response::builder()
            .status(StatusCode::TOO_MANY_REQUESTS)
            .header("retry-after", "60")
            .body(/* JSON error */)
            .unwrap_or_default();
    }
    next.run(request).await
}

11.4 速率限制器类型

pub type KeyedRateLimiter = RateLimiter<IpAddr, DashMapStateStore<IpAddr>, DefaultClock>;

DashMapStateStore 提供并发的每 IP 状态管理,并自动清理过期条目。


12. 路径遍历防护

源码: librefang-runtime/src/host_functions.rs

两个函数提供纵深防御以抵御目录遍历攻击。

12.1 safe_resolve_path(用于读取)

用于 fs_readfs_list 操作,要求目标文件必须存在:

fn safe_resolve_path(path: &str) -> Result<std::path::PathBuf, serde_json::Value> {
    let p = Path::new(path);

    // Phase 1: Reject any path with ".." components
    for component in p.components() {
        if matches!(component, Component::ParentDir) {
            return Err(json!({"error": "Path traversal denied: '..' components forbidden"}));
        }
    }

    // Phase 2: Canonicalize to resolve symlinks and normalize
    std::fs::canonicalize(p)
        .map_err(|e| json!({"error": format!("Cannot resolve path: {e}")}))
}

12.2 safe_resolve_parent(用于写入)

用于 fs_write 操作,目标文件可能尚不存在:

fn safe_resolve_parent(path: &str) -> Result<std::path::PathBuf, serde_json::Value> {
    let p = Path::new(path);

    // Phase 1: Reject ".." in any component
    for component in p.components() {
        if matches!(component, Component::ParentDir) {
            return Err(json!({"error": "Path traversal denied: '..' components forbidden"}));
        }
    }

    // Phase 2: Canonicalize the parent directory
    let parent = p.parent().filter(|par| !par.as_os_str().is_empty())
        .ok_or_else(|| json!({"error": "Invalid path: no parent directory"}))?;
    let canonical_parent = std::fs::canonicalize(parent)?;

    // Phase 3: Belt-and-suspenders check on filename
    let file_name = p.file_name()
        .ok_or_else(|| json!({"error": "Invalid path: no file name"}))?;
    if file_name.to_string_lossy().contains("..") {
        return Err(json!({"error": "Path traversal denied in file name"}));
    }

    Ok(canonical_parent.join(file_name))
}

12.3 执行顺序

  1. 能力检查首先使用原始路径执行。
  2. 路径遍历检查其次执行。
  3. 操作仅在两者都通过后才执行。

这种顺序确保即使能力被错误配置为宽泛的模式(如 "*"),路径遍历仍然 会被阻止。


13. 子进程沙箱

源码: librefang-runtime/src/subprocess_sandbox.rs

当运行时生成子进程(例如用于 shell 工具或技能执行)时,必须清除继承的 环境变量以防止意外的密钥泄露。

13.1 环境清除

pub fn sandbox_command(cmd: &mut tokio::process::Command, allowed_env_vars: &[String]) {
    cmd.env_clear();  // Remove ALL inherited env vars

    // Re-add platform-independent safe vars
    for var in SAFE_ENV_VARS {
        if let Ok(val) = std::env::var(var) {
            cmd.env(var, val);
        }
    }

    // Re-add Windows-specific safe vars (on Windows)
    #[cfg(windows)]
    for var in SAFE_ENV_VARS_WINDOWS { /* ... */ }

    // Re-add caller-specified allowed vars
    for var in allowed_env_vars { /* ... */ }
}

13.2 安全环境变量

所有平台:

pub const SAFE_ENV_VARS: &[&str] = &[
    "PATH", "HOME", "TMPDIR", "TMP", "TEMP", "LANG", "LC_ALL", "TERM",
];

仅限 Windows:

pub const SAFE_ENV_VARS_WINDOWS: &[&str] = &[
    "USERPROFILE", "SYSTEMROOT", "APPDATA", "LOCALAPPDATA",
    "COMSPEC", "WINDIR", "PATHEXT",
];

不在这些列表中且不在 allowed_env_vars 中的变量永远不会传递给 子进程。这意味着 OPENAI_API_KEYGEMINI_API_KEY、数据库凭据以及 所有其他密钥都会被清除。

13.3 可执行文件路径验证

pub fn validate_executable_path(path: &str) -> Result<(), String> {
    let p = Path::new(path);
    for component in p.components() {
        if let std::path::Component::ParentDir = component {
            return Err(format!(
                "executable path '{}' contains '..' component which is not allowed",
                path
            ));
        }
    }
    Ok(())
}

这可以防止 Agent 通过构造的路径(如 ../../bin/dangerous)逃离其 工作目录。

13.4 Shell 注入防护

host_shell_exec 函数使用 Command::new(command).args(&args), 这不会调用 shell。每个参数直接传递给进程,防止通过 ;|&& 等元字符进行 shell 注入。


14. 提示注入扫描器

源码: librefang-skills/src/verify.rs

SkillVerifier 提供两个扫描函数:security_scan() 用于技能清单, scan_prompt_content() 用于技能提示文本(SKILL.md 正文)。

14.1 清单安全扫描

SkillVerifier::security_scan(manifest) 检查技能声明的需求:

检查项严重级别触发条件
Node.js 运行时Warningruntime_type == SkillRuntime::Node
Shell 执行能力Critical能力包含 shellexecshell_exec
不受限的网络访问Warning能力包含 netconnect(*)
Shell 工具Critical工具为 shell_execbash
文件系统写入工具Warning工具为 file_writefile_delete
工具过多Info需要超过 10 个工具

14.2 提示注入扫描

SkillVerifier::scan_prompt_content(content) 检测技能提示文本中的 常见攻击模式:

Critical -- 提示覆盖尝试:

"ignore previous instructions", "ignore all previous",
"disregard previous", "forget your instructions",
"you are now", "new instructions:", "system prompt override",
"ignore the above", "do not follow", "override system"

Warning -- 数据外泄模式:

"send to http", "send to https", "post to http", "post to https",
"exfiltrate", "forward all", "send all data",
"base64 encode and send", "upload to"

Warning -- Shell 命令引用:

"rm -rf", "chmod ", "sudo "

Info -- 内容过长:

超过 50,000 字节的内容会触发信息级别的警告,提示可能影响 LLM 性能。

14.3 SHA256 校验和验证

pub fn verify_checksum(data: &[u8], expected_sha256: &str) -> bool {
    let actual = Self::sha256_hex(data);
    actual == expected_sha256.to_lowercase()
}

从 ClawHub 安装的技能会根据已知的 SHA256 哈希验证其内容,以检测 下载过程中的篡改。

14.4 警告结构

pub struct SkillWarning {
    pub severity: WarningSeverity,  // Info, Warning, Critical
    pub message: String,
}

15. 循环守卫

源码: librefang-runtime/src/loop_guard.rs

LoopGuard 追踪单次 Agent 循环执行中的工具调用,以检测 Agent 是否 陷入重复调用同一工具的状态。

15.1 配置

pub struct LoopGuardConfig {
    pub warn_threshold: u32,         // Default: 3
    pub block_threshold: u32,        // Default: 5
    pub global_circuit_breaker: u32, // Default: 30
}

15.2 检测算法

  1. 对每次工具调用,计算 tool_name + "|" + serialized_params 的 SHA-256 哈希。
  2. HashMap<String, u32> 中递增该哈希的计数。
  3. 递增 total_calls
  4. 返回分级裁决:
pub fn check(&mut self, tool_name: &str, params: &serde_json::Value) -> LoopGuardVerdict {
    self.total_calls += 1;

    // Global circuit breaker
    if self.total_calls > self.config.global_circuit_breaker {
        return LoopGuardVerdict::CircuitBreak(/* ... */);
    }

    let hash = Self::compute_hash(tool_name, params);
    let count = self.call_counts.entry(hash).or_insert(0);
    *count += 1;

    if *count >= self.config.block_threshold {
        LoopGuardVerdict::Block(/* ... */)
    } else if *count >= self.config.warn_threshold {
        LoopGuardVerdict::Warn(/* ... */)
    } else {
        LoopGuardVerdict::Allow
    }
}

15.3 裁决类型

裁决含义动作
Allow正常操作运行工具
Warn(msg)同一调用重复 >= 3 次运行,并在结果中附加警告
Block(msg)同一调用重复 >= 5 次跳过执行,返回错误
CircuitBreak(msg)总调用次数 > 30终止整个 Agent 循环

15.4 哈希计算

fn compute_hash(tool_name: &str, params: &serde_json::Value) -> String {
    let mut hasher = Sha256::new();
    hasher.update(tool_name.as_bytes());
    hasher.update(b"|");
    let params_str = serde_json::to_string(params).unwrap_or_default();
    hasher.update(params_str.as_bytes());
    hex::encode(hasher.finalize())
}

注意:serde_json::to_string 生成确定性输出(对象键已排序),确保 语义相同的参数产生相同的哈希值。

15.5 关键特性

使用不同参数的调用会被分别追踪。一个 Agent 使用 10 个不同查询调用 web_search 不会触发守卫,但使用 web_search({"query": "test"}) 调用 5 次将被阻止。


16. 会话修复

源码: librefang-runtime/src/session_repair.rs

在将消息历史发送给 LLM 之前,本模块会验证并修复常见的结构性问题, 避免 API 错误。

16.1 三阶段修复

pub fn validate_and_repair(messages: &[Message]) -> Vec<Message>

阶段 1 -- 收集 ToolUse ID:

扫描所有消息中的 ContentBlock::ToolUse { id, .. } 块,将其 ID 收集 到 HashSet<String> 中。

阶段 2 -- 过滤孤立条目和空消息:

  • 孤立的 ToolResult: ContentBlock::ToolResult { tool_use_id, .. } 块中 tool_use_id 不在 ToolUse ID 集合中的会被丢弃。
  • 空消息: 文本为空或没有内容块的消息会被丢弃。

阶段 3 -- 合并连续的同角色消息:

Anthropic API 要求严格的角色交替(user、assistant、user、 assistant...)。如果两条连续消息具有相同的角色,它们会被合并为一条 包含组合内容块的消息。

16.2 每种修复的必要性

问题原因不修复的后果
孤立的 ToolResult压缩或截断删除了 ToolUseAPI 错误:"tool_use_id not found"
空消息取消的生成、空的用户提交API 错误:内容为空
连续的同角色消息手动编辑历史、会话修复本身API 错误:角色交替违规

16.3 内容合并

合并连续的同角色消息时,两条消息都被转换为块格式并拼接:

fn merge_content(dst: &mut MessageContent, src: MessageContent) {
    let dst_blocks = content_to_blocks(std::mem::replace(dst, MessageContent::Text(String::new())));
    let src_blocks = content_to_blocks(src);
    let mut combined = dst_blocks;
    combined.extend(src_blocks);
    *dst = MessageContent::Blocks(combined);
}

17. 健康端点信息脱敏

源码: librefang-api/src/routes.rs

LibreFang 提供两个信息级别不同的健康端点。

17.1 公开端点:GET /api/health

无需认证。 仅返回存活信息:

{
    "status": "ok",
    "version": "0.1.0"
}

该端点不会暴露 Agent 数量、数据库详情、配置警告、运行时间或任何内部 系统信息。适用于负载均衡器健康检查。

17.2 详细端点:GET /api/health/detail

需要认证。 返回完整诊断信息:

{
    "status": "ok",
    "version": "0.1.0",
    "uptime_seconds": 3600,
    "panic_count": 0,
    "restart_count": 2,
    "agent_count": 15,
    "database": "connected",
    "config_warnings": []
}

17.3 本地回环回退

当未配置 API 密钥时,auth 中间件将所有非健康端点限制为仅允许回环 地址访问:

if api_key.is_empty() {
    let is_loopback = request.extensions()
        .get::<ConnectInfo<SocketAddr>>()
        .map(|ci| ci.0.ip().is_loopback())
        .unwrap_or(false);
    if !is_loopback {
        return Response::builder()
            .status(StatusCode::FORBIDDEN)
            .body(/* "No API key configured. Remote access denied." */)
            ...;
    }
}

18. Dashboard 认证

LibreFang 支持为 Web Dashboard 配置可选的用户名/密码认证。

18.1 配置

# config.toml
dashboard_user = "admin"
dashboard_pass = "vault:dashboard_password"

当两个字段都设置后,Dashboard 会显示登录页面。如果未配置凭据, Dashboard 无需登录即可访问(或者在设置了 api_key 的情况下受其保护)。

18.2 凭据来源(优先级顺序)

优先级来源示例
1环境变量LIBREFANG_DASHBOARD_PASS=secret
2加密 Vaultdashboard_pass = "vault:dashboard_password"
3配置文件明文dashboard_pass = "my-password"

18.3 Vault 存储(推荐)

# Store password in encrypted vault
librefang vault set dashboard_password

# Reference in config.toml
dashboard_pass = "vault:dashboard_password"

Vault 使用 AES-256-GCM 加密,主密钥存储在操作系统密钥链中(macOS Keychain / Windows Credential Manager / Linux Secret Service),或 通过 LIBREFANG_VAULT_KEY 环境变量指定。

18.4 安全属性

  • 常数时间比较:使用 subtle::ConstantTimeEq 比较凭据,防止时序攻击。
  • HMAC 会话令牌:登录成功后,使用 HMAC-SHA256 从凭据派生确定性令牌,作为后续 API 请求的 Bearer token。
  • 无服务端会话状态:令牌是确定性的,服务端无需存储会话状态。
  • 公开端点/api/auth/dashboard-login/api/auth/dashboard-check 无需认证即可访问。

19. TOTP 二次验证

19.1 威胁模型

审批门将确认请求发送到通知渠道(Telegram、Slack 等)。如果发起渠道被攻破,攻击者可以同时发起和批准危险操作——审批门无法提供任何保护。

TOTP(基于时间的一次性密码,RFC 6238)添加了一个带外第二因子:TOTP 密钥存储在操作员设备上的验证器应用中,完全独立于消息渠道。仅攻破 Telegram 账号是不够的——攻击者还需要验证器应用。

19.2 架构

                                    ┌──────────────────┐
  Agent requests  Authenticator
  shell_exec  App (phone)     │

  TOTP Secret ──┐
  ┌─────────────┐                   └────────────────│─┘
 ApprovalMgr │◄── resolve(Approved, totp_verified)

 TOTP gate   │─────────┤
 Grace cache    ┌────────────┐
  └─────────────┘ API/Channel│ verify_totp_code()
 Layer      │◄─── 6-digit code─┘
                     └────────────┘

                     User types code

关键组件:

组件位置作用
SecondFactor 枚举librefang-types/approval.rs配置项:nonetotp
ApprovalManager::resolve()librefang-kernel/approval.rsTOTP 门控——未通过验证的 Approved 一律拒绝
ApprovalManager::verify_totp_code()librefang-kernel/approval.rsRFC 6238 验证(SHA-1,6 位,30 秒步长,±1 时间窗)
ApprovalManager::generate_totp_secret()librefang-kernel/approval.rs注册时生成密钥
宽限期缓存ApprovalManager::totp_graceHashMap<user_id, Instant>——窗口内跳过重复验证
保管库存储librefang-extensions/vault.rsTOTP 密钥以 totp_secret 键存于 AES-256-GCM 保管库

19.3 注册流程

1. POST /api/approvals/totp/setup
 生成随机 TOTP 密钥
 存储在加密保管库(vault.enc)中
 返回 base32 密钥 + otpauth:// URI

2. 用户将密钥添加到验证器应用(Google Authenticator、1Password 等)

3. POST /api/approvals/totp/confirm  { "code": "123456" }
 验证用户输入的验证码
 在保管库中设置 "totp_confirmed" = "true"

4. 在配置中设置 second_factor = "totp"(支持热重载)
 ApprovalManager.requires_totp() 返回 true
 后续所有审批都要求 TOTP 验证

也可以在 Dashboard 的 设置 > 安全 中完成注册。

19.4 启用后的审批流程

1. Agent 调用 shell_exec 需要审批
2. 内核发送交互通知到渠道
 消息包含:"TOTP required. Reply: /approve <id> <6位验证码>"
 隐藏批准按钮(验证码必须手动输入)
3. 用户回复:/approve abc123 654321
4. 渠道桥调用 ApprovalManager::verify_totp_code(secret, "654321")
5. 验证码有效 resolve(Approved, totp_verified=true, user_id="channel_user")
6. 为该 user_id 记录宽限期
7. Agent 解除阻塞 工具执行

19.5 宽限期

为避免每次审批都要输入验证码,可配置宽限期(totp_grace_period_secs,默认 300 秒)。

  • user_id 追踪,非按来源(如 "api" 或 "channel")
  • 设为 0 禁用宽限——每次审批都需要验证码
  • 最大值:3600 秒(1 小时)
  • 宽限缓存仅存在于内存中——重启守护进程会重置

19.6 安全属性

属性保证
密钥存储AES-256-GCM 加密保管库 + Argon2id 密钥派生
算法TOTP(RFC 6238),SHA-1,6 位,30 秒步长
时钟偏差容忍±1 窗口(±30 秒)
验证层API/渠道层在调用 resolve() 之前完成验证
内核强制启用 TOTP 时 resolve() 拒绝任何 totp_verified=true 之外的 Approved
拒绝放行拒绝/驳回决定不需要 TOTP(拒绝始终安全)
批量限制TOTP 启用时批量批准被阻止

20. 安全配置

20.1 config.toml 参考

# API Authentication
api_key = "your-secret-api-key"  # Empty = localhost-only mode

# OFP Wire Protocol
[network]
shared_secret = "your-pre-shared-key"  # Required for OFP

# WASM Sandbox
[sandbox]
fuel_limit = 1000000       # CPU instruction budget per execution
timeout_secs = 30          # Wall-clock timeout per execution
max_memory_bytes = 16777216 # 16 MB max WASM memory

# Rate Limiting
# 500 tokens/minute/IP (not currently configurable via config.toml)

# Web Search SSRF Protection
[web]
# SSRF protection is always on and cannot be disabled

18.2 密钥相关环境变量

变量用途
OPENAI_API_KEYOpenAI 兼容驱动
ANTHROPIC_API_KEYAnthropic 驱动
GEMINI_API_KEYGOOGLE_API_KEYGemini 驱动
DEEPSEEK_API_KEYDeepSeek 提供商
GROQ_API_KEYGroq 提供商
BRAVE_API_KEYBrave 网络搜索
TAVILY_API_KEYTavily 网络搜索
PERPLEXITY_API_KEYPerplexity 网络搜索

所有环境变量中的 API 密钥在加载到驱动结构体时都会被包装为 Zeroizing<String>

18.3 能力声明(Agent 清单)

能力在 Agent 的 TOML 清单中声明:

[agent]
name = "my-agent"

[[capabilities]]
type = "FileRead"
value = "/data/*"

[[capabilities]]
type = "NetConnect"
value = "*.openai.com:443"

[[capabilities]]
type = "ToolInvoke"
value = "web_search"

[[capabilities]]
type = "LlmMaxTokens"
value = 4096

18.4 循环守卫调优

LoopGuardConfig 的默认值:

参数默认值描述
warn_threshold3相同调用多少次后发出警告
block_threshold5相同调用多少次后阻止
global_circuit_breaker30总调用多少次后熔断

18.5 子进程沙箱白名单

要将特定环境变量传递给子进程:

sandbox_command(&mut cmd, &["MY_CUSTOM_VAR".to_string()]);

只有明确列在 allowed_env_vars 中的变量(加上安全默认值)才会被 子进程继承。


20. 安全相关依赖

Crate用途
sha2SHA-256 哈希(审计追踪、循环守卫、SSRF、校验和)
hmacHMAC-SHA256 用于 OFP 认证
hex哈希和签名的十六进制编解码
subtle常数时间比较(ConstantTimeEq)用于 HMAC 验证
ed25519-dalekEd25519 签名/验证用于清单签名
rand密码学安全随机数生成器用于密钥生成(OsRng
zeroizeZeroizing<T> 包装器用于自动密钥内存擦除
governorGCRA 速率限制算法
wasmtimeWASM 沙箱,带燃料 + 纪元计量
uuidOFP 握手的 nonce 生成
chrono审计条目的 ISO-8601 时间戳
reqwestHTTP 客户端(在受 SSRF 保护的 host_net_fetch 中使用)

19.1 为什么选择这些 Crate

  • sha2/hmac: 属于 RustCrypto 项目,经过审计,在生产环境的 Rust 项目中广泛使用。
  • ed25519-dalek: Rust 生态中事实上的 Ed25519 标准库,经过广泛审计。
  • subtle: 提供常数时间操作以防止时序侧信道攻击。
  • zeroize: RustCrypto 官方的密钥零化方案,与 Drop trait 集成。
  • governor: 久经考验的 GCRA 实现,使用 DashMap 支持的并发状态。

威胁模型总结

威胁缓解措施
Agent 请求未授权的文件访问基于能力的安全模型(第 2 节)
Agent 生成具有提升权限的子 Agent能力继承验证(第 2.4 节)
WASM 技能运行无限循环双重计量:燃料 + 纪元(第 3 节)
攻击者篡改审计日志Merkle 哈希链(第 4 节)
通过外部数据进行提示注入污点追踪(第 5 节)
通过 LLM 进行数据外泄污点汇聚点阻止 Secret/PII 流向 net_fetch(第 5.3 节)
被篡改的 Agent 清单Ed25519 签名(第 6 节)
SSRF 访问云元数据私有 IP + 主机名阻止 + DNS 检查(第 7 节)
从内存转储恢复 API 密钥Zeroizing<String>(第 8 节)
未授权的对等节点连接HMAC-SHA256 双向认证(第 9 节)
API 上的 XSS / 点击劫持安全响应头(第 10 节)
API 暴力破解 / 拒绝服务GCRA 速率限制器(第 11 节)
通过 ../ 进行路径遍历safe_resolve_path / safe_resolve_parent(第 12 节)
子进程泄露密钥env_clear() + 白名单(第 13 节)
来自 ClawHub 的恶意技能提示注入扫描器 + SHA256 校验和(第 14 节)
Agent 陷入工具调用循环LoopGuard 分级响应(第 15 节)
LLM 会话历史损坏会话修复(第 16 节)
健康端点信息泄露脱敏的公开端点(第 17 节)
针对 HMAC 验证的时序攻击subtle::ConstantTimeEq(第 9.2 节)
通过元字符进行 Shell 注入Command::new(无 shell)+ env_clear(第 13.4 节)
DNS 重绑定绕过 SSRF检查解析后的 IP,而非主机名(第 7.3 节)