Skip to content
Ironflow is pre-1.0: APIs can change between releases. Pin a version. What is ready →

Limit an agent key to specific functions

An agent key often needs to call two or three functions, not all of them. Layer 1 RBAC grants functions:invoke for every function. You narrow it with a deny rule in your policy set. This guide shows the full flow.

Find the ID of the role you want the key to hold:

Terminal window
ironflow role list

Then create the key with that role ID:

Terminal window
ironflow apikey create agent-bot \
--role <role-id> \
--tool-scope billing-tools

--tool-scope is repeatable. It sets the agent tool scopes of the key. The agent tool service checks them. They do not limit which functions the key can invoke. The policy in the next step does that. You can grant only scopes that you hold yourself, unless you are an admin.

Save the key value. Note the key ID (ak_...) in the output.

InvokeFunction and InvokeFunctionSync check functions:invoke twice:

  1. The route gate runs first. It sees only the RPC path, so the resource is an empty string.
  2. The handler runs next. It checks the real function IRN, for example irn:ironflow:org_acme:proj_default:function:env_default:fn-a.

A rule that says only “not fn-a” is true for the empty resource. The route gate then denies fn-a too. Guard the rule with input.request.resource != "":

package ironflow.authz
# The pre-route gate sees an Invoke RPC with an empty resource (the function id
# is in the body); the handler asks again with the real function IRN. The
# resource != "" guard lets the gate pass and leaves the decision to the handler.
deny contains "agent key may only invoke fn-a" if {
input.request.action == "functions:invoke"
input.subject.api_key_id == "ak_agent_bot"
input.request.resource != ""
not endswith(input.request.resource, ":fn-a")
}

Replace ak_agent_bot with the ID of your key. Save the rule as agent-bot.rego in your policy directory.

At the route gate the guard makes the rule not fire, so the request passes. The handler check then decides.

To allow more than one function, test each name with endswith and use not on the group. One way is a set of allowed suffixes:

package ironflow.authz
allowed_suffixes := {":fn-a", ":fn-b"}
deny contains "agent key may only invoke fn-a and fn-b" if {
input.request.action == "functions:invoke"
input.subject.api_key_id == "ak_agent_bot"
input.request.resource != ""
not any_suffix_matches
}
any_suffix_matches if {
some suffix in allowed_suffixes
endswith(input.request.resource, suffix)
}

Ironflow encodes a raw function ID once: literal % becomes %25, then : becomes %3A. A function team:fn-a is ...:function:env_default:team%3Afn-a. The literal ID team%3Afn-a instead ends in team%253Afn-a. Write the encoded identifier in your suffixes and dry-run inputs. Do not percent-decode input.request.resource. For existing rules containing literal % identifiers, follow Update existing policies when upgrading.

Run the dry-run before you push. Check that fn-a is allowed and fn-b is denied. Also check the empty resource, which is what the route gate sees. Write one input file for each case. This is the file for fn-b, named fn-b.json:

{
"request": {
"action": "functions:invoke",
"resource": "irn:ironflow:org_acme:proj_default:function:env_default:fn-b",
"environment": "env_default",
"org_id": "org_acme"
},
"subject": { "api_key_id": "ak_agent_bot", "roles": ["developer"] }
}

Make fn-a.json the same with fn-a at the end of the resource, and gate.json with "resource": "". Then:

Terminal window
# fn-b: expect decision deny
ironflow policy dry-run ./policies --input fn-b.json
# fn-a: expect decision allow
ironflow policy dry-run ./policies --input fn-a.json
# Route gate: expect decision allow (the guard lets it through)
ironflow policy dry-run ./policies --input gate.json
Terminal window
ironflow policy push ./policies

A new set starts in monitor mode. Read the would_deny rows for a while to check that only the key you meant is affected, then switch: ironflow policy push ./policies --mode enforce. See Roll out with monitor mode.

Each denied call is written to the auth audit log with the function IRN and the reason agent key may only invoke fn-a.

The rule limits InvokeFunction and InvokeFunctionSync only. The key can still reach other functions by other routes:

  • Events. Emit, Trigger, TriggerSync, TriggerBatch and webhooks use events:emit. An event can start any function that has a matching trigger.
  • Run control. ResumeRun, PatchStep, InjectStepOutput and PauseRun use runs:cancel. They act on any run.
  • Outbox. Outbox requeue uses outbox:manage.
  • Worker routes. Worker routes check functions:invoke with no function in the resource, so this rule does not match them.
  • Agent tools. agent:tools:invoke is a separate check.

Add a second rule for the same key on the actions it does not need:

package ironflow.authz
deny contains "agent key may not use this action" if {
input.subject.api_key_id == "ak_agent_bot"
input.request.action in {"events:emit", "runs:cancel", "outbox:manage"}
}

Or give the key a role that does not hold these actions.