@kya-brasil/agent-tools 0.10.0 → 0.11.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/README.md +17 -0
- package/dist/auth.d.ts +52 -5
- package/dist/auth.js +108 -27
- package/dist/index.d.ts +6 -0
- package/dist/index.js +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -100,6 +100,23 @@ const { response, callee } = await me.fetch("https://beta.com.br/orders", { meth
|
|
|
100
100
|
if (!callee?.ok) throw new Error("unsigned or invalid callee"); // callee.kya: principal, mandate, status
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
+
### Sub-agents (delegation, D-30): one certificate signature for the whole tree
|
|
104
|
+
|
|
105
|
+
When the mandate carries `delegation: { max_depth, max_ttl_seconds }`, the agent hands a narrower mandate to
|
|
106
|
+
another key with its own signature, no new e-CNPJ or e-CPF signature. The sub-agent signs its requests with its own
|
|
107
|
+
key and sends the chain; your middleware verifies it offline and `check()` / `allowed()` evaluate every mandate in it.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { signDelegation, signRequest } from "@kya-brasil/agent-tools";
|
|
111
|
+
const chain = await signDelegation({ credential, key: agentKey, delegate: { kind: "web-bot-auth", id: subPublicKey },
|
|
112
|
+
mandate: { actions: ["quote"], max_per_operation: { amount: 500, currency: "BRL" } }, ttlSeconds: 600 });
|
|
113
|
+
const headers = await signRequest({ credentialId, key: subKey, delegation: chain, method: "GET", url });
|
|
114
|
+
// server side: c.get("kya").delegation → { depth, delegate, mandates, expires_at, chain }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Short-lived (at most `max_ttl_seconds`, never past the credential), contained in the parent mandate, at most 3
|
|
118
|
+
levels; revoking the credential ends the whole tree. The issuer stores nothing. [SPEC](https://github.com/celfons/kya-brasil/blob/main/docs/SPEC.md) section 17.
|
|
119
|
+
|
|
103
120
|
## Person: an agent that acts for you (e-CPF)
|
|
104
121
|
|
|
105
122
|
```ts
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Delegation, type DelegationAnchor } from "@kya-brasil/core";
|
|
2
|
+
import { type ActionQuery, type AgentKey, type Credential, type CredentialStatus, type KyaClient, type Mandate, type Verdict, type Verification, type KyaOptions } from "./index.js";
|
|
2
3
|
export declare const KYA_HEADERS: {
|
|
3
4
|
readonly credential: "kya-credential";
|
|
4
5
|
readonly created: "kya-created";
|
|
5
6
|
readonly expires: "kya-expires";
|
|
6
7
|
readonly signature: "kya-signature";
|
|
7
8
|
readonly content: "kya-content-sha256";
|
|
9
|
+
readonly delegation: "kya-delegation";
|
|
8
10
|
};
|
|
11
|
+
export type { Delegation } from "@kya-brasil/core";
|
|
9
12
|
export declare function sha256Hex(data: string | ArrayBuffer | Uint8Array): Promise<string>;
|
|
10
13
|
/** Canonical URL for the signature: origin (lowercase host) + path + query, no fragment. */
|
|
11
14
|
export declare function canonicalUrl(url: string): string;
|
|
@@ -16,6 +19,7 @@ export declare function requestText(p: {
|
|
|
16
19
|
created: number;
|
|
17
20
|
expires: number;
|
|
18
21
|
contentSha256?: string | null;
|
|
22
|
+
delegationSha256?: string | null;
|
|
19
23
|
}): string;
|
|
20
24
|
export type SigningKey = {
|
|
21
25
|
kind: "evm" | "erc8004";
|
|
@@ -24,7 +28,10 @@ export type SigningKey = {
|
|
|
24
28
|
kind: "web-bot-auth";
|
|
25
29
|
privateKey: CryptoKey | string;
|
|
26
30
|
};
|
|
27
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Signs a request with the agent key and returns the KYA headers to send with it. A sub-agent passes its
|
|
33
|
+
* `delegation` chain (from signDelegation) and signs with its own key.
|
|
34
|
+
*/
|
|
28
35
|
export declare function signRequest(p: {
|
|
29
36
|
credentialId: string;
|
|
30
37
|
key: SigningKey;
|
|
@@ -33,7 +40,28 @@ export declare function signRequest(p: {
|
|
|
33
40
|
body?: string | ArrayBuffer | Uint8Array | null;
|
|
34
41
|
ttlSeconds?: number;
|
|
35
42
|
now?: Date;
|
|
43
|
+
delegation?: Delegation[];
|
|
36
44
|
}): Promise<Record<string, string>>;
|
|
45
|
+
/** The key a signing key verifies as, in the form a credential or a delegation names it. */
|
|
46
|
+
export declare function agentKeyOf(key: SigningKey): Promise<AgentKey>;
|
|
47
|
+
/**
|
|
48
|
+
* Delegation to a sub-agent (D-30): hands `delegate` a mandate contained in the delegator's, for `ttlSeconds`
|
|
49
|
+
* (at most the mandate's `delegation.max_ttl_seconds`). `key` is the credential's agent key, or, to delegate
|
|
50
|
+
* again, the key of the last delegate in `chain`. Returns the new chain; the sub-agent sends it with
|
|
51
|
+
* signRequest({ delegation }). Nothing goes to the issuer.
|
|
52
|
+
*/
|
|
53
|
+
export declare function signDelegation(p: {
|
|
54
|
+
credential: DelegationAnchor | Credential;
|
|
55
|
+
key: SigningKey;
|
|
56
|
+
delegate: {
|
|
57
|
+
kind: "evm" | "web-bot-auth";
|
|
58
|
+
id: string;
|
|
59
|
+
};
|
|
60
|
+
mandate: Mandate;
|
|
61
|
+
ttlSeconds: number;
|
|
62
|
+
chain?: Delegation[];
|
|
63
|
+
now?: Date;
|
|
64
|
+
}): Promise<Delegation[]>;
|
|
37
65
|
export interface KyaAuthOptions extends KyaOptions {
|
|
38
66
|
/** How long a fetched credential is reused before asking the issuer again (ms). Default 60 000. */
|
|
39
67
|
cacheTtlMs?: number;
|
|
@@ -51,13 +79,28 @@ export interface KyaContext {
|
|
|
51
79
|
status: CredentialStatus;
|
|
52
80
|
mandate: Mandate;
|
|
53
81
|
credential: Verification["credential"];
|
|
54
|
-
/** Which key verified the request signature. */
|
|
82
|
+
/** Which key verified the request signature (the sub-agent's key on a delegated request). */
|
|
55
83
|
signer: string;
|
|
84
|
+
/**
|
|
85
|
+
* D-30: present when a sub-agent signed the request under a delegation chain. `agent` above stays the
|
|
86
|
+
* credential's agent; `delegate` is the sub-agent; every mandate in `mandates` must allow an action.
|
|
87
|
+
*/
|
|
88
|
+
delegation: {
|
|
89
|
+
depth: number;
|
|
90
|
+
delegate: AgentKey;
|
|
91
|
+
expires_at: string;
|
|
92
|
+
chain: Delegation[];
|
|
93
|
+
mandates: Mandate[];
|
|
94
|
+
} | null;
|
|
56
95
|
/** Whether the body hash was checked ("verified"), absent from the signature ("none") or not checkable ("skipped"). */
|
|
57
96
|
body: "verified" | "none" | "skipped";
|
|
58
|
-
/** Local evaluation of the mandate (no daily ceilings; those need the issuer). */
|
|
97
|
+
/** Local evaluation of the mandate (no daily ceilings; those need the issuer). On a delegated request, of every mandate in the chain. */
|
|
59
98
|
check(q: ActionQuery): LocalVerdict;
|
|
60
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* Signed verdict from the issuer ($0.01, needs privateKey or internalKey in the options). The issuer evaluates
|
|
101
|
+
* the credential's mandate; on a delegated request the delegated mandates are evaluated here first, and when
|
|
102
|
+
* one refuses the answer is that local refusal (unsigned, `delegation_level` set, the issuer is not called).
|
|
103
|
+
*/
|
|
61
104
|
allowed(q: ActionQuery): Promise<Verdict>;
|
|
62
105
|
}
|
|
63
106
|
export type KyaAuthResult = {
|
|
@@ -84,6 +127,10 @@ export interface LocalVerdict {
|
|
|
84
127
|
* max_per_day when you pass spent_today (nobody else counts it: the issuer keeps no record of acts).
|
|
85
128
|
*/
|
|
86
129
|
export declare function checkMandate(mandate: Mandate, q: ActionQuery, now?: Date): LocalVerdict;
|
|
130
|
+
/** checkMandate over every mandate of a delegation chain (credential first): allowed only when all allow it. */
|
|
131
|
+
export declare function checkMandates(mandates: Mandate[], q: ActionQuery, now?: Date): LocalVerdict & {
|
|
132
|
+
levels: LocalVerdict[];
|
|
133
|
+
};
|
|
87
134
|
export interface KyaAuth {
|
|
88
135
|
client: KyaClient;
|
|
89
136
|
/** Authenticate any Web-standard Request. Never throws; read `ok`. */
|
package/dist/auth.js
CHANGED
|
@@ -16,9 +16,14 @@
|
|
|
16
16
|
* const headers = await signRequest({ credentialId, key: { kind: "evm", privateKey }, method: "POST", url, body });
|
|
17
17
|
* await fetch(url, { method: "POST", headers: { ...headers, "content-type": "application/json" }, body });
|
|
18
18
|
*
|
|
19
|
-
*
|
|
19
|
+
* Sub-agent (D-30, the credential's mandate allows delegation):
|
|
20
|
+
* const chain = await signDelegation({ credential, key: parentKey, delegate: { kind: "evm", id: sub }, mandate: { actions: ["quote"] }, ttlSeconds: 600 });
|
|
21
|
+
* const headers = await signRequest({ credentialId, key: subKey, delegation: chain, method: "GET", url });
|
|
22
|
+
*
|
|
23
|
+
* Request text (what the agent signs; the profile is in docs/SPEC.md section 14):
|
|
20
24
|
* KYA Brasil: request
|
|
21
25
|
* credential: <credential_id>
|
|
26
|
+
* delegation: <sha256 hex of the KYA-Delegation header> (only on delegated requests)
|
|
22
27
|
* method: <METHOD>
|
|
23
28
|
* url: <origin + path + query, lowercase host>
|
|
24
29
|
* created: <unix seconds>
|
|
@@ -26,10 +31,11 @@
|
|
|
26
31
|
* content-sha256: <hex of the body | ->
|
|
27
32
|
*/
|
|
28
33
|
import { verifyMessage } from "viem";
|
|
34
|
+
import { encodeDelegationHeader, decodeDelegationHeader, prepareDelegation, verifyDelegationChain } from "@kya-brasil/core";
|
|
29
35
|
import { privateKeyToAccount } from "viem/accounts";
|
|
30
36
|
import { createKyaClient } from "./index.js";
|
|
31
37
|
import { fromBase64Url, toBase64Url } from "./base64url.js";
|
|
32
|
-
export const KYA_HEADERS = { credential: "kya-credential", created: "kya-created", expires: "kya-expires", signature: "kya-signature", content: "kya-content-sha256" };
|
|
38
|
+
export const KYA_HEADERS = { credential: "kya-credential", created: "kya-created", expires: "kya-expires", signature: "kya-signature", content: "kya-content-sha256", delegation: "kya-delegation" };
|
|
33
39
|
const enc = new TextEncoder();
|
|
34
40
|
const hex = (buf) => [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
35
41
|
const b64u = toBase64Url;
|
|
@@ -47,6 +53,7 @@ export function requestText(p) {
|
|
|
47
53
|
return [
|
|
48
54
|
"KYA Brasil: request",
|
|
49
55
|
`credential: ${p.credentialId}`,
|
|
56
|
+
...(p.delegationSha256 ? [`delegation: ${p.delegationSha256}`] : []),
|
|
50
57
|
`method: ${p.method.toUpperCase()}`,
|
|
51
58
|
`url: ${canonicalUrl(p.url)}`,
|
|
52
59
|
`created: ${p.created}`,
|
|
@@ -54,26 +61,57 @@ export function requestText(p) {
|
|
|
54
61
|
`content-sha256: ${p.contentSha256 ?? "-"}`
|
|
55
62
|
].join("\n");
|
|
56
63
|
}
|
|
57
|
-
|
|
64
|
+
// --- Agent side -------------------------------------------------------------------------------
|
|
65
|
+
const importPkcs8 = (pem, extractable = false) => crypto.subtle.importKey("pkcs8", unb64u(pem.replace(/-----[^-]+-----/g, "").replace(/\s+/g, "")), { name: "Ed25519" }, extractable, ["sign"]);
|
|
66
|
+
async function signText(key, text) {
|
|
67
|
+
if (key.kind === "web-bot-auth") {
|
|
68
|
+
const k = typeof key.privateKey === "string" ? await importPkcs8(key.privateKey) : key.privateKey;
|
|
69
|
+
return b64u(new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, k, enc.encode(text))));
|
|
70
|
+
}
|
|
71
|
+
return privateKeyToAccount(key.privateKey).signMessage({ message: text });
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Signs a request with the agent key and returns the KYA headers to send with it. A sub-agent passes its
|
|
75
|
+
* `delegation` chain (from signDelegation) and signs with its own key.
|
|
76
|
+
*/
|
|
58
77
|
export async function signRequest(p) {
|
|
59
78
|
const created = Math.floor((p.now ?? new Date()).getTime() / 1000);
|
|
60
79
|
const expires = created + (p.ttlSeconds ?? 60);
|
|
61
80
|
const contentSha256 = p.body !== undefined && p.body !== null && (typeof p.body !== "string" || p.body.length > 0) ? await sha256Hex(p.body) : null;
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
signature = b64u(new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, key, enc.encode(text))));
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
signature = await privateKeyToAccount(p.key.privateKey).signMessage({ message: text });
|
|
70
|
-
}
|
|
81
|
+
const delegationHeader = p.delegation && p.delegation.length ? encodeDelegationHeader(p.delegation) : null;
|
|
82
|
+
const delegationSha256 = delegationHeader ? await sha256Hex(delegationHeader) : null;
|
|
83
|
+
const text = requestText({ credentialId: p.credentialId, method: p.method, url: p.url, created, expires, contentSha256, delegationSha256 });
|
|
84
|
+
const signature = await signText(p.key, text);
|
|
71
85
|
const headers = { [KYA_HEADERS.credential]: p.credentialId, [KYA_HEADERS.created]: String(created), [KYA_HEADERS.expires]: String(expires), [KYA_HEADERS.signature]: signature };
|
|
72
86
|
if (contentSha256)
|
|
73
87
|
headers[KYA_HEADERS.content] = contentSha256;
|
|
88
|
+
if (delegationHeader)
|
|
89
|
+
headers[KYA_HEADERS.delegation] = delegationHeader;
|
|
74
90
|
return headers;
|
|
75
91
|
}
|
|
76
|
-
|
|
92
|
+
/** The key a signing key verifies as, in the form a credential or a delegation names it. */
|
|
93
|
+
export async function agentKeyOf(key) {
|
|
94
|
+
if (key.kind !== "web-bot-auth")
|
|
95
|
+
return { kind: "evm", id: privateKeyToAccount(key.privateKey).address };
|
|
96
|
+
const k = typeof key.privateKey === "string" ? await importPkcs8(key.privateKey, true) : key.privateKey;
|
|
97
|
+
const jwk = (await crypto.subtle.exportKey("jwk", k));
|
|
98
|
+
if (!jwk.x)
|
|
99
|
+
throw new Error("cannot derive the Ed25519 public key from this private key");
|
|
100
|
+
return { kind: "web-bot-auth", id: jwk.x };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Delegation to a sub-agent (D-30): hands `delegate` a mandate contained in the delegator's, for `ttlSeconds`
|
|
104
|
+
* (at most the mandate's `delegation.max_ttl_seconds`). `key` is the credential's agent key, or, to delegate
|
|
105
|
+
* again, the key of the last delegate in `chain`. Returns the new chain; the sub-agent sends it with
|
|
106
|
+
* signRequest({ delegation }). Nothing goes to the issuer.
|
|
107
|
+
*/
|
|
108
|
+
export async function signDelegation(p) {
|
|
109
|
+
const chain = p.chain ?? [];
|
|
110
|
+
const { delegation, text } = await prepareDelegation({ credential: p.credential, chain, delegate: p.delegate, mandate: p.mandate, ttlSeconds: p.ttlSeconds, now: p.now });
|
|
111
|
+
const signature = await signText(p.key, text);
|
|
112
|
+
return [...chain, { ...delegation, signature }];
|
|
113
|
+
}
|
|
114
|
+
const RESERVED = ["actions", "max_per_operation", "max_per_day", "currencies", "counterparties", "payment_instruments", "valid_from", "valid_until", "delegation"];
|
|
77
115
|
function normalizeCounterparty(c) {
|
|
78
116
|
const t = c.trim();
|
|
79
117
|
const digits = t.replace(/\D/g, "");
|
|
@@ -133,6 +171,17 @@ export function checkMandate(mandate, q, now = new Date()) {
|
|
|
133
171
|
not_evaluated.push("max_per_day (pass spent_today: the issuer keeps no record of acts)");
|
|
134
172
|
return { allowed: checks.every((c) => c.ok), checks, not_evaluated };
|
|
135
173
|
}
|
|
174
|
+
/** checkMandate over every mandate of a delegation chain (credential first): allowed only when all allow it. */
|
|
175
|
+
export function checkMandates(mandates, q, now = new Date()) {
|
|
176
|
+
const levels = mandates.map((m) => checkMandate(m, q, now));
|
|
177
|
+
const prefix = (i, s) => (i === 0 ? s : `delegation ${i}: ${s}`);
|
|
178
|
+
return {
|
|
179
|
+
allowed: levels.length > 0 && levels.every((l) => l.allowed),
|
|
180
|
+
checks: levels.flatMap((l, i) => l.checks.map((c) => ({ ...c, rule: prefix(i, c.rule) }))),
|
|
181
|
+
not_evaluated: [...new Set(levels.flatMap((l, i) => l.not_evaluated.map((n) => prefix(i, n))))],
|
|
182
|
+
levels
|
|
183
|
+
};
|
|
184
|
+
}
|
|
136
185
|
const headerOf = (headers, name) => {
|
|
137
186
|
if (headers instanceof Headers)
|
|
138
187
|
return headers.get(name);
|
|
@@ -177,6 +226,7 @@ export function createKyaAuth(opts = {}) {
|
|
|
177
226
|
const expires = Number(headerOf(p.headers, KYA_HEADERS.expires));
|
|
178
227
|
const signature = headerOf(p.headers, KYA_HEADERS.signature);
|
|
179
228
|
const contentHeader = headerOf(p.headers, KYA_HEADERS.content);
|
|
229
|
+
const delegationHeader = headerOf(p.headers, KYA_HEADERS.delegation);
|
|
180
230
|
if (!credentialId || !signature || !Number.isFinite(created) || !Number.isFinite(expires))
|
|
181
231
|
return fail(401, "KYA_HEADERS_MISSING", `send ${KYA_HEADERS.credential}, ${KYA_HEADERS.created}, ${KYA_HEADERS.expires} and ${KYA_HEADERS.signature}`);
|
|
182
232
|
const now = Math.floor(Date.now() / 1000);
|
|
@@ -208,16 +258,43 @@ export function createKyaAuth(opts = {}) {
|
|
|
208
258
|
const status = e.status;
|
|
209
259
|
return status === 404 ? fail(401, "KYA_CREDENTIAL_UNKNOWN", "unknown credential_id") : fail(502, "KYA_ISSUER_UNAVAILABLE", e.message);
|
|
210
260
|
}
|
|
211
|
-
|
|
212
|
-
|
|
261
|
+
let delegation = null;
|
|
262
|
+
if (delegationHeader) {
|
|
263
|
+
const d = await delegationOf(credentialId, v, delegationHeader);
|
|
264
|
+
if (!d.ok)
|
|
265
|
+
return d.result;
|
|
266
|
+
delegation = d.delegation;
|
|
267
|
+
}
|
|
268
|
+
const delegationSha256 = delegationHeader ? await sha256Hex(delegationHeader) : null;
|
|
269
|
+
const text = requestText({ credentialId, method: p.method, url: p.url, created, expires, contentSha256: contentHeader, delegationSha256 });
|
|
270
|
+
return finish(credentialId, v, text, signature, bodyState, delegation);
|
|
271
|
+
}
|
|
272
|
+
/** D-30: verifies a KYA-Delegation chain against the credential (offline, core rules). */
|
|
273
|
+
async function delegationOf(credentialId, v, header) {
|
|
274
|
+
const no = (status, error, message) => ({ ok: false, result: fail(status, error, message) });
|
|
275
|
+
if (!v.credential)
|
|
276
|
+
return no(403, "KYA_CREDENTIAL_NOT_ACTIVE", `credential is ${v.status}`);
|
|
277
|
+
let chain;
|
|
278
|
+
try {
|
|
279
|
+
chain = decodeDelegationHeader(header);
|
|
280
|
+
}
|
|
281
|
+
catch (e) {
|
|
282
|
+
return no(401, "KYA_DELEGATION_MALFORMED", e.message);
|
|
283
|
+
}
|
|
284
|
+
const anchor = { credential_id: credentialId, agent: v.agent, mandate: v.credential.mandate, expires_at: v.credential.expires_at };
|
|
285
|
+
const r = await verifyDelegationChain({ credential: anchor, chain });
|
|
286
|
+
if (!r.ok)
|
|
287
|
+
return no(r.error === "KYA_DELEGATION_NOT_ALLOWED" || r.error === "KYA_DELEGATION_BROADER" ? 403 : 401, r.error, r.message);
|
|
288
|
+
return { ok: true, delegation: { depth: r.depth, delegate: r.delegate, expires_at: r.expires_at, chain: chain, mandates: r.mandates } };
|
|
213
289
|
}
|
|
214
|
-
async function finish(credentialId, v, text, signature, bodyState) {
|
|
215
|
-
const sig = await verifySignature(v.agent, text, signature);
|
|
290
|
+
async function finish(credentialId, v, text, signature, bodyState, delegation = null) {
|
|
291
|
+
const sig = await verifySignature(delegation ? delegation.delegate : v.agent, text, signature);
|
|
216
292
|
if (!sig.ok)
|
|
217
|
-
return fail(401, "KYA_BAD_SIGNATURE", "signature does not match the credential's agent key over the signed text");
|
|
293
|
+
return fail(401, "KYA_BAD_SIGNATURE", delegation ? "signature does not match the delegate's key over the signed text" : "signature does not match the credential's agent key over the signed text");
|
|
218
294
|
if (v.status !== "active")
|
|
219
295
|
return fail(403, "KYA_CREDENTIAL_NOT_ACTIVE", `credential is ${v.status}${v.status_reason ? `: ${v.status_reason}` : ""}`);
|
|
220
296
|
const mandate = v.credential?.mandate ?? {};
|
|
297
|
+
const mandates = delegation ? delegation.mandates : [mandate];
|
|
221
298
|
const ctx = {
|
|
222
299
|
credential_id: credentialId,
|
|
223
300
|
agent: v.agent,
|
|
@@ -227,8 +304,19 @@ export function createKyaAuth(opts = {}) {
|
|
|
227
304
|
credential: v.credential,
|
|
228
305
|
signer: sig.signer,
|
|
229
306
|
body: bodyState,
|
|
230
|
-
|
|
231
|
-
|
|
307
|
+
delegation,
|
|
308
|
+
check: (q) => (delegation ? checkMandates(mandates, q) : checkMandate(mandate, q)),
|
|
309
|
+
allowed: async (q) => {
|
|
310
|
+
if (delegation) {
|
|
311
|
+
for (let i = 1; i < mandates.length; i++) {
|
|
312
|
+
const local = checkMandate(mandates[i], q);
|
|
313
|
+
if (!local.allowed) {
|
|
314
|
+
return { credential_id: credentialId, status: v.status, allowed: false, reason: `outside the mandate of delegation ${i}`, checks: local.checks.map((c) => ({ field: c.rule, ok: c.ok, detail: c.detail })), not_evaluated: local.not_evaluated, delegation_level: i };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return client.allowed(credentialId, q);
|
|
319
|
+
}
|
|
232
320
|
};
|
|
233
321
|
return { ok: true, kya: ctx };
|
|
234
322
|
}
|
|
@@ -309,13 +397,6 @@ export const KYA_REPLY_HEADER = "kya-in-reply-to";
|
|
|
309
397
|
export function responseText(p) {
|
|
310
398
|
return ["KYA Brasil: response", `credential: ${p.credentialId}`, `in-reply-to: ${p.inReplyTo}`, `created: ${p.created}`, `expires: ${p.expires}`, `content-sha256: ${p.contentSha256 ?? "-"}`].join("\n");
|
|
311
399
|
}
|
|
312
|
-
async function signText(key, text) {
|
|
313
|
-
if (key.kind === "web-bot-auth") {
|
|
314
|
-
const k = typeof key.privateKey === "string" ? await crypto.subtle.importKey("pkcs8", unb64u(key.privateKey.replace(/-----[^-]+-----/g, "").replace(/\s+/g, "")), { name: "Ed25519" }, false, ["sign"]) : key.privateKey;
|
|
315
|
-
return b64u(new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, k, enc.encode(text))));
|
|
316
|
-
}
|
|
317
|
-
return privateKeyToAccount(key.privateKey).signMessage({ message: text });
|
|
318
|
-
}
|
|
319
400
|
/** Callee side: headers that prove who answered, bound to the request signature it replies to. */
|
|
320
401
|
export async function signResponse(p) {
|
|
321
402
|
const created = Math.floor((p.now ?? new Date()).getTime() / 1000);
|
package/dist/index.d.ts
CHANGED
|
@@ -33,6 +33,11 @@ export interface Mandate {
|
|
|
33
33
|
ref_sha256: string;
|
|
34
34
|
label?: string;
|
|
35
35
|
}[];
|
|
36
|
+
/** D-30: the agent may hand a narrower mandate to sub-agents (signDelegation), up to max_depth levels below it. */
|
|
37
|
+
delegation?: {
|
|
38
|
+
max_depth: number;
|
|
39
|
+
max_ttl_seconds: number;
|
|
40
|
+
};
|
|
36
41
|
valid_from?: string;
|
|
37
42
|
valid_until?: string;
|
|
38
43
|
[k: string]: unknown;
|
|
@@ -408,6 +413,7 @@ export declare function verifyCredentialOffline(credential: Credential, opts?: {
|
|
|
408
413
|
}): Promise<OfflineCheck>;
|
|
409
414
|
export * from "./auth.js";
|
|
410
415
|
export { isValidCpf, personAgentsText } from "@kya-brasil/core";
|
|
416
|
+
export { checkDelegatedAction, decodeDelegationHeader, encodeDelegationHeader, isMandateSubset, mandateSubsetProblems, verifyDelegationChain } from "@kya-brasil/core";
|
|
411
417
|
export { buildDetachedCms, cnpjFromCommonName, ecnpjSigner, openPfx, readSigningLink, signWithECnpjPfx, type ECnpjSigner, type RawSigner } from "@kya-brasil/core";
|
|
412
418
|
export { listTokenCertificates, openToken, type TokenCertificate, type TokenOptions } from "./pkcs11.js";
|
|
413
419
|
/**
|
package/dist/index.js
CHANGED
|
@@ -255,6 +255,8 @@ export async function verifyCredentialOffline(credential, opts = {}) {
|
|
|
255
255
|
export * from "./auth.js";
|
|
256
256
|
// e-CNPJ signing (D-26, D-27): the implementation lives in @kya-brasil/core, shared with anyone using the library.
|
|
257
257
|
export { isValidCpf, personAgentsText } from "@kya-brasil/core";
|
|
258
|
+
// Delegation to sub-agents (D-30): chain rules live in the core, signing and the middleware in ./auth.
|
|
259
|
+
export { checkDelegatedAction, decodeDelegationHeader, encodeDelegationHeader, isMandateSubset, mandateSubsetProblems, verifyDelegationChain } from "@kya-brasil/core";
|
|
258
260
|
export { buildDetachedCms, cnpjFromCommonName, ecnpjSigner, openPfx, readSigningLink, signWithECnpjPfx } from "@kya-brasil/core";
|
|
259
261
|
export { listTokenCertificates, openToken } from "./pkcs11.js";
|
|
260
262
|
import { signingLink as coreSigningLink } from "@kya-brasil/core";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kya-brasil/agent-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "KYA Brasil for AI agents: verify that an agent acts for a Brazilian company (e-CNPJ-signed credential, revocation and company registration checked daily against the Receita's open data, no third-party service), check an action against its mandate, or give your own agent a credential (sign with the company's e-CNPJ: A1 .pfx or A3 token via PKCS#11, in code or with the kya-sign command). Sign in with KYA middleware (Hono, Express, Workers) and request signing. x402 payments (USDC on Base) handled for you. Adapters for Vercel AI SDK, LangChain, OpenAI and Anthropic.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@x402/fetch": "^2.28.0",
|
|
57
57
|
"pkijs": "^3.4.1",
|
|
58
58
|
"viem": "^2.57.5",
|
|
59
|
-
"@kya-brasil/core": "^0.
|
|
59
|
+
"@kya-brasil/core": "^0.11.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@peculiar/asn1-schema": "^2.3.15",
|