npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@imbue-ai/detent

v1.3.0

Published

Fine-grained HTTP permissions for AI agents.

Readme

Detent

npm CI license downloads

Fine-grained HTTP permissions for AI agents.

Quick example

# Store rules.
echo '{
  "rules": [
    {"github-rest-api": ["github-read-all", "github-write-issues"]},
    {"slack-api": ["slack-read-all"]}
  ]
}' > ~/.config/detent/config.json

# Check a request before sending.
# (Exit code: 0 = approved, 1 = rejected.)
detent curl -s https://api.github.com/repos/octocat/Hello-World/issues/1

Installation

npm install -g @imbue-ai/detent

(This is not needed if you only intend to use Detent as a JavaScript library.)

Motivation

Users of AI agents sometimes give them access to services like Slack, Google Drive, GitHub and others. Giving agents full access is unnecessarily risky - the best practice in most cases is giving only the necessary level of access. In practice, that can be challenging, especially for services that do not offer native granular permissions.

Detent is meant to address this. Users or developers can easily specify permissions, from broad ones ("only read access to my Slack") to more specific ones ("only read access to GitHub issues for this one repository").

Integrations

Checking permissions is only useful if the results are respected. To be effective, Detent needs to be integrated into whatever tool the agent uses to access third-party services.

Latchkey

Latchkey lets users point to a Detent config in order to control what agents can and can't access.

Details and architecture

Detent is a command line tool and a Typescript library. It allows users and developers to:

  1. Define named permissions for outgoing HTTP requests.
  2. Check that a given request is allowed given the defined permissions.

Usage

Store your permission setup in a config file as documented below. Then assemble a curl invocation for an HTTP request and prepend it with the detent command (detent curl ...) to check whether it's allowed. Detent returns 0 if the request is allowed based on the config, 1 if it's not allowed and 2 or higher in case of errors. No requests are actually sent.

Alternatively, use detent as a library:

import { check } from "detent";

const request = new Request("https://api.example.com/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "alice" }),
});

const result = await check(request);

Configuration

All the configuration goes to ~/.config/detent/config.json (or $XDG_CONFIG_HOME/detent/config.json if XDG_CONFIG_HOME is set). Use the DETENT_CONFIG environment variable to specify a different path.

Matching requests

An HTTP(s) request can be represented as an object that has several well-defined properties: protocol, domain, port, path, method, headers, queryParams and body. Using this representation, the detent tool uses JSON schema to:

  1. Match requests to permission checks.
  2. Define the "acceptable" shape of a request that is subject to a permission check.

Some of the fields are normalized to canonical form before matching: method is always uppercase (e.g. "GET"), protocol, domain and headers keys are always lowercase (e.g. "content-type").

Request schemas

We use JSON schema for matching and validating request objects. For example:

{
  "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 Detent config, schemas are identified by names, like this:

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

For a complete example config that defines custom schemas, see docs/example-cloudflare.json.

Permission rules

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

{
  "schemas": {...},
  "rules": [
    {"github-api": ["github-read-issues-detent", "github-write-comments-detent", ...] },
    {"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 Detent 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.

Hooks (custom executable checks)

In addition to the plain list of schemas shown above, a rule's value can be an object with schemas and/or hooks:

{
  "rules": [
    {
      "github-rest-api": {
        "schemas": ["github-read-issues-detent"],
        "hooks": ["./check-actor.sh", "audit-log"]
      }
    }
  ]
}

Semantics:

  • schemas works exactly like the plain list form: the request is approved (by this gate) if any listed schema matches the decomposed request.
  • hooks is a list of executables; the request is approved (by this gate) only if all of them exit 0.
  • When both fields are present, both must succeed (AND).
  • Hooks run before schemas.

Each hook is invoked with a single argument: the path to a temporary JSON file containing the decomposed request (the same shape used by schema validators - see the request schema fields listed above). Hooks must follow detent's exit-code convention:

  • 0: allowed by this hook
  • 1: rejected by this hook
  • 2 or higher: error; the whole detent invocation aborts with that exit code (no further rules are evaluated)

Hook paths are resolved at execution time, in this order:

  1. Absolute path: used as-is.
  2. Path containing a separator: resolved relative to the directory of the config file that defined the rule.
  3. Bare name: looked up via $PATH.

Within a single rule, hooks run sequentially in array order. Evaluation short-circuits on the first non-zero exit: a 1 rejects the rule (no further hooks are run) and a 2+ aborts the whole detent invocation with that exit code. When the library API (check) hits a hook with exit code 2+, it throws a HookExecutionError carrying the offending hook spec and exit code; the CLI re-uses that exit code.

Built-in schemas

Detent 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 docs/builtin-schemas.md for the full list

Run detent dump to see your current config together with all the available built-in schemas. If you only want to list the schema names, run detent dump | jq '.schemas | keys'.

If you don't want to use the built-in schemas, set the DETENT_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.

{
  "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.

Contributing

Contributions of all kinds are welcome!

Disclaimer

We're providing the preconfigured schemas for convenience, but it's likely that some of them may not work entirely as intended. We hope that the community will help us refine the built-in permission definitions over time. In the meantime, preferably double-check built-in definitions before using them, and when possible, use API tokens with reduced permission scopes.

We still think the tool is useful in its current form as a protection against accidental agent actions and the first line of defense against malicious or compromised agents.