agent-authority 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +118 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +91 -0
- package/README.md +553 -0
- package/dist/a2a.d.ts +73 -0
- package/dist/a2a.d.ts.map +1 -0
- package/dist/a2a.js +117 -0
- package/dist/a2a.js.map +1 -0
- package/dist/audit.d.ts +12 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +52 -0
- package/dist/audit.js.map +1 -0
- package/dist/behalf.d.ts +173 -0
- package/dist/behalf.d.ts.map +1 -0
- package/dist/behalf.js +475 -0
- package/dist/behalf.js.map +1 -0
- package/dist/capability.d.ts +56 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +176 -0
- package/dist/capability.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +273 -0
- package/dist/cli.js.map +1 -0
- package/dist/control-plane.d.ts +57 -0
- package/dist/control-plane.d.ts.map +1 -0
- package/dist/control-plane.js +332 -0
- package/dist/control-plane.js.map +1 -0
- package/dist/crypto.d.ts +68 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +105 -0
- package/dist/crypto.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +40 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/lint.d.ts +17 -0
- package/dist/lint.d.ts.map +1 -0
- package/dist/lint.js +75 -0
- package/dist/lint.js.map +1 -0
- package/dist/mandate.d.ts +99 -0
- package/dist/mandate.d.ts.map +1 -0
- package/dist/mandate.js +141 -0
- package/dist/mandate.js.map +1 -0
- package/dist/mcp-server.d.ts +26 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +111 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/mcp.d.ts +63 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +123 -0
- package/dist/mcp.js.map +1 -0
- package/dist/persist.d.ts +51 -0
- package/dist/persist.d.ts.map +1 -0
- package/dist/persist.js +150 -0
- package/dist/persist.js.map +1 -0
- package/dist/quickstart.d.ts +63 -0
- package/dist/quickstart.d.ts.map +1 -0
- package/dist/quickstart.js +171 -0
- package/dist/quickstart.js.map +1 -0
- package/dist/remote.d.ts +93 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +120 -0
- package/dist/remote.js.map +1 -0
- package/dist/seal.d.ts +12 -0
- package/dist/seal.d.ts.map +1 -0
- package/dist/seal.js +96 -0
- package/dist/seal.js.map +1 -0
- package/dist/store.d.ts +119 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +139 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +173 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/llms.txt +106 -0
- package/package.json +107 -0
- package/schemas/capability.schema.json +14 -0
- package/schemas/mandate.schema.json +68 -0
- package/vectors/mandate-vector.json +63 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for Behalf.
|
|
3
|
+
*
|
|
4
|
+
* A Mandate is a signed, scoped, time-bound capability token that proves who
|
|
5
|
+
* authorized what, within which limits, and through which chain of agents.
|
|
6
|
+
*
|
|
7
|
+
* The token is a biscuit-style Ed25519 signature chain of blocks. Each block
|
|
8
|
+
* carries restrictions (caveats) and publishes a fresh public key; the next
|
|
9
|
+
* block is signed by the matching private key, which the holder must possess to
|
|
10
|
+
* attenuate (append a narrowing block) or to authorize (prove possession of the
|
|
11
|
+
* terminal key). The intersection rule is enforced structurally — every cap
|
|
12
|
+
* caveat must be satisfied — so a downstream agent can only ever shrink
|
|
13
|
+
* authority, never widen it, and a holder cannot present a truncated prefix of
|
|
14
|
+
* its chain because it lacks that prefix's terminal key.
|
|
15
|
+
*/
|
|
16
|
+
/** Restriction attached to a mandate. */
|
|
17
|
+
export type Caveat = {
|
|
18
|
+
t: "principal";
|
|
19
|
+
principal: string;
|
|
20
|
+
} | {
|
|
21
|
+
t: "agent";
|
|
22
|
+
agent: string;
|
|
23
|
+
} | {
|
|
24
|
+
t: "cap";
|
|
25
|
+
can: string[];
|
|
26
|
+
} | {
|
|
27
|
+
t: "expires";
|
|
28
|
+
at: number;
|
|
29
|
+
} | {
|
|
30
|
+
t: "id";
|
|
31
|
+
id: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Cryptographic agent identity binding (SVID-style): the holder must, at
|
|
35
|
+
* authorize, also prove possession of the private key for this public key.
|
|
36
|
+
* Conjunctive — every `agentKey` caveat must be satisfied — so a thief who
|
|
37
|
+
* holds the credential cannot append their own binding to bypass it.
|
|
38
|
+
*/
|
|
39
|
+
| {
|
|
40
|
+
t: "agentKey";
|
|
41
|
+
key: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* One link in the delegation chain: a set of restrictions plus the public key
|
|
45
|
+
* (`nextPub`) that authorizes whoever signs the *next* block.
|
|
46
|
+
*/
|
|
47
|
+
export interface Block {
|
|
48
|
+
caveats: Caveat[];
|
|
49
|
+
/** base64url SPKI Ed25519 public key for the next block's signature. */
|
|
50
|
+
nextPub: string;
|
|
51
|
+
}
|
|
52
|
+
/** Wire form of a mandate — exactly what gets serialized/transmitted. */
|
|
53
|
+
export interface MandateToken {
|
|
54
|
+
/** Format version. */
|
|
55
|
+
v: 2;
|
|
56
|
+
/** Root identifier (stable across the whole delegation chain). */
|
|
57
|
+
id: string;
|
|
58
|
+
/** Ordered blocks: root grant first, narrowings appended after. */
|
|
59
|
+
blocks: Block[];
|
|
60
|
+
/** Per-block Ed25519 signatures (base64url). sigs[i] signs blocks[i]. */
|
|
61
|
+
sigs: string[];
|
|
62
|
+
/** Issuer (root) public key, base64url SPKI — pin this to establish trust. */
|
|
63
|
+
rootPub: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Proof of possession of a mandate's terminal key, presented at authorize time
|
|
67
|
+
* to prove the bearer is the legitimate tail of the chain (not a truncated
|
|
68
|
+
* prefix). `ts` is when it was minted (checked for freshness); `sig` is the
|
|
69
|
+
* Ed25519 signature over the proof message.
|
|
70
|
+
*/
|
|
71
|
+
export interface Proof {
|
|
72
|
+
ts: number;
|
|
73
|
+
sig: string;
|
|
74
|
+
/**
|
|
75
|
+
* Optional verifier-issued single-use nonce (from `engine.challenge()`).
|
|
76
|
+
* When present it is bound into the signature and consumed on use, giving
|
|
77
|
+
* true anti-replay; without it, replay is bounded only by `proofSkewMs`.
|
|
78
|
+
*/
|
|
79
|
+
nonce?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Agent-identity signatures over the same proof message, one per agent key the
|
|
82
|
+
* holder controls. At authorize, every `agentKey` caveat in the chain must be
|
|
83
|
+
* satisfied by one of these (conjunctive), proving the presenter is the bound
|
|
84
|
+
* agent — not merely a possessor of the credential.
|
|
85
|
+
*/
|
|
86
|
+
agentSigs?: string[];
|
|
87
|
+
}
|
|
88
|
+
/** Options for {@link Behalf.grant}. */
|
|
89
|
+
export interface GrantOptions {
|
|
90
|
+
/** The human/org authorizing the agent. */
|
|
91
|
+
principal: string;
|
|
92
|
+
/** The agent being authorized. */
|
|
93
|
+
agent: string;
|
|
94
|
+
/** Capabilities granted (capability grammar strings). */
|
|
95
|
+
can: string[];
|
|
96
|
+
/** Lifetime, e.g. "1h", "10m", "30s", or milliseconds as a number. */
|
|
97
|
+
expiresIn: string | number;
|
|
98
|
+
/**
|
|
99
|
+
* Cryptographically bind this grant to an agent identity (the agent's public
|
|
100
|
+
* key, base64url). The holder must then prove possession of the matching
|
|
101
|
+
* private key at authorize — see the `agentKey` caveat.
|
|
102
|
+
*/
|
|
103
|
+
bindAgent?: string;
|
|
104
|
+
}
|
|
105
|
+
/** Options for {@link Mandate.attenuate}. */
|
|
106
|
+
export interface AttenuateOptions {
|
|
107
|
+
/** Narrowed capability set — must be a subset/narrowing of the parent's. */
|
|
108
|
+
can?: string[];
|
|
109
|
+
/** New (shorter) lifetime relative to now. Never extends past the parent. */
|
|
110
|
+
expiresIn?: string | number;
|
|
111
|
+
/** Optionally re-bind to a specific sub-agent. */
|
|
112
|
+
agent?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Add a cryptographic agent-identity binding (the agent's public key,
|
|
115
|
+
* base64url). Conjunctive with any inherited `agentKey` caveats, so it can
|
|
116
|
+
* only ever add a requirement, never remove one.
|
|
117
|
+
*/
|
|
118
|
+
bindAgent?: string;
|
|
119
|
+
}
|
|
120
|
+
/** The decision fields of an audit record, before it is sealed into the chain. */
|
|
121
|
+
export type AuditFields = Pick<AuditEntry, "mandateId" | "chain" | "action" | "decision" | "reason" | "issuer">;
|
|
122
|
+
/** A single hash-chained audit record (integrity-chained; see README Limitations). */
|
|
123
|
+
export interface AuditEntry {
|
|
124
|
+
seq: number;
|
|
125
|
+
ts: number;
|
|
126
|
+
mandateId: string;
|
|
127
|
+
/** Issuer (root) public key of the mandate, for per-tenant scoping. */
|
|
128
|
+
issuer?: string;
|
|
129
|
+
/** Full chain of ids this token belongs to (root → leaf). */
|
|
130
|
+
chain: string[];
|
|
131
|
+
action: string;
|
|
132
|
+
decision: "allow" | "deny";
|
|
133
|
+
reason?: string;
|
|
134
|
+
/** Hash of the previous entry (hash chain). */
|
|
135
|
+
prevHash: string;
|
|
136
|
+
/** sha256 over (prevHash + canonical entry body). */
|
|
137
|
+
hash: string;
|
|
138
|
+
}
|
|
139
|
+
/** Result of verifying audit-log integrity. */
|
|
140
|
+
export interface AuditIntegrity {
|
|
141
|
+
ok: boolean;
|
|
142
|
+
/** seq of the first broken entry, if any. */
|
|
143
|
+
brokenAt?: number;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* A signed anchor over the audit log's head. Because the head hash commits to
|
|
147
|
+
* every prior entry, a stored checkpoint makes later tail-deletion or rewrites
|
|
148
|
+
* detectable — something the unkeyed hash chain alone cannot do.
|
|
149
|
+
*/
|
|
150
|
+
export interface AuditCheckpoint {
|
|
151
|
+
/** seq of the head entry at checkpoint time (-1 for an empty log). */
|
|
152
|
+
seq: number;
|
|
153
|
+
/** Head entry's hash (the genesis hash for an empty log). */
|
|
154
|
+
hash: string;
|
|
155
|
+
ts: number;
|
|
156
|
+
/** Public key (base64url) of the engine that signed this checkpoint. */
|
|
157
|
+
signer: string;
|
|
158
|
+
/** Ed25519 signature over the canonical {seq, hash, ts} message. */
|
|
159
|
+
sig: string;
|
|
160
|
+
}
|
|
161
|
+
/** A just-in-time consent request tracked by the control plane. */
|
|
162
|
+
export interface ConsentRecord {
|
|
163
|
+
id: string;
|
|
164
|
+
agent: string;
|
|
165
|
+
capability: string;
|
|
166
|
+
context?: Record<string, unknown>;
|
|
167
|
+
/** Tenant issuer this request belongs to (set when created with a tenant token). */
|
|
168
|
+
issuer?: string;
|
|
169
|
+
status: "pending" | "approved" | "denied" | "expired";
|
|
170
|
+
createdAt: number;
|
|
171
|
+
decidedAt?: number;
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for Behalf.
|
|
3
|
+
*
|
|
4
|
+
* A Mandate is a signed, scoped, time-bound capability token that proves who
|
|
5
|
+
* authorized what, within which limits, and through which chain of agents.
|
|
6
|
+
*
|
|
7
|
+
* The token is a biscuit-style Ed25519 signature chain of blocks. Each block
|
|
8
|
+
* carries restrictions (caveats) and publishes a fresh public key; the next
|
|
9
|
+
* block is signed by the matching private key, which the holder must possess to
|
|
10
|
+
* attenuate (append a narrowing block) or to authorize (prove possession of the
|
|
11
|
+
* terminal key). The intersection rule is enforced structurally — every cap
|
|
12
|
+
* caveat must be satisfied — so a downstream agent can only ever shrink
|
|
13
|
+
* authority, never widen it, and a holder cannot present a truncated prefix of
|
|
14
|
+
* its chain because it lacks that prefix's terminal key.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
package/llms.txt
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Behalf
|
|
2
|
+
|
|
3
|
+
> The reference implementation of agent authority. Behalf gives any AI agent a
|
|
4
|
+
> verifiable, scoped, revocable identity and delegation chain in five verbs.
|
|
5
|
+
> MIT-licensed and AI-legible: read this, then use it directly.
|
|
6
|
+
|
|
7
|
+
Everything is one primitive — a **Mandate**: a signed, scoped, time-bound
|
|
8
|
+
capability token proving who authorized what, within which limits, and through
|
|
9
|
+
which chain of agents. There is exactly one canonical way to do each thing.
|
|
10
|
+
|
|
11
|
+
## The five verbs (TypeScript)
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { Behalf } from "agent-authority";
|
|
15
|
+
|
|
16
|
+
// 1. GRANT — a principal authorizes an agent: scoped, capped, short-lived
|
|
17
|
+
const mandate = await Behalf.grant({
|
|
18
|
+
principal: user.id,
|
|
19
|
+
agent: "research-agent",
|
|
20
|
+
can: ["read:calendar", "spend:usd<=50"],
|
|
21
|
+
expiresIn: "1h",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// 2. AUTHORIZE — prove authority before acting (throws if denied)
|
|
25
|
+
await mandate.authorize("spend:usd=20");
|
|
26
|
+
|
|
27
|
+
// 3. ATTENUATE — hand a NARROWED mandate to a sub-agent; can only shrink scope
|
|
28
|
+
const child = mandate.attenuate({ can: ["read:calendar"], expiresIn: "10m" });
|
|
29
|
+
|
|
30
|
+
// 4. REVOKE — kill a mandate and its whole downstream chain, instantly
|
|
31
|
+
await Behalf.revoke(mandate.id);
|
|
32
|
+
|
|
33
|
+
// 5. AUDIT — every authorize() already wrote a hash-chained record
|
|
34
|
+
const trail = await Behalf.audit(mandate.id);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Five verbs: grant, authorize, attenuate, revoke, audit. Do not add a sixth.
|
|
38
|
+
|
|
39
|
+
## MCP middleware (secure a whole agent in ~6 lines)
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { withBehalf } from "agent-authority/mcp";
|
|
43
|
+
|
|
44
|
+
const server = withBehalf(myMcpServer, {
|
|
45
|
+
policy: {
|
|
46
|
+
send_email: "write:email",
|
|
47
|
+
read_calendar: "read:calendar",
|
|
48
|
+
transfer_funds: (args) => `spend:usd<=${args.amount}`,
|
|
49
|
+
},
|
|
50
|
+
onDenied: "throw", // or "prompt" for just-in-time user consent
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Pass the caller's mandate on the call context: `server.callTool(name, args, { mandate })`.
|
|
55
|
+
|
|
56
|
+
## Capability grammar
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
read:calendar # simple capability
|
|
60
|
+
write:repo/acme-app # resource-scoped (path segments)
|
|
61
|
+
spend:usd<=50 # quantitative limit
|
|
62
|
+
send:email rate<=10/h # rate limit
|
|
63
|
+
* # wildcard (discouraged; lint warns)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
When authorizing, name a concrete amount: `spend:usd=20` is checked against the
|
|
67
|
+
grant's `spend:usd<=50`.
|
|
68
|
+
|
|
69
|
+
## Rules an agent must follow
|
|
70
|
+
|
|
71
|
+
- Use only the five verbs. `attenuate()` only ever narrows (throws `WideningError`
|
|
72
|
+
on a wider or direction-flipped request).
|
|
73
|
+
- Effective authority = the principal's grant ∩ every narrowing along the chain.
|
|
74
|
+
A compromised middle agent can never widen scope.
|
|
75
|
+
- A mandate is a biscuit-style Ed25519 signature chain. **Authorizing requires
|
|
76
|
+
proving possession of the chain's terminal key**, so a serialized token is not
|
|
77
|
+
a usable bearer credential and a holder cannot truncate its chain to regain a
|
|
78
|
+
parent's scope. `mandate.authorize(action)` does this in-process. Across a
|
|
79
|
+
boundary, present `mandate.prove(action)` and verify with
|
|
80
|
+
`engine.authorize(token, action, proof)` (or use `agent-authority/a2a`).
|
|
81
|
+
- For an advisory "would this scope allow X?" check (no possession), use
|
|
82
|
+
`engine.inspect(token, action)`.
|
|
83
|
+
- Verification is offline (signature + scope + expiry + possession), public-key
|
|
84
|
+
only. Revocation and the shared rate cap need a network check.
|
|
85
|
+
- Prefer short `expiresIn` and the tightest `can` that still does the job.
|
|
86
|
+
|
|
87
|
+
## Discovery tools (MCP)
|
|
88
|
+
|
|
89
|
+
`agent-authority/mcp` exposes `request_mandate`, `present_mandate`, and `check_authority`
|
|
90
|
+
(advisory) so any agent can obtain and reason about authority natively. Get them
|
|
91
|
+
via `behalfMcpTools()`, or run the standalone server: `node dist/mcp-server.js`.
|
|
92
|
+
|
|
93
|
+
## Quickstarts & schemas
|
|
94
|
+
|
|
95
|
+
- `agent-authority quickstart <surface>` wires it into Claude Code, Cursor, Copilot,
|
|
96
|
+
Gemini, GPT, or any AI (see QUICKSTART.md).
|
|
97
|
+
- Typed schemas: /schemas/mandate.schema.json, /schemas/capability.schema.json.
|
|
98
|
+
|
|
99
|
+
## What maps to the standard underneath
|
|
100
|
+
|
|
101
|
+
- Mandate ↔ Agentic JWT / capability tokens (IBCT-style)
|
|
102
|
+
- attenuate() + proof-of-possession ↔ biscuit-style attenuation & sealing
|
|
103
|
+
- principal → agent grant ↔ OAuth 2.1 On-Behalf-Of / token exchange (RFC 8693)
|
|
104
|
+
- agent identity ↔ SPIFFE / SVID
|
|
105
|
+
- audit record ↔ provenance / non-repudiation records
|
|
106
|
+
- transport ↔ MCP + A2A authorization layers
|
package/package.json
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-authority",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Authorization for AI agents: verifiable, scoped, revocable capability tokens (mandates) with attenuable delegation, for MCP and A2A. The reference implementation of agent authority — zero-dependency TypeScript & Python.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/novaai0401-ui/agent-authority.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/novaai0401-ui/agent-authority#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/novaai0401-ui/agent-authority/issues"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"provenance": true
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"agent-authority",
|
|
22
|
+
"agent",
|
|
23
|
+
"authority",
|
|
24
|
+
"authorization",
|
|
25
|
+
"ai-agents",
|
|
26
|
+
"agentic",
|
|
27
|
+
"llm",
|
|
28
|
+
"delegation",
|
|
29
|
+
"capability",
|
|
30
|
+
"capability-tokens",
|
|
31
|
+
"capability-based-security",
|
|
32
|
+
"least-privilege",
|
|
33
|
+
"mandate",
|
|
34
|
+
"macaroons",
|
|
35
|
+
"biscuit",
|
|
36
|
+
"oauth",
|
|
37
|
+
"on-behalf-of",
|
|
38
|
+
"spiffe",
|
|
39
|
+
"ed25519",
|
|
40
|
+
"revocation",
|
|
41
|
+
"mcp",
|
|
42
|
+
"mcp-server",
|
|
43
|
+
"a2a",
|
|
44
|
+
"tool-calling",
|
|
45
|
+
"permissions"
|
|
46
|
+
],
|
|
47
|
+
"files": [
|
|
48
|
+
"dist",
|
|
49
|
+
"schemas",
|
|
50
|
+
"vectors",
|
|
51
|
+
"llms.txt",
|
|
52
|
+
"QUICKSTART.md",
|
|
53
|
+
"CHANGELOG.md"
|
|
54
|
+
],
|
|
55
|
+
"bin": {
|
|
56
|
+
"agent-authority": "./dist/cli.js",
|
|
57
|
+
"agent-authority-mcp": "./dist/mcp-server.js",
|
|
58
|
+
"agent-authority-control-plane": "./dist/control-plane.js"
|
|
59
|
+
},
|
|
60
|
+
"exports": {
|
|
61
|
+
".": {
|
|
62
|
+
"types": "./dist/index.d.ts",
|
|
63
|
+
"import": "./dist/index.js"
|
|
64
|
+
},
|
|
65
|
+
"./mcp": {
|
|
66
|
+
"types": "./dist/mcp.d.ts",
|
|
67
|
+
"import": "./dist/mcp.js"
|
|
68
|
+
},
|
|
69
|
+
"./a2a": {
|
|
70
|
+
"types": "./dist/a2a.d.ts",
|
|
71
|
+
"import": "./dist/a2a.js"
|
|
72
|
+
},
|
|
73
|
+
"./server": {
|
|
74
|
+
"types": "./dist/mcp-server.d.ts",
|
|
75
|
+
"import": "./dist/mcp-server.js"
|
|
76
|
+
},
|
|
77
|
+
"./control-plane": {
|
|
78
|
+
"types": "./dist/control-plane.d.ts",
|
|
79
|
+
"import": "./dist/control-plane.js"
|
|
80
|
+
},
|
|
81
|
+
"./remote": {
|
|
82
|
+
"types": "./dist/remote.d.ts",
|
|
83
|
+
"import": "./dist/remote.js"
|
|
84
|
+
},
|
|
85
|
+
"./llms.txt": "./llms.txt"
|
|
86
|
+
},
|
|
87
|
+
"scripts": {
|
|
88
|
+
"build": "tsc -p tsconfig.json",
|
|
89
|
+
"test": "tsc -p tsconfig.test.json && node scripts/run-tests.mjs",
|
|
90
|
+
"test:interop": "npm run build && node scripts/interop.mjs",
|
|
91
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
92
|
+
"prepublishOnly": "npm run build && npm test",
|
|
93
|
+
"example:data-access": "node dist-test/examples/data-access-agent.js",
|
|
94
|
+
"example:spend": "node dist-test/examples/spend-limited-agent.js",
|
|
95
|
+
"example:delegation": "node dist-test/examples/two-agent-delegation.js",
|
|
96
|
+
"example:a2a": "node dist-test/examples/a2a-delegation.js",
|
|
97
|
+
"example:control-plane": "node dist-test/examples/control-plane.js",
|
|
98
|
+
"examples": "tsc -p tsconfig.test.json && node scripts/run-examples.mjs"
|
|
99
|
+
},
|
|
100
|
+
"engines": {
|
|
101
|
+
"node": ">=18"
|
|
102
|
+
},
|
|
103
|
+
"devDependencies": {
|
|
104
|
+
"@types/node": "^25.9.2",
|
|
105
|
+
"typescript": "^5.4.0"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://behalf.dev/schemas/capability.schema.json",
|
|
4
|
+
"title": "Behalf Capability",
|
|
5
|
+
"description": "A capability string in the Behalf grammar: <verb>:<resource>[<op><amount>] [rate<op><value>/<unit>]. Examples: read:calendar, write:repo/acme-app, spend:usd<=50, send:email rate<=10/h, * (wildcard, discouraged).",
|
|
6
|
+
"type": "string",
|
|
7
|
+
"pattern": "^(\\*|[^:\\s]+:[^<>=\\s]+((<=|>=|<|>|=)[0-9]*\\.?[0-9]+)?(\\s+rate(<=|>=|<|>|=)[0-9]*\\.?[0-9]+(/[smhd])?)*)$",
|
|
8
|
+
"examples": [
|
|
9
|
+
"read:calendar",
|
|
10
|
+
"write:repo/acme-app",
|
|
11
|
+
"spend:usd<=50",
|
|
12
|
+
"send:email rate<=10/h"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://behalf.dev/schemas/mandate.schema.json",
|
|
4
|
+
"title": "Behalf Mandate Token",
|
|
5
|
+
"description": "The wire form of a Mandate: a signed, scoped, time-bound capability token. Macaroon-style: a root identifier plus ordered caveats bound by an HMAC chain. Holders may append narrowing caveats (attenuation) but can never widen.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["v", "id", "caveats", "sig"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"v": { "const": 1, "description": "Token format version." },
|
|
11
|
+
"id": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "Root identifier, stable across the whole delegation chain."
|
|
14
|
+
},
|
|
15
|
+
"caveats": {
|
|
16
|
+
"type": "array",
|
|
17
|
+
"description": "Ordered restrictions; root grant first, narrowings appended.",
|
|
18
|
+
"items": { "$ref": "#/$defs/caveat" }
|
|
19
|
+
},
|
|
20
|
+
"sig": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"pattern": "^[0-9a-f]+$",
|
|
23
|
+
"description": "HMAC chain signature over (id, caveats...). Hex."
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"$defs": {
|
|
27
|
+
"caveat": {
|
|
28
|
+
"oneOf": [
|
|
29
|
+
{
|
|
30
|
+
"type": "object",
|
|
31
|
+
"required": ["t", "principal"],
|
|
32
|
+
"additionalProperties": false,
|
|
33
|
+
"properties": { "t": { "const": "principal" }, "principal": { "type": "string" } }
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"type": "object",
|
|
37
|
+
"required": ["t", "agent"],
|
|
38
|
+
"additionalProperties": false,
|
|
39
|
+
"properties": { "t": { "const": "agent" }, "agent": { "type": "string" } }
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"type": "object",
|
|
43
|
+
"required": ["t", "can"],
|
|
44
|
+
"additionalProperties": false,
|
|
45
|
+
"properties": {
|
|
46
|
+
"t": { "const": "cap" },
|
|
47
|
+
"can": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"items": { "$ref": "https://behalf.dev/schemas/capability.schema.json" }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"type": "object",
|
|
55
|
+
"required": ["t", "at"],
|
|
56
|
+
"additionalProperties": false,
|
|
57
|
+
"properties": { "t": { "const": "expires" }, "at": { "type": "integer", "description": "Expiry, ms since epoch." } }
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"type": "object",
|
|
61
|
+
"required": ["t", "id"],
|
|
62
|
+
"additionalProperties": false,
|
|
63
|
+
"properties": { "t": { "const": "id" }, "id": { "type": "string" } }
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Cross-language Behalf test vector. Verify with trust=[pubkey] and a clock fixed at proof.ts: engine.authorize(token, action, proof) must ALLOW; tampering any field must DENY. Proof message: behalf-pop\\n{id}\\n{sigs,}\\n{ts}\\n{action}\\n{nonce-or-empty}.",
|
|
3
|
+
"action": "spend:usd=10",
|
|
4
|
+
"pubkey": "h4tQPvHL33UEH-y-vAbp37Q0DgCaUvKhNUb1RPITXBg",
|
|
5
|
+
"token": {
|
|
6
|
+
"v": 2,
|
|
7
|
+
"id": "fc05e43e-5299-4725-8912-20a525a54135",
|
|
8
|
+
"blocks": [
|
|
9
|
+
{
|
|
10
|
+
"caveats": [
|
|
11
|
+
{
|
|
12
|
+
"t": "principal",
|
|
13
|
+
"principal": "vector"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"t": "agent",
|
|
17
|
+
"agent": "root"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"t": "cap",
|
|
21
|
+
"can": [
|
|
22
|
+
"read:calendar",
|
|
23
|
+
"spend:usd<=50"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"t": "expires",
|
|
28
|
+
"at": 1781270690715
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"nextPub": "DuqOtYTt-eMazEo0IRCEFK77rtMz0GVGgRGeUX3wBtE"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"caveats": [
|
|
35
|
+
{
|
|
36
|
+
"t": "cap",
|
|
37
|
+
"can": [
|
|
38
|
+
"spend:usd<=20"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"t": "agent",
|
|
43
|
+
"agent": "sub"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"t": "id",
|
|
47
|
+
"id": "d067b7fe-02a9-46be-ab40-dbebeecea96e"
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"nextPub": "hJn-jjbxXzcow0tf-bnMuTa5EVFAEZO-k5B7qfa9Www"
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"sigs": [
|
|
54
|
+
"ncNFmqcb5omNijLf1d2KZk-vj-Kw5VTQDZluc6L1XHYcKC9KJlWiuNQNd9RLWV1TU8bs1TZo367RC3Q6VG4gDA",
|
|
55
|
+
"uFaNyCSQI6P2lRiyvykqTDyobZ2SKOvG8yetGzv0po9ee6oqn-2QeW0SFWxQRPnhWfj4xXwA_t6s1olF3X8dDQ"
|
|
56
|
+
],
|
|
57
|
+
"rootPub": "h4tQPvHL33UEH-y-vAbp37Q0DgCaUvKhNUb1RPITXBg"
|
|
58
|
+
},
|
|
59
|
+
"proof": {
|
|
60
|
+
"ts": 1781267090717,
|
|
61
|
+
"sig": "bgzRquVLdMnQrFKgD0Sese6MbcRGXC_7dXWMVSV9F3EMT6LpfrCgH_YQABhVWpgvCqkX9z9ZJsrq_9OGPAioAg"
|
|
62
|
+
}
|
|
63
|
+
}
|