firma-sidecar (root module)
Module: firma_sidecar
Section titled “Module: firma_sidecar”Contents
Section titled “Contents”Modules
audit- Audit event emitter.authority_client- Authority stream client facade.authority_credentials- Sidecar credentials presented to Authority RPCs.body_encoding-Content-Encodingaware body decode/encode for secret placeholdercomposio- Decodes supported Composio execution protocols into logical actions.config- Sidecar configuration types.connector- Connector layer for the sidecar dispatch hot path.credentialenforcement- Two-phase enforcement engine.handler- Request handler.health- Lightweight HTTP health-check server for readiness probes.interceptor- Interceptor.local_exec- Local-exec governance endpoint.normalizer- Intent Normalizer / Envelope Builder.pipeline- Enforcement pipeline orchestrator.run_audit-firma runaudit channel ingest.secret_rewrite- Content-Type-driven rewriter for the secret redact path.secret_store- In-memory placeholder ↔ secret dictionary for the Sidecar MITM pipeline.startup- Startup builders that translate the validated
Module: audit
Section titled “Module: audit”Audit event emitter.
Produces a signed [ExecutionEvent] for every enforcement decision
(ALLOW, DENY, ABORT). No call enters or exits the sidecar without a
corresponding audit record.
The emitter is invoked by the RequestHandler
after every handled call, regardless of outcome. Each event carries an
ECDSA signature over all fields, making the audit log tamper-evident and
independently verifiable.
Output sinks
Section titled “Output sinks”Four output modes are supported, selectable via the [sidecar.audit]
configuration section:
| Sink | Description |
|---|---|
stdout | Structured JSON lines (default for containers). |
file | Append-only file at a configured path. |
grpc | Client-streaming RPC to a downstream audit service. |
wal | Write-ahead log: buffers events locally when gRPC is |
| unavailable and replays on reconnect. Bounded by size cap; | |
| oldest events are evicted when the cap is exceeded. |
Signing
Section titled “Signing”Every event is signed with an ECDSA private key loaded at startup from a file path or environment variable (mutually exclusive). The signature covers all event fields preceding it, enabling downstream consumers to verify event integrity without trusting the transport.
Module: authority_client
Section titled “Module: authority_client”Authority stream client facade.
Spawns the background streams that keep policy bundles and revocations current without putting the Authority on the request hot path.
Module: authority_credentials
Section titled “Module: authority_credentials”Sidecar credentials presented to Authority RPCs.
Module: body_encoding
Section titled “Module: body_encoding”Content-Encoding aware body decode/encode for secret placeholder
rehydration and secret masking.
Request and response bodies inspected for secrets (placeholder tokens on
the way out, leaked/echoed values on the way back) must be scanned as
plaintext, but may arrive or need to be forwarded compressed. This module
decodes a supported Content-Encoding before scanning and re-encodes
afterward so the forwarded body’s declared encoding still matches its
bytes.
Decode/encode run on tokio::task::spawn_blocking rather than inline, so
the CPU-bound (de)compression work never blocks the async task’s worker
thread — mirroring this crate’s existing convention for offloading
synchronous work from an async context (see Cedar policy evaluation in
enforcement::constraint_enforcement). Bodies here are already fully
buffered Vec<u8> by the time this module sees them, so an async
(de)compression adapter over an in-memory buffer would still run
synchronously during poll() without a real streaming source to await on
— spawn_blocking is what actually avoids blocking a worker thread.
Module: composio
Section titled “Module: composio”Decodes supported Composio execution protocols into logical actions.
The decoder runs before generic HTTP normalization. It recognizes the two exact Composio hosts, rejects ambiguous execution shapes, and never retains tool arguments in logical envelopes or diagnostics.
Module: config
Section titled “Module: config”Sidecar configuration types.
All configuration for the sidecar binary is defined here. The
behavior-free shape lives in [firma_config_schema::sidecar]; the
validated types here are built from it via TryFrom, so an invalid
configuration can never be constructed.
The top-level [SidecarConfig] groups the enforcement-specific
[EnforcementConfig] after the behavior-free schema has deserialized its
three direct tables ([sidecar.mapping], [sidecar.capability_validation],
and [sidecar.constraint_enforcement]).
Validation runs eagerly when the schema is converted at startup, so misconfigurations surface before the first request arrives.
Module: connector
Section titled “Module: connector”Connector layer for the sidecar dispatch hot path.
Combines three concerns:
- a [
ConnectorRegistry] that maps the target host to aConnectorimplementation; - a [
provider] submodule containing the in-tree connector implementations (currently just the generic HTTP one); - (future) adapters for out-of-tree connectors plugged in as host overrides.
The trait and its associated types live in firma-core so that
external crates can implement connectors without depending on the
sidecar binary. The registry and the concrete implementations are
sidecar-private: they are consumed only by the
RequestHandler.
Module: credential
Section titled “Module: credential”Module: enforcement
Section titled “Module: enforcement”Two-phase enforcement engine.
Contains both enforcement stages that evaluate an already-normalized
ExecutionEnvelope. Intent normalization and interception live in
sibling modules ([crate::normalizer], [crate::interceptor]); the
[crate::pipeline] module orchestrates the full flow.
Stage 1 and Stage 2 are two sequential enforcement phases inside the same Sidecar process, not two separate components. The Authority is never contacted on the hot path — all evaluation is local.
Module layout
Section titled “Module layout”- [
capability_validation] — Stage 1: capability token selection and validation (parse, signature verify, expiry, revocation). - [
constraint_enforcement] — Stage 2: Constraint Enforcement Engine (CEE) — Cedar policy evaluation, scope and threshold checks. - [
capability_map] — Pre-provisioned capability tokens indexed by action class for fast selection. - [
decision] — Unified ALLOW/DENY result type for every enforcement call. - [
error] — Internal error types; every variant maps to a DENY decision (fail-closed boundary). - [
registry] — Canonical Action Class Registry v0.1 (52 action classes). - [
revocation] — Bloom filter + LRU revocation cache. - [
session] — Per-session runtime state store (LRU + persistent backend).
Module: handler
Section titled “Module: handler”Request handler.
Owns the post-enforcement call path shared by all interceptors:
enforcement, dispatch for allowed traffic, denial translation, and audit
payload emission. When a secret gateway is configured (see
[RequestHandler::with_gateway_client]), the same path also rehydrates
outbound secret placeholders and masks inbound secret values; when HTTP
secret providers are configured (see
[RequestHandler::with_http_secret_providers]), it additionally
intercepts and mints placeholders for matching HTTP-vault responses.
Module: health
Section titled “Module: health”Lightweight HTTP health-check server for readiness probes.
Exposes a single GET /healthz endpoint that returns 200 OK after the
sidecar has emitted its ready signal. All other paths return
404 Not Found.
Module: interceptor
Section titled “Module: interceptor”Interceptor.
Captures outbound agent traffic before it reaches the external system. Three modes: HTTP proxy (port 8080 default), gRPC hook (programmatic interceptor within the agent process), and Unix socket (avoids port binding in containers). eBPF capture is on the roadmap.
Regardless of interception mode, the raw intercepted request is converted
into a RawRequest and passed to the
[RequestHandler] for enforcement and
dispatch. If the intercepted request cannot be parsed into a valid
RawRequest, the interceptor returns a structured DENY with reason
MALFORMED_REQUEST (fail-closed).
Module: local_exec
Section titled “Module: local_exec”Local-exec governance endpoint.
This module implements the Sidecar-owned UDS endpoint that firma-run
contacts for pre-execution governance decisions on local tool invocations.
It is the authoritative implementation of the “mediator” role described in the canonical docs:
docs/architecture/linux-local-command-enforcement.mddocs/architecture/command-governance-local-exec-contract.md
Principle: one control plane and one audit surface.
The mock Python scripts in examples/ exercise the wire protocol but are
not production components.
Sub-modules
Section titled “Sub-modules”- [
token_store] — Approval token state machine (Pending → Approved → Consumed / Expired / Revoked). Enforces single-use, short-lived, context-bound tokens; operator must explicitly approve before a token can be consumed. - [
handler] — Decision logic. Processes one [handler::LocalExecRequest] and returns a [handler::LocalExecResponse], and processes management commands ([handler::LocalExecManagementRequest]) viadecide_management. - [
endpoint] — Async UDS listener. Binds the socket, accepts connections, dispatches to the handler (governance or management), and manages the pruning task lifecycle.
Module: normalizer
Section titled “Module: normalizer”Intent Normalizer / Envelope Builder.
Runs in the Sidecar hot path immediately after interception and before
token validation. Deterministically maps the raw intercepted event into a
canonical [NormalizedEnvelope] with a normalized intent.action_class.
This step performs deterministic rule-based canonicalization only — no language model, SLM, probabilistic classifier, or similarity-based inference is permitted on the hot path. It makes no policy decisions.
Intent sub-fields produced: action_class (canonical semantic type),
resource (normalized target resource identifier), params
(action-specific parameters), raw_transport (original transport form —
observational, not used by policy), raw_action_ref (original tool name /
route / method — observational only).
Failure behaviour: if classification fails or yields an ambiguous action
class for a protected operation, the normalizer returns
DENY: UNCLASSIFIED_INTENT and no Connector dispatch occurs (fail-closed).
Conforms to the FEP [I-N1] enforcement invariant.
Module: pipeline
Section titled “Module: pipeline”Enforcement pipeline orchestrator.
Wires the [IntentNormalizer],
Stage 1 ([CapabilityValidator]), and Stage 2 ([ConstraintEnforcer])
into a single enforce() entry point.
The pipeline is the ONLY public entry point for enforcement; callers never interact with individual stages directly.
Every code path returns ALLOW, DENY, ABORT, or PASSTHROUGH. PASSTHROUGH means the request targets a non-protected host and should be forwarded without enforcement. The pipeline short-circuits on any DENY or PASSTHROUGH.
Target: < 3 ms p95 end-to-end overhead (interceptor + Stage 1 + Stage 2 + credential injection + audit emit, excluding connector and external system latency).
Module: run_audit
Section titled “Module: run_audit”firma run audit channel ingest.
firma run performs some enforcement outside the Sidecar’s request pipeline
— most notably the egress guard, which blocks an agent’s direct loopback
connections at the sandbox boundary before they could reach HTTP_PROXY. To
keep the audit trail complete, firma run reports each such fact to the
Sidecar over a local control socket as a [RunAuditMessage]. This module
turns those messages into signed audit events by feeding the same
[AuditPayload] channel the enforcement hot path uses, so an out-of-band
block surfaces in firma monitor exactly like a hot-path DENY.
The Sidecar owns the audit semantics: [AuditPayload]::from(&msg) maps
each [RunAuditEvent] variant to a fixed (action_class, decision, deny_reason, resource). The producer only states observations, so the
signature over the resulting event keeps meaning.
The listener is mode-independent (it sits beside the audit channel, not inside the interceptor) and Unix-only (the control socket is a UDS).
Module: secret_rewrite
Section titled “Module: secret_rewrite”Content-Type-driven rewriter for the secret redact path.
Two operations:
- Rehydrate (outbound): replace placeholder tokens in a request body with the real secret bytes, encoded to fit the surrounding content type.
- Mask (inbound): replace occurrences of known secret values in a response body with their placeholder tokens.
Both operations work on a flat byte buffer. The caller is responsible for
chunking, streaming overlap buffers, and supplying the match positions (from
a SecretStore-style scanner). This module only handles the rewrite math
and encoding.
Module: secret_store
Section titled “Module: secret_store”In-memory placeholder ↔ secret dictionary for the Sidecar MITM pipeline.
In the pull model the Sidecar queries the firma-run secret gateway
([firma_secret_provider::gateway::client::GatewayClient]) for each outbound
request and builds a SidecarSecretStore from the returned (placeholder, secret_bytes) pairs.
The store is scoped to one request-response cycle and discarded after
forwarding. firma-run remains the single source of truth; the Sidecar never
caches secrets persistently.
The design mirrors [firma_secret_provider::store::SecretStore] but lives in the
Sidecar crate to avoid a circular dependency.
The two directions use different matching strategies. Rehydration scans
for placeholder tokens, which are fixed-format and never need decoding, so
it uses an Aho-Corasick automaton. Masking scans for secret values,
which the upstream response may have re-encoded for its content type
(JSON escaping, percent-encoding, XML entities); a raw byte scan would
miss those re-echoes, so masking instead uses the content-type-aware
[crate::secret_rewrite::find_decoded_secret_spans] per secret, gated by
[MIN_MASKABLE_SECRET_LEN] to avoid corrupting unrelated response content
that coincidentally equals a short secret value.
Module: startup
Section titled “Module: startup”Startup builders that translate the validated
SidecarConfig into the runtime
subsystems the main.rs entry point wires together.
Each submodule owns one subsystem so that main.rs stays short
and readable:
- [
pipeline] — enforcement pipeline + stubs for Authority / Cedar. - [
connector] —ConnectorRegistrybuilt from the[sidecar.connector]section. - [
credential] —CredentialInjectorbuilt from the[sidecar.credentials]section. - [
audit] — audit event builder + sink spawn helpers. - [
interceptor] — interceptor mode selection and spawn. - [
authority] — Authority stream clients (WatchPolicyBundle/WatchRevocations).