
Company News
Free Business Plan Upgrades for Open Source Maintainers
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.
@opena2a/aim-sdk
Advanced tools
Official TypeScript SDK for Agent Identity Management (AIM) - secure identity verification for AI agents.
Managed hosting available at aim.opena2a.org/get-started. Self-host instructions below.
npm install @opena2a/aim-sdk
# or
yarn add @opena2a/aim-sdk
# or
pnpm add @opena2a/aim-sdk
import { AIMClient, AgentType } from '@opena2a/aim-sdk';
// Create client
const client = new AIMClient({
baseUrl: 'https://aim.example.com',
apiKey: process.env.AIM_API_KEY,
});
// Register an agent
const agent = await client.registerAgent({
name: 'my-ai-agent',
displayName: 'My AI Agent',
agentType: AgentType.LANGCHAIN,
capabilities: ['file:read', 'api:call'],
});
// Verify an action before execution
const result = await client.verifyAction({
action: 'file:read',
resource: '/data/config.json',
});
console.log(`Action allowed: ${result.actionAllowed}`);
console.log(`Trust score: ${result.trustScore}`);
import express from 'express';
import { createAIMMiddleware, verifyAction, aimErrorHandler } from '@opena2a/aim-sdk/express';
const app = express();
// Add AIM middleware globally
app.use(createAIMMiddleware({
baseUrl: 'https://aim.example.com',
apiKey: process.env.AIM_API_KEY,
}));
// Verify specific actions on routes
app.post('/api/data',
verifyAction('data:write'),
(req, res) => {
// Action has been verified
res.json({ success: true });
}
);
// Access AIM context in handlers
app.get('/api/profile', (req, res) => {
const { agentId, trustScore } = req.aim ?? {};
res.json({ agentId, trustScore });
});
// Optional: map SDK errors thrown in later handlers to HTTP responses
// (ActionDeniedError -> 403, AuthenticationError -> 401). aimErrorHandler IS
// the four-argument handler — pass it to app.use, do not call it.
app.use(aimErrorHandler);
import Fastify from 'fastify';
import { aimPlugin, verifyAction } from '@opena2a/aim-sdk/fastify';
const fastify = Fastify();
// Register the AIM plugin
await fastify.register(aimPlugin, {
baseUrl: 'https://aim.example.com',
apiKey: process.env.AIM_API_KEY,
});
// Verify actions with preHandler hook
fastify.post('/api/data', {
preHandler: verifyAction('data:write'),
}, async (request, reply) => {
return { success: true };
});
An ATX (Agent Trust eXtension) credential is a signed, portable credential
designed to be verified locally: the signature is checked against the issuer's
cached public key in roughly a millisecond, the issuing node is never on the
verification path, and revocation rides on an asynchronously-refreshed,
short-lived cached list. When you configure localVerification with cached trust
anchors, the client verifies a resolved credential offline and decides
authorization from the credential's own signed claims — no per-action call to a
central service. The remote verifyAction POST is retained as the fallback for
when no local credential is available.
The verifier is the shared, conformance-locked @opena2a/atx-verify — byte-for-byte
interoperable with the Go (opena2a-registry/pkg/atcverify) and Python reference
verifiers.
import { AIMClient } from '@opena2a/aim-sdk';
const client = new AIMClient({
// Cached once from AIM/the Registry; refresh the CRL off the hot path.
localVerification: {
trustedIssuers: ['did:opena2a:issuer-1'],
publicKeys: [{ algorithm: 'Ed25519', publicKeyHex: '<issuer raw ed25519 pubkey hex>' }],
// crl: { entries: [{ agentId, reason }] }, // optional cached revocation list
},
});
// Resolve the agent's ATX once (the AAP broker / network step), then cache it.
// `resolvedAtx` is the signed ATX credential returned by that broker/network step.
const resolvedAtx = await fetchAtxForAgent(); // your resolution step
client.setLocalCredential(resolvedAtx);
// Per action: verified offline, sub-millisecond, no network.
const result = await client.verifyActionLocally({ action: 'file:read' });
// or just call verifyAction(): it takes the local path automatically when a
// credential is cached, and falls back to the remote POST otherwise.
Authorization is gated on signed capabilities. ATX v1.1 credentials carry
capabilities under the signature, so they are trusted; v1.0 capabilities are
forgeable by the holder and are refused by default. Passing
requireSignedCapabilities: false to LocalVerifier.authorize overrides this,
but then authorization runs on holder-forgeable capabilities — only do this for a
closed, trusted v1.0 deployment, never across a trust boundary.
Multi-issuer anchor sets. Give each key a DID-URL
keyId(e.g.did:opena2a:authority:opena2a.org#key-1).@opena2a/atx-verifybinds a key to its controller DID, so a key may only verify credentials issued by that DID (or, for v1.1, a signedissuerChainauthority) — one trusted issuer cannot impersonate another. A key with nokeyIdfragment is unbound and eligible for any issuer: fine for a single-issuer anchor set, unsafe for a multi-issuer one.
For credential verification without the action-authorization adaptation, use the verifier directly:
const verifier = client.getLocalVerifier();
const { valid, context, rejectCategory } = await verifier!.verifyCredential(atx);
Network is reserved for credential resolution (the AAP broker hands the agent its ATX) and the periodic CRL refresh — never for a per-action decision.
A delegation is a signed statement that one key (the delegator) grants a set of
scopes to another key (the delegate), optionally chained so authority passes from
a root through intermediaries to a leaf. Delegations use did:key (Ed25519) and a
canonical signing form for cross-engine interop.
import {
generateKeyPair,
createDelegation,
verifyDelegation,
verifyDelegationChain,
} from '@opena2a/aim-sdk';
const root = await generateKeyPair();
const coordinator = await generateKeyPair();
const worker = await generateKeyPair();
// Root delegates to a coordinator, which sub-delegates a narrower scope set.
const d1 = await createDelegation({
delegatorKeyPair: root,
delegatePublicKey: coordinator.publicKey,
scopes: ['search', 'memory.read', 'memory.write'],
// expiresAt defaults to 7 days from now; pass an ISO-8601 string to set it.
});
const d2 = await createDelegation({
delegatorKeyPair: coordinator,
delegatePublicKey: worker.publicKey,
scopes: ['search', 'memory.read'], // must be a subset of the parent's scopes
parentDelegation: 'd1',
parentExpiresAt: d1.expiresAt, // a child must not outlive its parent
});
const { valid, results } = await verifyDelegationChain([d1, d2]);
// valid === true only if every hop passes: signature, delegator identity,
// scope narrowing, chain linkage, trust attenuation, AND temporal validity.
verifyDelegation and verifyDelegationChain enforce the delegation's signed
createdAt/expiresAt window. An expired delegation is rejected, and verification
fails closed on a missing, unparseable, or inverted (createdAt after expiresAt)
timestamp. A chain is evaluated against a single instant so every hop is judged by
the same clock, and a child that outlives its parent is rejected even while the
parent is still live (a delegate cannot hold authority in time beyond its
delegator). When creating a sub-delegation, pass parentExpiresAt so the child's
default expiry is capped at the parent's and an over-long child expiry is refused
at creation.
Pass an explicit evaluation time for deterministic tests or offline / as-of verification:
// Verify as the chain would have stood at a specific instant.
await verifyDelegation(d1, { verifyAt: '2026-07-15T00:00:00.000Z' });
await verifyDelegationChain([d1, d2], { verifyAt: new Date('2026-07-15T00:00:00Z') });
For the raw signature check without temporal evaluation (archival or audit
inspection where authenticity matters independent of time), use
verifyDelegationSignature; for the standalone temporal check and its reason
string, use checkDelegationTemporalValidity:
import { verifyDelegationSignature, checkDelegationTemporalValidity } from '@opena2a/aim-sdk';
await verifyDelegationSignature(d1); // true if the signature is authentic
checkDelegationTemporalValidity(d1); // { valid, error? } at the current time
checkDelegationTemporalValidity(d1, '2026-07-15T00:00:00.000Z');
The SDK can correlate why a blocked agent action happened by joining three signals around one verified action: the authorization outcome (an observed fact), the classified intent, and the injection cause (both inferences). The full correlated record is authoritative and stays on the machine; only an anonymized shared indicator is ever uploaded. This section covers the causal-denial channel only — the runtime-protection module ships a separate structural-signature channel that is on by default; see the Runtime Protection section for its scope and opt-out.
The causal-denial channel is off by default and gated by two independent opt-ins:
Capture (telemetry.enabled) — mints a correlation ID per verifyAction,
assembles records, and appends them to a local log at
~/.opena2a/correlated-events.jsonl. Nothing leaves the machine.
Share (telemetry.relay.enabled) — a best-effort relay reduces local
records to anonymized indicators and uploads only denied_injection_attempt
events to the public, count-only Registry endpoint
(POST /api/v1/telemetry/runtime).
The shared indicator carries no payloads, paths, credentials,
resource/capability names, correlation ID, agent ID, or denial reason text.
It carries only: a validated Threat Matrix techniqueId (T-NNNN, dropped
if malformed) and its source, a detection confidence, the coarse enforcement
outcome (deny/allow), the integrator's self-declared packageName/
agentCategory, daySinceInstall, runtimeEnv, triggeredAt, and a
sensorToken — a stable per-device pseudonym (sha256(host+user+local salt))
that lets the Registry de-duplicate without identifying you.
const client = new AIMClient({
baseUrl: 'https://aim.example.com',
apiKey: process.env.AIM_API_KEY,
telemetry: {
enabled: true, // stage 1: capture records locally
relay: {
enabled: true, // stage 2: upload anonymized indicators
packageName: 'acme/support-agent', // your app's public sensor label
packageVersion: '2.1.0',
// registryUrl defaults to https://api.oa2a.org
// intervalMs defaults to 60000, batchSize to 50
},
},
});
// Per-action inputs (populated by the runtime-protection module, or supplied
// directly). Ignored unless telemetry is enabled.
await client.verifyAction({
action: 'file:read',
resource: '/data/sensitive.json',
telemetry: {
intent: { intentClass: 'exfiltration', confidence: 0.7, blocked: true, source: 'nanomind-intent' },
detection: {
injectionDetected: true,
techniqueId: 'T-2002',
techniqueSource: 'interim-mapping',
confidence: 0.84,
detector: 'nanomind-guard',
detectedAt: new Date().toISOString(),
},
},
});
// Stop the internally managed flush timers when shutting down.
client.closeTelemetry();
Guarantees. Telemetry runs off the enforcement path and is best-effort: a capture, join, or upload failure is swallowed and never changes an action's verification result. When both opt-ins are off, no correlation ID is minted, no header is attached, and nothing is written or sent.
TelemetryConfig fields: enabled (capture switch), enforcementSource
(stamped on the enforcement fact, default aim-pdp), joiner (supply your own
to control the sink/lifecycle), and relay (RelayConfig: enabled,
registryUrl, packageName, packageVersion, agentCategory, dataDir,
batchSize, intervalMs, timeoutMs). The CorrelatedRelay class is also
exported for standalone use (e.g. draining the local log from a CLI).
@opena2a/aim-sdk/arp)The SDK ships the ARP (Agent Runtime Protection) engine as a subpath module. It observes an agent process from the inside — monitors and interceptors for process, network, filesystem, prompt, MCP, and A2A activity; a rule-based event engine (L0); a behavioral anomaly twin (L1); and an intelligence coordinator (L2) — and produces the detection inputs shown in the telemetry section above.
The boundary with the hackmyagent scanner: scan-time analysis (static
scanning, hardening rules, artifact classification) lives in hackmyagent;
runtime protection lives here, inside the agent process. This module is the
canonical home of the ARP engine; hackmyagent's ./arp export is being
converted to a thin re-export of it.
import { EventEngine, FilesystemMonitor, EnforcementEngine } from '@opena2a/aim-sdk/arp';
const engine = new EventEngine(config);
const monitor = new FilesystemMonitor(engine);
await monitor.start();
Interception scope. The filesystem and process interceptors patch the CJS
module registry (require('fs'), require('child_process')). Code that loads
those builtins via require or an ESM namespace/default import
(import fs from 'fs') is observed. Code that captured named ESM bindings
before the interceptor started (import { readFileSync } from 'fs') bypasses
them — ESM bindings are resolved at link time and cannot be patched. The
monitors (which poll rather than intercept) are unaffected. Start interceptors
as early as possible in the process, and treat them as one observation layer,
not a sandbox.
Classification of runtime events is supplied through the injectable
ClassificationProvider seam. The default NanoMindGuardClassificationProvider
talks to the local NanoMind-Guard daemon over a Unix socket and only accepts
signed classification results; when the daemon is absent the annotator degrades
to "no classification" — it never fabricates a label and never blocks.
Invariant: the runtime-protection module is a telemetry producer. Its
detection outputs flow through the telemetry.detection seam into the
correlated record; they never enter verifyAction's allow/deny decision.
Structural signature telemetry (on by default, opt-out). Unlike the
opt-in causal-denial channel above, the ARP engine shares structural attack
signatures by default: when a detection fires, the structural shape of the
event sequence — technique identifier, event-type sequence, severity, and a
one-way hash of structural tokens — is signed and reported to the OpenA2A
registry (https://api.oa2a.org), so an attack shape first seen at one
deployment can protect others. Prompts, model responses, tool arguments, file
contents or paths, command lines, environment values, secrets, IP addresses,
hostnames, and account, tool, agent, and model names are never shared. A
one-time disclosure is printed before first collection, and every payload is
appended to a local audit log (~/.opena2a/telemetry-audit.log, JSONL) before
it is sent. Opt out with any one of:
OPENA2A_TELEMETRY_OPTOUT=1 (or ARP_TELEMETRY_DISABLED=1) in the
environment,signatureTelemetry: { enabled: false } in your ARP config, orwriteOptOutMarker() from @opena2a/aim-sdk/arp, which persists
~/.opena2a/telemetry-optout across processes.Any one of these is the runtime-protection module's master switch: it disables
every telemetry channel the module can produce (structural signatures and the
opt-in legacy GTIN runtime channel). The causal-denial channel above is
controlled solely by its own telemetry client config and is off unless you
enabled it. To also delete signatures this sensor already shared, call
purgeRemoteSignatures() (right-to-delete; best-effort, never blocks the
local opt-out).
The SDK automatically reads from these environment variables:
| Variable | Description |
|---|---|
AIM_BASE_URL | AIM server base URL |
AIM_API_KEY | API key for authentication |
AIM_ORGANIZATION_ID | Organization ID |
AIM_AGENT_ID | Pre-registered agent ID |
AIM_PRIVATE_KEY | Ed25519 private key (base64) |
AIM_PUBLIC_KEY | Ed25519 public key (base64) |
AIM_DEBUG | Enable debug logging (true/false) |
const client = new AIMClient({
baseUrl: 'https://aim.example.com', // AIM server URL
apiKey: 'your-api-key', // API key
organizationId: 'org-uuid', // Organization ID
autoRegister: true, // Auto-register if not registered
timeout: 30000, // Request timeout in ms
debug: false, // Debug logging
headers: {}, // Custom headers
});
The SDK supports various agent types:
import { AgentType } from '@opena2a/aim-sdk';
// LLM Providers
AgentType.CLAUDE
AgentType.GPT
AgentType.GEMINI
// Frameworks
AgentType.LANGCHAIN
AgentType.CREWAI
AgentType.AUTOGEN
// Assistants
AgentType.COPILOT
AgentType.ASSISTANT
import {
ActionDeniedError,
AuthenticationError,
RateLimitError,
} from '@opena2a/aim-sdk';
try {
await client.verifyAction({ action: 'file:delete' });
} catch (error) {
if (error instanceof ActionDeniedError) {
console.log(`Denied: ${error.reason}`);
console.log(`Trust score: ${error.trustScore}`);
} else if (error instanceof RateLimitError) {
console.log(`Retry after: ${error.retryAfter} seconds`);
}
}
Class identity is per entry point. Each entry point (
.,/arp,/express,/fastify) bundles its own copy of the error classes, soinstanceofonly matches errors raised by the same entry point you imported from. When you use an integration, import the error classes from that same integration (@opena2a/aim-sdk/expressand/fastifyre-export the full error family); for checks that must work across entry points, match onerror.code(e.g.'ACTION_DENIED') instead.
import {
loadCredentialsFromFile,
saveCredentialsToFile,
} from '@opena2a/aim-sdk';
// Save credentials after registration
const credentials = client.getCredentials();
await saveCredentialsToFile(credentials, '.aim/credentials.json');
// Load credentials on startup
const saved = await loadCredentialsFromFile('.aim/credentials.json');
client.setCredentials(saved);
registerAgent(options) - Register a new agentverifyAction(options, atx?) - Verify an action (local path when a credential is cached, remote POST fallback otherwise)verifyActionLocally(options, atx?) - Verify an action against a locally-held ATX credential, fully offlinesetLocalCredential(atx) - Cache the resolved ATX credential for offline verification (pass null to clear)getLocalVerifier() - The configured LocalVerifier, or null when local verification is not enabledgetAgent() - Get current agent infoupdateAgent(updates) - Update agent metadatareportCapabilities(capabilities) - Report agent capabilitiesgetTrustScore() - Get current trust scoregetCredentials() - Get stored credentialssetCredentials(credentials) - Set credentialsSee
src/types/index.ts
for complete type definitions (source is not shipped in the npm package; the
bundled .d.ts files carry the same types).
Apache-2.0
See CONTRIBUTING.md for guidelines.
FAQs
Agent Identity Management SDK for TypeScript/Node.js
We found that @opena2a/aim-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.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.