Security and trust
Security and trust
BehalfID is designed to verify agent actions before they happen, keep secrets out of public views, and make denied actions fail closed when integrated.
This page explains the enforcement model, what secrets are stored and how, the public passport design, and the current known limitations.
Enforcement model
BehalfID does not magically control an agent by itself. The app or provider must call BehalfID before acting. If the action is denied, the integration should fail closed and not execute the action.
Fail closed means the agent throws on denial and the code that would execute the action never runs. The opposite — fail open — would let the agent proceed if the check fails or is unavailable. BehalfID's recommended enforcement pattern is always fail closed.
const result = await behalf.verify({
agentId,
action: "purchase",
vendor: "coachella.com",
amount: 742
});
if (!result.allowed) {
throw new Error(`Blocked by BehalfID: ${result.reason}`);
}
// Only execute the action after this point.Enforcement only works when the integration calls BehalfID before acting and respects the response. See the live sandbox for a browser-based demonstration.
Manual mode limitations
Manual mode is for testing with existing agents — Ollie, ChatGPT, Claude, Zapier, Make, or others — without requiring the provider to integrate BehalfID.
- Passport links let an agent or human read the allowed scopes for one agent. The agent can read what it is permitted to do and use those constraints to guide its decisions.
- Fragment token limitation. Passport links use a
#token=…URL fragment, keeping the token out of server logs and referrer headers. However, most AI agents — Gemini memory, ChatGPT system prompts, Claude project instructions — do not execute JavaScript or send authorization headers and cannot retrieve the scoped data. For these agents, use the copyable blocks on the passport page. - Agent memory block (best-effort). A structured plain-English copy of the active scopes. Paste into a memory field or system prompt. Some assistants compress or ignore saved memory and may not preserve exact scopes — treat this as best-effort, not a reliable enforcement boundary.
- Per-task permission prompt (more reliable). Paste this directly into the same chat where the agent is about to act. It includes the full scope list, a blocked actions section, and asks the agent to answer three questions before proceeding. More reliable than memory because it is in the active context window, not stored state.
- Manual mode does not automatically enforce behavior inside a third-party provider. If an agent reads the passport and ignores the constraints, BehalfID cannot stop it.
- Developer integration — calling
behalf.verify()before every action — remains the only path to automatic enforcement.
Secrets and tokens
Shown once at creation or rotation and never again. Only a SHA-256 hash is stored. If you lose the key, rotate it to get a new one.
A separate bhf_pass_token scoped to one agent. Passport tokens allow viewing the agent's active permission scopes and running manual previews. They are not API keys.
Create or edit permissions, rotate API keys, view audit logs, access developer accounts, or read webhook secrets. Treat the passport link like a secret — anyone with the token can read the allowed scopes.
Shown once at creation or rotation. Stored as a derived hash. Only a preview prefix is shown after creation. Receivers verify signatures with verifyWebhookSignature from the SDK.
Hashed with scrypt. The developer portal requires at least 10 characters. Sessions use HTTP-only cookies backed by the database.
API key hash comparisons use crypto.timingSafeEqual to prevent timing-based enumeration.
Public passport safety
Passport pages intentionally expose active allowed scopes. That is the point: an external agent needs to read what it is allowed to do before acting.
What the passport page exposes:
- Agent name, type, and provider metadata.
- Active permission scopes: action, resource, allowed actions, blocked actions, requires-approval flag, expiration.
- Passport version and mode label.
What the passport page never exposes:
- API keys or key hashes.
- Developer email, account ID, or session data.
- Webhook secrets or endpoint URLs.
- Audit logs or verification history.
- Internal MongoDB IDs.
- Revoked or expired permissions.
Audit logs
Every authenticated verification decision is logged with a stable requestId, the decision result, reason, risk level, action, vendor, and amount when provided.
- Logs help developers debug allowed and denied decisions.
- Logs are scoped: agent API keys can only read logs for their own agent. Dashboard users only see logs for their own agents.
- Manual passport previews do not create audit log entries by design. Only integrated
POST /api/verifycalls are logged. - Optional
metadatais only stored whenBEHALFID_LOG_METADATAis not set tofalse. Action, vendor, and amount are always stored and may be sensitive.
Webhooks
BehalfID signs every webhook event with HMAC-SHA256 over timestamp.rawBody using the stored derived key from the whsec_ secret. Verify signatures with verifyWebhookSignature from @behalfid/sdk before processing any event.
- Events are persisted to an outbox before delivery and retried with a capped five-attempt backoff.
- Failed events reach a dead-letter state after five attempts. Developers can replay them from the console.
- Delivery is at-least-once, not exactly-once. Receivers must deduplicate events by
eventIdto handle replays safely. - Webhook payloads never include API keys, setup tokens, webhook secrets, or rotated keys.
- Webhook URL validation requires
https://in production and rejects obvious localhost or private IP destinations.
Revocation
All critical objects can be invalidated without waiting for expiration.
The next verify call for that action returns denied immediately.
All verify calls for that agent are denied while it is disabled.
The old key stops working at the next request. A new key is shown once.
The old passport token becomes invalid. Anyone with the old link can no longer read scopes or run previews.
Current limitations
BehalfID is a prototype. These are real limitations worth knowing before deploying in sensitive environments.
- No provider-native integrations yet. Connected agents are represented manually inside BehalfID — BehalfID does not call Ollie, ChatGPT, Claude, or Zapier APIs on your behalf.
- Manual mode depends on user and agent cooperation. The external agent must read the passport and respect the listed constraints; BehalfID cannot enforce behavior inside third-party providers.
- No enterprise SSO or team roles yet. The admin console uses one shared password. The developer portal has individual accounts but no organizations.
- No formal external security audit yet. BehalfID is suitable for constrained deployments and demos, not open public multi-tenant use without further hardening.
- Not a replacement for app-level authorization. BehalfID is a pre-action verification layer. Your app still needs its own auth, input validation, and access control.
- Rate limiting falls back to process memory without Upstash Redis. In serverless environments, in-memory counters are not shared across instances.
See Concepts for the full model and the project README for production deployment recommendations.
Contact
Questions or security concerns? Contact the project owner through GitHub or open an issue in the project repository.