Skip to content

[Feature]: Expose the security enforcement layer as a pluggable provider interface #7142

@singlerider

Description

@singlerider

Tracking: Pluggable security provider interface

Target v0.9.0 · Type: Security / Architecture · Tracking Issue

Tracking Issue — umbrella for exposing the in-tree security enforcement,
reporting, and incident-response surface behind a single trait. Each
Definition-of-Done item is required for delivery and lands as its own scoped
PR. Discussion happens here.

Related work (v0.9.0 security wave):


Revision History

Rev Date Summary
1 2026-06-03 Initial draft — identifies the in-tree surface to expose, the trait seam, and the DoD checklist
2 2026-06-03 Added Related-work cross-references to #7141 and #6996 (v0.9.0 security wave); tied to the existing Sandbox trait

Table of Contents

  1. Motivation
  2. Goals
  3. Non-Goals
  4. Surface To Expose
  5. Design Overview
  6. Invariants
  7. Risk and Rollback
  8. Definition of Done (all required)
  9. Delivery Sequencing

1. Motivation

ZeroClaw's security enforcement, audit reporting, and incident-response logic
are implemented as concrete in-tree types with no pluggable boundary. The
decision logic that gates command and tool execution, the audit trail that
records those executions, and the incident-response engine are each callable
only as fixed implementations. There is no trait a third party (or an alternate
in-tree implementation) can satisfy to participate in these decisions or receive
these events.

This is inconsistent with the rest of the codebase, where capabilities are
trait-driven and selected from a registry (channels, providers, storage, and the
OS-isolation Sandbox trait already follow this pattern). The security layer is
the notable exception: it is hardcoded at every call site.

Exposing this surface behind a single, stable interface lets the runtime select
an implementation, lets alternate implementations be supplied without forking,
and gives the enforcement, reporting, and incident-response responsibilities one
cohesive seam instead of being scattered across unrelated call sites.

2. Goals

  • Define one trait that abstracts the security enforcement layer, mirroring the
    existing Sandbox trait precedent (crates/zeroclaw-runtime/src/security/traits.rs).
  • Route the existing per-call-site enforcement entry points through the trait
    rather than calling the concrete type directly.
  • Make the existing audit/reporting sink and incident-response engine reachable
    through the same interface, so an implementation can decide, report, and
    respond as one unit.
  • Keep the current behavior as the default implementation — zero behavior change
    for existing deployments when no alternate is configured.
  • Make the implementation selectable per agent (and, where applicable, per
    authenticated principal — see [Feature]: OIDC Authentication Provider support for the RPC/WSS transport #7141).
  • Registry is canonical in the backend crate; surfaces enumerate it via RPC (no
    hardcoded implementation lists in any surface).

3. Non-Goals

  • Changing what the default implementation decides, records, or responds with.
    This issue is about exposing the existing surface as an interface, not about
    altering its behavior.
  • Replacing the OS-isolation Sandbox trait (it already exists; this composes
    with it).
  • Network protocol or transport work (covered separately).

4. Surface To Expose

The following in-tree code is currently concrete and must become reachable
through the interface. This section names the source, not the semantics.

Enforcement entry points (the decision seam):

  • SecurityPolicy::validate_command_execution — called from
    tools/shell.rs, tools/cron_run.rs, tools/cron_add.rs,
    tools/skill_tool.rs, cron/mod.rs.
  • SecurityPolicy::enforce_tool_operation — called from
    tools/model_switch.rs, tools/delegate.rs, tools/verifiable_intent.rs.
  • SecurityPolicy::is_command_allowed, command_risk_level,
    is_path_allowed / is_resolved_path_{readable,allowed}, is_tool_allowed
    — the decision primitives these entry points compose.
  • SecurityPolicy::ensure_no_escalation_beyond — the inheritance invariant that
    must continue to hold across the interface boundary.

Reporting sink (the report seam):

  • security/audit.rsAuditLogger, AuditEvent, AuditEventType. Already
    the tamper-evident (hash-chained) record of command/tool executions and
    security events. Currently invoked separately from the decision path.

Incident-response engine (the respond seam):

  • security/playbook.rs — the incident-response engine (Playbook,
    PlaybookStep, builtin_playbooks).
  • security/vulnerability.rs, security/leak_detector.rs,
    security/prompt_guard.rs — existing finding/detection sources that feed
    incidents.

Construction pipeline (the source of the materialized object):

  • SecurityPolicy::from_profiles / for_agent — builds the per-agent
    enforced object from its inputs. The interface must be able to supply or wrap
    what this produces.

5. Design Overview

Introduce a trait in crates/zeroclaw-runtime/src/security/ alongside the
existing Sandbox trait. It carries three responsibilities so an implementation
participates as one cohesive unit:

  1. Decide — evaluate a command/tool invocation and return an
    allow / deny / require-approval decision plus the risk classification. The
    per-call-site enforcement entry points (§4) call the trait instead of the
    concrete type.
  2. Report — receive a structured record of every evaluated invocation
    (reusing AuditEvent as the canonical record shape) and forward it to its
    sink. The default forwards to the existing hash-chained AuditLogger.
  3. Respond — on a qualifying signal, raise an incident through the existing
    playbook engine (or an alternate).

The default implementation wraps the current concrete types verbatim
(SecurityPolicy + AuditLogger + playbook.rs), so behavior is unchanged
when nothing else is configured. The trait + registry follow the Sandbox
precedent: name(), availability, and selection at construction time.

Selection is per agent (resolved in for_agent) and, where an authenticated
principal is in scope (#7141), per principal.

6. Invariants

These must hold regardless of which implementation is active:

  • Deny-by-default. An unknown or unresolved decision is a denial.
  • No escalation. ensure_no_escalation_beyond semantics survive the
    interface boundary; an implementation can only narrow, never widen, an
    inherited context (SubAgent spawn).
  • Per-agent context. There is no global security context; resolution stays
    per agent.
  • The default decision primitives remain authoritative unless an
    implementation explicitly owns the decision
    , and even then the escalation
    and deny-by-default invariants are enforced by the runtime, not the
    implementation. An alternate implementation may further restrict but cannot
    loosen the default guarantees.

7. Risk and Rollback

  • The decision primitives (command lexer, path confinement) are security-critical
    and heavily tested. Routing them through a trait must not bypass or weaken
    them; the default implementation calls the exact same code paths.
  • Rollback: the interface is opt-in; with no alternate configured the default
    implementation is byte-for-byte the current behavior. A misconfiguration fails
    closed (deny / refuse), never open.
  • Breaking change: No — additive interface; default preserves current
    behavior; existing configs unaffected.

8. Definition of Done (all required)

Every box is a hard gate. None optional. Each maps to one or more scoped PRs.

Interface

  • Trait defined in security/ mirroring the Sandbox precedent
    (name(), availability, selection); registry canonical in the backend crate.
  • Trait spans decide / report / respond as one cohesive interface.
  • Surfaces enumerate available implementations via RPC (no hardcoded lists).

Decision seam

  • validate_command_execution call sites (shell, cron_run, cron_add,
    skill_tool, cron/mod) route through the trait.
  • enforce_tool_operation call sites (model_switch, delegate,
    verifiable_intent) route through the trait.
  • Decision primitives (is_command_allowed, command_risk_level,
    path checks, is_tool_allowed) reachable through the interface.
  • ensure_no_escalation_beyond enforced at the boundary, not inside any
    single implementation.

Report seam

  • Every evaluated invocation produces a structured record (reusing
    AuditEvent) delivered through the interface to its sink.
  • Default implementation forwards to the existing hash-chained AuditLogger
    with no change to the on-disk trail format.

Respond seam

  • Qualifying signals raise an incident through the existing playbook engine
    via the interface.
  • Existing finding sources (vulnerability, leak_detector,
    prompt_guard) feed incidents through the interface.

Construction + selection

Default-preserves-behavior

  • With no alternate configured, decisions, records, and incident behavior
    are identical to today (characterization tests pin this).

Documentation — REQUIRED for delivery (FND-002)

  • Conceptual page: the interface, its three responsibilities, the default
    implementation, and how an alternate is selected.
  • Working, copy-pasteable example of a minimal alternate implementation
    (decide + report + respond) and how to register and select it.
  • Example config selecting an implementation per agent (and per
    principal where applicable).
  • Reference for the trait surface and the record shape forwarded to sinks.
  • Invariants documented (deny-by-default, no-escalation, per-agent).

Validation

  • Characterization tests: default implementation matches current behavior at
    every routed call site.
  • Invariant tests: escalation rejected and deny-by-default enforced
    regardless of implementation.
  • Report tests: every evaluated invocation yields a record; hash-chain
    integrity preserved for the default sink.
  • Incident tests: qualifying signals trigger the response path through the
    interface.
  • cargo fmt --all -- --check, cargo clippy --all-targets -- -D warnings,
    cargo test green; docs lint + link integrity for new pages.

9. Delivery Sequencing

Each item is a scoped PR; order minimizes risk.

  1. Define the trait + registry + default implementation wrapping the current
    concrete types (no call-site changes yet; pure addition).
  2. Route the decision-seam call sites through the trait (characterization tests
    pin parity).
  3. Fold the report seam into the interface (default → existing AuditLogger).
  4. Fold the respond seam into the interface (default → existing playbook engine).
  5. Construction + per-agent / per-principal selection + config schema.
  6. Documentation with a worked alternate-implementation example — required
    before close.

Data hygiene

  • No personal/sensitive data in examples, payloads, or logs.
  • Neutral, project-scoped placeholders only.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestsecurityAuto scope: src/security/** changed.type:rfcArchitectural proposal — RFC discussion issue

Type

No type
No fields configured for issues without a type.

Projects

Status
Backlog

Relationships

None yet

Development

No branches or pull requests

Issue actions