Security & privacy

Command safety

How Gatesys SSH rates risky commands before they run: caution, danger and critical levels, a typed host name, and blocked reverse shells and fork bombs.

Every command Farabi suggests is rated on this computer before it can reach your shell. The rating comes from the command’s text and a fixed set of rules, never from asking a model. That is the point: a model that confidently suggests something ruinous is exactly the mistake this catches.

The rules and the tester are part of the Free plan. See Plans.

The “!” marker

A command that matches a rule carries a ! marker on its card. Hover or focus the marker to see:

  • each rule that matched, with why it is risky and a safer alternative
  • the context that raised the level, such as production host

The same marker appears wherever the app shows a command: Farabi’s answers, the Insights card and Health recommendations, Explain, watch and setup drafts, your snippets, Hop Doctor’s fixes and Open as Safe Change. A command reads the same wherever you meet it.

Three risk levels

LevelMarkerExamplesWhat Run asks
CautionAmber !A service restart, rm -r of a project folder, sudo, a redirect that overwrites a file, kill by nameNothing extra
DangerRed !rm -rf of a path, DROP TABLE, docker volume rm, git reset --hard, ufw enableRun this anyway? › Run it
CriticalRed !! with a slow pulserm -rf /, mkfs, dd to a disk, DROP DATABASE, kubectl delete namespace, terraform destroyType the host’s name, then Run it

A critical command runs only once you type the host’s name, as molly-guard asks before a reboot over SSH. The name is not case-sensitive. The pulse stops if your system asks for reduced motion.

Insert is never gated for these levels: it only types the command at the prompt, and you still press Return. The main process rates the command again when you click Run, and refuses one that has not been confirmed.

What raises the level

Where a command lands matters as much as what it says. Any of these turns a Danger into a Critical:

  • A production host: its group or one of its tags contains prod.
  • Jump depth two or more: the host sits behind two bastions or more.
  • A root shell: you logged in as root, or the prompt ends in # after sudo -i.

A Caution stays a caution. Turning a routine restart on production into a confirm would only train the click the confirms exist to slow down. The marker’s popover names the factor, for example production host.

In a local terminal there is no production or jump factor, but a root shell still raises a danger to critical.

How a command is read

The rules read what would run, not what the text looks like.

  • Quotes and escapes are resolved first. The command is split into words the way a POSIX shell does it, so r''m, "rm", \rm and $'rm' are all rm.
  • Each command in a line is rated on its own, split at |, &&, ||, ; and &.
  • Wrappers are peeled: sudo, doas, env, nohup, nice, timeout, xargs and busybox. The rules see rm -rf / in sudo -E env X=1 nice rm -rf /.
  • Text handed to another shell is read too: sh -c and bash -c strings, su -c, eval, watch, ssh host …, docker exec, kubectl exec --, find -exec, ansible -a, every $(…), backtick and <(…), and text echoed into a shell, with base64 decoded first.
  • SQL is read out of database clients: psql -c, mysql -e, sqlite3, mongosh --eval, clickhouse -q, cqlsh -e, a here-string, or text echoed into one. Redis commands are read out of redis-cli. psql -c "DROP DATABASE shop" is critical.
  • Invisible and reordering characters are removed before rating, so a zero-width space cannot hide rm. A command that had any is shown as at least Caution.

Some constructs hide what will run. These are rated at least Danger, because the app cannot measure them:

  • a script fetched with curl or wget and run, however it is written: piped into a shell or an interpreter, sourced, substituted, or saved and then started
  • base64, hex or compressed text decoded and run
  • eval, and source <(…)
  • a command name worked out at run time, such as $cmd or $(echo rm)
  • $IFS in place of spaces, as in rm$IFS-rf
  • a line nested more than four levels deep, or longer than 4 KB

These are the tricks the GuardFall research used in 2026 to walk past command guards that read raw text.

Blocked commands

Some commands are attacks, not admin tasks. Gatesys SSH blocks them outright:

  • Reverse and bind shells: bash -i >& /dev/tcp/…, nc -e and ncat --exec, socat with EXEC: or SYSTEM:, a network socket piped into sh (often through mkfifo), and Python, Perl, Ruby, PHP or Node one-liners that open a socket and start a shell.
  • The fork bomb, :(){ :|:& };: and its variants.
  • Decoded text run as a command, such as base64 piped into a shell.

A suggestion that matches one shows a red Blocked pill with a short reason instead of Run, Insert, Copy and + Snippet. You can read its text, but you cannot select or copy it. The main process refuses it whatever it is asked, confirmed or not, host name typed or not, and writes each refusal to the audit log.

If you really mean it, type it yourself. The block stops Farabi from putting it in your shell; it does not stop you.

A one-way port test such as </dev/tcp/host/22 is not a reverse shell and is not blocked. To test a port, nc -zv host port works too.

Blocking cannot be turned off

The blocking rules are always on. They cannot be turned off or lowered in Settings or in settings.json, even with an approval. A rule of your own can block too.

Browse and test the rules

Open Settings › Safety › Command risks. The card works without Pro, and your own rules are kept whatever your plan.

  • Try a command. Paste a command into the tester to see which rules match, why, and at what level. No rule matches means Gatesys SSH would not mark it.
  • Rules by tag. 167 built-in rules, grouped as Files, Disks, System, Processes, Network, Firewall, Users and permissions, Packages, Containers, Kubernetes, Databases, Git, Cloud and infrastructure, Secrets and history, and Hidden or fetched code. Search rules… filters them. The summary line counts the built-ins, your own, and any you turned off or lowered.
  • Change a built-in. Each rule has a level switch (Caution, Danger, Critical) and an on/off switch. A critical rule can be lowered to Danger but never turned off; Run then confirms instead of asking for the host name. A blocking rule reads Always on.

Add your own rule

  1. In Settings › Safety › Command risks, click Add rule.
  2. Fill in the rule:
    • Id: lowercase letters, digits and dashes. Findings show it as user:<id>.
    • Command: the command it applies to, however it is wrapped, such as kubectl, or a glob such as pg_*.
    • Arguments contain: words, flags or globs that must all be present. -f also matches inside -rf, and delete namespace is two words in a row.
    • Level, a Title, and optionally Why and a Safer alternative.
    • Block: Farabi never runs, types or copies a command this matches.
    • Tags, to file it with the built-ins.
  3. Save it. It applies at once.

Command risks in settings.json

The same set lives in settings.json under commandRisks:

{
  "commandRisks": {
    "add": [
      {
        "id": "no-vault-seal",
        "command": "vault",
        "contains": ["operator", "seal"],
        "level": "danger",
        "title": "Seals the Vault"
      }
    ],
    "disable": ["git-clean"],
    "lower": { "git-reset-hard": "caution" }
  }
}

Protection only goes up without asking:

  • Adding a rule, or raising a built-in’s level, applies as soon as you save the file.
  • Turning a built-in off, or lowering it, through a file edit waits for a one-time approval. The card reads settings.json weakens a rule — approve each before it takes hold, with Approve beside each change. The approval is kept in the vault as a hash, so nothing that can write the file can quietly weaken protection.
  • What can never be weakened: a critical rule cannot be turned off or lowered below danger, and a blocking rule cannot be turned off or lowered at all. The card says why it refused.
  • A rule id the app does not have is named on the card and ignored.

Changes made in Settings need no approval.

On the record

The audit log records which rules matched a command and at what level. The ai.ask hook event carries the levels, never the command text.

Something unclear or wrong? Tell us.