ML.ai

Every shell command is classified by effect before it runs

Each shell command is classified by its effect before it runs. Read-only commands run freely, writes need approval, and a few are always refused.

ML.ai
New sessionAutomationsReview

Sessions

Clean the workspace and rebuildRotate refresh tokens on use1hAdd rate limiting to /api/search6mUpgrade to React 192dFlaky checkout test3d

The agent proposes

$git statusRead&&rm -rf ~Irreversible deletion

ReadPromptRefuse

Earlier in this session

Who command safety is for

Security-conscious teams who want to know exactly what a coding agent will do before it touches the file system or runs destructive commands.

  • Security-conscious teams

    Every shell command is classified by its effect before it runs.

  • Developers using auto-approve

    Absolute refusals hold in every mode, so auto-approve never lets them through.

  • Anyone running agents on real repos

    Reads run without asking, while writes and riskier commands open a prompt first.

How it works

Before a command runs, ML.ai lexes it, works out what it would do, and decides whether to run it, ask you first or refuse it outright.

Classify every command by what it would do

The command is lexed into its parts and judged by its effect on your machine. Reads run straight away, while writes and riskier effects wait for you.

Reads skip the prompt and writes wait for you

A read like grep or git log runs without a prompt. A write, or anything riskier, opens a permission prompt so you can allow it once, always or deny it.

A denial goes back to the model with your reason

Deny a broad command and say why. Your reason is passed back to the model, which can propose a narrower command that goes through the same check.

A short list of refusals holds in every mode

A few commands are refused outright in every mode, auto-approve included. No prompt appears, nothing runs, and the agent has to take another route.

Pattern matching versus judging by effect

Pattern matching

A list of banned strings misses the variant nobody thought of, and blocks harmless commands that happen to contain a scary word somewhere in an argument.

Judged by effect

ML.ai lexes the command and works out what it would do. A read runs, a write or riskier effect asks first, and the short refusal list stops the rest.

What happens to a command before it runs

Here is what happens to a command between the agent proposing it and your shell running it.

1The agent proposes a commandThe working subagent writes the shell command it wants to run, in Build mode or in Plan mode, where tests and typecheckers are still allowed.
2The command is lexed into partsProgram, flags, arguments, pipes and chains are split out, so the classifier sees the whole command and every step in it.
3Its effect sets the verdictReads run straight away. Writes and riskier effects open an Allow, Always or Deny prompt before anything happens.
4Refusals stop it in every modeCommands on the short refusal list never run, whether you are in Build, Plan or auto-approve.

Command risks the classifier handles

What to know about how commands get classified.

Harmless commands that still prompt

A prompt for every ls and grep trains you to click Allow without reading, which defeats the point of asking.

How ML.ai handles it

Reads never prompt, so the prompts you do see are for writes and riskier effects that deserve a look.

Dangerous steps hiding in a chain

A destructive step tucked after && or inside a pipe can slip past a check that only looks at the first word.

How ML.ai handles it

The whole command is lexed, chains and pipes included, and judged by what each part would do.

Auto-approve going too far

Turning on auto-approve to move faster can mean a destructive command runs with nobody watching.

How ML.ai handles it

The short list of absolute refusals holds in every mode, auto-approve included, so those commands never run.

ML.ai

Explorer

app/auth/login.tssession.tsMrotate.tsapi/me.tsmiddleware.tstests/session.test.tspackage.json

Found the refresh path. Updating the session helper now.

GreprefreshToken · 6 matches in app/auth

Fs.readapp/auth/session.ts

Fs.writeapp/auth/session.tspending

app/auth/session.ts2 lines changedlines 42-50

42export async function refresh(token) {

43 if (!token) return null;

44 if (!token?.exp) throw new Error('expired');

45 return await rotate(token);

46}

47

48async function rotate(token) {

49 const next = await issue(token.sub);

50 return next;

Permission requiredFs.write app/auth/session.ts

Permission prompts

How Allow, Always and Deny work, and how you see the real diff before any edit lands.

ML.ai

Explorer

src/auth/registration.tssession.tsvalidate.tsapi/search.tstests/registration.test.tssession.test.tspackage.jsontsconfig.json

Fix the registration validation bug and run the relevant tests.

plan.md

Fix registration validation

  1. 1.Reproduce: an empty email passes signupSchema
  2. 2.Tighten email and password rules in src/auth/validate.ts
  3. 3.Add empty, whitespace and short password cases to tests
  4. 4.Run pnpm test registration
Edit refused in Plan modeInvestigate and describe the fix instead: switch to Build to apply it.

Ask ML.ai...

Plan mode

Plan mode denies edit and write tools for the turn while still letting tests and typecheckers run.

FAQs

Can I override the classifier for specific commands?

You can choose Always on a write prompt so that command stops asking. The absolute refusal list works differently: it holds in every mode, auto-approve included, and nothing lets those commands through.

Which commands run without asking?

Reads like ls, cat, git status and grep never prompt. Test runners and typecheckers also run without a prompt, even in Plan mode; that trust is by name, so the classifier recognizes npm test without inspecting what the script does.

Which commands never run?

A short list of absolute refusals: 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. It holds regardless of mode, agent or setting, auto-approve included.

What happens when I deny a command?

Your reason is passed back to the model rather than a bare rejection, so it can propose a narrower command instead of retrying the same one. The new command goes through the same check.

How are chained commands classified?

Every position where a program could appear is walked, including pipelines, subshells and wrappers like sudo, xargs and timeout. The worst verdict in the chain wins, and a refusal names the part it objects to: git status && rm -rf ~ is refused for the rm -rf ~.

Let the agent use your shell with the risky parts gated

Every command is classified by effect before it runs, so reads move fast, writes wait for you, and the worst never run.