Fenzi

Team

Team plan

One policy on every developer's machine. One log of what agents were allowed and refused. A person approving more access, from one place.

$20 per developer a month, billed yearly$25 billed monthly

Set up a team

Team is opened by invitation; request access for a new team. We open your team and email its first admin a link. The link sets a password and lands on the console at https://fenzi.dev/console.

Team costs $20 per developer a month, billed yearly, or $25 billed monthly, for two developers or more. A developer is a member whose machine runs agents; a reviewer or auditor who enrolls no machine is not counted. Machines, sandboxes and sandbox hours are never counted.

  1. Publish a policy. Start from developer-default and add the hosts your projects need. Check it, read what the prover says, then publish. See Publish a policy.

  2. Invite people from Members, each with a role. Each one gets an email that names the role, sets a password, and installs the CLI.

  3. Each developer signs their machine in. The browser shows which machine is asking before they approve it.

fenzi login
Example output
open      https://fenzi.dev/device
code      KQXT-MRWD
signed in dana@acme.dev, team acme
machine   dana-mbp, enrolled
policy    acme r1, verified and cached

From then on, fenzi run on that machine uses the team's policy. The project file is not used to widen it. See Enroll machines, and for many laptops at once, Rolling out to many machines.

Each signed-in machine sends its decisions and requests to the console and applies the reviews made there, every five seconds while fenzi run, the Mac app, or fenzi forward is running. An approval in the console reaches the agent in about fifteen seconds. See Forwarding decisions, and to keep forwarding between runs, Run it as a service.

fenzi sync is one pass: it fetches the team's newest policy revision, loads any host change onto this machine's running sandboxes, and sends the decisions waiting to go. A change to paths or processes still needs a new sandbox.

If our cloud is unreachable, fenzi status still shows the last signed revision, and a sandbox that already started keeps enforcing the policy it was given.

Roles

Every member has one role. It decides what they can do in the console and from the CLI. The person we open the team for is its first admin; an invite picks a role, and developer is the default.

RoleCanCannot
AdminEverything: invite and remove members, change roles, publish policy, set the team boundary, approve or deny requests, sign out any machine, sign in their own machines, read everything, export decisions.Change their own role, or remove the last admin.
ReviewerApprove or deny agent requests, read everything, export decisions, sign in and sign out their own machines.Publish policy, change the boundary, manage members, or sign out someone else's machine.
DeveloperSign in their own machines with fenzi login, sign them out, read the console (policy, revisions, requests, decisions, drift, machines), and export decisions.Review requests, publish policy, or manage members.
AuditorRead the console and export decisions.Sign in a machine, review, publish, or manage members. The page that approves a login code refuses an auditor.

An admin changes a role on Members. A team always keeps one admin who can sign in. Making someone an auditor signs out the machines they enrolled; the sandboxes already running there keep the policy they hold. fenzi status shows your role on that machine. Your account page in the console shows what your role allows and changes your password.

An admin or reviewer can approve a request their own agent filed. The console records who approved every request. There is no separation-of-duties rule yet. See Who approves where.

Sign-in security

Members sign in to the console with an email and a password. Multi-factor sign-in and single sign-on are not available on Team yet. Single sign-on is part of the License plan, which is not built yet. A machine signs in once with fenzi login, approved by its member in the browser, and then uses its own token, which the control plane stores only as a hash.

Publish a policy

Edit the policy in the console. On publish, the control plane compiles it for every agent the policy names, runs the same prover check as the CLI, against the team boundary when there is one, and refuses to publish anything that is not within_boundary. Each published version gets a revision number and is signed. The signed bundle is the policy document and that revision.

A machine checks the signature before it uses a bundle, and it ignores a bundle that fails or that is older than the revision it already has. Before a sandbox starts, the CLI compiles that document for the agent and runs the prover again. A check that fails means the sandbox does not start. The details are in Publish and revisions.

You changedRunning sandboxesThe console says
HostsUpdated live, on the machine's next sync or forward pass."Hosts only: running sandboxes load it on their next forwarder pass."
Paths, workspace access, agents, or approvalsKeep running on the old revision. The next run creates a new sandbox."Running sandboxes keep the old policy, and each next fenzi run creates a new sandbox."

Team boundary

A team boundary is the most any policy of the team may allow. Only an admin saves it, from the console's Policy page, and only an admin can change or remove it. Every member can read it.

It is written like a policy file, with agents, workspace, paths, and hosts, and it must name the agents it covers. Fenzi compiles it for each agent the way it compiles a policy, adds that agent's model endpoints, and gives the result to the prover as the boundary.

A team boundary
version: 1
name: acme-boundary
agents: [claude, codex]
workspace: read-write
hosts:
  - host: api.github.com
    access: read-only
  - host: registry.npmjs.org
    access: read-only
  - host: api.stripe.com
    access: read-only

When an admin saves it

  • Saving checks that it compiles for every agent it names. If a policy is already published, that policy is proved against the new boundary first. If it is outside, nothing is saved: publish a narrower policy, or widen the boundary.
  • Once saved, the policy in force is signed again as the next revision, with the boundary inside the signature. Machines take it on their next sync or run. The console lists who saved each boundary and when.

At publish

Every policy published after that, from the console or with fenzi policy publish, is proved against it. A policy outside it is refused with the prover's example. A policy for an agent the boundary does not name is refused as well.

On each machine

  • A signed-in machine proves against the boundary in its signed revision: before fenzi run starts a sandbox, again on the policy the sandbox holds, and before fenzi sync loads a host change.
  • fenzi policy check uses it too, also for a file you name, and says which boundary it proved against. --boundary with a file uses that file instead.
  • The console refuses to approve a request whose host and port are not in the team boundary. fenzi approve, and a review from the console that a machine applies, stop for the same reason. Methods and paths are not compared at that step. See Approving on Team.

Without a boundary

Each policy is proved against the boundary derived from itself, as on Free. A pass then proves the sandbox gets what the policy says, not that the policy is safe. See The check before start.

Older CLIs

A CLI from before team boundaries refuses a revision that carries one. Update every machine before an admin saves the first boundary.

Machines

On every forwarder pass, each enrolled machine reports on itself; the fields are in What our cloud receives. Machines call us. We never call them. Signing a machine out in the console stops its token at once; its sandboxes keep enforcing the policy they hold. Anyone can sign out their own machine; an admin can sign out any machine on the team.

Decisions

The decision log shows every allow and deny the enrolled machines forwarded, with a gap row wherever part of a stream was lost. Filter by machine, sandbox, destination, or action. Records are kept for 90 days by default.

Decision log, example
time      machine    sandbox      action  destination              process
10:42:07  dana-mbp   bl-web-4f2a  deny    paste.example.net:443    curl
10:42:31  dana-mbp   bl-web-4f2a  allow   GET pypi.org/simple/...  python3.12
10:44:02  li-x1      bl-api-90cc  deny    POST api.github.com/...  gh

A gap in that log means the machine lost part of the stream. The usual cause is a gateway restart, or a buffer that dropped decisions before the machine could send them. The row names the sandbox and the span. Those decisions are not recovered from another file.

Approvals work from the console the same way they work in the terminal. See Approving on Team.

Exposure search

When an advisory names a host, a URL, or a package path, paste it into Exposure in the console. For each one it shows which machines, sandboxes, and programs reached it in the last 90 days, allowed or denied, and when. It also says whether the policy in force still lists it, whether a reviewer approved it for a sandbox, or whether an agent's key profile opens it, as it does for model endpoints. Nothing you paste is stored.

If the policy lists the host, an admin can draft the policy without it. The draft opens on the Policy page; a person checks it with the prover and publishes it. Removing a host loads on running sandboxes at their next forwarder pass.

A match is the record of a connection, not proof of a compromise. The search cannot see records older than 90 days, query strings, the path of a request on a host the policy does not inspect, or machines whose forwarder was not running.

What our cloud receives

This is the complete list. Every other page links here instead of listing fields.

WhatFieldsSent
Decision recordTime, sandbox, machine, policy revision, kind of event (network or HTTP activity), allowed or denied, destination (host and port, or method and path with the query string removed), program, the rule that matched, the sandbox's reason for a deny, and on an approval the sandbox logged, the id of the request it approved.Each forwarder pass
GapThe sandbox, the time of the last decision we have, and the time the stream resumed. No destination and no action.When decisions were lost
Pending requestSandbox, machine, destination, program, the agent's reason and any notes the runtime attached (its own check of the proposed rule), the access level it needs with the method and path, whether the agent asked or the sandbox drafted it, the rule name the runtime gave it, and the policy revision the machine held.Each pass, the full pending list for each sandbox
Machine reportMachine name (its host name, sent at sign-in), OpenShell version, sandbox driver, the policy revision it holds, whether its gateway approves requests automatically, the sandboxes Fenzi started with their agent, revision, and whether they run, notes the CLI writes for the console, and for each sandbox the result of the policy compare with the names of the sections that differ, not their contents.Each pass
Member accountEmail, name, and role. The password is stored only as a hash.When a member is invited
Team policyThe policy documents and the team boundary your admins publish or save, with who and when.On publish

We never receive your source code, file contents, request bodies or headers, query strings, terminal output, the agent's conversation, model keys, or Git tokens. The model vendor still receives what the agent sends it: the files it read, command output, and your instructions, as it does without Fenzi.

Drift

A developer owns their laptop. They can change the runtime directly or approve a request on the machine. On a laptop the developer administers, nothing stops them running an agent outside Fenzi, and Team does not see that run. For sandboxes Fenzi started, Team reports what the machine sends: a running sandbox started without the team policy, a gateway that approves on its own, an approval the sandbox logged with no console review, and a machine that stopped reporting. See Drift.

If you need enforcement the machine's owner cannot change, run the gateway where developers do not administer it. That is the License plan, on your cluster, which is planned and not built yet. Team and License are opened on request until there is a public price.

When our cloud is unreachable

Sandboxes keep enforcing the last policy they loaded, and the CLI keeps using the last signed policy it verified. Decision records wait on the machine, requests stay pending, and the blocked call stays blocked. Every case is in When something is down.

Not in Team

The License plan is planned to add single sign-on, a SIEM export, and the control plane in your VPC. It is not built yet. Today, a JSONL export of the same decision records is what you point your own tools at.