Security
Three things about humanize are load-bearing and surprising. Read them before you point one at a repository you care about.
Every agent runs with permission prompts disabled
humanize drives coding agents unattended, as flowbench does. No setting turns the prompts back on. An agent under a flow edits files, runs commands and makes commits without asking.
/afk governs whether an agent may stop and ask you a question. It does not govern whether the agent may act. Nothing does.
A flow can narrow what its agents may do at all — four rungs, declared beside the agent it drives:
class Agents(NamedTuple):
reviewer: Annotated[Agent, AgentDefaults(permission="read-only")]A place that says nothing declares bypass, the loosest rung, which settles nothing: an agent already on a tighter rung stays on it, and a flow you call runs at your rung or tighter rather than at its own. Reach for read-only where a flow has a second agent look at a change without being able to touch it. It is the flow's to say and not the line's: what an agent may do is a thing about the work, so the flow you run is what decides what its agents may do to your workspace. Read one before you run it.
Drive a flow only in a workspace you are willing to have rewritten. That includes a container of the agent's own, which confines the agent to that image but mounts your workspace into it.
A flow is Python, and reading one means running it
Choosing a flow is running it: humanize runs the flow's __init__.py to find the @flow in it, whether the flow was chosen at /flow or named on an hmz exec -f line. Listing what a flowverse holds imports every file in its flows/.
So adding a flowverse trusts that git repository with this machine, exactly as installing a package does. Add the ones you would clone and run.
official is always there — chat ships with the package, and the rest is humanfia/flowverse. humanize does not fetch that repository until something wants what is in it.
An hmz internal anchor port is equivalent to a shell on that machine
Remote execution has three transports. Two of them need no open port at all:
| Transport | What it is |
|---|---|
ssh://host | bootstrapped over your own ssh. Nothing listens. |
docker://container | over docker exec. Nothing listens. |
tcp://host:port | an hmz internal anchor serve listening there. |
For the third, --export bounds which files a request may name. It does not confine the commands that request can run. Anyone who can reach the port can run anything on that machine as the user serving it.
- Give
--tokena real secret. - humanize refuses outright to listen on anything but loopback without a token.
- Prefer
ssh://ordocker://.
hmz internal anchor serve --listen 0.0.0.0:7777 --export /srv/project --token "$SECRET"What humanize does not hold
- No API key. humanize drives the CLI you already logged in. The credential goes from that CLI to its own provider.
- No transcript of its own. The backends write their own logs. An epic records only which sessions belonged to which agent.
- No values from a provider.
/providersdraws every account under the CLI it is for, with the way it was made by and the names of the variables it sets. It never draws what those variables are. A secret you type at the prompt appears as bullets and never shows again.
Provider credentials are copies of the CLI's own credential files. humanize keeps them at 0600 in a directory at 0700 under ~/.humanize/providers/. A turn under a provider runs with the other accounts' variables unset. So an ANTHROPIC_API_KEY left in a shell profile cannot silently outrank the account the agent was told to run as.
While a turn is running, the credential it reads is also held in memory — a directory of that turn's own under /dev/shm, at 0700, holding files at 0600, under a name that cannot be guessed. /dev/shm is shared between everyone on the machine, so the directory is made rather than opened: a name somebody else got in first with is refused and another taken, and nobody else can list it or read what is in it. It is unlinked when the turn ends — by the turn itself where it exits, and by whoever ended it where it was killed, since a killed process runs no teardown of its own. Anything a killed driver leaves behind is swept away by the next turn on that machine, and /dev/shm is empty again after a reboot either way.
Reporting something
Open an issue at humanfia/humanize. If it is a vulnerability rather than a bug, say so in the title. Leave the details out of the public thread.