Zero-dependency TypeScript SDK for AI agent governance: policy enforcement, injection detection, tamper-evident audit, and standards mapping (EU AI Act, OWASP, NIST, ISO 42001)
26
stars
135
commits
TypeScript
primary language
Sep 10, 2026
updated
AI Agent Governance for TypeScript — policy enforcement, behavioral scoring, injection detection, tamper-evident audit, and standards-mapped compliance for AI agents. Zero runtime dependencies.
Every AI agent framework lets you build agents. None of them govern what those agents actually do at runtime. governance-sdk adds policy enforcement, behavioral scoring, injection detection, and compliance auditing to any TypeScript agent — regardless of framework.
Three things make governance real, and this SDK does all three:
Everything downstream (scoring, audit, compliance) follows from those three.
Proof, not promises — tamper-evident audit by default. Every enforce()
decision and recordOutcome() outcome can be HMAC hash-chained (opt in with
integrityAudit: { signingKey }). Any edit, deletion, or sequence-renumber
breaks chain verification — verifiable offline anywhere with just the
secret. No competitor in the comparison table below ships this.
| governance-sdk | NVIDIA NeMo Guardrails | Guardrails AI | LangChain guardrails | |
|---|---|---|---|---|
| Runtime dependencies | 0 | Python runtime + LLM | Python + validator stack | LangChain |
| TypeScript-first | ✅ | ❌ (Python) | ❌ (Python) | ✅ |
| Framework-agnostic | ✅ (12 framework integrations) | Rails-only | Model-wrapping | LangChain-only |
| Policy enforcement (block/approval/mask) | ✅ | ✅ | ✅ | Partial |
| Behavioral scoring / trust levels | ✅ | ❌ | ❌ | ❌ |
| Tamper-evident audit (HMAC chain) | ✅ | ❌ | ❌ | ❌ |
| Standards mapping (EU AI Act / OWASP / NIST / ISO 42001) | ✅ | ❌ | Partial | ❌ |
| Ed25519 agent identity | ✅ | ❌ | ❌ | ❌ |
| Zero-dep embedded use in any TS runtime | ✅ | ❌ | ❌ | ❌ |
governance-sdk is the only option that's zero-dep TypeScript, framework-agnostic, and maps to all four major AI-governance standards out of the box.
The SDK is a thin client for local policy evaluation, scoring, and detection — nothing more. To pre-empt procurement and scope questions, here is exactly what it does and does not do:
node:vm is intentionally not used as a sandbox (per Node
docs, it's not a security boundary). For untrusted code execution, isolate
at the container/VM layer (containers, gVisor, Firecracker). This is a
deliberate scope choice: the SDK governs known-trusted application code
calling LLMs and tools, not arbitrary attacker-supplied JS.InjectionClassifier interface for production coverage.metrics and
otel-hooks exports produce passive in-memory data structures you serialize
to your own monitoring system; they are NOT OpenInference-compliant and NOT
a replacement for Phoenix, Langfuse, Braintrust, or a real OpenTelemetry
exporter. A first-class OTel/OpenInference exporter is on the roadmap.gov.eval.* was removed in 0.11. Use inspect-ai,
PyRIT, Garak, Phoenix, Langfuse, or your harness of choice and route results
into your audit stream via gov.audit.log().enforce() does not hash-chain by default — opt in with
integrityAudit: { signingKey } for tamper-evident audit. Since 0.12
the chain is persisted durably (survives process restart) when the
storage adapter supports createAuditEventWithIntegrity (memory and
Postgres adapters both do). Since 0.18.2 that chain is also
multi-process-safe when the adapter implements appendToAuditChain
(Postgres) — see Multi-process deployments.
HMAC chains are still only tamper-evident to holders of the signing
secret — rotate and pair with an external anchor if you need
adversary-grade non-repudiation.register() is a synthetic confirmation — the API auto-registers
on first enforce().invokeAgent and (with a helper) the final response text.
Tool executions inside AWS action groups are opaque — the adapter
cannot see them, let alone block them. Use guardToolUse() to enforce
at the tool level manually, or push tool calls onto the host side.registerModalityScanner() and call scanMultiModal()
from governance-sdk/scan/multi-modal before enforce(); the result's
concatenated text feeds the existing cascade. The SDK ships the
orchestration only — the actual OCR / PDF parser / ASR is caller-
supplied so the zero-dep promise stands. Defaults to text-only;
per-block timeouts and fail-closed semantics (onMissingScanner,
onExtractError) are configurable.| Package | Description |
|---|---|
governance-sdk | Core SDK — policy engine, scoring, injection detection, audit, compliance, standards mapping, 12 framework integrations (10 featured + MCP toolkit + Bedrock). 0 runtime deps. |
governance-sdk-platform | Optional PostgreSQL storage layer — auto-migrating schema, org settings, policy tiers. |
# Core SDK (zero dependencies)
npm install governance-sdk
# PostgreSQL storage (optional)
npm install governance-sdk-platform
Or scaffold a project with the CLI:
npx governance-sdk init
import { createGovernance, blockTools, rateLimit } from 'governance-sdk';
const governance = createGovernance({
rules: [
blockTools(['shell_exec', 'eval']),
rateLimit(100, 60_000), // 100 actions per 60s — host populates ctx.recentActionCount
],
});
const result = await governance.enforce({
agentId: 'support-bot',
action: 'tool_call',
tool: 'send_email',
input: { to: 'user@example.com', body: 'Your ticket has been resolved.' },
});
if (result.outcome === 'block') {
console.error(`Blocked: ${result.reason}`);
} else {
// proceed with agent action
}
Connect to Lua Governance Cloud for ML-powered injection detection, approval workflows, fleet analytics, and a real-time dashboard.
import { createGovernance } from 'governance-sdk';
const gov = createGovernance({
serverUrl: 'https://api.heygovernance.ai',
apiKey: process.env.GOVERNANCE_API_KEY,
fallbackMode: 'allow', // fail-open if API unreachable (default)
});
// Verify connection at startup
const status = await gov.connect();
console.log(status);
// => { connected: true, mode: 'remote', latencyMs: 45, plan: 'pro', features: [...], agentQuota: { used: 3, limit: 25 } }
The SDK retries transient failures with exponential backoff (3 attempts) and falls back gracefully when the API is unreachable — your agent never crashes from a governance outage.
When a policy returns require_approval, the SDK provides the approval ID and a polling helper:
const decision = await gov.enforce({ agentId: 'bot', action: 'deploy', tool: 'prod_deploy' });
if (decision.outcome === 'require_approval') {
console.log(`Waiting for approval: ${decision.approval?.pollUrl}`);
const result = await gov.waitForApproval(decision.approvalId!, { timeoutMs: 300_000 });
if (result === 'approved') {
// proceed with deployment
}
}
When the built-in condition types aren't enough, register your own evaluators directly on the governance instance — no need to drop down to createPolicyEngine for this. Pass them at construction or register them at runtime:
import { createGovernance } from 'governance-sdk';
// Option A — register at construction time
const gov = createGovernance({
conditions: [
{
name: 'geo_fence',
description: 'Block actions outside allowed regions',
evaluator: (ctx, params) => {
const region = (ctx.metadata?.region as string | undefined) ?? '';
const allowed = params.allowedRegions as string[];
return region.length > 0 && !allowed.includes(region);
},
},
],
rules: [{
id: 'geo-rule',
name: 'Geo fence',
condition: { type: 'geo_fence', params: { allowedRegions: ['us', 'eu'] } },
outcome: 'block',
reason: 'Region not allowed',
priority: 100,
enabled: true,
}],
});
// Option B — register after construction
gov.registerCondition({
name: 'high_cost',
description: 'Block when session cost exceeds threshold',
evaluator: (ctx, params) => (ctx.sessionCost ?? 0) > (params.maxCost as number),
});
gov.addRule({
id: 'cost-check',
name: 'Cost check',
condition: { type: 'high_cost', params: { maxCost: 10 } },
outcome: 'block',
reason: 'Session cost over budget',
priority: 100,
enabled: true,
});
Mirror methods are available on the instance: registerCondition, unregisterCondition, getRegisteredCondition, getRegisteredConditions, clearConditionRegistry. Custom evaluators must be synchronous — the policy engine is sync by design.
# Scaffold governance in your project
npx governance-sdk init
# Test API connectivity and show diagnostics
GOVERNANCE_API_URL=https://api.heygovernance.ai GOVERNANCE_API_KEY=ak_... npx governance-sdk connect
Define rules that govern agent behavior at runtime. Policies return one of five outcomes: allow, block, warn, require_approval, or mask (non-blocking redaction).
Preset policy builders:
blockTools(toolNames) — block specific tools from being calledallowOnlyTools(toolNames) — whitelist-only tool accessrequireApproval(actionTypes) — gate action categories (ctx.action) behind human approvalrequireToolApproval(toolNames) — gate specific tools by name (ctx.tool) behind human approvaltokenBudget(limit) — enforce token consumption limitsrateLimit(config) — throttle agent requests. Stateless — the rule
reads ctx.recentActionCount, which your host populates. Durable
distributed rate limiting belongs in your API layer.Extended presets (also exported from the main package): inputBlocklist,
inputLength, inputPattern, networkAllowlist, scopeBoundary,
costBudget, concurrentLimit, outputLength, outputPattern,
sensitiveDataFilter, maskSensitiveOutput, maskOutputPattern. Most
rely on the host supplying relevant ctx.* fields (token counts, domain,
cost, etc.) — like rateLimit, they are declarative gates, not accumulators.
requireLevel(level) — require minimum trust levelrequireSequence(steps) — enforce ordered execution stepstimeWindow(config) — restrict actions to time windowsrequireSignedIdentity() — require Ed25519 signed agent identity tokensPolicies compose with policy-compose for complex rule sets, serialize to YAML (policy-yaml), and ship with a fluent policy-builder.
7-dimension scoring model quantifying agent trustworthiness: identity, permissions, observability, guardrails, auditability, compliance, lifecycle.
import { assessAgent, getGovernanceLevel } from 'governance-sdk/scorer';
const assessment = assessAgent('my-agent', {
name: 'my-agent', framework: 'mastra', owner: 'platform-team',
hasAuth: true, hasGuardrails: true, hasObservability: true, hasAuditLog: true,
});
// => { compositeScore: 87, level: 4, dimensions: { identity, permissions, ... } }
getGovernanceLevel(assessment.compositeScore);
// => { level: 4, label: 'Certified', description: '...' }
Behavioral signals (block rate, injection hits, approval misses) are
available via the optional behavioral-scorer module — feed them in to
adjust the score against how the agent has behaved, not just its
configured posture. This is opt-in and not wired by default; we plan to
promote dynamic trust scoring as a first-class feature in a future
release.
Weight rationale + inflation risk: the default weights
(identity/permissions 1.5; guardrails 1.3; observability 1.2;
auditability/compliance 1.0; lifecycle 0.8) are opinionated, not
research-validated. Override with a custom weight map if your risk profile
differs. Also: the scorer trusts self-reported hasAuth/hasGuardrails/
hasObservability/hasAuditLog booleans at face value — to defend against
score inflation, cross-check callers' claims against
scanRepoContents(fileContents) from governance-sdk/repo-patterns and
flag mismatches. See src/scorer-dimensions.ts header comment and
src/scorer-inflation.test.ts for the full pattern.
56 regex patterns across 7 categories (instruction override, role manipulation,
context escape, data exfiltration, encoding attack, social engineering,
obfuscation). Input normalisation includes: zero-width character stripping,
NFKC Unicode folding (fullwidth/compatibility variants → ASCII), Cyrillic/Greek
confusable (homoglyph) folding (systеm prоmpt → system prompt),
spaced-character collapsing (i g n o r e → ignore), markdown-emphasis
stripping (ig**no**re → ignore), leetspeak de-obfuscation (1gn0r3 pr3v10us 1nstruct10ns → ignore previous instructions), and Base64 decode-and-rescan.
Scoring is max-pattern-weight + multi-pattern and multi-category boosts, capped
at 1.0.
import { detectInjection } from 'governance-sdk/injection-detect';
const result = detectInjection(userInput);
if (result.detected) {
// block or flag the input — score, matched patterns, and category available
}
Lua Injection Benchmark (LIB) — 6,931 labeled samples (2,096 attacks + 4,835 benign) across 12 sources: TrustAIRLab in-the-wild jailbreak prompts (1,779), databricks-dolly-15k (1,490), neuralchemy prompt-injection-dataset (990), jackhhao jailbreak-classification (538), reshabhs SPML (537), OpenAssistant oasst2 (463), synthesized encoding attacks (458), llm-semantic-router jailbreak-detection (371), deepset prompt-injections (114), JailbreakBench JBB-Behaviors (106), synthesized hard negatives (75), walledai JailbreakHub (10).
Shipped regex detector baseline on the full 6,931 samples (reproducible
via benchmark/scripts/run-full-baseline.ts; committed report at
benchmark/data/lua-injection-benchmark-v1-regex-baseline.json):
| Metric | Value |
|---|---|
| Precision | 68.51% |
| Recall | 37.26% |
| F1 | 48.27% |
| Accuracy | 75.85% |
| False-positive rate | 7.43% |
Reading this honestly: the zero-dep regex detector is a high-precision /
low-recall first layer — good for catching common attack phrasings with few
false positives on benign text, but not a replacement for an ML classifier
on adversarial corpora. Layer in an ML detector via the InjectionClassifier
interface (reference implementation in the governance-ml package) if you
need stronger recall against in-the-wild jailbreak prompts.
HMAC-SHA256 hash-chained audit. Each entry's hash covers the previous hash + sequence number + canonicalised event body, so any edit, deletion, or reorder-via-sequence-renumbering breaks verification. Constant-time hash comparison throughout — no timing oracle.
Opt-in via a single config flag. Pass integrityAudit: { signingKey } to
createGovernance() and every audit write the SDK makes is chained
automatically — no separate wrapper, no ceremony:
import { createGovernance, runWithOutcome } from 'governance-sdk';
import { verifyAuditIntegrity } from 'governance-sdk/audit-integrity-verify';
const gov = createGovernance({
rules: [/* ... */],
integrityAudit: {
signingKey: process.env.AUDIT_SECRET!,
onFailure: 'allow', // or 'block' to fail-closed on chain errors
},
});
// Every one of these is HMAC-chained:
await gov.register({ name: 'sales-bot', framework: 'mastra', owner: 'team' });
await gov.enforce({ agentId, action: 'tool_call', tool: 'search' });
// Close the decision → outcome loop with runWithOutcome():
const result = await runWithOutcome(gov, { agentId, tool: 'search' }, async () => {
return await searchApi.query(q);
});
// ↑ success (or failure, with error + duration) auto-recorded in the chain
// Verify the chain offline, anywhere, with just the secret:
const chain = await gov.integrityChain!.export();
const { valid, brokenAt, breakDetail } = await verifyAuditIntegrity(chain, process.env.AUDIT_SECRET!);
Per-org (multi-tenant) chains. Since 0.18, chains are scoped per
organizationId: each org gets its own head, its own 1..N sequence, and
its own write lock, so one tenant's events never interleave with another's.
Pass the org on the context (or via metadata.organizationId) and export /
verify a single tenant's contiguous chain:
await gov.enforce({ agentId, organizationId: 'org_acme', action: 'tool_call', tool: 'search' });
const acme = await gov.integrityChain!.export({ organizationId: 'org_acme' });
await verifyAuditIntegrity(acme, process.env.AUDIT_SECRET!); // contiguous, standalone-verifiable
Events without an organizationId share a single org-less chain, byte-for-byte
compatible with chains written before 0.18 — no migration needed. The org is
bound into each event's hash (when present), so an event can't be relabelled
into another tenant's chain without breaking verification.
What gets chained (when integrityAudit is set):
| Event type | Written by | What it captures |
|---|---|---|
agent_registered | gov.register() | name, framework, owner, initial score |
policy_evaluation | gov.enforce() | agent, action, tool, rule matched, outcome, reason |
policy_evaluation_preprocess / _postprocess | gov.enforcePreprocess() / Postprocess() | stage-scoped enforcement result |
action_outcome | gov.recordOutcome() or runWithOutcome() | success / failure, duration, tokens, output summary, error |
agent_killed | killSwitch.kill() | agent, reason, killedBy |
| (caller-supplied) | gov.audit.log() | anything you pass — custom LLM calls, approvals, etc. |
What is NOT automatically chained: anything you log directly via
storage.createAuditEvent() (bypasses the chain), anything your host app
does outside governance (raw fetch(), filesystem I/O without going through
a governed tool), and anything the agent did between enforce() calls that
didn't invoke enforce() or recordOutcome() itself.
Honest caveats:
integrityAudit.onFailure: 'allow' (default) means a storage failure
creates a chain gap that verifyAuditIntegrity will detect; set
'block' to reject the enforce() call instead when you can't tolerate
gaps.If more than one process writes to the same audit store — Kubernetes
replicas, a pm2 cluster, or serverless instances all pointed at one
Postgres database — the chain must allocate each event's sequence and
previousHash from the current durable head, not from process-local
state. Otherwise two processes derive the same sequence for the same org
(one INSERT wins, the other is dropped by the unique index) and their
per-process previousHash forks the chain.
This is the job of the optional storage-contract method
appendToAuditChain(event, computeIntegrity). The adapter, under a per-org
lock that spans the whole operation, reads the org's durable head, calls back
into the SDK to compute the HMAC (the signing key never leaves the SDK core),
and persists the event + integrity as one indivisible write:
import { createGovernance } from 'governance-sdk';
import { createPostgresStorage } from 'governance-sdk/storage-postgres';
const gov = createGovernance({
storage: await createPostgresStorage({ pool }), // pg.Pool — real transactions
integrityAudit: { signingKey: process.env.AUDIT_SECRET! },
});
// Every process using this config appends atomically against the shared DB —
// no duplicate-sequence drops, no per-process chain fork.
createGovernance() uses appendToAuditChain automatically whenever the
storage adapter provides it. Support by shipped adapter:
| Adapter | appendToAuditChain | Multi-process safe? |
|---|---|---|
Postgres (createPostgresStorage) | ✅ per-org pg_advisory_xact_lock transaction (falls back to a bounded 23505-retry loop for query-only pools) | ✅ across processes sharing the database |
Memory (createMemoryStorage) | ✅ per-org in-process async lock | Single process by design — memory is not shared across processes |
| Third-party adapter without the method | — | Falls back to the legacy process-local-sequence path (correct under a single writer only) |
Custom storage adapters: to be multi-process-safe, implement
appendToAuditChain so the head-read → compute → insert sequence is atomic
against concurrent writers (a row lock, an advisory lock, a serializable
transaction, or a compare-and-set retry on your uniqueness constraint). If you
can't, leave it unimplemented and run a single writer — the SDK falls back
safely and warns.
integrityChain.stats() reads the durable head (async since 0.19). It
resolves the latest sequence + hash from storage.getChainHead() on every
call — so under multiple writers it reports the true tip, including writes made
by other processes, not just this process's last append. export() and
verifyAuditIntegrity() are already durable-backed; as of 0.19 stats() joins
them and is async (was sync ≤0.18) — await it. Adapters with no
getChainHead fall back to the process-local cache (single-process only).
const { latestSequence, latestHash } = await gov.integrityChain!.stats('org_acme');
The standalone createIntegrityAudit() wrapper is single-process only. It
keeps its chain in process memory and never persists integrity metadata, so it
forks across processes and loses verifiability across restarts. Use it for
prototyping and tests; use createGovernance({ integrityAudit }) (above) for
durable, multi-process audit.
Rolling deploys: the lock only protects writers that take it. During a mixed-version window (some processes pre-0.18.2), the old processes still allocate from process-local counters and can collide with or fork past the locked writers. Replace all writers together and expect residual unique-violation warnings until the last old process drains.
Emergency halt for any agent, enforced via a reserved-priority policy rule (999). User-supplied rules are clamped to a max priority of 998 by the engine, so the kill switch remains unconditionally top priority — no "attacker rule at 1000 beats the kill switch" hole.
import { createKillSwitch } from 'governance-sdk/kill-switch';
const killSwitch = createKillSwitch(gov);
await killSwitch.kill('rogue-agent', 'Unauthorized data access');
Scope: per-process, not distributed. The authoritative kill state lives
in-memory on the instance where kill() was called. Storage is best-effort
updated so other instances can discover the kill, but they do NOT re-query
storage on every enforce() — that would hurt the thin-client design. For
fleet-wide guaranteed halt, route through the governance-cloud remote
enforce API or publish kill events over pub/sub and call kill() on
every instance.
Each module emits a self-assessment report mapping governance state to a subset of the named framework. These are engineering tools for posture tracking — not legal advice, not regulatory certifications, and not substitutes for qualified counsel or a chartered auditor. Each report output includes its own disclaimer field so downstream consumers see the caveat.
Scope disclosures:
import { mapToEuAiAct } from 'governance-sdk/compliance'; // EU AI Act (6 articles) — preferred
import { mapToOwaspAgentic } from 'governance-sdk/owasp-agentic'; // alias of assessOwaspAgentic
import { mapToNistAiRmf } from 'governance-sdk/nist-ai-rmf'; // alias of assessNistAiRmf
import { mapToIso42001 } from 'governance-sdk/iso-42001'; // alias of assessIso42001
const report = await mapToEuAiAct({
governance: gov, agents: [agent],
auditIntegrity: true, humanOversight: true,
});
// report.disclaimer — embedded "not legal advice" notice
// report.phasedDeadlines — { prohibitedPractices, gpaiTransparency, highRiskObligations, postMarketAndDownstream }
Cryptographically-signed agent identity tokens using Ed25519 (RFC 8032) via
crypto.subtle. Zero runtime dependencies. Tokens include a nonce (jti),
expiry (exp), optional kid for key rotation, and the agent's public key
so any verifier can re-check the signature.
Pair with the requireSignedIdentity() policy to guarantee that enforce
calls come from an agent that actually holds the private key. Note that the
policy checks a boolean (ctx.identityVerified) that your host layer sets
after calling verifyAgentIdentity() — the SDK itself stays zero-state.
import {
createEd25519Identity,
signAgentIdentity,
verifyAgentIdentity,
} from 'governance-sdk/agent-identity-ed25519';
const identity = createEd25519Identity();
const keys = await identity.generateKeyPair();
const token = await signAgentIdentity({
agentId: 'sales-bot',
keys,
ttlSeconds: 3600,
kid: 'v2', // optional: pick-by-id on rotation
capabilities: ['search'], // optional: capability assertions
});
// On the receiving side:
const result = await verifyAgentIdentity(token, {
pinnedPublicKeyHex: pinnedKey, // optional but recommended — see below
});
// => { valid: true, agentId: 'sales-bot' }
Pin your public keys. A token self-describes the public key it was signed
with, so without pinning you're verifying "someone signed this" rather than
"the expected agent signed this." Use pinnedPublicKeyHex whenever you
already know which key the agent should be using.
Test policies against scenarios without affecting production.
import { simulateFleetPolicy } from 'governance-sdk/dry-run';
const result = await simulateFleetPolicy(gov, scenarios);
// => { fleetSummary: { agentsAffected: 11, blockRate: 0.12 }, results: [...] }
Governance needs three things to be real: a point of interception (we sit between the agent and the tool/LLM before it fires), a deterministic agent identity (we know who's calling), and the ability to block or modify (not just observe after the fact). The matrix below is scoped to frameworks where all three hold.
| Framework | Import Path | Input pre-scan | Output post-scan | Output streaming | Tool-call |
|---|---|---|---|---|---|
| Mastra (processor) | governance-sdk/plugins/mastra-processor | ✅ | ✅ | ✅ | ✅ |
| Vercel AI SDK | governance-sdk/plugins/vercel-ai | ✅ | ✅ | ✅ | ✅ |
| OpenAI Agents SDK | governance-sdk/plugins/openai-agents | ✅ | ✅ | ✅¹ | ✅ |
| LangChain | governance-sdk/plugins/langchain | ✅ | ✅ | ✅ | ✅ |
| Anthropic SDK | governance-sdk/plugins/anthropic | ✅ | ✅ | ✅ | ✅ |
| Google Genkit | governance-sdk/plugins/genkit | ✅ | ✅ | ✅ | ✅ |
| LlamaIndex | governance-sdk/plugins/llamaindex | ✅ | ✅ | ✅ | ✅ |
| Mistral | governance-sdk/plugins/mistral | ✅ | ✅ | ✅ | ✅ |
| Ollama | governance-sdk/plugins/ollama | ✅ | ✅ | ✅ | ✅ |
| Mastra (middleware) | governance-sdk/plugins/mastra | ✅² | ✅² | ✅² | ✅ |
¹ OpenAI Agents output guardrails fire at stream final assembly (SDK-native behavior).
² Mastra middleware exposes scanInput / scanOutput / scanOutputStream helpers — explicit calls you make from your runtime loop, rather than automatic lifecycle hooks. Use the mastra-processor export if you want automatic hooks via inputProcessors[] / outputProcessors[].
| Framework | Import Path | Scope |
|---|---|---|
| Model Context Protocol | governance-sdk/plugins/mcp | Build a governed MCP server — input injection pre-scan on tool arguments + output injection scan + tool-call audit for tools you publish. Not for governing MCP servers you consume (govern those at the agent framework layer). |
| MCP trust + chain audit | governance-sdk/plugins/mcp-trust, governance-sdk/plugins/mcp-chain-audit | Declarative trusted-MCP-server registry (allowlist + per-server capability tags — not cryptographic pin-trust; signature/TLS pinning is not implemented) + caller-driven chain-of-custody audit across nested MCP invocations (requires manual recordCall() per hop; not automatic propagation). |
| AWS Bedrock Agents | governance-sdk/plugins/bedrock | Entry-gate only — Bedrock Agents execute tools server-side inside AWS, so we can pre-scan the InvokeAgent input and post-scan the assembled output via scanOutput, but we can't see individual internal tool calls. |
If your agent is not TypeScript, use the Lua Governance REST API directly — it exposes the same policy, scoring, audit, and injection-detection endpoints the SDK uses locally. Native Python / Go SDKs are not shipped yet; a REST client works everywhere.
The SDK itself is pure ESM with zero runtime dependencies, so it runs unmodified under Node, Deno, Bun, Cloudflare Workers, and other Web-standard runtimes — no separate adapter needed.
All framework dependencies are optional peer dependencies — install only what you use.
Vercel AI SDK — experimental_wrapLanguageModel middleware:
import { experimental_wrapLanguageModel, generateText } from 'ai';
import { createGovernance } from 'governance-sdk';
import { createGovernanceMiddleware } from 'governance-sdk/plugins/vercel-ai';
const gov = createGovernance({ rules: [/* ... */] });
const { id: agentId } = await gov.register({
name: 'sales', framework: 'vercel-ai', owner: 'team',
});
const model = experimental_wrapLanguageModel({
model: openai('gpt-4o'),
middleware: createGovernanceMiddleware(gov, { agentId }),
});
OpenAI Agents SDK — native input/output guardrails:
import { Agent } from '@openai/agents';
import {
createInputGuardrail,
createOutputGuardrail,
} from 'governance-sdk/plugins/openai-agents';
const agent = new Agent({
name: 'research',
instructions: '...',
inputGuardrails: [createInputGuardrail(gov, { agentId })],
outputGuardrails: [createOutputGuardrail(gov, { agentId })],
});
LangChain — chat model wrapper:
import { ChatOpenAI } from '@langchain/openai';
import { wrapChatModel } from 'governance-sdk/plugins/langchain';
const model = new ChatOpenAI({ model: 'gpt-4o' });
const guarded = wrapChatModel(model, gov, { agentId });
const res = await guarded.invoke([new HumanMessage('hello')]);
Anthropic SDK — messages.create wrapper:
import Anthropic from '@anthropic-ai/sdk';
import { createGovernedMessages } from 'governance-sdk/plugins/anthropic';
const client = new Anthropic();
const messages = createGovernedMessages(client.messages, gov, { agentId });
const res = await messages.create({
model: 'claude-sonnet-4-5', max_tokens: 1024,
messages: [{ role: 'user', content: 'hi' }],
});
Every pre/post adapter accepts { preprocess: false } or { postprocess: false }
to disable a stage. Both stages are on by default.
All adapters handle all 5 enforcement outcomes with configurable callbacks:
const middleware = createGovernanceMiddleware(gov, {
agentName: 'my-agent',
owner: 'platform-team',
framework: 'mastra',
onBlocked: (decision, tool) => log.warn(`Blocked: ${tool}`),
onWarn: (decision, tool) => log.info(`Warning: ${tool} — ${decision.reason}`),
onMask: (decision, tool, masked) => log.info(`Masked output for ${tool}`),
onApprovalRequired: (decision, tool) => log.info(`Approval needed: ${tool}`),
});
The SDK ships 47 targeted exports so you can import only what you need:
# Core
governance-sdk createGovernance, enforce, presets
governance-sdk/policy policy types and builders
governance-sdk/policy-builder fluent policy builder
governance-sdk/policy-compose compose + conflict resolution
governance-sdk/policy-yaml serialize/deserialize policies
governance-sdk/dry-run simulatePolicy / simulateFleetPolicy
# Scoring
governance-sdk/scorer 7-dimension governance scoring
governance-sdk/behavioral-scorer behavioral signal adjustments
governance-sdk/repo-patterns repository capability detection
# Injection detection
governance-sdk/injection-detect 56-pattern regex detector
governance-sdk/injection-classifier pluggable ML classifier interface
governance-sdk/injection-benchmark LIB — 6.9K-sample benchmark runner
# Audit + identity
governance-sdk/audit-integrity HMAC hash-chain primitives (createIntegrityAudit, verifyAuditIntegrity)
governance-sdk/audit-integrity-verify standalone chain verifier (for offline audit)
governance-sdk/agent-identity agent identity tokens
governance-sdk/agent-identity-ed25519 Ed25519 signing + verification
governance-sdk/kill-switch priority-999 emergency halt
# Standards / compliance
governance-sdk/compliance EU AI Act (6 articles + deadlines)
governance-sdk/owasp-agentic OWASP Top 10 for LLMs / Agentic
governance-sdk/nist-ai-rmf NIST AI RMF (Govern/Map/Measure/Manage)
governance-sdk/iso-42001 ISO/IEC 42001 controls
# Storage
governance-sdk/storage-postgres PostgreSQL storage adapter
governance-sdk/storage-postgres-schema schema DDL + migrations
# Optional observability primitives — passive in-memory, host wires to its own
# monitoring; NOT OpenInference-compliant. A real OTel exporter is on the roadmap.
governance-sdk/events typed event emitter
governance-sdk/metrics in-memory counter / timing snapshots
governance-sdk/otel-hooks governance-prefixed span shape (passive — user must wire)
# Scanner + type surface
governance-sdk/scanner-plugins scanner plugin interface
governance-sdk/token-types token type guards
# Framework integrations (10 featured + MCP toolkit + Bedrock)
governance-sdk/plugins/mastra
governance-sdk/plugins/mastra-processor
governance-sdk/plugins/vercel-ai
governance-sdk/plugins/openai-agents
governance-sdk/plugins/langchain
governance-sdk/plugins/anthropic
governance-sdk/plugins/genkit
governance-sdk/plugins/llamaindex
governance-sdk/plugins/mistral
governance-sdk/plugins/ollama
governance-sdk/plugins/mcp # build a governed MCP server
governance-sdk/plugins/mcp-trust # trusted-server allowlist + capability tags
governance-sdk/plugins/mcp-allowlist # tool/resource allowlist enforcement
governance-sdk/plugins/mcp-chain-audit # caller-driven chain-of-custody audit
governance-sdk/plugins/mcp-call-recorder # nested-invocation call recorder
governance-sdk/plugins/bedrock # entry-gate only (action groups opaque)
runWithOutcome() (a thin helper around gov.recordOutcome) is exposed at the
top-level package export — import { runWithOutcome } from 'governance-sdk'.
npm test)any types in source# Install dependencies
npm install
# Build all packages
npm run build
# Run tests
npm test
# Type-check without emitting
npm run lint
See CONTRIBUTING.md. Security issues: see SECURITY.md.
TypeScript
98.0%
Zero-dependency TypeScript SDK for AI agent governance: policy enforcement, injection detection, tamper-evident audit, and standards mapping (EU AI Act, OWASP, NIST, ISO 42001)
26
stars
135
commits
TypeScript
primary language
Sep 10, 2026
updated
AI Agent Governance for TypeScript — policy enforcement, behavioral scoring, injection detection, tamper-evident audit, and standards-mapped compliance for AI agents. Zero runtime dependencies.
Every AI agent framework lets you build agents. None of them govern what those agents actually do at runtime. governance-sdk adds policy enforcement, behavioral scoring, injection detection, and compliance auditing to any TypeScript agent — regardless of framework.
Three things make governance real, and this SDK does all three:
Everything downstream (scoring, audit, compliance) follows from those three.
Proof, not promises — tamper-evident audit by default. Every enforce()
decision and recordOutcome() outcome can be HMAC hash-chained (opt in with
integrityAudit: { signingKey }). Any edit, deletion, or sequence-renumber
breaks chain verification — verifiable offline anywhere with just the
secret. No competitor in the comparison table below ships this.
| governance-sdk | NVIDIA NeMo Guardrails | Guardrails AI | LangChain guardrails | |
|---|---|---|---|---|
| Runtime dependencies | 0 | Python runtime + LLM | Python + validator stack | LangChain |
| TypeScript-first | ✅ | ❌ (Python) | ❌ (Python) | ✅ |
| Framework-agnostic | ✅ (12 framework integrations) | Rails-only | Model-wrapping | LangChain-only |
| Policy enforcement (block/approval/mask) | ✅ | ✅ | ✅ | Partial |
| Behavioral scoring / trust levels | ✅ | ❌ | ❌ | ❌ |
| Tamper-evident audit (HMAC chain) | ✅ | ❌ | ❌ | ❌ |
| Standards mapping (EU AI Act / OWASP / NIST / ISO 42001) | ✅ | ❌ | Partial | ❌ |
| Ed25519 agent identity | ✅ | ❌ | ❌ | ❌ |
| Zero-dep embedded use in any TS runtime | ✅ | ❌ | ❌ | ❌ |
governance-sdk is the only option that's zero-dep TypeScript, framework-agnostic, and maps to all four major AI-governance standards out of the box.
The SDK is a thin client for local policy evaluation, scoring, and detection — nothing more. To pre-empt procurement and scope questions, here is exactly what it does and does not do:
node:vm is intentionally not used as a sandbox (per Node
docs, it's not a security boundary). For untrusted code execution, isolate
at the container/VM layer (containers, gVisor, Firecracker). This is a
deliberate scope choice: the SDK governs known-trusted application code
calling LLMs and tools, not arbitrary attacker-supplied JS.InjectionClassifier interface for production coverage.metrics and
otel-hooks exports produce passive in-memory data structures you serialize
to your own monitoring system; they are NOT OpenInference-compliant and NOT
a replacement for Phoenix, Langfuse, Braintrust, or a real OpenTelemetry
exporter. A first-class OTel/OpenInference exporter is on the roadmap.gov.eval.* was removed in 0.11. Use inspect-ai,
PyRIT, Garak, Phoenix, Langfuse, or your harness of choice and route results
into your audit stream via gov.audit.log().enforce() does not hash-chain by default — opt in with
integrityAudit: { signingKey } for tamper-evident audit. Since 0.12
the chain is persisted durably (survives process restart) when the
storage adapter supports createAuditEventWithIntegrity (memory and
Postgres adapters both do). Since 0.18.2 that chain is also
multi-process-safe when the adapter implements appendToAuditChain
(Postgres) — see Multi-process deployments.
HMAC chains are still only tamper-evident to holders of the signing
secret — rotate and pair with an external anchor if you need
adversary-grade non-repudiation.register() is a synthetic confirmation — the API auto-registers
on first enforce().invokeAgent and (with a helper) the final response text.
Tool executions inside AWS action groups are opaque — the adapter
cannot see them, let alone block them. Use guardToolUse() to enforce
at the tool level manually, or push tool calls onto the host side.registerModalityScanner() and call scanMultiModal()
from governance-sdk/scan/multi-modal before enforce(); the result's
concatenated text feeds the existing cascade. The SDK ships the
orchestration only — the actual OCR / PDF parser / ASR is caller-
supplied so the zero-dep promise stands. Defaults to text-only;
per-block timeouts and fail-closed semantics (onMissingScanner,
onExtractError) are configurable.| Package | Description |
|---|---|
governance-sdk | Core SDK — policy engine, scoring, injection detection, audit, compliance, standards mapping, 12 framework integrations (10 featured + MCP toolkit + Bedrock). 0 runtime deps. |
governance-sdk-platform | Optional PostgreSQL storage layer — auto-migrating schema, org settings, policy tiers. |
# Core SDK (zero dependencies)
npm install governance-sdk
# PostgreSQL storage (optional)
npm install governance-sdk-platform
Or scaffold a project with the CLI:
npx governance-sdk init
import { createGovernance, blockTools, rateLimit } from 'governance-sdk';
const governance = createGovernance({
rules: [
blockTools(['shell_exec', 'eval']),
rateLimit(100, 60_000), // 100 actions per 60s — host populates ctx.recentActionCount
],
});
const result = await governance.enforce({
agentId: 'support-bot',
action: 'tool_call',
tool: 'send_email',
input: { to: 'user@example.com', body: 'Your ticket has been resolved.' },
});
if (result.outcome === 'block') {
console.error(`Blocked: ${result.reason}`);
} else {
// proceed with agent action
}
Connect to Lua Governance Cloud for ML-powered injection detection, approval workflows, fleet analytics, and a real-time dashboard.
import { createGovernance } from 'governance-sdk';
const gov = createGovernance({
serverUrl: 'https://api.heygovernance.ai',
apiKey: process.env.GOVERNANCE_API_KEY,
fallbackMode: 'allow', // fail-open if API unreachable (default)
});
// Verify connection at startup
const status = await gov.connect();
console.log(status);
// => { connected: true, mode: 'remote', latencyMs: 45, plan: 'pro', features: [...], agentQuota: { used: 3, limit: 25 } }
The SDK retries transient failures with exponential backoff (3 attempts) and falls back gracefully when the API is unreachable — your agent never crashes from a governance outage.
When a policy returns require_approval, the SDK provides the approval ID and a polling helper:
const decision = await gov.enforce({ agentId: 'bot', action: 'deploy', tool: 'prod_deploy' });
if (decision.outcome === 'require_approval') {
console.log(`Waiting for approval: ${decision.approval?.pollUrl}`);
const result = await gov.waitForApproval(decision.approvalId!, { timeoutMs: 300_000 });
if (result === 'approved') {
// proceed with deployment
}
}
When the built-in condition types aren't enough, register your own evaluators directly on the governance instance — no need to drop down to createPolicyEngine for this. Pass them at construction or register them at runtime:
import { createGovernance } from 'governance-sdk';
// Option A — register at construction time
const gov = createGovernance({
conditions: [
{
name: 'geo_fence',
description: 'Block actions outside allowed regions',
evaluator: (ctx, params) => {
const region = (ctx.metadata?.region as string | undefined) ?? '';
const allowed = params.allowedRegions as string[];
return region.length > 0 && !allowed.includes(region);
},
},
],
rules: [{
id: 'geo-rule',
name: 'Geo fence',
condition: { type: 'geo_fence', params: { allowedRegions: ['us', 'eu'] } },
outcome: 'block',
reason: 'Region not allowed',
priority: 100,
enabled: true,
}],
});
// Option B — register after construction
gov.registerCondition({
name: 'high_cost',
description: 'Block when session cost exceeds threshold',
evaluator: (ctx, params) => (ctx.sessionCost ?? 0) > (params.maxCost as number),
});
gov.addRule({
id: 'cost-check',
name: 'Cost check',
condition: { type: 'high_cost', params: { maxCost: 10 } },
outcome: 'block',
reason: 'Session cost over budget',
priority: 100,
enabled: true,
});
Mirror methods are available on the instance: registerCondition, unregisterCondition, getRegisteredCondition, getRegisteredConditions, clearConditionRegistry. Custom evaluators must be synchronous — the policy engine is sync by design.
# Scaffold governance in your project
npx governance-sdk init
# Test API connectivity and show diagnostics
GOVERNANCE_API_URL=https://api.heygovernance.ai GOVERNANCE_API_KEY=ak_... npx governance-sdk connect
Define rules that govern agent behavior at runtime. Policies return one of five outcomes: allow, block, warn, require_approval, or mask (non-blocking redaction).
Preset policy builders:
blockTools(toolNames) — block specific tools from being calledallowOnlyTools(toolNames) — whitelist-only tool accessrequireApproval(actionTypes) — gate action categories (ctx.action) behind human approvalrequireToolApproval(toolNames) — gate specific tools by name (ctx.tool) behind human approvaltokenBudget(limit) — enforce token consumption limitsrateLimit(config) — throttle agent requests. Stateless — the rule
reads ctx.recentActionCount, which your host populates. Durable
distributed rate limiting belongs in your API layer.Extended presets (also exported from the main package): inputBlocklist,
inputLength, inputPattern, networkAllowlist, scopeBoundary,
costBudget, concurrentLimit, outputLength, outputPattern,
sensitiveDataFilter, maskSensitiveOutput, maskOutputPattern. Most
rely on the host supplying relevant ctx.* fields (token counts, domain,
cost, etc.) — like rateLimit, they are declarative gates, not accumulators.
requireLevel(level) — require minimum trust levelrequireSequence(steps) — enforce ordered execution stepstimeWindow(config) — restrict actions to time windowsrequireSignedIdentity() — require Ed25519 signed agent identity tokensPolicies compose with policy-compose for complex rule sets, serialize to YAML (policy-yaml), and ship with a fluent policy-builder.
7-dimension scoring model quantifying agent trustworthiness: identity, permissions, observability, guardrails, auditability, compliance, lifecycle.
import { assessAgent, getGovernanceLevel } from 'governance-sdk/scorer';
const assessment = assessAgent('my-agent', {
name: 'my-agent', framework: 'mastra', owner: 'platform-team',
hasAuth: true, hasGuardrails: true, hasObservability: true, hasAuditLog: true,
});
// => { compositeScore: 87, level: 4, dimensions: { identity, permissions, ... } }
getGovernanceLevel(assessment.compositeScore);
// => { level: 4, label: 'Certified', description: '...' }
Behavioral signals (block rate, injection hits, approval misses) are
available via the optional behavioral-scorer module — feed them in to
adjust the score against how the agent has behaved, not just its
configured posture. This is opt-in and not wired by default; we plan to
promote dynamic trust scoring as a first-class feature in a future
release.
Weight rationale + inflation risk: the default weights
(identity/permissions 1.5; guardrails 1.3; observability 1.2;
auditability/compliance 1.0; lifecycle 0.8) are opinionated, not
research-validated. Override with a custom weight map if your risk profile
differs. Also: the scorer trusts self-reported hasAuth/hasGuardrails/
hasObservability/hasAuditLog booleans at face value — to defend against
score inflation, cross-check callers' claims against
scanRepoContents(fileContents) from governance-sdk/repo-patterns and
flag mismatches. See src/scorer-dimensions.ts header comment and
src/scorer-inflation.test.ts for the full pattern.
56 regex patterns across 7 categories (instruction override, role manipulation,
context escape, data exfiltration, encoding attack, social engineering,
obfuscation). Input normalisation includes: zero-width character stripping,
NFKC Unicode folding (fullwidth/compatibility variants → ASCII), Cyrillic/Greek
confusable (homoglyph) folding (systеm prоmpt → system prompt),
spaced-character collapsing (i g n o r e → ignore), markdown-emphasis
stripping (ig**no**re → ignore), leetspeak de-obfuscation (1gn0r3 pr3v10us 1nstruct10ns → ignore previous instructions), and Base64 decode-and-rescan.
Scoring is max-pattern-weight + multi-pattern and multi-category boosts, capped
at 1.0.
import { detectInjection } from 'governance-sdk/injection-detect';
const result = detectInjection(userInput);
if (result.detected) {
// block or flag the input — score, matched patterns, and category available
}
Lua Injection Benchmark (LIB) — 6,931 labeled samples (2,096 attacks + 4,835 benign) across 12 sources: TrustAIRLab in-the-wild jailbreak prompts (1,779), databricks-dolly-15k (1,490), neuralchemy prompt-injection-dataset (990), jackhhao jailbreak-classification (538), reshabhs SPML (537), OpenAssistant oasst2 (463), synthesized encoding attacks (458), llm-semantic-router jailbreak-detection (371), deepset prompt-injections (114), JailbreakBench JBB-Behaviors (106), synthesized hard negatives (75), walledai JailbreakHub (10).
Shipped regex detector baseline on the full 6,931 samples (reproducible
via benchmark/scripts/run-full-baseline.ts; committed report at
benchmark/data/lua-injection-benchmark-v1-regex-baseline.json):
| Metric | Value |
|---|---|
| Precision | 68.51% |
| Recall | 37.26% |
| F1 | 48.27% |
| Accuracy | 75.85% |
| False-positive rate | 7.43% |
Reading this honestly: the zero-dep regex detector is a high-precision /
low-recall first layer — good for catching common attack phrasings with few
false positives on benign text, but not a replacement for an ML classifier
on adversarial corpora. Layer in an ML detector via the InjectionClassifier
interface (reference implementation in the governance-ml package) if you
need stronger recall against in-the-wild jailbreak prompts.
HMAC-SHA256 hash-chained audit. Each entry's hash covers the previous hash + sequence number + canonicalised event body, so any edit, deletion, or reorder-via-sequence-renumbering breaks verification. Constant-time hash comparison throughout — no timing oracle.
Opt-in via a single config flag. Pass integrityAudit: { signingKey } to
createGovernance() and every audit write the SDK makes is chained
automatically — no separate wrapper, no ceremony:
import { createGovernance, runWithOutcome } from 'governance-sdk';
import { verifyAuditIntegrity } from 'governance-sdk/audit-integrity-verify';
const gov = createGovernance({
rules: [/* ... */],
integrityAudit: {
signingKey: process.env.AUDIT_SECRET!,
onFailure: 'allow', // or 'block' to fail-closed on chain errors
},
});
// Every one of these is HMAC-chained:
await gov.register({ name: 'sales-bot', framework: 'mastra', owner: 'team' });
await gov.enforce({ agentId, action: 'tool_call', tool: 'search' });
// Close the decision → outcome loop with runWithOutcome():
const result = await runWithOutcome(gov, { agentId, tool: 'search' }, async () => {
return await searchApi.query(q);
});
// ↑ success (or failure, with error + duration) auto-recorded in the chain
// Verify the chain offline, anywhere, with just the secret:
const chain = await gov.integrityChain!.export();
const { valid, brokenAt, breakDetail } = await verifyAuditIntegrity(chain, process.env.AUDIT_SECRET!);
Per-org (multi-tenant) chains. Since 0.18, chains are scoped per
organizationId: each org gets its own head, its own 1..N sequence, and
its own write lock, so one tenant's events never interleave with another's.
Pass the org on the context (or via metadata.organizationId) and export /
verify a single tenant's contiguous chain:
await gov.enforce({ agentId, organizationId: 'org_acme', action: 'tool_call', tool: 'search' });
const acme = await gov.integrityChain!.export({ organizationId: 'org_acme' });
await verifyAuditIntegrity(acme, process.env.AUDIT_SECRET!); // contiguous, standalone-verifiable
Events without an organizationId share a single org-less chain, byte-for-byte
compatible with chains written before 0.18 — no migration needed. The org is
bound into each event's hash (when present), so an event can't be relabelled
into another tenant's chain without breaking verification.
What gets chained (when integrityAudit is set):
| Event type | Written by | What it captures |
|---|---|---|
agent_registered | gov.register() | name, framework, owner, initial score |
policy_evaluation | gov.enforce() | agent, action, tool, rule matched, outcome, reason |
policy_evaluation_preprocess / _postprocess | gov.enforcePreprocess() / Postprocess() | stage-scoped enforcement result |
action_outcome | gov.recordOutcome() or runWithOutcome() | success / failure, duration, tokens, output summary, error |
agent_killed | killSwitch.kill() | agent, reason, killedBy |
| (caller-supplied) | gov.audit.log() | anything you pass — custom LLM calls, approvals, etc. |
What is NOT automatically chained: anything you log directly via
storage.createAuditEvent() (bypasses the chain), anything your host app
does outside governance (raw fetch(), filesystem I/O without going through
a governed tool), and anything the agent did between enforce() calls that
didn't invoke enforce() or recordOutcome() itself.
Honest caveats:
integrityAudit.onFailure: 'allow' (default) means a storage failure
creates a chain gap that verifyAuditIntegrity will detect; set
'block' to reject the enforce() call instead when you can't tolerate
gaps.If more than one process writes to the same audit store — Kubernetes
replicas, a pm2 cluster, or serverless instances all pointed at one
Postgres database — the chain must allocate each event's sequence and
previousHash from the current durable head, not from process-local
state. Otherwise two processes derive the same sequence for the same org
(one INSERT wins, the other is dropped by the unique index) and their
per-process previousHash forks the chain.
This is the job of the optional storage-contract method
appendToAuditChain(event, computeIntegrity). The adapter, under a per-org
lock that spans the whole operation, reads the org's durable head, calls back
into the SDK to compute the HMAC (the signing key never leaves the SDK core),
and persists the event + integrity as one indivisible write:
import { createGovernance } from 'governance-sdk';
import { createPostgresStorage } from 'governance-sdk/storage-postgres';
const gov = createGovernance({
storage: await createPostgresStorage({ pool }), // pg.Pool — real transactions
integrityAudit: { signingKey: process.env.AUDIT_SECRET! },
});
// Every process using this config appends atomically against the shared DB —
// no duplicate-sequence drops, no per-process chain fork.
createGovernance() uses appendToAuditChain automatically whenever the
storage adapter provides it. Support by shipped adapter:
| Adapter | appendToAuditChain | Multi-process safe? |
|---|---|---|
Postgres (createPostgresStorage) | ✅ per-org pg_advisory_xact_lock transaction (falls back to a bounded 23505-retry loop for query-only pools) | ✅ across processes sharing the database |
Memory (createMemoryStorage) | ✅ per-org in-process async lock | Single process by design — memory is not shared across processes |
| Third-party adapter without the method | — | Falls back to the legacy process-local-sequence path (correct under a single writer only) |
Custom storage adapters: to be multi-process-safe, implement
appendToAuditChain so the head-read → compute → insert sequence is atomic
against concurrent writers (a row lock, an advisory lock, a serializable
transaction, or a compare-and-set retry on your uniqueness constraint). If you
can't, leave it unimplemented and run a single writer — the SDK falls back
safely and warns.
integrityChain.stats() reads the durable head (async since 0.19). It
resolves the latest sequence + hash from storage.getChainHead() on every
call — so under multiple writers it reports the true tip, including writes made
by other processes, not just this process's last append. export() and
verifyAuditIntegrity() are already durable-backed; as of 0.19 stats() joins
them and is async (was sync ≤0.18) — await it. Adapters with no
getChainHead fall back to the process-local cache (single-process only).
const { latestSequence, latestHash } = await gov.integrityChain!.stats('org_acme');
The standalone createIntegrityAudit() wrapper is single-process only. It
keeps its chain in process memory and never persists integrity metadata, so it
forks across processes and loses verifiability across restarts. Use it for
prototyping and tests; use createGovernance({ integrityAudit }) (above) for
durable, multi-process audit.
Rolling deploys: the lock only protects writers that take it. During a mixed-version window (some processes pre-0.18.2), the old processes still allocate from process-local counters and can collide with or fork past the locked writers. Replace all writers together and expect residual unique-violation warnings until the last old process drains.
Emergency halt for any agent, enforced via a reserved-priority policy rule (999). User-supplied rules are clamped to a max priority of 998 by the engine, so the kill switch remains unconditionally top priority — no "attacker rule at 1000 beats the kill switch" hole.
import { createKillSwitch } from 'governance-sdk/kill-switch';
const killSwitch = createKillSwitch(gov);
await killSwitch.kill('rogue-agent', 'Unauthorized data access');
Scope: per-process, not distributed. The authoritative kill state lives
in-memory on the instance where kill() was called. Storage is best-effort
updated so other instances can discover the kill, but they do NOT re-query
storage on every enforce() — that would hurt the thin-client design. For
fleet-wide guaranteed halt, route through the governance-cloud remote
enforce API or publish kill events over pub/sub and call kill() on
every instance.
Each module emits a self-assessment report mapping governance state to a subset of the named framework. These are engineering tools for posture tracking — not legal advice, not regulatory certifications, and not substitutes for qualified counsel or a chartered auditor. Each report output includes its own disclaimer field so downstream consumers see the caveat.
Scope disclosures:
import { mapToEuAiAct } from 'governance-sdk/compliance'; // EU AI Act (6 articles) — preferred
import { mapToOwaspAgentic } from 'governance-sdk/owasp-agentic'; // alias of assessOwaspAgentic
import { mapToNistAiRmf } from 'governance-sdk/nist-ai-rmf'; // alias of assessNistAiRmf
import { mapToIso42001 } from 'governance-sdk/iso-42001'; // alias of assessIso42001
const report = await mapToEuAiAct({
governance: gov, agents: [agent],
auditIntegrity: true, humanOversight: true,
});
// report.disclaimer — embedded "not legal advice" notice
// report.phasedDeadlines — { prohibitedPractices, gpaiTransparency, highRiskObligations, postMarketAndDownstream }
Cryptographically-signed agent identity tokens using Ed25519 (RFC 8032) via
crypto.subtle. Zero runtime dependencies. Tokens include a nonce (jti),
expiry (exp), optional kid for key rotation, and the agent's public key
so any verifier can re-check the signature.
Pair with the requireSignedIdentity() policy to guarantee that enforce
calls come from an agent that actually holds the private key. Note that the
policy checks a boolean (ctx.identityVerified) that your host layer sets
after calling verifyAgentIdentity() — the SDK itself stays zero-state.
import {
createEd25519Identity,
signAgentIdentity,
verifyAgentIdentity,
} from 'governance-sdk/agent-identity-ed25519';
const identity = createEd25519Identity();
const keys = await identity.generateKeyPair();
const token = await signAgentIdentity({
agentId: 'sales-bot',
keys,
ttlSeconds: 3600,
kid: 'v2', // optional: pick-by-id on rotation
capabilities: ['search'], // optional: capability assertions
});
// On the receiving side:
const result = await verifyAgentIdentity(token, {
pinnedPublicKeyHex: pinnedKey, // optional but recommended — see below
});
// => { valid: true, agentId: 'sales-bot' }
Pin your public keys. A token self-describes the public key it was signed
with, so without pinning you're verifying "someone signed this" rather than
"the expected agent signed this." Use pinnedPublicKeyHex whenever you
already know which key the agent should be using.
Test policies against scenarios without affecting production.
import { simulateFleetPolicy } from 'governance-sdk/dry-run';
const result = await simulateFleetPolicy(gov, scenarios);
// => { fleetSummary: { agentsAffected: 11, blockRate: 0.12 }, results: [...] }
Governance needs three things to be real: a point of interception (we sit between the agent and the tool/LLM before it fires), a deterministic agent identity (we know who's calling), and the ability to block or modify (not just observe after the fact). The matrix below is scoped to frameworks where all three hold.
| Framework | Import Path | Input pre-scan | Output post-scan | Output streaming | Tool-call |
|---|---|---|---|---|---|
| Mastra (processor) | governance-sdk/plugins/mastra-processor | ✅ | ✅ | ✅ | ✅ |
| Vercel AI SDK | governance-sdk/plugins/vercel-ai | ✅ | ✅ | ✅ | ✅ |
| OpenAI Agents SDK | governance-sdk/plugins/openai-agents | ✅ | ✅ | ✅¹ | ✅ |
| LangChain | governance-sdk/plugins/langchain | ✅ | ✅ | ✅ | ✅ |
| Anthropic SDK | governance-sdk/plugins/anthropic | ✅ | ✅ | ✅ | ✅ |
| Google Genkit | governance-sdk/plugins/genkit | ✅ | ✅ | ✅ | ✅ |
| LlamaIndex | governance-sdk/plugins/llamaindex | ✅ | ✅ | ✅ | ✅ |
| Mistral | governance-sdk/plugins/mistral | ✅ | ✅ | ✅ | ✅ |
| Ollama | governance-sdk/plugins/ollama | ✅ | ✅ | ✅ | ✅ |
| Mastra (middleware) | governance-sdk/plugins/mastra | ✅² | ✅² | ✅² | ✅ |
¹ OpenAI Agents output guardrails fire at stream final assembly (SDK-native behavior).
² Mastra middleware exposes scanInput / scanOutput / scanOutputStream helpers — explicit calls you make from your runtime loop, rather than automatic lifecycle hooks. Use the mastra-processor export if you want automatic hooks via inputProcessors[] / outputProcessors[].
| Framework | Import Path | Scope |
|---|---|---|
| Model Context Protocol | governance-sdk/plugins/mcp | Build a governed MCP server — input injection pre-scan on tool arguments + output injection scan + tool-call audit for tools you publish. Not for governing MCP servers you consume (govern those at the agent framework layer). |
| MCP trust + chain audit | governance-sdk/plugins/mcp-trust, governance-sdk/plugins/mcp-chain-audit | Declarative trusted-MCP-server registry (allowlist + per-server capability tags — not cryptographic pin-trust; signature/TLS pinning is not implemented) + caller-driven chain-of-custody audit across nested MCP invocations (requires manual recordCall() per hop; not automatic propagation). |
| AWS Bedrock Agents | governance-sdk/plugins/bedrock | Entry-gate only — Bedrock Agents execute tools server-side inside AWS, so we can pre-scan the InvokeAgent input and post-scan the assembled output via scanOutput, but we can't see individual internal tool calls. |
If your agent is not TypeScript, use the Lua Governance REST API directly — it exposes the same policy, scoring, audit, and injection-detection endpoints the SDK uses locally. Native Python / Go SDKs are not shipped yet; a REST client works everywhere.
The SDK itself is pure ESM with zero runtime dependencies, so it runs unmodified under Node, Deno, Bun, Cloudflare Workers, and other Web-standard runtimes — no separate adapter needed.
All framework dependencies are optional peer dependencies — install only what you use.
Vercel AI SDK — experimental_wrapLanguageModel middleware:
import { experimental_wrapLanguageModel, generateText } from 'ai';
import { createGovernance } from 'governance-sdk';
import { createGovernanceMiddleware } from 'governance-sdk/plugins/vercel-ai';
const gov = createGovernance({ rules: [/* ... */] });
const { id: agentId } = await gov.register({
name: 'sales', framework: 'vercel-ai', owner: 'team',
});
const model = experimental_wrapLanguageModel({
model: openai('gpt-4o'),
middleware: createGovernanceMiddleware(gov, { agentId }),
});
OpenAI Agents SDK — native input/output guardrails:
import { Agent } from '@openai/agents';
import {
createInputGuardrail,
createOutputGuardrail,
} from 'governance-sdk/plugins/openai-agents';
const agent = new Agent({
name: 'research',
instructions: '...',
inputGuardrails: [createInputGuardrail(gov, { agentId })],
outputGuardrails: [createOutputGuardrail(gov, { agentId })],
});
LangChain — chat model wrapper:
import { ChatOpenAI } from '@langchain/openai';
import { wrapChatModel } from 'governance-sdk/plugins/langchain';
const model = new ChatOpenAI({ model: 'gpt-4o' });
const guarded = wrapChatModel(model, gov, { agentId });
const res = await guarded.invoke([new HumanMessage('hello')]);
Anthropic SDK — messages.create wrapper:
import Anthropic from '@anthropic-ai/sdk';
import { createGovernedMessages } from 'governance-sdk/plugins/anthropic';
const client = new Anthropic();
const messages = createGovernedMessages(client.messages, gov, { agentId });
const res = await messages.create({
model: 'claude-sonnet-4-5', max_tokens: 1024,
messages: [{ role: 'user', content: 'hi' }],
});
Every pre/post adapter accepts { preprocess: false } or { postprocess: false }
to disable a stage. Both stages are on by default.
All adapters handle all 5 enforcement outcomes with configurable callbacks:
const middleware = createGovernanceMiddleware(gov, {
agentName: 'my-agent',
owner: 'platform-team',
framework: 'mastra',
onBlocked: (decision, tool) => log.warn(`Blocked: ${tool}`),
onWarn: (decision, tool) => log.info(`Warning: ${tool} — ${decision.reason}`),
onMask: (decision, tool, masked) => log.info(`Masked output for ${tool}`),
onApprovalRequired: (decision, tool) => log.info(`Approval needed: ${tool}`),
});
The SDK ships 47 targeted exports so you can import only what you need:
# Core
governance-sdk createGovernance, enforce, presets
governance-sdk/policy policy types and builders
governance-sdk/policy-builder fluent policy builder
governance-sdk/policy-compose compose + conflict resolution
governance-sdk/policy-yaml serialize/deserialize policies
governance-sdk/dry-run simulatePolicy / simulateFleetPolicy
# Scoring
governance-sdk/scorer 7-dimension governance scoring
governance-sdk/behavioral-scorer behavioral signal adjustments
governance-sdk/repo-patterns repository capability detection
# Injection detection
governance-sdk/injection-detect 56-pattern regex detector
governance-sdk/injection-classifier pluggable ML classifier interface
governance-sdk/injection-benchmark LIB — 6.9K-sample benchmark runner
# Audit + identity
governance-sdk/audit-integrity HMAC hash-chain primitives (createIntegrityAudit, verifyAuditIntegrity)
governance-sdk/audit-integrity-verify standalone chain verifier (for offline audit)
governance-sdk/agent-identity agent identity tokens
governance-sdk/agent-identity-ed25519 Ed25519 signing + verification
governance-sdk/kill-switch priority-999 emergency halt
# Standards / compliance
governance-sdk/compliance EU AI Act (6 articles + deadlines)
governance-sdk/owasp-agentic OWASP Top 10 for LLMs / Agentic
governance-sdk/nist-ai-rmf NIST AI RMF (Govern/Map/Measure/Manage)
governance-sdk/iso-42001 ISO/IEC 42001 controls
# Storage
governance-sdk/storage-postgres PostgreSQL storage adapter
governance-sdk/storage-postgres-schema schema DDL + migrations
# Optional observability primitives — passive in-memory, host wires to its own
# monitoring; NOT OpenInference-compliant. A real OTel exporter is on the roadmap.
governance-sdk/events typed event emitter
governance-sdk/metrics in-memory counter / timing snapshots
governance-sdk/otel-hooks governance-prefixed span shape (passive — user must wire)
# Scanner + type surface
governance-sdk/scanner-plugins scanner plugin interface
governance-sdk/token-types token type guards
# Framework integrations (10 featured + MCP toolkit + Bedrock)
governance-sdk/plugins/mastra
governance-sdk/plugins/mastra-processor
governance-sdk/plugins/vercel-ai
governance-sdk/plugins/openai-agents
governance-sdk/plugins/langchain
governance-sdk/plugins/anthropic
governance-sdk/plugins/genkit
governance-sdk/plugins/llamaindex
governance-sdk/plugins/mistral
governance-sdk/plugins/ollama
governance-sdk/plugins/mcp # build a governed MCP server
governance-sdk/plugins/mcp-trust # trusted-server allowlist + capability tags
governance-sdk/plugins/mcp-allowlist # tool/resource allowlist enforcement
governance-sdk/plugins/mcp-chain-audit # caller-driven chain-of-custody audit
governance-sdk/plugins/mcp-call-recorder # nested-invocation call recorder
governance-sdk/plugins/bedrock # entry-gate only (action groups opaque)
runWithOutcome() (a thin helper around gov.recordOutcome) is exposed at the
top-level package export — import { runWithOutcome } from 'governance-sdk'.
npm test)any types in source# Install dependencies
npm install
# Build all packages
npm run build
# Run tests
npm test
# Type-check without emitting
npm run lint
See CONTRIBUTING.md. Security issues: see SECURITY.md.
TypeScript
98.0%