0.1.1
Released on 2026-06-09
Added
- firma-run: gate non-structural backends on explicit opt-in
Non-structural (proxy-only) backends (macOS vz, WSL2 wsl2) now fail
closed by default. Users must pass --allow-non-structural, set
allow_non_structural = true in firma.toml, or set
FIRMA_RUN_ALLOW_NON_STRUCTURAL=1 to proceed.Runtime logs for non-structural backends emit a warn-level
"backend compatibility proof" with mode=proxy_only enforced=false
instead of the unqualified "backend network enforcement proof".Docs rewritten to make structural vs proxy-only the primary claim.
Changed
- move firma-run.toml into firma.toml- share agent profile
Documentation
- remove precompiled binary install path from quickstart
The precompiled binary tab pointed at an install.openfirma.ai script and
prebuilt downloads that do not exist yet (slated for v0.1.1). Drop the tab,
flatten the from-source instructions into the single install method, and
update the intro to reflect build-from-source.
Fixed
- doctor,monitor: reflect runtime reality (FIR-193)
Doctor verdicts contradicted what the runtime does. Make them match:
- sandbox: reuse firma-run's WSL/userns detection. On WSL, bwrap is no
longer [OK] (runtime refuses it) and wsl2 is [OK] (the selected
backend), not "not supported on linux"; native-Linux userns sysctl
off => bwrap [FAIL]. - reachability: cross-check live per-run sidecars via sidecar markers and
report them [OK]; absence of a long-lived daemon in a firma-run-only
workflow is a [WARN], never a hard [FAIL]. - optional dirs: capability seed and data dir report [OK] when absent
(expected on a healthy install), not [WARN].
Monitor showed nothing after a real decision. Two bugs:
- per-run sidecars synthesized from the minimal template defaulted audit
to stdout, which is discarded to the spawned sidecar's null stdout.
Default the audit sink to file at <state_dir>/audit.jsonl (the path
monitor tails) when the template configures none; explicit sinks win. - the tailer seeked to EOF even for one-shot reads, so
'firma monitor --no-follow' never showed existing records. Read from
start when one-shot or backfilling.
Docs updated for the new doctor verdicts, follow vs one-shot, and the
per-run default audit sink.- run: default FIRMA_RUN_BWRAP_RUNTIME_HOME to false (#124)-
make FIRMA_RUN_BWRAP_RUNTIME_HOME false by default
-
fix(bwrap): rebind $HOME writable when runtime_home_isolation=false
ro-bind on / makes $HOME read-only; without runtime_home_isolation the
agent writes to real $HOME (config, session state, plugins) and hits
EROFS. Add --bind $HOME $HOME before the tmpfs masks so writes succeed
while mask_home_paths overlays still take precedence.- refactor(bwrap): extract bind_host_home helper to fix clippy too_many_lines- seccomp: embed policy in binary, extract to XDG_RUNTIME_DIR (#127)
- fix(seccomp): embed policy in binary and extract to XDG_RUNTIME_DIR
- Replace CARGO_MANIFEST_DIR default path (broken for installed binaries)
with include_str! embedding; extracted to XDG_RUNTIME_DIR/firma/seccomp/
on first use so users can inspect and override the file - Use default_runtime_dir() for seccomp artifact dir instead of temp_dir()
(temp_dir() returns TMPDIR which bwrap's --tmpfs /tmp masks in nix-shell) - Add fs::write_private_file (0o600, atomic via OpenOptions::mode) to
firma-stack; use it alongside create_private_dir_all (0o700) when
extracting the policy to avoid a write+chmod race - Rename policies/ -> seccomp/ in source tree for clarity
- refactor(seccomp): rename default_ -> ensure_, add staleness note and artifact dir log
- Rename default_managed_policy_path -> ensure_managed_policy_path to
signal side-effecting behavior (dir creation + file write) - Add doc comment to MANAGED_SECCOMP_POLICY noting stale-file behavior:
existing file is not overwritten; delete to pick up a newer embedded version - Log seccomp artifact dir at debug level so operators can confirm which
path is in use without needing strace
-
fix(clippy): shorten first doc paragraph, add semicolon in match arm
-
override file
-
add test- stack: probe correct transport for per-run sidecar health
firma sidecar status reported a healthy per-run sidecar as unhealthy.
probe_entry unconditionally connected to <marker_dir>/sidecar.sock, but
an http_proxy per-run sidecar (the default profile) binds a loopback TCP
port and exposes no UDS, so the probe always failed.
Persist the interceptor listen endpoint in metadata.toml and probe the
recorded transport: a bounded TCP connect when listen parses as a
SocketAddr, else a UDS connect. Legacy markers without the field fall
back to sidecar.sock, so daemon and pre-existing per-run behavior are
unchanged.Closes FIR-195- cli: standardize warn/info/err formatting across CLI surfaces (FIR-211)
Introducefirma::outputwith[OK]/[INFO]/[WARN]/[ERR]prefixes,
TTY-gated owo-colors palette, and 80-col wrap with hanging indent. Wrap
is skipped when stderr/stdout is not a TTY so scripted captures keep
greppable single-line messages.Replace ad-hoc
println!/eprintln!warning, info and error calls in
main,services/{authority,config,doctor,monitor,sidecar,sidecar_status, supervise,token}, andpolicy/validatewith the new helpers. Structured
multi-line reports (key-gen, TLS bootstrap, scaffold) are left as-is so
their column alignment survives.Doctor's pretty render gains color on its
[OK]/[WARN]/[FAIL]tags
through the same owo-colors TTY gate.Swap
tracing-subscriber's default formatter for a compact one when
logs go to stderr: emits[LEVEL] message key=valuewith no timestamp,
target, or line number, and dropsFmtSpan::CLOSEevents.--log-file
keeps the full structured format for machine consumers.- monitor: surface network-layer DENYs with identity + audit pre-pipeline denials (FIR-208)
Network-layer DENYs were emitted but EnforcementDecision::Deny discarded
the validated CapabilityClaims, so deny audit events had empty
agent_id/token_id.firma monitor --agent <id>then dropped every deny
while keeping allows. Pre-pipeline deny paths (malformed request,
strict-MITM preflight fail-closed) emitted no audit event at all.-
Add DenyIdentity to EnforcementDecision::Deny; populate in enforce_inner
for Stage-2 and credential-injection denials so deny audit carries
agent/token/context_hash attribution. -
Add RequestHandler::emit_synthetic_deny; wire into all pre-pipeline
bypass paths (deny_malformed helper, strict-MITM preflight) so those
denials surface in monitor. -
Document below-network (seccomp/filesystem) events as an explicit
V0.1 limitation in the audit-log guide (not structurally feasible:
SECCOMP_RET_ERRNO, bwrap EROFS, no audit channel from firma-run).- sidecar: standalone firma sidecar --config startup resilience (FIR-214)
Afirma config-scaffolded firma.toml only started via thefirma run
autostart path. Standalonefirma sidecar --config <path>hit three
startup blockers; fix them sidecar-side so the scaffold drift surface
stays small. -
Empty
https_mitm.intercept_hostsno longer fatal. Add
HttpsMitmConfig::is_active()(enabled AND non-empty hosts); validate()
treats enabled-but-empty as disabled, and the HTTP interceptor skips
building the MITM runtime (and CA load) when inactive. -
Preflight falls back to
[sidecar.authority].public_key_pathwhen
[sidecar.preflight].authority_pub_key_pathis unset, via new
resolve_authority_pub_key_path; error now names both sources. -
listen_addr is already scaffolded; pinned by a standalone-startup
regression test that also calls SidecarConfig::validate().- config: platform-aware scaffold backend, WSL selects wsl2 (FIR-191)
firma config wrote backend = "bwrap" on WSL because WSL compiles as
target_os = "linux"; the compile-time cfg gating could not tell WSL from
native Linux, and WSL kernels refuse bwrap. The Linux branch of
default_run_backend now probes detect_wsl() at runtime and routes through
a pure backend_for_linux(WslKind): native Linux keeps bwrap, WSL selects
wsl2. macOS (vz) and Windows (wsl2) are unchanged.
Quickstart docs gain a per-platform default-backend table.- codex: detect and handle nested bwrap restrictions (#148)
-
fix codex inner bwrap error
-
improve tests
-
fix(codex): restrict danger-full-access to kernels that block nested bwrap
Use kernel sysctls to detect restricted unprivileged user namespaces
instead of applying danger-full-access unconditionally. Covers Ubuntu
(AppArmor unpriv_bwrap), Debian ≥12, and hardened kernels with
unprivileged_userns_clone=0. Other platforms keep workspace-write.-
fix mac test
-
fix(codex): use nested bwrap probe and align profile config
- Replace sysctl checks with a bwrap-inside-bwrap probe that catches
all restriction mechanisms (AppArmor profiles, setuid bwrap, etc.) - Move nested_userns_restricted() to backend/platform.rs alongside
userns_restricted() - Extract codex_executable_policy(restricted) to test both sandbox
branches independently - Fix ensure_run_profiles_section to write [run.profiles.]
instead of hardcoded [run.profiles.generic] - Deduplicate command-basename profile inference into profile_from_command()
- resolve_profile_name falls back to command name when config has no profile
- fix: enable managed seccomp for all profiles and fix profile round-trip
- default_managed_seccomp_policy now applies to all profiles on
Linux+bwrap, not just "generic" - scaffold_from_plan resolves profile from agent name directly when it
is a recognized AgentProfile, avoiding the lossy provider round-trip
that caused --profile generic to write profile="claude-code" in toml- install: restore one-line installer and bump to v0.1.1 (#144)
-
restore install instructions
-
bump crate- run: apply managed seccomp to all bwrap profiles (FIR-274)
The managed default seccomp policy (deny filesystem.delete,
credential.write) was gated to thegenericprofile only, so the two
real agent profiles — codex and claude-code — ran with no seccomp
filter at all. The deterministic syscall-level enforcement layer was
dead code for actual agents, breaking allow/deny parity between agents
on the same firma.toml.
Extract
managed_seccomp_applies(profile_id, backend)— true for any
recognized AgentProfile on the bwrap backend — and use it as the
profile/backend gate indefault_managed_seccomp_policy. generic,
codex, and claude-code now share the same managed baseline.This is the seccomp half of FIR-274; the bwrap nested-userns half was
fixed in #148. New unit tests cover the wiring cross-platform. The
end-to-end kernel-deny check (rmdir -> EPERM under bwrap) is a
Linux-only follow-up. - sandbox: reuse firma-run's WSL/userns detection. On WSL, bwrap is no
[FIR-213]
- firma run on macOS: proxy bridge not started, all outbound HTTP denied with empty session_id (#133)