Attenu Guard
БесплатноПоддерживаетсяAttenu Guard for TypeScript: enforces per-agent permissions on every tool call and sub-agent handoff — a sub-agent never holds more than its parent — and writes
Описание
Attenu Guard for TypeScript: enforces per-agent permissions on every tool call and sub-agent handoff — a sub-agent never holds more than its parent — and writes a hash-chained audit log you verify offline. Same bundle format as the Python library.
README
Permissions for multi-agent systems, enforced at the point of the call.
A sub-agent holds a subset of what its parent holds. Every tool call is checked against the calling agent's own permissions, every handoff narrows them, and every decision lands in a hash-chained log that anyone can verify offline — without this library, its authors, or a network.
This is the TypeScript implementation. It writes the same ledger and the same evidence bundle as the Python library, and reads either one's output.
- Zero runtime dependencies — Node's own
crypto, nothing else - ESM and CommonJS builds, types included
- Node 20 and above (the LangGraph.js adapter depends on
langsmith, which needs the global WebCrypto that Node 19 introduced; Node 18 is end-of-life)
Install
npm install attenu-guard
Verify a run you did not produce
You have a bundle exported from an agent run, and the public half of the key that anchored it. You do not need an account, a network, or the system that produced it.
npx attenu-guard verify run.bundle.json --pubkey <hex>
integrity=True monotonicity=True containment=True anchor=verified nodes=3 actions_checked=2
OK
Three things were checked from the file alone:
- integrity — every entry hashes to the next, and the head matches the signed anchor. Nothing was inserted, removed, or rewritten afterwards.
- monotonicity — every delegated agent's permissions are a subset of its parent's, all the way down.
- containment — every allowed action fell inside what the acting agent held at the time.
Those are different findings. A broken chain says the record was edited. A broken monotonicity check says the record is honest and the delegation was not — which points at a different person. The same checks are available in code:
import { parseBundle, verifyBundle, Ed25519Verifier } from "attenu-guard";
import { readFileSync } from "node:fs";
const bundle = parseBundle(readFileSync("run.bundle.json", "utf8"));
const report = verifyBundle(bundle, new Ed25519Verifier(Buffer.from(pubkeyHex, "hex")));
report.ok; // false if any check failed
report.checks; // { integrity, monotonicity, containment, anchor }
report.failures; // one line per finding
Pass null instead of a key and the chain, subset and containment checks still
run; the anchor is reported as not checked, and ok then means consistent
rather than verified — a consistent full rewrite by someone holding the signing
key cannot be excluded without the key.
Want to try this before wiring it in? The repo ships a sample: from a clone,
npx attenu-guard@latest verify test/fixtures/clean_ed25519.bundle.json --pubkey d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511aprints the sameanchor=verified/OKresult shown above. For the three-bundle version — clean, tampered, widened, each a different kind of failure — see the Python library's walkthrough.
Guard a delegation chain
import { Authority, Guard, RowLimit, EgressRank } from "attenu-guard";
const orchestrator = Guard.issue("orchestrator", new Authority({
scopes: ["crm.*", "mail.send"],
ceilings: [new RowLimit(100_000), new EgressRank("any")],
ttl: 3600,
}));
// The handoff is where permissions narrow. A request wider than the parent
// holds comes back narrowed — `fs.write` was never the orchestrator's to give.
const summarizer = orchestrator.delegate("summarizer", new Authority({
scopes: ["crm.read", "fs.write"],
ceilings: [new RowLimit(5_000), new EgressRank("none")],
ttl: 900,
}), "summarise the Q3 pipeline");
summarizer.authority.scopes; // Set { "crm.read" }
summarizer.isNarrowerThan(orchestrator); // true, by construction
const decision = summarizer.check("crm.read", { context: { rows: 4200 } });
if (!decision.allowed) console.log(decision.explain());
summarizer.enforce("crm.export", { context: { egress: "any" } }); // throws AuthorityDenied
check returns a Decision and never throws — a denial is an outcome to reason
about, not a bug. enforce throws AuthorityDenied where a denial should stop
the caller. wouldAllow runs the same evaluation and writes nothing, so a
planner can ask without leaving a record of an attempt.
Beyond scopes, a chain has ceilings no single-agent permission model expresses:
row and spend caps, an ordered egress rank, per-scope call limits that meter
themselves, membership and prefix bounds, a TTL, and depth and fanout limits on
the tree. revoke cascades to a whole subtree; revokeAgent bans a principal
chain-wide, so a framework that hands off to it again cannot mint it fresh
permissions. A constraint type this build does not recognise denies rather than
going unenforced.
Export the evidence when the run is done:
import { exportBundle, Ed25519Signer } from "attenu-guard";
const signer = Ed25519Signer.generate();
const bundle = exportBundle(orchestrator.auditLog(), signer);
// Publish the bundle; distribute signer.publicBytesRaw() out of band.
LangGraph.js
import { ToolNode } from "@langchain/langgraph/prebuilt";
import { delegateTo, guardTools, toolArgs } from "attenu-guard/adapters/langgraph";
const researcher = delegateTo(supervisor, {
agentId: "researcher",
request: new Authority({ scopes: ["crm.read"], ceilings: [new RowLimit(500)], ttl: 900 }),
task: "summarise the pipeline",
});
const node = new ToolNode(guardTools(researcher, [crmQuery, wireMoney], {
scopes: { crm_query: "crm.read", wire_money: "payments.send" },
contexts: { crm_query: (input) => ({ rows: toolArgs(input).limit }) },
onDenied: (decision) => `Denied by attenu-guard: ${decision.explain()}`,
}));
wire_money is refused: the researcher does not hold payments.send, and the
supervisor could not have granted it either. The tool body never runs. With
onDenied the model sees the refusal and can re-plan; leave it out and the call
throws AuthorityDenied instead.
Which hook this uses. LangGraph.js exposes no public before-tool-call hook —
ToolNode resolves the tool itself and calls tool.invoke(toolCall, runtime)
inside a protected runTool. So the adapter takes the tool-wrapping form:
guardTool returns a stand-in whose invoke authorizes first and forwards
everything else — name, description, schema — to the original, which is
all ToolNode matches on. guardNode wraps any node callable the same way, and
addGuardedNode registers one on a graph. LangGraph is never imported by the
adapter; it is a devDependency here, used to test against the real ToolNode.
Cross-language interoperability
Same bundle format as the Python library, verified by cross-language fixtures.
tools/gen_fixtures.py generates test/fixtures/ from the Python package —
canonical-form vectors, entry-hash vectors, meet and subsumption cases, a raw
ledger, and one bundle per tamper mode with the report Python produces for each.
The suite reproduces all of it, and a separate test writes a ledger and bundle
here and has the Python CLI verify them, so both directions are covered.
Versions 0.2 and later use RFC 8785 JCS for every
signed or hashed JSON surface: Delegation Tokens, ledger entries, anchors and
evidence bundles. Tokens, ledger entries, anchors and bundles emit
"c14n":"JCS" as an informational label. Verifiers enforce JCS from canonical
bytes and hashes regardless of that label. Duplicate object members, non-finite
numbers, lone UTF-16 surrogates and non-canonical spellings are rejected instead
of being normalized after verification.
This is the only supported format. There is no 0.1 compatibility reader or dual-signing mode. The 19 committed interop vectors include the separating cases for number spelling, raw Unicode, UTF-16 member ordering, large integers, duplicates, non-finite values and an unmarked canonical header.
test/fixtures/vectors/bundles/bundle_vectors_v1.json is the second, bundle-level
suite: whole evidence bundles for verifyBundle, the check an auditor runs on a
published ledger with no engine and no vendor in the loop. The token vectors pin
what a delegation token means; these pin what the LEDGER of a run has to satisfy.
A bundle verifier reports a LIST of failures rather than one reject reason, so
each rejecting case declares the minimal set of {reason, seq, node} that MUST
appear, at that exact position. A conformant verifier may report more, never
fewer and never elsewhere. verifyBundle returns those positions as
failure_details, the structured twin of failures: one
{reason, seq, node, call_id, detail} entry per string, in the same order.
test/fixtures/vectors/envelopes/envelope_vectors_v1.json is the third suite:
observer envelopes. An envelope is a witness's Ed25519 signature over the
IDENTITY of one committed ledger entry — chain_id, node, seq,
entry_hash, event, and call_id on an allow — carried beside the ledger in
a bundle's top-level envelopes array. It answers the one question the other
two suites cannot: was this delegation event signed by something outside the
process that wrote it? An envelope is never required, and a bundle without them
verifies exactly as before with every entry reporting process-asserted. A
present one has to verify, and a broken one lands in the same failure list under
one of seven named reasons. Pass the trust set as
verifyBundle(bundle, signer, { witnessKeys }), or attenu-guard verify --witness-keys FILE from the command line; whose signatures count is yours to
decide, so the keys come from you and never from the bundle. The report carries
the per-entry state, the result and the report line — witness-signed (matched), and so on — for every entry, so a reader sees which hops were
covered before reading which one failed.
One entry, at most one envelope. A second envelope naming a subject.seq an
earlier one in the same array already named is envelope_duplicate_subject at
the covered entry, and that entry reports process-asserted: two observations
of one event contradict each other by construction, and an entry whose coverage
is disputed must not read as clean.
witness.alg is checked against the contract, not against the trust-set row.
v1 defines EdDSA and no other algorithm, so an envelope naming anything else
is envelope_unknown_witness whatever the row it is compared with happens to
say, and a trust-set row declaring another algorithm is refused when the trust
set is built.
A bundle is attacker-supplied, so verifyBundle reports and never throws.
Every envelope member is an untrusted JSON value of any type: a seq that is
not an integer, an event or a witness.kid that is not a string, a sig
that is not a hex string, and a value JCS cannot represent are each a named
reason at a defined position. witnessKeys is the one envelope input that is
not attacker-supplied — the deployment chose those keys — so a malformed row
there throws, naming its kid, rather than being folded into a finding about
the bundle. Python and TypeScript are checked against each other on a matrix of
hostile values in every envelope member: same verdict, same reason, same
position, same failure string.
What it does not do
- It does not decide what permissions a task needs. You state them; this library enforces that a child never exceeds its parent and records what happened.
- It does not sandbox anything. A tool called around the guard is not guarded.
- The in-process integrity seal catches bugs and casual mutation, not code running in the same process. Tamper-evidence comes from the signed bundle.
HS256TestSigneris for tests and local development. It is symmetric, so anyone who can verify can also forge. UseEd25519Signerin production.
Development
npm install
npm run build
npm test
npm test builds first. The Python-CLI interoperability test skips unless a
Python attenu-guard is reachable:
ATTENU_GUARD_PY=path/to/venv/bin/attenu-guard npm test
Regenerate the fixtures with npm run fixtures (needs the Python package and
cryptography importable).
Publishing runs on a v* tag through npm trusted publishing, which needs a
one-time trusted-publisher configuration on the npm side for this package.
Licence
Apache-2.0. Security reports: see SECURITY.md.
Установить Attenu Guard в Claude Desktop, Claude Code, Cursor
unyly install attenu-guardСтавит в Claude Desktop, Claude Code, Cursor и VS Code — сам разбирается с npx, uvx и сборкой из исходников.
Впервые? Поставь CLI: curl -fsSL https://unyly.org/install | sh
Или настроить вручную
Выполни в терминале:
claude mcp add attenu-guard --env ATTENU_GUARD_PY="" -- npx -y attenu-guardПошаговые гайды: как установить Attenu Guard
FAQ
Attenu Guard MCP бесплатный?
Да, Attenu Guard MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Attenu Guard?
Да, требуются переменные окружения: ATTENU_GUARD_PY. Unyly подставит их в конфиг при установке.
Attenu Guard — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Attenu Guard в Claude Desktop, Claude Code или Cursor?
Открой Attenu Guard на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
Fetch
Web content fetching and conversion for efficient LLM usage.
Roblox Studio
Enables AI coding tools to control Roblox Studio for workspace exploration, instance manipulation, and script management. It provides tools for playtesting, sce
автор: paralovAWS KB Retrieval
Retrieval from AWS Knowledge Base using Bedrock Agent Runtime.
автор: modelcontextprotocolSpring AI MCP Server
Provides auto-configuration for setting up an MCP server in Spring Boot applications.
llm-analysis-assistant
A very streamlined mcp client that supports calling and monitoring stdio/sse/streamableHttp, and can also view request responses through the /logs page. It also
автор: xuzexin-hzMCP-Agent
A simple, composable framework to build agents using Model Context Protocol by [LastMile AI](https://www.lastmileai.dev)
автор: lastmile-aiSpring AI MCP Client
Provides auto-configuration for MCP client functionality in Spring Boot applications.
mcp.natoma.ai
A Hosted MCP Platform to discover, install, manage and deploy MCP servers by [Natoma Labs](https://www.natoma.ai)
MCPHub
Website to list high quality MCP servers and reviews by real users. Also provide online chatbot for popular LLM models with MCP server support.
MCP Servers Rating and User Reviews
Website to rate MCP servers, write authentic user reviews, and [search engine for agent & mcp](http://www.deepnlp.org/search/agent)
Compare Attenu Guard with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории ai
