These examples show how to write a Kiro Enterprise Management policy in KGL for common governance goals, from the smallest policy that works to a complete one. This page is for administrators who edit a policy in the YAML view of the console's Controls page. Kiro Enterprise Management is in preview for new Kiro profiles created in US East (N. Virginia) (us-east-1). It is on for all newly created profiles there; existing profiles are not affected by this change. Each example is a YAML fragment you can paste into the YAML view, with what it does and where Kiro enforces it. The examples build on each other, so read them in order the first time. For the fields and error codes, see the KGL reference; to publish a policy, follow Get started with Enterprise Management.
Two rules shape every example. Every example that adds a deny keeps an ask or allow beside it, because naming a built-in tool in any rule denies every use the rule does not cover; the full mechanism is on the KGL reference under How rules combine. A deny always wins over an allow or ask that covers the same thing, and rule order has no effect. A rule applies to every surface where Kiro runs, because the Kiro IDE, the Kiro CLI, and Kiro Web run the same agent. Which capabilities the Kiro service enforces, and which the clients apply (Kiro CLI 3.1.0 or later, Kiro IDE 1.4.154 or later), is listed in Enforcement status.
The <group id> and <user id> placeholders stand for the IDs Kiro evaluates for your identity source. Find them as described in Find user and group IDs.
Pick the row that matches your organization, then build your policy from the examples each cell names. Start from example 1 in every case: it names every built-in tool once, so a deny you add later does not close everything else, and each row below replaces some of its rules with stricter or looser ones.
| Regulated | Standard | Open | |
|---|---|---|---|
| Who | Data-handling rules, contractors on the same profile, audit needs | One engineering organization, no contractor split | A pilot or a test account |
| Models | Lock the lineup and the default (2) | Set a default developers can change, within an allow list (3) | Allow every model (1) |
| API keys | Deny (4) | Deny (4) | Deny (4) |
| Web | Ask, and deny a named domain (5) | Ask (1) | Ask (1) |
| Files | Ask, and deny secrets (6) | Ask (1) | Ask (1) |
| Shell | Ask, and deny git push, rm -rf, sudo (7) | Ask, and deny git push, rm -rf, sudo (7) | Ask (1) |
| MCP tools | Ask (10) | Ask (10) | Ask (1) |
| Rules for one group | Stricter and looser (8, 9) | Optional (8) | No |
| Logging and reports | On and locked (11) | On and locked (11) | Not set (1) |
| Assembled | Example 12, plus the user activity report settings from example 11 | Examples 1, 3, 4, 7, 10, 11 | Example 1 as written |
Prefer Regulated when an auditor would flag a capability you have not restricted. Prefer Standard when developers should choose their own model and the main risk is an accidental push or API key creation. Prefer Open when you are testing what a policy does before you assign it to a real profile.
Whatever row you pick, read example 13 before you publish: it lists four mistakes that pass a glance and then fail at Publish, or publish and do something other than what you wrote.
rules: - { id: fs-read-ask, capability: fs_read, effect: ask } - { id: fs-write-ask, capability: fs_write, effect: ask } - { id: shell-ask, capability: shell, effect: ask } - { id: web-search-ask, capability: web_search, effect: ask } - { id: web-fetch-ask, capability: web_fetch, effect: ask } - { id: mcp-ask, capability: mcp, effect: ask } - { id: model-use-allow, capability: model_use, effect: allow } - { id: apikey-create-deny, capability: apikey_create, effect: deny } settings: default_model: !replace "claude-sonnet-4.5"
One rule for each of the eight capabilities: ask on the six built-in tools, allow on models, deny on API keys. A settings block sets default_model. With no rule, Kiro follows the developer's own configuration for these six tools; the ask rules make Kiro ask every time, and they are here so that a deny you add later (examples 5 to 7) does not turn the rest of that capability from ask to denied. The Kiro Crew capabilities need no rule here: cron runs and permission_mode keeps normal, trust, and trust_reads, with yolo off, until a rule says otherwise; messaging_channel, messaging, and network_egress stay denied until you add allow rules as you need them. Start every policy from this floor; the examples that follow replace or add rules to it.
Where it is enforced: model_use, apikey_create, web_search, and web_fetch by the Kiro service, on every surface. The ask rules on files, shell, and MCP by Kiro CLI 3.1.0 or later and Kiro IDE 1.4.154 or later; Enforcement status does not list Kiro Web for them.
rules: - id: model-use-allow capability: model_use effect: allow match: ["claude-*"] - id: model-deny-opus capability: model_use effect: deny match: ["claude-opus-*"] settings: model_governance.enabled: true default_model: !locked "claude-sonnet-4.5"
Developers get every Claude model except the Opus tier, and cannot change the default. model_governance.enabled: true turns the rules on; a new profile ships with it off. claude-* also matches Claude models that Kiro adds later; claude-opus-* removes the Opus ones from that set. !locked means the developer cannot change the value. default_model must be a model that a model_use rule allows, which claude-sonnet-4.5 is; otherwise Publish returns POLICY_BOUNDARY_VIOLATION.
Where it is enforced: by the Kiro service, on every surface.
rules: - id: model-use-allow capability: model_use effect: allow match: ["claude-sonnet-4.5", "claude-haiku-4.5", "gpt-5.6-sol"] settings: model_governance.enabled: true default_model: !replace "claude-sonnet-4.5"
Three named models, no patterns, so a new model that Kiro adds is not allowed until you add it. model_governance.enabled: true turns the rules on; a new profile ships with it off. !replace sets the default and lets the developer change it to another allowed model. Use !replace when you want a sane default; use !locked from example 2 when the default is the only choice. !replace works on default_model only.
Where it is enforced: by the Kiro service, on every surface.
rules: - id: apikey-create-deny capability: apikey_create effect: deny
Developers cannot create API keys. apikey_create takes no match, so the rule is all or nothing; adding match returns BINARY_CAPABILITY_SCOPED. It also cannot ask. The controls Kiro creates for a new profile have no apikey_create rule, so API key generation is already denied; this explicit deny keeps keys off if a later edit adds an apikey_create allow, and states the choice in the policy.
Where it is enforced: by the Kiro service, on every surface.
rules: - id: web-search-ask capability: web_search effect: ask - id: web-fetch-ask capability: web_fetch effect: ask - id: web-fetch-deny-pastebin capability: web_fetch effect: deny match: ["*.pastebin.com", "pastebin.com"]
Kiro asks before a web search and before it fetches a page, and refuses to fetch anything on pastebin.com. web_search takes no match. web_fetch takes a domain; list both the bare domain and the wildcard so the rule covers pastebin.com and its subdomains. The deny removes part of what the ask opens. Keep the ask: naming web_fetch in a rule denies every fetch the rules do not cover, so a deny on its own would block every other domain.
Write the blocked domains in match on a deny rule, as here. A web_fetch deny that uses exclude to carve out allowed domains is under investigation and asked the developer instead of denying; see Known issues.
Where it is enforced: by the Kiro service, on every surface. web_fetch is enforced on the results of a web search.
rules: - id: fs-read-ask capability: fs_read effect: ask - id: fs-read-deny-secrets capability: fs_read effect: deny match: ["**/.env", "**/.env.*", "**/secrets/**", "**/*.pem"] - id: fs-write-ask capability: fs_write effect: ask - id: fs-write-deny-secrets capability: fs_write effect: deny match: ["**/.env", "**/.env.*", "**/secrets/**", "**/*.pem"]
Kiro asks before it reads or writes any file, and never touches environment files, anything under a secrets folder, or private keys. File patterns are globs: ** matches any folders, so **/secrets/** matches every file under any secrets folder at any depth. Patterns are case-sensitive.
Where it is enforced: by Kiro CLI 3.1.0 or later and Kiro IDE 1.4.154 or later. Enforcement status does not list Kiro Web for them.
rules: - id: shell-ask capability: shell effect: ask - id: shell-deny-destructive capability: shell effect: deny match: ["rm -rf *", "sudo *", "git push *"]
Kiro asks before every shell command and refuses the ones listed. "git push *" also matches the bare command git push, so developers push from their own terminal, not through Kiro.
Where it is enforced: by Kiro CLI 3.1.0 or later and Kiro IDE 1.4.154 or later. Enforcement status does not list Kiro Web for them.
rules: - id: model-use-allow capability: model_use effect: allow match: ["claude-*"] - id: model-deny-opus-contractors capability: model_use effect: deny match: ["claude-opus-*"] when: 'groups in ["<group id>"]' settings: model_governance.enabled: true
Everyone gets Claude models; members of one group do not get Opus. model_governance.enabled: true turns the rules on; a new profile ships with it off. The deny carries a when condition, so it wins only for users in that group; for everyone else the allow stands alone. This is also how to test a stricter rule on a pilot group before you apply it to everyone: put the group in when, publish, confirm the behavior with a member of the group, then remove the when.
Use the group ID, not the group name; see Find user and group IDs. A name makes Publish fail. Put the whole when value in single quotes.
Where it is enforced: wherever the capability is enforced; model_use is enforced by the Kiro service, on every surface.
rules: - id: shell-ask capability: shell effect: ask when: '!(groups in ["<group id>"]) && user != "<user id>"' - id: shell-allow-platform capability: shell effect: allow when: 'groups in ["<group id>"] || user == "<user id>"'
Members of one group, and one more user, run shell commands without a prompt; everyone else is asked. The two when conditions are complements of each other, so every user matches exactly one rule. when supports groups in [...], user == "...", user != "...", and &&, ||, !, and parentheses.
Write both rules. Once any rule names shell, every command the rules do not cover is denied, so a single allow with a when would block shell commands for everyone outside the condition.
Where it is enforced: by Kiro CLI 3.1.0 or later and Kiro IDE 1.4.154 or later. Enforcement status does not list Kiro Web for them.
rules: - { id: mcp-ask, capability: mcp, effect: ask }
Kiro asks before it calls an MCP tool. mcp takes server/tool patterns, so a narrower rule can name one server with match: ["corp-tools/*"] or one tool with match: ["corp-tools/deploy"]. Skills and Powers have no rule of their own; to give developers a Skill or Power, add it on the Agent customizations tab.
Keep mcp at ask or allow. An mcp deny rule behaved unexpectedly for some users and is under investigation; see Known issues.
Where it is enforced: by Kiro CLI 3.1.0 or later and Kiro IDE 1.4.154 or later. Enforcement status does not list Kiro Web for them.
settings: prompt_logging.enabled: !locked true prompt_logging.destination: !locked "s3://my-bucket/prompts/" user_activity_report.enabled: !locked true user_activity_report.csv_export: !locked true user_activity_report.csv_destination: !locked "s3://my-bucket/reports/" usage_dashboard: !locked true
Prompts are logged to one S3 prefix, the user activity report is exported as CSV to another, and the usage dashboard is on. !locked means a developer cannot turn any of these off. A setting that is turned on may need another one: prompt_logging.enabled: true needs prompt_logging.destination, and Publish returns SETTING_REQUIREMENT_MISSING without it. The console shows no error until you publish, so set every destination together with the setting that uses it. For the S3 bucket and KMS prerequisites behind each destination, see Setting keys.
Where it is enforced: by the Kiro service.
version: "0.1" metadata: name: "Engineering baseline" rules: # Models: Claude only, no Opus for contractors, Sonnet by default - id: model-use-allow capability: model_use effect: allow match: ["claude-*"] - id: model-deny-opus-contractors capability: model_use effect: deny match: ["claude-opus-*"] when: 'groups in ["<contractors group id>"]' # Keys off - { id: apikey-create-deny, capability: apikey_create, effect: deny } # Web: ask, and never pastebin - { id: web-search-ask, capability: web_search, effect: ask } - { id: web-fetch-ask, capability: web_fetch, effect: ask } - id: web-fetch-deny-pastebin capability: web_fetch effect: deny match: ["*.pastebin.com", "pastebin.com"] # Files: ask, and never secrets - { id: fs-read-ask, capability: fs_read, effect: ask } - { id: fs-write-ask, capability: fs_write, effect: ask } - id: fs-deny-secrets-read capability: fs_read effect: deny match: ["**/.env", "**/.env.*", "**/secrets/**", "**/*.pem"] - id: fs-deny-secrets-write capability: fs_write effect: deny match: ["**/.env", "**/.env.*", "**/secrets/**", "**/*.pem"] # Shell: ask, platform team runs without prompts, nobody runs destructive commands - id: shell-ask capability: shell effect: ask when: '!(groups in ["<platform group id>"])' - id: shell-allow-platform capability: shell effect: allow when: 'groups in ["<platform group id>"]' - id: shell-deny-destructive capability: shell effect: deny match: ["rm -rf *", "sudo *", "git push *"] # MCP: ask - { id: mcp-ask, capability: mcp, effect: ask } settings: model_governance.enabled: true default_model: !locked "claude-sonnet-4.5" prompt_logging.enabled: !locked true prompt_logging.destination: !locked "s3://my-bucket/prompts/" usage_dashboard: !locked true
Examples 2, 4, 5, 6, 7, and 10 in one file, with the platform-group pattern from example 9 (the group only, without the single-user clause) and the prompt logging and dashboard settings from example 11. Every built-in tool has an ask rule beside its deny, so a deny closes only what it names. The six deny rules remove exactly the parts you named from what the allow and ask rules open: the Opus tier for contractors, API keys, pastebin.com, secret files for reading and for writing, and destructive commands. The platform group skips the shell prompt and still cannot run destructive commands, because deny wins. metadata.name is a note for you; the service ignores it. Comments are plain YAML comments.
To publish it, choose Edit, switch to YAML, paste it in, replace the two placeholder IDs, then choose Publish, as in Get started. To verify, restart the Kiro CLI or the Kiro IDE as a developer: expect the Claude models in the model picker and a prompt before a web search on either. In the Kiro CLI, also expect a prompt before a shell command and a refusal on git push.
rules: - id: shell-deny-push capability: shell effect: deny matches: ["git push *"] # 1. misspelled field - id: model-deny-opus capability: model_use effect: deny match: ["anthropic.claude-opus-*"] # 2. prefix that matches nothing - id: web-search-deny-contractors capability: web_search effect: deny when: 'groups in ["Contractors"]' # 3. a name, not an ID - id: web-search-deny-site capability: web_search effect: deny match: ["*.example.com"] # 4. match on a binary capability
| Line | What happens | Fix |
|---|---|---|
1. matches: | No error. The service ignores the unknown field, and the deny then covers every shell command. | Spell it match. Check every optional field before you publish. |
2. anthropic.claude-opus-* | No error. Model identifiers have no anthropic. prefix, so the rule matches nothing and Opus stays allowed. | Write claude-opus-*. |
3. "Contractors" | Publish fails with UNKNOWN_GROUP, or in the console with "Encountered unexpected error when processing request". | Use the group ID. See Find user and group IDs. |
4. match on web_search | Publish returns BINARY_CAPABILITY_SCOPED. web_search takes nothing. | Remove match. To limit domains, use web_fetch. |
Lines 1 and 2 are the dangerous ones: they publish without an error and do something other than what you wrote. For every code Publish can return, see Common errors.
Every rule on this page decides on the developer's behalf. An allow rule lets Kiro act without asking, a deny rule blocks the action, and an ask rule makes Kiro ask the developer before it acts. Models, API keys, and Kiro Crew messaging and network access stay denied until a rule allows them; the built-in tools and Kiro Crew scheduled jobs keep Kiro's own default until a rule names them. The examples keep ask on files, shell, MCP, and the web, and use allow without a prompt only for models and, in examples 9 and 12, shell commands for one group. To remove every control from a profile, remove the assignment, as described in Stop applying controls.
Write your own rules with the KGL reference, and check which capabilities each surface enforces in Enforcement and limits.
KGL examples