# Permissions

Giving agents full access to connected services is usually unnecessarily risky. Some services let you generate API tokens constrained to specific scopes. What if that's not an option, though? For example, when the service doesn't support scopes or when they aren't granular enough?

That's when you can use Latchkey's permission system.

## Quick start

To add a check for all outgoing `latchkey curl` calls, start by creating a `permissions.json` file in your `LATCHKEY_DIRECTORY` (`~/.latchkey` by default). Here's a simple example:

```json
{
  "rules": [
    {"google-gmail-api": ["google-gmail-read-all"]},
    {"slack-api": ["slack-read-all"]}
  ]
}
```

With this example, only read access to Gmail and Slack APIs will be allowed. No requests will be allowed to any other domains.

Ideally, make the file read-only: `chmod -w ~/.latchkey/permissions.json`. That's it! Latchkey will now check every `latchkey curl` request against these rules before passing it through.

## How it works

### Request schemas

The underlying library for agent authorization, [Detent](https://github.com/imbue-ai/detent), uses JSON schema for matching and validating request objects. For example:

```json
{
  "properties": {
    "method": { "const": "GET" }
  },
  "required": ["method"]
}
```

This would match all GET requests, regardless of the domain, path, or anything else. Schemas must use normalized field values (uppercase for methods etc.).

In the permissions config, schemas are identified by names, like this:

```json
{
  "schemas": {
    "github-api": {
      "properties": {
        "domain": { "const": "api.github.com" }
      },
      "required": ["domain"]
    },
    "github-read-issues-latchkey": {
      "properties": {
        "method": { "const": "GET" },
        "path": {
          "type": "string",
          "pattern": "^/repos/imbue-ai/latchkey/issues(/[0-9]+)?$"
        }
      },
      "required": ["method", "path"]
    },
    ...
  }
}
```

### Permission rules

Once defined, request schemas can be combined in a two-level rules hierarchy, like this:

```json
{
  "schemas": {...},
  "rules": [
    {"github-api": ["github-read-issues-latchkey", "github-write-comments-latchkey", ...] },
    {"slack-api": ["slack-read-all"] }
  ]
}
```

In each rule, the key defines scope ("should a given request be subject to this rule") and each value represents a list of permissions allowed by this rule.

This is the meaning of the rules in the example above:

* When accessing the GitHub API, the only allowed actions are reading issues and writing comments in the Latchkey repository.
* When accessing the Slack API, only read actions are allowed.
* No other requests are allowed.

### Rule resolution, default outcomes

When a request gets checked, the rules in your config are simply evaluated from top to bottom. The first rule whose scope matches the request determines the outcome: if the request matches any of the permissions in the rule, it's approved. Otherwise, it's rejected. Further rules are not evaluated. By default, requests that don't match any rule get rejected. If you want to allow requests by default, append the `{"any": ["any"]}` rule to the end of your rule list.

### Built-in schemas

The permission system comes with a number of preconfigured schemas out of the box that are automatically available and recognized in rule bodies:

* `any` (to match and allow any and all requests)
* `aws-s3` (to identify requests going to AWS S3)
* `aws-s3-read` (to allow read operations on AWS S3)
* `stripe-read-all` (to allow all read operations in Stripe API)
* `google-drive-write-comments` (to allow adding comments to Google Drive items)
* ... and many others, see [the underlying library's docs](https://github.com/imbue-ai/detent/blob/main/docs/builtin-schemas.md) for the full list

If you don't want to use the built-in schemas, set the `LATCHKEY_PERMISSIONS_DO_NOT_USE_BUILTIN_SCHEMAS` environment variable to a non-empty value.

### Including other config files

Use the `include` key to split your configuration across multiple files. Paths are resolved relative to the directory of the config that contains the `include`.

```json
{
  "include": ["shared/example.json", "shared/another_example.json"],
  "rules": [
    {"github-rest-api": ["github-read-issues"]}
  ]
}
```

Included configs are merged recursively: schemas and rules from all included files are collected first (in list order), then the including config's own schemas and rules are applied on top. This means the parent config's schemas override equally-named included schemas, and its rules are evaluated after included rules. Circular includes are detected and rejected.

### Recommendation

For the highest security guarantees, use the permission system in conjunction with Latchkey's [gateway mode](/latchkey/basics/gateway.md) while running agents in isolated sandboxes.

Even without that, the permission system should still help users and developers control their AI agents and reduce the odds of agents accidentally performing harmful actions or getting exposed to untrusted content.

## A more advanced example: Cloudflare API permissions

```json
{
  "schemas": {
    "cloudflare-api": {
      "properties": {
        "domain": { "const": "api.cloudflare.com" },
        "path": {
          "type": "string",
          "pattern": "^/client/v4/"
        }
      },
      "required": ["domain", "path"]
    },
    "cloudflare-read-zones": {
      "properties": {
        "method": { "const": "GET" },
        "path": {
          "type": "string",
          "pattern": "^/client/v4/zones(/[0-9a-f]{32})?$"
        }
      },
      "required": ["method", "path"]
    },
    "cloudflare-read-dns": {
      "properties": {
        "method": { "const": "GET" },
        "path": {
          "type": "string",
          "pattern": "^/client/v4/zones/[0-9a-f]{32}/dns_records(/[0-9a-f]{32})?$"
        }
      },
      "required": ["method", "path"],
      "if": {
        "properties": {
          "path": {
            "type": "string",
            "pattern": "/dns_records$"
          }
        },
        "required": ["path"]
      },
      "then": {
        "properties": {
          "queryParams": {
            "type": "object",
            "propertyNames": {
              "enum": ["type", "name", "content", "page", "per_page", "order", "direction", "match"]
            }
          }
        }
      }
    },
    "cloudflare-write-dns": {
      "properties": {
        "method": { "enum": ["POST", "PUT", "PATCH", "DELETE"] },
        "path": {
          "type": "string",
          "pattern": "^/client/v4/zones/[0-9a-f]{32}/dns_records(/[0-9a-f]{32})?$"
        }
      },
      "required": ["method", "path"],
      "not": {
        "properties": {
          "path": {
            "type": "string",
            "pattern": "/dns_records$"
          },
          "method": { "const": "DELETE" }
        },
        "required": ["path", "method"]
      }
    },
    "cloudflare-purge-cache": {
      "properties": {
        "method": { "const": "POST" },
        "path": {
          "type": "string",
          "pattern": "^/client/v4/zones/[0-9a-f]{32}/purge_cache$"
        }
      },
      "required": ["method", "path"]
    }
  },
  "rules": [
    {
      "cloudflare-api": [
        "cloudflare-read-zones",
        "cloudflare-read-dns",
        "cloudflare-write-dns",
        "cloudflare-purge-cache"
      ]
    }
  ]
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.imbue.com/latchkey/basics/permissions.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
