> ## Documentation Index
> Fetch the complete documentation index at: https://docs.engini.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Flo safety

> What Flo will never do, which layers enforce it, where the enforcement stops, and the settings to paste yourself.

> [!NOTE]
>
> **Uses:** CLI · Claude Code · Codex

[Flo](https://docs.engini.ai/developers/ai-agents/flo.md) holds a shell and your API key, and can do anything your key can do. This page says what Flo is told never to do, what actually stops it, and where that protection ends. Read it before you rely on Flo, and keep it in mind when you choose the key you give it.

## What Flo will never do

Four tools are the only ones Flo refuses by name - the never-verbs:

* `engini_workflows_delete_workflow`
* `engini_workflows_run_workflow`
* `engini_workflows_enable_or_disable_workflow`
* `engini_workflows_rerun_execution`

In plain words: Flo never turns a workflow on or off, runs it, reruns an execution, or deletes a workflow. It also never creates or changes a connection and never handles your API key.

## The layers

Four layers stand between those four verbs and your account. Only the last one is a boundary, and it refuses nothing:

1. **Flo's instructions** name them as never. This is prose: a model follows it, nothing enforces it.
2. **Flo's guard hook - Claude Code only.** Installed with the plugin and live at once. See [The guard](#the-guard-claude-code-only).
3. **Your own settings** - the [settings below](#claude-code-settings). Copy-paste; a plugin cannot install them for you.
4. **The Engini server** refuses nothing here: your key is allowed to do all of it.

> [!WARNING]
>
> **Codex has no hook.** In Codex, the rules file you paste from [Codex rules file](#codex-rules-file) is the only layer besides Flo's instructions. If you skip it, nothing enforces the four verbs at all.

## The guard (Claude Code only)

The guard is a hook installed with the plugin. It needs Python 3.8 or later (`python3` or `python`) and refuses these:

* **The four never-verbs.** An `engini tools call` naming any of them is refused in any argument order and through quoting, case and `bash -c` wrappers. A tool name the guard cannot read (built from a shell variable or a substitution, such as `engini_workflows_$V`) is refused too, because it could be one of the four.
* **Commands that handle a raw key:** `engini connect`, `engini login`, `engini connections create|update|delete`, `engini connections auth-providers create|update`, and any `--api-key`.
* **Reads of your stored login and of key variables:** reading the CLI's stored login (see [Where the CLI stores your login](#where-the-cli-stores-your-login)), an `ENGINI_API_KEY` / `ENGINI_API_TOKEN` / `ENGINI_COMPANY_TOKEN` variable, or dumping the environment. A presence test such as `[ -n "$ENGINI_API_KEY" ]` is fine.
* **Edits to the hook itself or to your Claude Code or Codex settings.** This includes any command that names your `.claude` or `.codex` folder itself, a wildcard name or a `.`/`..` step inside one, or a `cd` into a folder under one - so even `ls ~/.claude` is refused. Read, Write, Edit, Grep and Glob paths are judged as given and again after resolving them against the session's folder and following symlinks.

An `engini tools call` with a heredoc is judged as code unless it is Flo's own one-call shape: the `<<'EOF'` opener ends the command's first line and the reader is the `engini` CLI itself (`engini`, `npx @engini/cli` or `npx @engini/cli@<exact version>`).

### Scope of the guard

The guard applies to **every** Claude Code session where the plugin is enabled - not only while Flo is working. It also refuses your own `engini connections delete`, or a never-verb you genuinely meant to run. Run those in your own terminal, or turn the plugin off for the session with `/plugin disable flo@engini-flo` (that removes the guard's other protections too). Everything else - your builds, git, other tools, other Engini apps' tools - runs normally.

> [!NOTE]
>
> This applies to every project's own `.claude` and `.codex` folders too. With the plugin enabled, `ls .claude`, `git add .claude`, `grep -r x .claude` and a `cd` into a folder inside `.claude` are refused in every project and every session, not only while Flo is working. Run those in your own terminal, or turn the plugin off for the session.

### When the guard refuses something

The message starts with `FLO-HOOK-DENY` and says which of these it is:

| Message says | What to do |
| - | - |
| **never-verb** | Intended - do not work around it. If you really mean it, do it yourself in the Engini app or your own terminal |
| **tool name it cannot read** | Write the tool name literally |
| **raw-key command** | Run it yourself in your own terminal |
| **key read** | Nothing to fix; check presence only, with `[ -n "$ENGINI_API_KEY" ]` |
| **protected file** | Make that change yourself |

The plugin's [troubleshooting guide](https://github.com/engini/flo-plugin/blob/v0.1.0/plugin/TROUBLESHOOTING.md) covers each case in more detail.

Claude Code's own deny rules (layer 3) also catch the four verbs inside a compound command such as `cd /tmp && engini tools call ...` - tested on Claude Code 2.1.316. The guard's extra reach is that it needs no paste, and the quoting and wrapper forms above.

## What the guard does not stop

While Flo has a shell and your key, it can do anything your key can do, including acting through your other connections. The guard narrows one literal command form. It is a text matcher, not a sandbox. These are deliberately open, and tested as open:

* **Every other tool.** The guard refuses only the four verbs. Flo's rule against calling other apps' tools (for example `slack_send_message`) is an instruction Flo follows, not something enforced; so is "read execution data only when asked". Builder tools Engini adds later run normally too.
* **Indirection the guard never sees:** `eval "$X"`, `bash -c "$X"`, a shell function, a script file.
* **Anything that skips the CLI:** a `python -c` against the SDK, or a plain HTTPS call with your key.
* **A tool name that is never in the command text at all,** for example `xargs engini tools call --args - < slugfile`.
* **The key file reached through a glob** (`cd ~/.config && cat engini/conf*`), **a recursive search from a parent folder in the shell** (`grep -r ... ~/.config`), and **a custom `XDG_CONFIG_HOME` folder** named by its own path in a shell command.
* **A settings folder name built from a variable or a wildcard** in the folder name itself (`~/.cl*`).
* **In the shell, a symlink** to your settings or key folder that already exists (the command names only the link), and a program that changes folder itself before it writes (`os.chdir` in a script, `git -C`, `find -execdir`). Read, Write, Edit, Grep and Glob follow symlinks, so those tools are covered.

## Settings to paste yourself

Each rule below matches the **literal command form only**. `bash -c`, a shell variable, `eval`, reordered arguments or a `python -c` against the SDK walk past every one of them. They are what holds when the guard does not run, and in Codex they are all there is.

* **Allow only `Bash(engini:*)`** and never run Flo on auto-accept or `--dangerously-skip-permissions`. Flo holds a shell and your key; this is the widest control by far.
* The two `ask` rules make Claude Code ask **you**, outside the conversation, before every create or update - the one approval the model cannot give itself.
* The `Read` rules cover the stored login on each platform for Claude's own file tools; they do not bind a shell command, and the one `Bash(cat ...)` rule covers only that exact command. What keeps other shell reads (`grep`, `head`, the other paths) from running unasked is that `Bash(engini:*)` is the only `allow` rule, so any other command asks you first.

### Claude Code settings

Merge this into your user settings (`settings.json` in your `~/.claude` folder):

```json theme={null}
{
  "permissions": {
    "allow": [
      "Bash(engini:*)"
    ],
    "ask": [
      "Bash(engini tools call engini_workflows_create_workflow_from_plan:*)",
      "Bash(engini tools call engini_workflows_update_workflow_from_plan:*)"
    ],
    "deny": [
      "Bash(engini tools call engini_workflows_delete_workflow:*)",
      "Bash(engini tools call engini_workflows_run_workflow:*)",
      "Bash(engini tools call engini_workflows_enable_or_disable_workflow:*)",
      "Bash(engini tools call engini_workflows_rerun_execution:*)",
      "Bash(engini connect:*)",
      "Bash(engini login:*)",
      "Bash(engini connections create:*)",
      "Bash(engini connections update:*)",
      "Bash(engini connections delete:*)",
      "Bash(engini connections auth-providers create:*)",
      "Bash(engini connections auth-providers update:*)",
      "Bash(engini login --api-key:*)",
      "Read(~/.config/engini/**)",
      "Read(~/AppData/Roaming/engini/**)",
      "Read(~/engini/config.toml)",
      "Bash(cat ~/.config/engini/config.toml)",
      "Bash(printenv:*)",
      "Bash(env)"
    ]
  }
}
```

### Codex rules file

Save this as `~/.codex/rules/flo.rules`:

```python theme={null}
# Flo: the four never-verbs, the raw-key commands, and a prompt before every write.
prefix_rule(pattern = ["engini", "tools", "call", "engini_workflows_delete_workflow"], decision = "forbidden", justification = "Flo never turns on, runs, reruns or deletes a workflow")
prefix_rule(pattern = ["engini", "tools", "call", "engini_workflows_run_workflow"], decision = "forbidden", justification = "Flo never turns on, runs, reruns or deletes a workflow")
prefix_rule(pattern = ["engini", "tools", "call", "engini_workflows_enable_or_disable_workflow"], decision = "forbidden", justification = "Flo never turns on, runs, reruns or deletes a workflow")
prefix_rule(pattern = ["engini", "tools", "call", "engini_workflows_rerun_execution"], decision = "forbidden", justification = "Flo never turns on, runs, reruns or deletes a workflow")
prefix_rule(pattern = ["engini", "connect"], decision = "forbidden", justification = "takes or stores a raw API key - run it yourself")
prefix_rule(pattern = ["engini", "login"], decision = "forbidden", justification = "takes or stores a raw API key - run it yourself")
prefix_rule(pattern = ["engini", "connections", "create"], decision = "forbidden", justification = "takes or stores a raw API key - run it yourself")
prefix_rule(pattern = ["engini", "connections", "update"], decision = "forbidden", justification = "takes or stores a raw API key - run it yourself")
prefix_rule(pattern = ["engini", "connections", "delete"], decision = "forbidden", justification = "takes or stores a raw API key - run it yourself")
prefix_rule(pattern = ["engini", "connections", "auth-providers", "create"], decision = "forbidden", justification = "takes or stores a raw API key - run it yourself")
prefix_rule(pattern = ["engini", "connections", "auth-providers", "update"], decision = "forbidden", justification = "takes or stores a raw API key - run it yourself")
prefix_rule(pattern = ["engini", "tools", "call", "engini_workflows_create_workflow_from_plan"], decision = "prompt")
prefix_rule(pattern = ["engini", "tools", "call", "engini_workflows_update_workflow_from_plan"], decision = "prompt")
```

> [!WARNING]
>
> Codex support is not yet verified on a real Codex install. If commands are not refused in Codex, check that this file exists and that you started a new session after saving it.

### Codex sandbox

Codex runs commands without network by default, so every `engini` call fails. Allow network for commands in your Codex config:

```toml theme={null}
[sandbox_workspace_write]
network_access = true
```

Or approve the commands outside the sandbox. Flo's optional session log and lessons live in `~/.engini/flo/`, outside the workspace: add it as a writable root or Flo skips them. Not yet verified on a real Codex install.

## Where the CLI stores your login

The guard refuses reads that name any of these paths, and Grep or Glob searches started from a folder above them, such as your home folder or `~/.config` - however that folder is spelled: a relative path, no path while the session runs in it, a symlink, or a second macOS name for your home folder such as the one under `/System/Volumes/Data`.

* `~/.config/engini/config.toml` (macOS, Linux)
* `$XDG_CONFIG_HOME/engini/config.toml` (when `XDG_CONFIG_HOME` is set)
* `%APPDATA%\engini\config.toml` (Windows)
* `%USERPROFILE%\engini\config.toml` (Windows, when `APPDATA` is unset)

For how the CLI stores credentials, see the [CLI overview](https://docs.engini.ai/developers/cli.md).

## Things to know

* **Your key carries its creator's whole permission set.** Use a dedicated, least-privilege, workspace-scoped key you can rotate. See [API keys](https://docs.engini.ai/developers/get-started/api-keys.md).
* **Log in interactively.** `engini login`, never `--api-key` on the command line.
* **What Flo reads enters the model's context:** workflow definitions, and execution data when you ask for it.
* **The stored key is reachable by the agent.** It is a file in your own home folder; the agent can reach it through the shell as much as through file reads. The guard refuses the literal reads; what actually protects you is a scoped, rotatable key plus the `Bash(engini:*)` allow rule - not secrecy from the model.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.