Every shell command is parsed, not pattern-matched.

A shell command is not a string to check against a blocklist. ML.ai parses it, walks every position where a program name could appear, so a dangerous command hiding behind a pipeline, a subshell, or a wrapper like sudo, xargs or timeout is still judged on what it actually does, not on the wrapper around it.

ML.ai Code
src/auth/session.ts

42 async function refresh(token) {

- 43 if (!token) return null

+ 43 if (!token?.exp) throw new Error('expired')

+ 44 return await rotate(token)

45 }

2 lines changed

Waiting for your approval

AllowDeny

How it works

Every shell command is lexed and classified by effect before it runs. Reads never prompt, writes and higher-risk effects do, and a short list of absolute refusals holds regardless of mode.

01

Classified by effect, not by name

Read, verify, write inside or outside the workspace, network, irreversible deletion, history rewrite, system change, privilege escalation, and three grades of opaque execution. When a command mixes effects, the worst verdict wins.

02

Reads never prompt

ls, cat, git status and grep never trigger a permission card. That is what makes prompting everywhere else affordable: you are not asked about the vast majority of commands that only look at things.

03

The offending part is named

git status && rm -rf ~ is judged on the rm -rf ~, not the harmless half. The refusal names the specific part it objects to, rather than rejecting the whole line without saying why.

04

Verification is trusted by name

Test runners and typecheckers run without a prompt, even in Plan mode, because verification is what planning needs. This trust is by spelling: the classifier recognizes npm test, it does not inspect what the underlying script does.

One command, two effects, one verdict

    1

    The agent proposes

    git status && rm -rf ~

    2

    Every position gets parsed

    The classifier walks both halves of the chain. git status is a Read. rm -rf ~ is an irreversible deletion outside the workspace, one of the absolute refusals.

    3

    The worst verdict wins

    When a command mixes effects, the whole line is judged on its most severe part. This one is refused, and the refusal names rm -rf ~ specifically, not the harmless git status half.

    4

    The agent sees why

    Every verdict, approved, prompted, or refused, is logged to the ML.ai output channel with a stated reason rather than a bare rejection.

ML.ai Code
git status &&rm -rf ~
git statusRead
rm -rf ~Irreversible deletion

Refused: rm -rf ~ targets outside the workspace.

Worst verdict wins across the chain

Worth knowing

  • A short list of absolute refusals holds regardless of mode, agent, or setting, including auto-approve: deletion outside the workspace, writes to devices or system roots, privilege escalation, fork bombs, reading credentials into a command that reaches the network, and anything whose program can’t be identified before running.

  • The classifier cannot throw. Every failure path resolves to a stated refusal rather than an unhandled error, and it errs toward asking too often, never toward allowing something it shouldn’t.

  • Turning off ml-ai.commandSafety restores prompt-on-every-command behavior; it does not stop verdicts from being logged to the ML.ai output channel.

Questions worth asking

Related

Permission prompts

Every edit and command routes through one gate: Allow, Always, or Deny. A native diff opens before you decide, and a denial is passed back to the model with its reason.

Read more

Plan mode

Plan is genuinely restricted, not a suggestion. The edit and write tools are denied to it, and a stricter command budget applies.

Read more

Try ML.ai Code today, or talk to us about what is next.

Install the editor agent on your own machine, or book a call to talk through your team's workloads.