Corsair Makes the Approval Gate a Database Row Your Agent Cannot Reach
An open-source integration layer where the agent never sees your API keys and destructive calls stall on a pending row in your own database. The mechanism is good. The part its documentation offers next is where the human comes out.
Read the FAQ in corsairdev/corsair and one answer does more work than the rest of the repo:
Can the agent go around the permission request? No. Corsair creates a permission request in a database the agent doesn't have access to. Your agent cannot get past the permission request until that database row is set to
approved.
That is a different claim than every permission system built on a prompt. There is no instruction telling the model to ask first, no system-prompt rule the model can be talked out of, no approval flag living in a file the agent can edit. There is a row, and the row is somewhere the agent has no reach.
Corsair is an Apache 2.0 project, almost entirely TypeScript, and it hit the trending boards this week alongside four vendor launches that all did the same thing from the other direction: take the safety decision away from a human clicking allow and hand it somewhere else. Corsair is the open-source version of that move, and it is worth reading precisely because you can read it.
What the mechanism actually is
Corsair runs as a library inside your own app. You install the core package plus a plugin per service, hand it a database and an encryption key, and every integration becomes a typed client.
import { createCorsair } from 'corsair';
import { github } from '@corsair-dev/github';
import { slack } from '@corsair-dev/slack';
export const corsair = createCorsair({
plugins: [slack(), github()],
database: db,
kek: process.env.CORSAIR_KEK!,
});
Credentials live in your database under envelope encryption. A key encryption key you control encrypts per-tenant data keys, and those encrypt the actual secrets. Corsair resolves them internally at call time. The agent sees method names and results, never the token. That alone removes a category of leak, because a context window that never contained a key cannot leak one when somebody publishes a trajectory.
The permission layer is the more interesting half. Every plugin endpoint carries a risk level of read, write, or destructive. Each integration gets a mode, and the mode is a mapping from risk level to policy:
| Mode | Read | Write | Destructive |
|---|---|---|---|
open |
allow | allow | allow |
cautious |
allow | allow | require approval |
strict |
allow | require approval | deny |
readonly |
allow | deny | deny |
Set GitHub to strict and Slack to cautious, then override individual endpoints where the mode is wrong for you. "repositories.delete": "deny" never runs even with an approval. Invalid override paths fail at compile time, which is a small detail that says something about the care in this codebase.
When a call resolves to require_approval, Corsair writes a row to a corsair_permissions table with a 64-character hex token and the call's arguments frozen as JSON. Approval replays those exact arguments. The agent does not get a second chance to rewrite the email body between the moment you approved it and the moment it sends. Approvals are single-use, and once the endpoint runs the record moves to completed and cannot be replayed.
Two design choices in there deserve applause.
The first is failing closed. Straight from the permissions docs: "Permissions require a database. Without corsair_permissions, any endpoint that needs approval falls back to deny." Forgetting the migration breaks your agent loudly instead of opening it silently. That is the correct direction for a mistake to fall, and plenty of security tooling gets it backwards.
The second is the frozen-args replay. Most approval interfaces show you a rendering of what the model intends and then re-ask the model to do it. Corsair shows you the arguments and then executes those arguments, with executePermission running the stored call directly and no model involved in the second half.
Where the human comes out
Now the part I keep thinking about.
Approve and deny in Corsair are a SQL UPDATE on a status column. The docs say so, with the statement written out. The corsair.permissions namespace deliberately exposes only reads and leaves the approve and deny transitions to you, because how you approve is your business.
And then the docs list two common patterns for building that review flow. The first is a page in your app with Approve and Deny buttons. The second, quoted directly:
Automated reviewer agent. "Send the pending request to a second agent that evaluates whether the action is safe, then programmatically sets
statustoapprovedordenied. Useful when you want policy checks without a human in the loop for every write."
I do not think that is a mistake, and I am not going to pretend it is scandalous. It is the honest consequence of good architecture. Once the gate is a data structure rather than a dialog, whoever can write that row is the approver, and the design has no opinion about whether that is a person. Corsair built a clean interface and left the hard question on the other side of it.
Which puts the security of the whole system in a place the README does not discuss: the authorization on your review route. The token is described as the public handle embedded in review URLs. If your /approve/:token endpoint checks the token and nothing else, then your agent's destructive-action gate is a link, and links travel. That is your code to write and your mistake to make, and it is the first thing I would review in any Corsair deployment.
There is also a configuration flag worth knowing about. Pending requests expire, defaulting to ten minutes, and onTimeout accepts either deny or approve. The docs recommend deny and label approve as suitable only for low-risk, fully trusted environments. It is one string in a config object that converts silence into consent. Set it wrong once and your gate becomes a delay.
Put this into practice
The lowest-friction path is one service, one afternoon.
Install the core package and a single plugin for something you already automate. The quick start creates Corsair's four core tables; the permissions table is a separate migration the docs give you as its own SQL block, and you want it in place before your first gated call, because without it every endpoint needing approval denies and you will spend twenty minutes confused.
Start tighter than feels comfortable. Put strict on anything that touches customers and cautious everywhere else, then loosen with overrides as you learn which endpoints you actually approve without reading. The endpoints you always rubber-stamp are the ones to demote to allow deliberately, rather than by wearing down.
Set timeout: "30m", onTimeout: "deny", and mode: "asynchronous". Asynchronous is the default for a reason the docs are candid about: synchronous mode polls the table every 500 milliseconds while the tool call hangs, and most agent harnesses kill long-running tool calls before you can walk back to your desk.
Put the review route behind your real authentication. Not the token. Your actual session auth, with the token as the lookup key rather than the credential.
If you want to try it against a coding agent instead of your own app, the MCP adapter wires into Claude Code or Cursor through .mcp.json or claude mcp add, and permissions gate those calls the same way.
Honest limitations
The integration count depends on who is talking. The live catalog at api.corsair.dev/md/integrations listed 61 integrations when I pulled it, running from Airtable to Zoom. The repo's own AGENTS.md says roughly 70 plugin packages. The docs invite you to browse "Slack, Linear, Gmail, GitHub, HubSpot, Stripe, and hundreds more." Sixty-odd is a genuinely useful set and it is not hundreds. Check the catalog for the service you actually need before you plan around this.
I am also not going to quote you a star count. GitHub's rendered repo page gave me one number, a second fetch in the same session gave a number five times higher, and the project's own site shows a third. Somebody's cache is stale and I cannot tell whose, so treat popularity figures for this repo as noise and judge it on the code.
It is TypeScript, and only TypeScript. Python agent stacks reach it through the MCP adapter or the Hub REST API, which works but is not the typed-client experience the README is selling.
The gate covers the API-call boundary and nothing else. Your agent's shell access, filesystem writes, and any SDK you call directly are all outside it, and the FAQ says so plainly: use Corsair where the permission layer helps and drop down to individual SDKs when you need custom logic. Every one of those drops is a path around the gate that your own code opened.
Risk levels ship inside each plugin. Whether posting a Slack message counts as a write or a destructive action is a maintainer's judgment call, repeated across dozens of integrations, and your entire mode matrix inherits those judgments. Read the risk labels for the endpoints you care about rather than trusting cautious to mean what you assume.
The repo is young and moving, with a healthy stack of open issues and pull requests, and there is a Hub product alongside the open-source library that handles hosted OAuth, connect pages, and approval surfaces. The docs say Hub stores none of your credentials and that self-hosting those surfaces stays fully supported. That is the right answer, and it is also the answer every company gives before the hosted path becomes the maintained one.
Last, the thing no architecture fixes. Anthropic published data this month showing users approve 97% of permission prompts in Claude Code. Corsair makes the gate unavoidable. It does not make you read it.
What to do with this
Go look at the one integration where your agent has write access to something customers can see. Find out today what stops a bad call: a prompt instruction, a code review, or nothing.
If the answer is nothing, Corsair is one afternoon and a database table. If the answer is a prompt instruction, it is the same afternoon and a considerably better night's sleep.
Sources: corsairdev/corsair on GitHub, Corsair permissions documentation, Corsair introduction, Corsair plugins guide, Corsair integrations catalog, Anthropic on auto mode.