TL;DR
Most AI runtimes still behave like “god processes”: if an agent can reach the runtime, it can often do almost anything the host can.
AINL’s capability grant model takes a different approach:
- every execution surface (runner, MCP server) loads a server-level grant from a named security profile;
- callers attach their own policy; the host merges them using restrictive-only rules — you can tighten permissions, but never widen them;
- adapters carry rich metadata (privilege tiers,
destructive,network_facing,sandbox_safe) so policies can be expressed in terms of capabilities, not just raw names.
This post explains how capability grants work in AINL and why they’re a safer default for AI runtimes.
The “god key” problem in AI runtimes
As more teams move from LLM demos to production agents, a recurring problem appears:
The runtime has broad, ambient access to everything. If an agent can hit the right endpoint with the right token, it can do far more than it should.
Common symptoms:
- runners with full outbound network + filesystem + DB access;
- shared credentials that don’t encode which tools or effects were intended;
- policy that lives mostly in prompts (“please don’t delete anything”) instead of enforceable constraints;
- difficulty answering “which capabilities did this agent actually have?”.
AINL’s capability grant model is designed to tackle this directly.
Capability grants: the core idea
At a high level, a capability grant says:
“For this host and this request, these are the adapters, effects, and privilege tiers that are allowed. Everything else is forbidden.”
In AINL, each execution surface:
- runner service (HTTP
/run,/enqueue,/capabilities, etc.); - MCP server (
ainl-mcpfor IDEs and agent hosts);
loads a server-level grant at startup from a named security profile, then merges it with per-request restrictions.
Key properties:
- restrictive-only merge: callers can’t expand what the server allows — they can only add more restrictions;
- capabilities instead of identities: what matters is which adapters/effects are allowed, not which user/agent you are;
- machine-readable: capabilities and profiles are JSON, not prose in a runbook.
Named security profiles at startup
Security profiles live in tooling/security_profiles.json. Each profile bundles:
- an adapter allowlist;
- forbidden adapters/effects/privilege tiers;
- runtime limits (max steps, depth, adapter calls, time);
- orchestrator expectations (network rules, container constraints).
Examples (simplified):
-
local_minimal- adapters:
coreonly; - forbidden privilege tiers:
local_state,network,operator_sensitive; - limits: very small; intended for local dev and dry-run usage.
- adapters:
-
sandbox_network_restricted- adapters: core + filesystem + SQLite + HTTP + queue, etc.;
- no “operator_sensitive” adapters (email, calendar, social, db);
- expects strict egress allowlists at the network layer.
-
operator_full- adapters: operator‑defined full surface;
- intended for tightly controlled, operator-managed environments with their own policy engines and network controls.
At startup, the runner loads a profile via:
AINL_SECURITY_PROFILE=<profile>for the HTTP runner;AINL_MCP_PROFILE=<profile>for the MCP server.
That profile becomes the server grant — the outermost capability envelope.
Restrictive-only merge: callers can’t widen the grant
When a caller submits a request (for example, to /run or via MCP tools), they can
attach an optional policy object that further constrains the run:
- forbidden adapters,
- forbidden effects / effect tiers,
- forbidden privilege tiers,
- stricter runtime limits.
The host applies a restrictive-only merge between:
- the server-level grant (from the named security profile), and
- the caller’s policy.
Rules of the merge:
- anything forbidden in either grant is treated as forbidden;
- limits (steps, depth, time, calls) take the minimum of the two;
- callers cannot enable adapters or privilege tiers that the server grant does not allow at all.
This guarantees:
- no client, IDE, or agent can “turn on” destructive capabilities that the server didn’t already permit;
- operators can centralize the broad shape of access, while callers refine it.
Capability-aware adapter metadata
To make capability grants expressive, AINL adapters carry rich metadata in
tooling/adapter_manifest.json, including:
- verbs and effect defaults;
- privilege tiers (e.g.
core,local_state,network,operator_sensitive); - flags like:
destructive,network_facing,sandbox_safe.
This metadata shows up in:
- the HTTP
/capabilitiesendpoint; and - the MCP resource
ainl://adapter-manifest.
With this, policies can say things like:
- “forbid all
destructiveadapters”; - “forbid all
operator_sensitiveprivilege tiers”; - “only allow adapters marked
sandbox_safein this environment”.
…without hard-coding adapter names everywhere.
How this looks in practice
Imagine three environments:
-
Local dev:
AINL_SECURITY_PROFILE=local_minimal- only
coreops allowed; - no I/O, no network, no durable state;
- perfect for testing compiler/runtime behavior without side effects.
- only
-
Sandboxed staging:
AINL_SECURITY_PROFILE=sandbox_network_restricted- filesystem, SQLite, cache, HTTP, tools, queue;
- no email/calendar/db/social adapters;
- network egress locked to a small set of internal hosts.
-
Trusted operator production:
AINL_SECURITY_PROFILE=operator_full- operator decides which adapters to enable per deployment;
- network and secrets locked down by infra;
- strong observability and audit pipelines in place.
Now add a caller policy, for example:
{
\"forbidden_privilege_tiers\": [\"operator_sensitive\"],
\"forbidden_destructive\": true,
\"max_steps\": 1000,
\"max_time_ms\": 15000
}
Regardless of what the caller sends:
- if the server grant doesn’t allow an adapter, the policy can’t resurrect it;
forbidden_destructive: truewill reject any adapter markeddestructive;- runtime limits will be no higher than what the server profile permits.
The runtime responds with a structured 403 and a list of violations if the compiled graph tries to violate the effective policy — without executing the workflow.
Why this is better than “just prompts”
Prompt-level guardrails (“please don’t call dangerous tools”) are fragile:
- they rely on the model behaving as instructed;
- they aren’t machine-checkable;
- they’re hard to audit over time.
Capability grants give you:
- a formal, checkable contract about what a runtime instance can and cannot do;
- clear separation between:
- what capabilities exist (adapter manifest),
- which are enabled in a given environment (security profile),
- how they are further restricted per request (policy);
- enforcement in code, not in vibes.
For security and platform teams, this is a much more familiar, auditable model.
Using capability grants with MCP
The same model applies to the MCP server (ainl-mcp):
AINL_MCP_PROFILEchooses a base security profile for MCP tools;AINL_MCP_EXPOSURE_PROFILEand env vars likeAINL_MCP_TOOLS/AINL_MCP_RESOURCESlimit which tools/resources are even visible;- hosts (e.g. Gemini CLI, Claude Code) can treat AINL as a least-privilege workflow tool, not a backdoor into your infrastructure.
This lets you do things like:
- expose only
ainl_validateandainl_compileto IDEs (noainl_run); - expose only
ainl://adapter-manifestandainl://security-profilesto security dashboards; - run
ainl-mcpbehind a gateway with its own policies on top.
When to reach for capability grants
You should care about capability grants if:
- you run AI workflows in shared or multi-tenant environments;
- you have strict requirements around:
- outbound network control,
- secrets exposure,
- auditability and change review;
- you want a model that security engineers and SREs can reason about.
AINL’s capability grant model is not a silver bullet, but it is:
- safer by default than “agent + god token”;
- explicit about where security decisions live;
- consistent across runner and MCP surfaces.
If you already run AINL, the next step is to:
- Pick an appropriate security profile in
tooling/security_profiles.json. - Set
AINL_SECURITY_PROFILE(runner) and/orAINL_MCP_PROFILE(MCP) in your environments. - Start tightening policies per environment, rather than relying solely on prompts and human convention.
It’s a small change in configuration, but a big one in how seriously you can take AI runtimes as part of your production infrastructure.
