Monday, October 26, 2026

Kiro permissions.yaml: how it works

Kiro IDE 1.x controls what the agent can run with a permissions.yaml file. The matching rules are not obvious or well documented, so I tested the behavior on Windows with PowerShell. This post covers how matching works, what surprised me, and a short starter list of commands that are safe to allow.

Versions: most tests ran on Kiro 1.2.37 (October 5-7, 2026). On October 26, 2026 I re-ran a spot check of 12 commands on Kiro 1.4.26 (VS Code 1.131.0, Windows 10.0.26100). Every result matched. I did not re-run everything, so treat the details as observed behavior, not a spec.

Security philosophy

If you approach AI agent permissions with the perspective of a malicious agent hell-bent on breaking through, you will have to review every prompt, leading to user fatigue. There are too many bypasses.

Instead, I guard against serious errors, like an AI agent inferring "fix merge conflict" implies "and push to production." (That happened yesterday.)

If you need higher security, either review every prompt, run agents in a sandbox, or look into control through MCPs.

Where rules live

  • User level: ~/.kiro/settings/permissions.yaml (on Windows, %USERPROFILE%\.kiro\settings\permissions.yaml).
  • Workspace level: <repo>/.kiro/settings/permissions.yaml. I did not test this one.
  • Organization level: admins can deploy a managed policy that takes precedence. See the Kiro enterprise permissions docs.
  • Choosing "Always allow" in a prompt can write a rule to a separate file under ~/.kiro/workspace-roots/<id>/permissions.yaml. Review that file now and then, because nothing else shows you those rules.

Kiro reloads the file on the next tool call. No restart is needed.

I maintain a local, personal Git repository to manage my permissions and AGENTS.md file for multiple agents. It also includes documentation similar to this page. After an update, I launch a deploy script to copy it to the user profile path above.

File format

Each rule has a capability (shell, fs_read, fs_write, web_fetch, web_search), an effect (allow, ask, deny), and a list of patterns.

rules:
  - capability: shell
    effect: allow
    match:
      - git status *
      - git diff *

  - capability: shell
    effect: deny
    match:
      - git push -f *
      - git push *--force*

YAML quoting trap

A parse error makes Kiro deny every tool call, including the agent's own file edits, until you fix the file by hand. Inside double quotes, YAML treats a backslash as an escape, so "b:\sql" fails to parse. Worse, "C:\repo" loads silently because \r is a valid escape, and it becomes a rule that never matches. Use single quotes or no quotes for Windows paths.

How matching works

BehaviorExample
Rules match from the start of the command, word by word.git status * matches git status --short.
A trailing * also matches the bare command. You do not need both X and X *.nvidia-smi * matches plain nvidia-smi.
The space before * is a word boundary.Get-Item * does not match Get-ItemProperty.
A rule without * is exact.Write-Output does not match Write-Output 'hi'.
A * in the middle spans multiple words.snow.exe * --help matches snow.exe stage list --help.
The program name is literal. Each spelling is a different name.git, git.exe, and a full path to git need separate rules.
A flag placed before the subcommand breaks the match. This applies to deny rules too.git -C . status does not match git status *, and git -C . push -f escapes git push -f *.
Matching is case-sensitive, for the command name and for text inside arguments.GIT status and get-date do not match rules written in the usual case.
Quoting style and flag order are literal text.A pattern ending in " will not match single quotes or -q ... -c x order.
A command that starts with ( does not match a rule starting with the cmdlet name.(Get-Item x).Length prompts even with Get-Item * allowed. Use Get-Item x | Select-Object Length instead.

Compound commands

Kiro splits a command on ;, &&, and | and checks each part on its own.

  • Allow: every part must match an allow rule. If one part is unmatched, you get a prompt for the whole command.
  • Deny: if any part matches a deny rule, the whole command is blocked before anything runs. This holds wherever the denied part sits: first, last, after a pipe, or after cd. A deny also wins over an unmatched part, so you get a block and no prompt.
  • Subexpressions: code inside $(...) is checked, and so are recognized cmdlets inside Where-Object { } and & { } script blocks. A denied command inside is blocked, and an ask-listed command inside prompts.
  • Navigation: cd x; git status needs both cd * and a git rule.

Results from my tests, where "allowed" means both parts have allow rules:

CommandResult
nvidia-smi -L; where.exe git (both allowed)Ran, no prompt
nvidia-smi -L; hostname (second part unmatched)Prompt for the whole command
hostname; git push --force (unmatched + denied)Blocked, no prompt
git status --short && git push -f origin mainBlocked
git status --short $(git push -f origin main)Blocked

Precedence

  • A command with no matching rule prompts you.
  • Deny beats allow, and deny also beats Kiro's built-in allow list.
  • Kiro has a built-in auto-allow list that is separate from your file. It covers some read-only commands for plain git (status, log, show, blame, rev-parse). It did not cover git branch, git -C ..., or anything starting with git.exe. When you test rules, use a command that is not on that list, such as git.exe, or you cannot tell whether your rule or the built-in list allowed it.

Gotchas

The deny list is a speed bump, not a security control

  • Case: PowerShell is case-insensitive but Kiro's matching is not. FORMAT-VOLUME was not caught by deny rules for Format-Volume * or format-volume *. Add lowercase variants, knowing that mixed case still escapes.
  • Aliases: iex was not caught by a deny rule for Invoke-Expression *. I expect the same for other aliases but only tested this one.
  • Force pushes without -f: git push origin +main force-pushed past a rule set that blocked -f and --force. Remote branch deletion (--delete, -d, origin :branch) needs its own rules.
  • SQL keywords: a deny pattern like snow.exe sql * -q "*DROP*" missed lowercase, single quotes, --query, a different flag order, and the snow spelling without .exe. Broader patterns such as snow sql *DROP* catch more, and these blocked the lowercase and no-.exe forms on 1.4.26. Mixed case (Drop), -f file.sql, and piped input can still escape. The real control is a least-privilege database role.

The practical protection is that unknown commands still prompt, plus limits from the operating system itself.

Read commands with write flags

  • npm audit * includes npm audit fix. Deny npm audit fix *.
  • git log * and git diff * include --output=<file>, which writes a file. A deny rule on git log *--output* blocked it, even though git log * was allowed.
  • Redirection: an approved Get-Date > file.txt ran and wrote the file. Any broad allow rule for a cmdlet that produces output can overwrite files this way. I did not find a rule-based fix.
  • File readers such as cat * and Get-Content * read any file, including .env files and keys, and they bypass fs_read limits.

Be careful with * in allow rules

Because the middle * spans words, git -C * status * would also match git -C x push origin status. Keep write-capable commands out of broad allow patterns. In deny rules, a broad * is the safe direction. Over-blocking costs you a retry, not a mistake.

Deny messages are long

The automatic deny message lists every pattern in the matching rule block, not just the one that matched. For compound commands it ends with Triggered by: "<part>". If you want readable messages, split your deny rules into smaller blocks.

Starter allow list

These are read-only or low-risk, and common in agent workflows. Add the .exe spelling where your tools use it.

rules:
  - capability: shell
    effect: allow
    match:
      # Navigation, so "cd x; cmd" can pass when cmd is allowed
      - cd *
      - Set-Location *

      # Git, read-only (list both spellings)
      - git status *
      - git.exe status *
      - git diff *
      - git.exe diff *
      - git log *
      - git.exe log *
      - git show *
      - git.exe show *
      - git blame *
      - git.exe blame *
      - git rev-parse *
      - git.exe rev-parse *
      - git remote -v
      - git.exe remote -v
      - git branch --show-current
      - git.exe branch --show-current

      # PowerShell read-only cmdlets
      - Get-Command *
      - Get-Date *
      - Get-Item *
      - Get-ChildItem *
      - Test-Path *
      - Select-String *
      - Select-Object *
      - Where-Object *

      # Lookups
      - where.exe *

      # Python checks that do not run your code
      - python -m py_compile *
      - python --version

      # npm, read-only
      - npm ls *
      - npm audit *

A few choices behind that list:

  • I left out git -C ... on purpose. Run cd <repo>; git status instead, which works with the rules above.
  • I left out Get-Content and cat. They are popular, but they can read secrets. Add them if you accept that.
  • I left out npm test and python -m pytest. They run project code, so allow them only for repositories you trust.
  • Python is a program-name match like git. If the agent calls it by full path, you need a rule for that path (single-quoted in YAML).

Starter deny and ask list

Put these in separate blocks from your allow rules. Use ask for things you sometimes want, and deny for things you never want an agent to do.

  - capability: shell
    effect: ask
    match:
      - Remove-Item *
      - remove-item *
      - Clear-Content *
      - clear-content *
      - Stop-Process *
      - stop-process *

  - capability: shell
    effect: deny
    match:
      # Destructive git history operations (both spellings)
      - git push -f *
      - git push * -f
      - git push * -f *
      - git push *--force*
      - git push * +*
      - git push *--delete*
      - git push -d *
      - git push * -d *
      - git push * :*
      - git -C * push *
      - git filter-branch *
      - git filter-repo *
      - git reflog expire *
      - git.exe push -f *
      - git.exe push * -f
      - git.exe push * -f *
      - git.exe push *--force*
      - git.exe push * +*
      - git.exe push *--delete*
      - git.exe push -d *
      - git.exe push * -d *
      - git.exe push * :*
      - git.exe -C * push *
      - git.exe filter-branch *
      - git.exe filter-repo *
      - git.exe reflog expire *

      # Write flags on otherwise read-only commands
      - git log *--output*
      - git.exe log *--output*
      - git diff *--output*
      - git.exe diff *--output*
      - npm audit fix *

      # High-impact PowerShell (add lowercase copies; see case gotcha)
      - Invoke-Expression *
      - invoke-expression *
      - Start-Process *
      - start-process *
      - Set-ExecutionPolicy *
      - set-executionpolicy *
      - Format-Volume *
      - format-volume *
      - Restart-Computer *
      - restart-computer *

In my tests, the same ask rule for Remove-Item also prompted when the command sat inside a Where-Object { } or & { } block.

Checking your own rules

  • Test with commands that are harmless if they run: a nonexistent drive, -WhatIf, a nonexistent connection name, a repo with no remote.
  • Do not click "Always allow" or "Always deny" during tests. They can write rules into a permissions file behind your back.
  • Add a control for each test. For example, test a; b with an unmatched b first.
  • The agent only sees the tool result text. It can tell an automatic deny from a user rejection, but it cannot tell an auto-allowed run from one you approved by hand.

Steering

Combine permissions with steering. For example, it's not enough to deny git -C, I instruct agents to use an alternate form that works better with Kiro.

Summary

  • Patterns match from the start of the command, word by word, and are case-sensitive.
  • A trailing * covers the bare command and any arguments.
  • Compound commands are split on ;, &&, and |. Allow needs every part to match. One denied part blocks everything.
  • List each program spelling separately (git, git.exe).
  • Treat deny rules as a speed bump. Case, aliases, and unusual flags can get around them.
  • Watch for read commands with write flags, and for redirection.

These notes come from hands-on testing, not from Kiro's source or a complete specification. Behavior may change between versions.

Kiro permissions.yaml: how it works

Kiro IDE 1.x controls what the agent can run with a permissions.yaml file. The matching rules are not obvious or well documented, so I test...