
Security News
Happy Birthday, Shai-Hulud
It has been one year since Shai-Hulud made its first appearance on npm.
@keychains/server-sdk
Advanced tools
Server SDK for Keychains.dev (BETA) — register trusted apps, create permissions, mint tokens, and make proxy calls via JWKS authentication
BETA — This SDK is under active development. APIs may change.
Server SDK for Keychains.dev — give your server-side app secure access to user credentials (OAuth tokens, API keys) through the Keychains proxy.
npm install @keychains/server-sdk
npx @keychains/server-sdk register myapp.com
The register script will:
jwks.json to the right location for your projectKEYCHAINS_* env vars to your .envimport { KeychainsApp } from '@keychains/server-sdk';
const keychains = KeychainsApp.fromEnv();
Everything is transparent until the proxy request — identical to how keychains curl works. A wildcard permission is created automatically, and the auth URL only surfaces when the user actually needs to connect a service.
// One-liner — fetch through the proxy:
try {
const res = await keychains.forUser(userId).withTask('Project Albatros').fetch('https://api.github.com/user');
const data = await res.json();
} catch (err) {
if (err instanceof KeychainsAppError && err.approvalUrl) {
// User hasn't connected GitHub yet — show them this URL
console.log(`Please authorize: ${err.approvalUrl}`);
}
}
Or step by step:
// Get a token (creates a wildcard permission if needed)
const { token } = await keychains.forUser(userId).withTask('Project Albatros').getToken();
// Use the token for one or more proxy requests
const res = await keychains.fetch('https://api.github.com/user', { permissionToken: token });
When to use: Quick prototyping, AI agents, cases where you don't know upfront which services the user will need.
Create a scoped permission with specific scopes. The user approves upfront, then all subsequent requests go through without interruption.
const scopes = ['gmail.com::oauth2::read', 'linear.app::oauth2::read'];
const permission = await keychains.forUser(userId).withTask('Project Albatros').getPermission(scopes);
// Show approval URL to the user
console.log('Please approve at: ' + permission.approvalUrl());
// Check if approved (poll or after redirect)
if (await permission.isApproved()) {
const res = await permission.fetch('https://gmail.googleapis.com/gmail/v1/users/me/messages');
// or: const { token } = await permission.getToken();
}
When to use: Apps with explicit user consent UIs, when you know exactly which services are needed.
KeychainsApp// From environment variables (recommended)
const keychains = KeychainsApp.fromEnv();
// Manual configuration
const keychains = new KeychainsApp({
domain: 'myapp.com',
privateKey: process.env.KEYCHAINS_PRIVATE_KEY!,
keyId: 'key-1',
serverUrl: 'https://keychains.dev', // optional
});
keychains.setAppId(process.env.KEYCHAINS_APP_ID!);
| Method | Returns | Description |
|---|---|---|
.forUser(userId) | UserContext | Scope operations to a user |
.forUser(id).withTask(name) | TaskContext | Scope to a user + task |
.forUser(id).withTask(name).fetch(url, opts?) | Response | Wildcard token + proxied fetch |
.forUser(id).withTask(name).getToken() | TokenResult | Wildcard token for manual fetch |
.forUser(id).withTask(name).getPermission(scopes?) | Permission | Scoped permission for pre-approval |
.forUser(id).getTokenForTask(name) | TokenResult | Shortcut for .withTask(name).getToken() |
.forUser(id).getPermissionForTask(name, scopes) | Permission | Shortcut for .withTask(name).getPermission(scopes) |
| Method | Description |
|---|---|
.createPermission(opts) | Create a permission request |
.listPermissions(appUserId) | List permissions for a user |
.getPermissionStatus(id, appUserId) | Check permission status |
.mintToken(permissionId, appUserId, ttl?) | Mint a short-lived token |
.fetch(url, { permissionToken }) | Proxied fetch with a token |
.register({ verify? }) | DNS registration flow |
.delegate(permissionId, appUserId, opts) | Delegate access to VMs |
Permission| Method | Returns | Description |
|---|---|---|
.approvalUrl() | string | undefined | URL for user to approve |
.isApproved() | boolean | Poll if permission is active |
.getToken(ttl?) | TokenResult | Mint a token |
.fetch(url, opts?) | Response | Mint token + proxied fetch |
import { KeychainsAppError } from '@keychains/server-sdk';
try {
const res = await keychains.forUser(userId).withTask('My Task').fetch(url);
} catch (err) {
if (err instanceof KeychainsAppError) {
if (err.approvalUrl) {
// User needs to authorize — redirect or show the URL
console.log(`Authorize: ${err.approvalUrl}`);
} else {
console.error(`${err.code}: ${err.message}`);
}
}
}
| Code | Description |
|---|---|
authorization_required | Proxy request needs user approval (has approvalUrl) |
missing_env | Required environment variables not set |
not_registered | App ID not set — run register or call setAppId() |
unauthorized | Invalid or expired JWT |
not_found | Permission or app not found |
forbidden | Permission revoked or app revoked |
npx @keychains/server-sdk register myapp.com
This generates a keypair, writes jwks.json, and walks you through DNS verification.
Your domain must serve the JWKS file at:
https://myapp.com/.well-known/keychains.dev/jwks.json
For Next.js / static sites, the register script places it in public/ by default.
Add a TXT record as instructed by the script. Once verified, your app is registered and the env vars are written to .env.
Delegate access to VMs or sub-agents:
const delegate = await keychains.delegate(permissionId, userId, {
publicKey: vmPublicKey,
scopes: ['github.com::oauth2::repo'],
});
// Or use bootstrap token for self-registration
const delegate = await keychains.delegate(permissionId, userId, {
useBootstrapToken: true,
scopes: ['github.com::oauth2::repo'],
});
// delegate.bootstrapToken → send to VM
| Variable | Required | Description |
|---|---|---|
KEYCHAINS_DOMAIN | Yes | Your registered domain |
KEYCHAINS_PRIVATE_KEY | Yes | PEM-encoded private key |
KEYCHAINS_KEY_ID | Yes | Key ID (kid) from your JWKS |
KEYCHAINS_APP_ID | No | App ID (auto-set by register script) |
KEYCHAINS_SERVER_URL | No | Override server URL (default: https://keychains.dev) |
Proprietary — Interagentic Inc.
FAQs
Server SDK for Keychains.dev (BETA) — register trusted apps, create permissions, mint tokens, and make proxy calls via JWKS authentication
We found that @keychains/server-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.