Skip to content

feat: fnox secret-resolver integration + zeroclawed service#15

Closed
bglusman wants to merge 5 commits intomainfrom
feat/fnox-integration
Closed

feat: fnox secret-resolver integration + zeroclawed service#15
bglusman wants to merge 5 commits intomainfrom
feat/fnox-integration

Conversation

@bglusman
Copy link
Copy Markdown
Owner

Summary

Three related changes that together land the fnox secret-resolution path
and give zeroclawed a persistent home across reboots:

  • onecli-client/vault.rs — env → fnox → vaultwarden lookup chain.
    Cherry-picked from the prior librarian spike (77eda45c), rebased onto
    current main. Fnox is invoked as a subprocess (fnox get <name>), no
    library dep. Resolver falls through gracefully if fnox isn't present.
  • install.sh (fnox) — new ensure_fnox helper and section 4:
    brew install fnox on macOS, cargo install fnox fallback on Linux.
  • install.sh (zeroclawed service) — launchd plist + systemd unit for
    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=30
    avoids 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 --workspace green
  • cargo test -p onecli-client — 24 pass
  • bash -n scripts/install.sh syntax OK
  • Live fnox proof: brew-installed fnox, set TEST_FNOX_SECRET via
    keychain provider, curl :8091/vault/TEST_FNOX_SECRET via onecli
    returns the value; log shows Looking up ... in fnox /
    Found ... in fnox.
  • Live launchd proof: plist loaded, zeroclawed running under launchd,
    Telegram bot reconnects.

🤖 Generated with Claude Code

Librarian and others added 3 commits April 23, 2026 17:57
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>
Copilot AI review requested due to automatic review settings April 24, 2026 04:04
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-client secret lookup order to env → fnox → VaultWarden.
  • Add ensure_fnox to scripts/install.sh to install fnox (brew on macOS; cargo fallback on Linux).
  • Add launchd/systemd service provisioning for zeroclawed in scripts/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.

Comment thread crates/onecli-client/src/vault.rs Outdated
Comment thread crates/onecli-client/src/vault.rs Outdated
Comment thread scripts/install.sh
Comment thread scripts/install.sh Outdated
Comment thread crates/onecli-client/src/vault.rs Outdated
bglusman and others added 2 commits April 24, 2026 00:12
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>
Copilot AI review requested due to automatic review settings April 24, 2026 14:35
bglusman added a commit that referenced this pull request Apr 24, 2026
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>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread crates/onecli-client/tests/vault_fallthrough.rs
Comment thread crates/onecli-client/tests/vault_fallthrough.rs
Comment thread crates/onecli-client/tests/vault_fallthrough.rs
Comment thread crates/onecli-client/tests/vault_fallthrough.rs
Comment thread crates/onecli-client/tests/vault_fallthrough.rs
Comment thread crates/onecli-client/src/vault.rs
Comment thread scripts/install.sh
@bglusman
Copy link
Copy Markdown
Owner Author

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.

bglusman added a commit that referenced this pull request Apr 25, 2026
…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)
@bglusman
Copy link
Copy Markdown
Owner Author

Subsumed by #44 (squashed to 9ed51fbc on main). All commits from this branch are present in the squash. Closing as redundant rather than merging again.

@bglusman bglusman closed this Apr 25, 2026
bglusman added a commit that referenced this pull request Apr 25, 2026
…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)
@bglusman bglusman deleted the feat/fnox-integration branch May 1, 2026 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants