feat: fnox secret-resolver integration + zeroclawed service#15
feat: fnox secret-resolver integration + zeroclawed service#15
Conversation
Extracts the vault.rs portion of 77eda45 (GatewayBackend PR on integrate-traceloop branch) — the surrounding gateway/traceloop work landed on main separately, but the fnox integration did not. Lookup order becomes: env → fnox → vaultwarden. Fnox is invoked as a subprocess (fnox get <name>); no library dep, keeping the spike lightweight per task #19. Cherry-picked from: 77eda45
Adds ensure_fnox() helper (brew on macOS, cargo fallback elsewhere) and a new section 4 that runs it. Matches the env → fnox → vaultwarden lookup order in onecli-client/src/vault.rs so fnox is available on hosts where install.sh provisions the toolchain. Fnox is soft-required: the resolver falls through gracefully if the binary is absent, so a failed install emits a warning rather than aborting the installer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a launchd plist (macOS) / systemd unit (Linux) for zeroclawed so channels (Telegram, Matrix, WhatsApp) reconnect automatically after reboots. Previously the binary was installed but had no service wiring, so any power event or log-out left channels offline until manual restart. The service targets ~/.zeroclawed/config.toml by default (overridable via ZEROCLAWED_CONFIG). Logs go to ~/.zeroclawed/logs/. ThrottleInterval=30 prevents tight restart loops while still recovering from transient network/upstream failures. Process presence is the health signal, not an HTTP port, because the optional proxy binding is config-gated and most installs don't enable it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds an additional secret-resolution layer via the fnox CLI and updates the unified installer to provision zeroclawed as a persistent system service so channels reconnect automatically after reboot.
Changes:
- Extend
onecli-clientsecret lookup order toenv → fnox → VaultWarden. - Add
ensure_fnoxtoscripts/install.shto installfnox(brew on macOS; cargo fallback on Linux). - Add launchd/systemd service provisioning for
zeroclawedinscripts/install.sh.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| scripts/install.sh | Installs fnox and installs/loads a zeroclawed service (launchd on macOS, systemd on Linux). |
| crates/onecli-client/src/vault.rs | Adds fnox subprocess lookup between env var lookup and VaultWarden. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Six integration tests that try to falsify the env → fnox → vaultwarden
precedence chain claimed in docs/rfcs/agent-secret-gateway.md §7 T4:
- env_var_wins_over_fnox — env takes precedence when both resolve
- env_lookup_uses_uppercase_api_key_suffix — documents the naming
convention (get_secret("foo") hits env var FOO_API_KEY) as a
regression guard; a silent refactor of this transform would break
callers that rely on the current shape
- fnox_value_returned_when_env_empty — fnox fires when env is missing
- fnox_failure_falls_through_to_vault_error — non-zero fnox exit doesn't
short-circuit; caller sees a user-actionable error
- fnox_binary_missing_is_graceful — no panic/hang when fnox isn't on
PATH (the common fresh-host case)
- fnox_empty_output_is_rejected — empty stdout from fnox is NOT returned
as Ok(""), which would otherwise produce "Authorization: Bearer "
with nothing after it and silently authenticate as anonymous
Strategy: fake `fnox` binary via a shell script prepended to PATH;
ENV_MUTEX serializes the tests that mutate process-global env so they
interleave correctly under `cargo test` default threading.
The T4-precondition test caught a non-obvious implementation detail
during authoring — the env lookup is `<NAME_UPPER>_API_KEY`, not the
raw name — so the suite also documents that convention explicitly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot review surfaced five concrete improvements:
- vault.rs: log fnox errors at debug! on fallthrough. The previous
`if let Ok(...)` silently discarded real fnox failures (auth,
backend IO, permission issues), making them unrecoverable during
incident investigation. Fallthrough behavior unchanged; observability
restored.
- vault.rs: make `get_secret_from_fnox` private. It was never part of
the intended public surface; keeping it private prevents consumers
from depending on a specific backend and decouples them from the
aggregated resolver.
- vault.rs: restore "No ONECLI_VAULT_TOKEN set" wording in the bail
message. The previous "not found in env, fnox, or vault" phrasing
was reached BEFORE querying vault at all, and lost the actionable
hint (which env var to set) for operators enabling the vault leg.
- install.sh: check `${PIPESTATUS[0]}` after `brew install fnox | tail -3`
so a real brew failure doesn't get masked by `tail`'s zero exit. The
ok/return path now triggers only on actual success, and a failure
message names the exit code for debugging.
- install.sh: same PIPESTATUS pattern for `cargo install fnox | grep | tail`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three polish fixes from the Copilot review:
- §3 substitution scope — the original said "unsupported content-types
pass through unchanged, log warning", which creates a §11.8-shaped
bypass (agent claims multipart/form-data with `{{secret:` in the
body). Rewrote the bullet: a cheap raw-bytes scan runs FIRST; if
the bytes contain `{{secret:` we fail-closed. Only bodies with no
ref-shaped content pass through.
- §6 installer audit table — replaced "✅ (this PR)" with concrete
branch/PR references ("feat/fnox-integration, PR #15") so the
attribution stays correct when read outside this PR.
- §§5/7 — path consistency on the `security-gateway.md` reference;
use `docs/security-gateway.md` everywhere so the link is unambiguous.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 5 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Codex integration sweep note: I reviewed the inline comments on this PR. GitHub rejected direct inline replies for these older/outdated review comments with HTTP 422, so I am responding top-level instead: 3135377410, 3138422176, 3138422219, 3138422242, 3138422272, 3138422300, 3138422324, 3138422347.\n\nI did not edit this branch. Items that overlap the secure/fnox/host-agent/digest integration work are addressed in draft PR #38 (codex-integration-code), including stdin-based fnox set, bounded fnox waits, whitespace-safe !secure parsing, identity-aware !secure audit logs, valid-input host-agent properties, real WhatsApp HMAC verification, loopback OneCLI default bind, and race-free digest temp paths. Remaining PR-specific findings stay actionable for this branch owner or a follow-up. |
…ning (#44) Squash-merge of integration/super-combined — 4 weeks of feature work + cross-PR security fixes + codex agent's hardening, all green CI (14/14 checks). ## Features landing - **fnox secret-resolver integration** (#15) + FnoxClient subprocess wrapper (#21) - **Adversarial commit-reviewer + mechanical pre-commit gate** (#18) - **{{secret:NAME}} substitution engine** in security-proxy URL/headers/body (#19) - **Per-secret destination allowlist** (#22) — RFC §11.1 attack defense - **!secure chat commands** (set/list) on Telegram (#20), Matrix (#28), WhatsApp (#31) - **zeroclawed-mcp** scaffold — agent-facing secret discovery server (#23) - **install.sh wires MCP** into Claude Code agent configs (#26) - **zeroclawed-secret-paste** — localhost web UI for one-shot secret input (#34) - **Bulk paste UI** — .env-style multi-secret onboarding with per-line results - **LAN-friendly defaults** — bind 0.0.0.0 + RFC 1918 Origin acceptance - **WhatsApp HMAC verification** (was always-true placeholder before — codex hardening) ## Security fixes folded in - /vault/:secret bearer auth + 127.0.0.1 default bind (#39) - URL-embedded secrets honor destination allowlist (#41) - Paste-flow: bearer URL only at debug, fnox set via stdin not argv (#40) - Paste-flow: graceful shutdown, exit-on-submit, reject Origin: null (#43) - Subprocess timeouts + kill_on_drop on FnoxClient - BrokenPipe-tolerant stdin write (Linux CI surface) - Header-value log redaction - OneCLI bound to 127.0.0.1 by default - Sanitized real API token + Telegram IDs from sample configs (#36) ## Architecture / refactors - Consolidated onecli binary into security-proxy (#17) - Hardcoded vault URL removed from onecli-client - security-proxy resolver wired into hot path - Extracted build_app router; migrated /vault/:secret route - !secure parser uses split_whitespace (was splitn), audit-logs invocations ## Test coverage added - security-proxy substitution engine + body/headers tests - onecli-client retry + Http(_) variant + adversarial fallthrough suite - onecli-client client.rs rewritten from tautologies to wiremock-backed - config/validator coverage (was zero, now 290-line module covered) - 16 zeroclawed-secret-paste tests including bulk-mode cases ## Docs / RFCs - agent-secret-gateway holistic architecture - consolidation-findings (what #28 must address) - secret-input-web-ui RFC (input-only, new-by-default) - browser-harness integration spike - test-quality-audit Round 1+2+3 (host-agent + zeroclawed priority files) ## Codex agent's hardening cherry-picks - Subprocess timeouts on fnox calls - map_spawn_error helper - Validator hardening + atomic-counter digest race fix - WhatsApp HMAC implementation + tests - proxy header-value log redaction CI: all 14 checks green at squash time. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
Subsumed by #44 (squashed to |
…ning (#44) Squash-merge of integration/super-combined — 4 weeks of feature work + cross-PR security fixes + codex agent's hardening, all green CI (14/14 checks). ## Features landing - **fnox secret-resolver integration** (#15) + FnoxClient subprocess wrapper (#21) - **Adversarial commit-reviewer + mechanical pre-commit gate** (#18) - **{{secret:NAME}} substitution engine** in security-proxy URL/headers/body (#19) - **Per-secret destination allowlist** (#22) — RFC §11.1 attack defense - **!secure chat commands** (set/list) on Telegram (#20), Matrix (#28), WhatsApp (#31) - **zeroclawed-mcp** scaffold — agent-facing secret discovery server (#23) - **install.sh wires MCP** into Claude Code agent configs (#26) - **zeroclawed-secret-paste** — localhost web UI for one-shot secret input (#34) - **Bulk paste UI** — .env-style multi-secret onboarding with per-line results - **LAN-friendly defaults** — bind 0.0.0.0 + RFC 1918 Origin acceptance - **WhatsApp HMAC verification** (was always-true placeholder before — codex hardening) ## Security fixes folded in - /vault/:secret bearer auth + 127.0.0.1 default bind (#39) - URL-embedded secrets honor destination allowlist (#41) - Paste-flow: bearer URL only at debug, fnox set via stdin not argv (#40) - Paste-flow: graceful shutdown, exit-on-submit, reject Origin: null (#43) - Subprocess timeouts + kill_on_drop on FnoxClient - BrokenPipe-tolerant stdin write (Linux CI surface) - Header-value log redaction - OneCLI bound to 127.0.0.1 by default - Sanitized real API token + Telegram IDs from sample configs (#36) ## Architecture / refactors - Consolidated onecli binary into security-proxy (#17) - Hardcoded vault URL removed from onecli-client - security-proxy resolver wired into hot path - Extracted build_app router; migrated /vault/:secret route - !secure parser uses split_whitespace (was splitn), audit-logs invocations ## Test coverage added - security-proxy substitution engine + body/headers tests - onecli-client retry + Http(_) variant + adversarial fallthrough suite - onecli-client client.rs rewritten from tautologies to wiremock-backed - config/validator coverage (was zero, now 290-line module covered) - 16 zeroclawed-secret-paste tests including bulk-mode cases ## Docs / RFCs - agent-secret-gateway holistic architecture - consolidation-findings (what #28 must address) - secret-input-web-ui RFC (input-only, new-by-default) - browser-harness integration spike - test-quality-audit Round 1+2+3 (host-agent + zeroclawed priority files) ## Codex agent's hardening cherry-picks - Subprocess timeouts on fnox calls - map_spawn_error helper - Validator hardening + atomic-counter digest race fix - WhatsApp HMAC implementation + tests - proxy header-value log redaction CI: all 14 checks green at squash time. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Summary
Three related changes that together land the fnox secret-resolution path
and give zeroclawed a persistent home across reboots:
Cherry-picked from the prior librarian spike (
77eda45c), rebased ontocurrent main. Fnox is invoked as a subprocess (
fnox get <name>), nolibrary dep. Resolver falls through gracefully if fnox isn't present.
ensure_fnoxhelper and section 4:brew install fnoxon macOS,cargo install fnoxfallback on Linux.zeroclawed itself. The installer was provisioning services for clashd
and security-proxy but not the gateway, so after any power event
channels stayed offline until manual restart.
ThrottleInterval=30avoids restart loops; process-presence is the health signal (the HTTP
proxy is config-gated, so there's no guaranteed health port).
Test plan
cargo build --workspacegreencargo test -p onecli-client— 24 passbash -n scripts/install.shsyntax OKkeychain provider,
curl :8091/vault/TEST_FNOX_SECRETvia oneclireturns the value; log shows
Looking up ... in fnox/Found ... in fnox.Telegram bot reconnects.
🤖 Generated with Claude Code