Skip to content

feat(agent): enterprise desktop agent hardening#80

Open
bb-connor wants to merge 22 commits intomainfrom
feat/cs-agent-hardening
Open

feat(agent): enterprise desktop agent hardening#80
bb-connor wants to merge 22 commits intomainfrom
feat/cs-agent-hardening

Conversation

@bb-connor
Copy link
Collaborator

@bb-connor bb-connor commented Feb 13, 2026

Summary

  • Pre-execution tool interception — guards fire BEFORE destructive actions execute, not after. The critical gap that motivated this work.
  • Per-action approval queue — allow-once/allow-session/allow-always/deny with OS notifications, TTL expiry, and tray integration.
  • 25 hardening items across Rust core, hushd daemon, OpenClaw adapter, Tauri desktop agent, Python SDK, Helm charts, and rulesets.

Phase 1: Security gaps closed

  • Spine Ed25519 seeds moved to K8s Secrets (no more inline env values)
  • Decision type cleaned up (deprecated boolean fields removed)
  • NATS securityContext hardened (runAsNonRoot, drop ALL, readOnlyRootFilesystem)
  • mTLS client cert verification added to hushd
  • SQLite calls wrapped with spawn_blocking (no more async starvation)
  • Request body size limits via RequestBodyLimitLayer
  • /ready readiness probe with deep health checks

Phase 2: Desktop agent UX

  • Pre-flight tool guard with preventDefault for destructive ops
  • Approval queue with 4 resolution types and OS notification integration
  • LRU decision cache (skip destructive ops, TTL-based)
  • Offline fallback with cached policy + audit queue
  • Session lifecycle (create/heartbeat/terminate) with tray display
  • SSE event subscription for real-time policy/violation/posture events

Phase 3: Operational hardening

  • Helm PDB, HPA, bridge probes, rolling update strategy
  • v1.2.0 posture-aware ruleset (restricted/standard/elevated states)
  • Unified V1Error JSON envelope across all hushd API handlers
  • Secret leak patterns for Anthropic, Stripe, GCP, Azure, GitHub, GitLab
  • 41 new tests for instruction hierarchy and jailbreak guard

Phase 4: Enterprise polish

  • Python SDK: extends resolution, 2 new guards, regex-based secret detection
  • Windows forbidden paths support
  • Pod topology + anti-affinity for hushd
  • Desktop agent deployment guide
  • Shared text_utils module (deduplicated from 4 sources)
  • Deep merge for secret_leak config with additional_patterns/remove_patterns

Stats

  • 94 files changed, +6,631 / -826 lines
  • cargo test --workspace — all passing (0 failures)
  • cargo clippy --workspace -- -D warnings — clean
  • cargo fmt --all -- --check — clean

Test plan

  • cargo test --workspace passes
  • cargo clippy --workspace -- -D warnings clean
  • helm template renders all new resources (PDB, HPA, spine-secrets, readiness probe)
  • OpenClaw adapter tests pass (npx vitest run)
  • Python SDK tests pass (pytest packages/sdk/hush-py/tests)
  • Manual: trigger forbidden write via Claude Code → notification appears → approve → write succeeds

🤖 Generated with Claude Code


Note

High Risk
Touches core policy enforcement and security decision paths (session IDs, fail-closed behavior, new guards, approvals) and adds new network/backoff/background tasks; regressions could incorrectly allow/deny actions or break agent↔daemon interoperability.

Overview
Adds posture-aware session lifecycle management to the Tauri agent (create/heartbeat/terminate) and threads session_id through evaluate_policy_check, with normalization of action types/targets and explicit fail-closed behavior when hushd is unreachable or returns errors (optional debug inclusion of daemon error bodies).

Introduces a per-action approval queue (ApprovalQueue) with TTL/cleanup, OS notifications, tray badge integration, and a local API surface (/api/v1/approval/*) to submit/status/resolve/list pending approvals; the agent health/settings APIs now expose session state and a debug flag.

Improves resilience/operations by adding a policy cache synced from hushd and an offline audit queue that flushes on startup/reconnect, plus richer SSE handling that distinguishes daemon-level events (policy updates, violations, posture transitions) from audit events and updates tray/notifications accordingly.

Hardens rules and guards: adds a new clawdstrike:ai-agent-posture ruleset and expands existing rulesets/ForbiddenPathGuard for Windows credential/registry paths; adds ShellCommandGuard for dangerous command patterns and forbidden-path touches; expands SecretLeakGuard patterns and adds additional_patterns/remove_patterns merge semantics. Also updates agent integration mappings to canonical action types (egress, shell, mcp_tool) and bumps related Rust deps (notably axum 0.8 in the agent, plus new crypto/x509 deps for rcgen).

Written by Cursor Bugbot for commit c69fc8f. This will update automatically on new commits. Configure here.

Pre-execution tool interception, per-action approval queue, session
lifecycle, offline fallback, SSE events, mTLS, unified error format,
decision caching, Python SDK parity, posture-aware rulesets, and
operational Helm improvements across 79 files.

Phase 1 — Security gaps:
- Move Spine Ed25519 seeds to K8s Secrets (secretKeyRef)
- Remove deprecated Decision boolean fields, migrate to status-only
- Add NATS securityContext (runAsNonRoot, drop ALL, readOnlyRootFilesystem)
- Add mTLS client cert verification to hushd (with_client_cert_verifier)
- Wrap SQLite calls with spawn_blocking to avoid async starvation
- Add RequestBodyLimitLayer (configurable, default 1MB)
- Add /ready readiness probe (SQLite ping, engine, signing key)

Phase 2 — Desktop agent UX:
- Pre-execution tool guard (tool-preflight/handler.ts) with preventDefault
- Per-action approval queue (approval.rs) with TTL, OS notifications
- LRU decision cache keyed on event+resource+policy_version
- Offline fallback with cached policy, audit queue, degraded provenance
- Session lifecycle (create/heartbeat/terminate) with tray display
- SSE event subscription for policy/violation/posture events

Phase 3 — Operational hardening:
- Helm PDB, HPA, bridge liveness probes, rolling update strategy
- v1.2.0 posture-aware ruleset (restricted/standard/elevated)
- prompt_injection + jailbreak guards in strict/ai-agent rulesets
- Secret leak patterns for Anthropic, Stripe, GCP, Azure, GitHub, GitLab
- Unified V1Error JSON envelope across all hushd API handlers
- Comprehensive tests for instruction_hierarchy (31) and jailbreak (10)

Phase 4 — Enterprise polish:
- Python SDK: extends resolution, PromptInjection/Jailbreak guards, regex
- Windows forbidden paths (cfg!(windows) + YAML)
- Pod topology + anti-affinity for hushd
- Desktop agent deployment guide (mdbook)
- Extract shared text_utils module from 4 duplicated sources
- Deep merge for secret_leak with additional_patterns/remove_patterns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d5b79337f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

bb-connor and others added 2 commits February 13, 2026 19:13
Rust:
- daemon.rs: AuditQueue Vec -> VecDeque for O(1) pop_front
- daemon.rs: fix re-queue ordering to discard oldest on truncation
- daemon.rs: fix spurious restart when child handle is None after stop
- approval.rs: cap HashMap at 500 entries to prevent unbounded growth
- session.rs: surface non-404 heartbeat errors instead of swallowing

TypeScript:
- Remove all `any` types from plugin.ts and engine.ts
- Replace unsafe `as string` casts with typeof narrowing in handlers
- Remove redundant type assertions in createEventData switch cases
- Fix decision-cache TTL test that passed by accident (fake timers
  called after set, so epoch 0 was always past real expiry)

Python:
- SecretLeakGuard: fix openai_key pattern false-positiving on any sk-*
- SecretLeakGuard/PatchIntegrityGuard: raise ValueError on invalid
  regex (was silently dropping patterns — fail-open, not fail-closed)
- PromptInjectionGuard: remove markdown code fence false positive,
  tighten "you are now" to require suspicious role words
- Policy merge: fix settings override losing child's explicit false
- PostureConfig: validate state references in transitions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rust:
- approval.rs: emit Expired events in list_pending/resolve so tray
  badge stays current
- daemon.rs: re-queue drained audit events on flush send failure
  instead of dropping them permanently
- secret_leak.rs: merge_with now folds base additional_patterns into
  child when child has explicit patterns

TypeScript:
- strike-cell.ts: remove unused denyDecision import
- secret-leak.ts: fix duplicate char in regex character class
- decision-cache.test.ts: remove unused decisionCache import

Python:
- Remove 7 unused imports across guards and tests
- policy.py: replace hardcoded monorepo rulesets path with
  _find_rulesets_dir() that works when installed via pip

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bb-connor
Copy link
Collaborator Author

@codex

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 92eb7cd587

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

bb-connor and others added 2 commits February 13, 2026 19:55
- daemon.rs: reverse requeue order so new events go first; truncation
  now sheds oldest failed events instead of newest arrivals
- approval.rs: evict oldest pending entry when queue is full and all
  entries are pending, preventing unbounded HashMap growth
- policy.py: replace silent `except Exception: pass` with warnings.warn
  so importlib.resources failures are visible

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- events.rs: use SSE `event:` field for daemon event type dispatch;
  hushd sends type via the SSE protocol field, not in the JSON data
  payload, so DaemonEvent deserialization was always failing silently.
  Added handle_sse_message that injects the type key from the SSE event
  field before deserializing. Includes tests for both daemon and audit
  event paths.
- secret_leak.rs: restructure merge_with to branch early on whether
  child has explicit patterns, eliminating the dead-code path where
  base effective patterns were computed then immediately discarded.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Base remove_patterns now exempt patterns the child explicitly added via
additional_patterns, preserving child intent to add detection coverage.
Also fixes cargo fmt formatting in the retain closure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- events.rs: replace unwrap_or_else(json!({})) with proper error
  propagation so malformed JSON in daemon SSE events returns an error
  instead of silently creating phantom DaemonEvent with all-None fields.
  Added test verifying malformed JSON is rejected.
- forbidden_path.rs: remove cfg!(windows) gate on Windows forbidden
  paths so the Rust API matches the YAML rulesets which include them
  unconditionally. On non-Windows these globs never match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bb-connor
Copy link
Collaborator Author

@codex

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a382671019

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

- api_server.rs: replace :id with {id} in all route paths for axum 0.8
  matchit syntax (routes were registering literal colon segments, 404ing)
- approval.rs: clamp ttl_secs to i64::MAX before cast to prevent
  negative duration on pathological input
- daemon.rs: wrap std::fs::create_dir_all and std::fs::write in
  spawn_blocking to avoid blocking the tokio runtime on policy sync
- session.rs: fix CreateSessionResponse and GetSessionResponse to match
  hushd's wrapped { session: SessionContext } envelope format; extract
  posture/budget from the session state map

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Align axum dep to 0.8 (matching workspace root/lockfile), add route param test
- Thread session_id into policy eval requests for posture tracking
- Document approval architecture split (gateway vs desktop agent)
- Return structured deny with reason "hushd_unavailable" on offline (fail-closed)
- Token-based preflight tool classification, fail-closed on unknown tools
- Harden ApprovalQueue: MAX_TTL_SECS cap, checked_add_signed, QueueFull error
- Add mTLS integration tests with rcgen (require_client_cert mode)
- Fix docs accuracy: approval response format, offline behavior, approval flow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Run `cargo vendor` to add rcgen + transitive deps (yasna, asn1-rs,
  der-parser, oid-registry, x509-parser) needed for mTLS tests
- Fix duplicate `"` in Azure Key Vault regex character class
  (secret-leak.ts)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bb-connor
Copy link
Collaborator Author

@codex

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 67b4ab4d08

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@bb-connor
Copy link
Collaborator Author

Addressed remaining hardening items:

  • clawdstrike: added shell_command guard for shell actions (dangerous patterns + best-effort forbidden-path extraction); enabled in built-in rulesets: default, ai-agent, strict, cicd.
  • openclaw: command_exec now enforces filesystem intent (forbidden paths + allowed_write_roots via redirection/path token extraction) and approval resource normalization is centralized (src/hooks/approval-utils.ts) with new tests.
  • agent: session lifecycle hardened (terminate existing session before replacement; background ensure-session retry with backoff) + unit tests with mock server.
  • agent: hushd error bodies are omitted by default and gated behind debug_include_daemon_error_body (settings + API) with tests.
  • claude-code hook: unknown tools are no longer auto-allowed; they are sent as mcp_tool with args so policy can decide.
  • bundled agent default policy updated to valid canonical schema (v1.2.0) and extends clawdstrike:ai-agent-posture.

Local validation:

  • cargo fmt --all
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test --workspace
  • npm --prefix packages/adapters/clawdstrike-openclaw test (requires a working node in PATH)
  • npm --prefix packages/adapters/clawdstrike-openclaw run typecheck

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.


let body = format!("Posture changed from {} to {}", old_posture, new_posture);
notifications::show_notification(&app_for_sse, "Posture Transition", &body);
}
Copy link

Choose a reason for hiding this comment

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

Posture transition event doesn't update session manager state

Medium Severity

When a SessionPostureTransition SSE event arrives, the handler builds a custom summary string with the new posture and updates the tray, but never writes the new posture back into the SessionManager's SessionState.posture field. Since SessionManager has no public method to update posture, the internal state remains stale. The /api/v1/agent/health endpoint (which calls session_manager.state().await) will return the old posture until the next heartbeat cycle — up to 30 seconds later — creating an inconsistency between the tray display and the health API.

Additional Locations (1)

Fix in Cursor Fix in Web

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.

1 participant