Security Hardening — Phase 9
Phase 9 implements 12 targeted security improvements identified through a gap analysis comparing OpenClaw vulnerabilities with ClawDesk's architecture. All changes compile cleanly (634 tests, 0 failures).
Overview Matrix
| ID | Title | Priority | Crate(s) Modified |
|---|---|---|---|
| T-01 | Ed25519 Skill Signature Verification | P0 | clawdesk-skills |
| T-02 | Plugin Sandbox TOCTOU Fix | P0 | clawdesk-plugin |
| T-03 | Channel Registration Attestation | P1 | clawdesk-channel, clawdesk-gateway |
| T-04 | ACL on Message Hot-Path | P0 | clawdesk-gateway |
| T-05 | Aho-Corasick Multi-Pattern Scanner | P1 | clawdesk-security |
| T-06 | Content-Addressed Skill Identity | P1 | clawdesk-skills |
| T-07 | Typed Channel Config Schemas | P2 | clawdesk-channels |
| T-08 | SHA-256 Cryptographic Audit Chain | P1 | clawdesk-security |
| T-09 | Hierarchical Rate Limiting | P1 | clawdesk-gateway |
| T-10 | Prompt Namespace Isolation | P2 | clawdesk-agents |
| T-11 | Manifest Schema Versioning | P2 | clawdesk-skills |
| T-12 | Staged Promotion Pipeline | P2 | clawdesk-skills |
T-01: Ed25519 Skill Signature Verification
File: crates/clawdesk-skills/src/verification.rs (new)
Skills are now cryptographically verified before loading. Each skill manifest can carry a signature and publisher_key field. The verifier checks:
- Trust levels:
Unsigned < SignedUntrusted < SignedTrusted < Builtin - Builtin skills always get
TrustLevel::Builtin(highest trust) - Signed skills with signatures are verified against a trusted publisher key set
- Gating:
verify_and_gate()rejects skills below a minimum trust level
pub struct SkillVerifier {
trusted_keys: HashSet<String>,
minimum_trust: TrustLevel,
}
// loader.rs integration:
let verification = self.verifier.verify_and_gate(&manifest, &source)?;
Dependencies added: ed25519-dalek = "2", sha2 = "0.10", hex = "0.4"
T-02: Plugin Sandbox TOCTOU Fix
File: crates/clawdesk-plugin/src/sandbox.rs, host.rs
The plugin sandbox resource allocation had a Time-of-Check-Time-of-Use (TOCTOU) race condition. Three allocation methods (try_allocate_memory, try_spawn_task, try_open_fd) used load(Relaxed) + fetch_add(Relaxed) — a non-atomic check-then-act pattern.
Fix: Replaced with compare_exchange_weak CAS loops using Ordering::AcqRel/Acquire:
loop {
let current = self.current_memory_bytes.load(Ordering::Acquire);
let new = current + bytes;
if new > self.config.max_memory_bytes {
return Err(SandboxError::MemoryLimitExceeded { ... });
}
match self.current_memory_bytes.compare_exchange_weak(
current, new, Ordering::AcqRel, Ordering::Acquire,
) {
Ok(_) => break,
Err(_) => continue, // CAS retry
}
}
Host integration: send_to_plugin() now acquires a TaskGuard, checks payload memory budget, and wraps execution in tokio::time::timeout().
T-03: Channel Registration Attestation
File: crates/clawdesk-channel/src/registry.rs (rewritten)
Channel registration now validates and attests:
- Validation: Display name non-empty, no duplicate channel IDs
- Capability probing: Auto-detects threading, streaming, reactions, group management from
ChannelMeta - Provenance tracking: Records capabilities + registration timestamp
- Return type:
register()returnsRegistrationResult(Ok/Rejected) instead of()
pub enum RegistrationResult {
Ok { id: ChannelId, capabilities: ChannelCapabilities },
Rejected { reason: String },
}
T-04: ACL on Message Hot-Path
File: crates/clawdesk-gateway/src/middleware.rs
Three ACL enforcement functions added for the message dispatch hot-path:
check_message_acl()— validates user can send messages on a channelcheck_tool_acl()— validates user can invoke a specific toolcheck_plugin_acl()— validates user can interact with a plugin
Each maps the request context to (Principal, Resource, Action) and queries the AclManager with O(1) indexed lookup.
T-05: Aho-Corasick Multi-Pattern Scanner
File: crates/clawdesk-security/src/scanner.rs (rewritten)
Replaced the sequential O(p×m) fixed-string scanner with a pre-compiled Aho-Corasick automaton for O(m+z) single-pass scanning:
Tier 0.5 → Aho-Corasick (fixed strings: eval, exec, script, javascript:, etc.)
Tier 1 → Regex patterns (character classes, backreferences)
Tier 2 → Structural checks (oversize content)
Tier 3 → Semantic analysis (placeholder)
Default patterns: eval(, exec(, <script, javascript:, data:text/html, password, -----BEGIN
Dependency added: aho-corasick = "1"
T-06: Content-Addressed Skill Identity
Files: crates/clawdesk-skills/src/definition.rs, loader.rs
Skills are now content-addressed via SHA-256:
SkillManifestgainscontent_hash: Option<String>- On load, the SHA-256 of
prompt.mdis computed and stored - Enables deduplication, tamper detection, and cache invalidation
T-07: Typed Channel Config Schemas
File: crates/clawdesk-channels/src/factory.rs
Channel factories now register typed config schemas alongside constructors:
let schema = ConfigSchema::new("telegram")
.required("bot_token", ConfigFieldType::String, "Telegram Bot API token")
.optional("enable_groups", ConfigFieldType::Bool, "Allow group chats");
factory.register_with_schema("telegram", schema, |config| { ... });
ConfigSchema::validate()checks required fields and type correctnessfactory.create()validates config against schema before construction- Constructor type changed from
Box<dyn Fn>toArc<dyn Fn>forClonesupport - All 7 built-in channels have schemas (telegram, discord, slack, whatsapp, matrix, msteams, webchat/internal via register)
T-08: SHA-256 Cryptographic Audit Chain
File: crates/clawdesk-security/src/audit.rs
Replaced the non-cryptographic FNV-1a (64-bit) hash chain with SHA-256 (256-bit):
- Genesis hash:
"0" × 64(was"0" × 16) - Each entry:
SHA-256(prev_hash || category || detail || actor || timestamp) epoch_intervalconfig for periodic epoch anchorsverify_chain()validates the full chain integrity
T-09: Hierarchical Rate Limiting
File: crates/clawdesk-gateway/src/rate_limiter.rs
Added a 4-tier hierarchical rate limiter that enforces limits at multiple scopes:
| Tier | Scope | Burst | Sustained |
|---|---|---|---|
| 0 | Global | 1000 | 200/s |
| 1 | Channel | 100 | 20/s |
| 2 | Plugin | 50 | 10/s |
| 3 | Skill | 20 | 5/s |
check() and check_named() validate requests against all tier limits — the narrowest tier that trips blocks the request.
T-10: Prompt Namespace Isolation
File: crates/clawdesk-agents/src/prompt_isolation.rs (new)
Skill prompt fragments are now wrapped in structured namespace envelopes:
--- BEGIN SKILL [core/web-search] (trust: builtin) ---
Allowed tools: web_search, fetch_url
You can search the web for information.
--- END SKILL [core/web-search] ---
Key types:
PromptIsolator— bipartite graph (skill → allowed tools), enforcescan_use_tool()IsolatedPrompt— renders namespaced prompt with boundary markersPromptNamespace— skill ID, trust label, fragment, allowed/provided tools
T-11: Manifest Schema Versioning
File: crates/clawdesk-skills/src/definition.rs
SkillManifest now includes schema_version: u32 (default: 1 via #[serde(default)]). This enables:
- Forward-compatible manifest evolution
- Migration logic keyed on version number
- Rejection of unsupported future versions
T-12: Staged Promotion Pipeline
File: crates/clawdesk-skills/src/promotion.rs (new)
Skills progress through a deterministic FSM before activation:
Submitted → Scanned → SandboxTested → Approved → Active
↓ ↓ ↓ ↓
Rejected Rejected Rejected Rejected
Features:
PromotionPipeline— tracks entries through stagesadvance()/reject()— stage transitions with validationRollbackBuffer— circular buffer of N active snapshots for instant rollbackat_stage()— query all skills at a given stage
Dependency Changes
Workspace-level (Cargo.toml)
# --- Cryptography ---
ed25519-dalek = { version = "2", features = ["std"] }
sha2 = "0.10"
hex = "0.4"
# --- Text search ---
aho-corasick = "1"
Crate-level additions
| Crate | New Dependencies |
|---|---|
clawdesk-skills | ed25519-dalek, sha2, hex |
clawdesk-security | sha2, hex, aho-corasick |