Skip to content

v0.1.1

Latest

Choose a tag to compare

@github-actions github-actions released this 09 Jun 07:55
· 18 commits to main since this release

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)
    Introduce firma::output with [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}, and policy/validate with 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=value with no timestamp,
    target, or line number, and drops FmtSpan::CLOSE events. --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)
      A firma config-scaffolded firma.toml only started via the firma run
      autostart path. Standalone firma sidecar --config <path> hit three
      startup blockers; fix them sidecar-side so the scaffold drift surface
      stays small.

    • Empty https_mitm.intercept_hosts no 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_path when
      [sidecar.preflight].authority_pub_key_path is 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 the generic profile 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 in default_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.

[FIR-213]

  • firma run on macOS: proxy bridge not started, all outbound HTTP denied with empty session_id (#133)