@agenticprimitives/a2a 0.0.0-alpha.4 → 0.0.0-alpha.40
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/CLAUDE.md +60 -21
- package/README.md +2 -0
- package/bin/a2a-conform.mjs +17 -0
- package/dist/agent.d.ts +170 -3
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +358 -66
- package/dist/agent.js.map +1 -1
- package/dist/auth.d.ts +72 -9
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +62 -19
- package/dist/auth.js.map +1 -1
- package/dist/authority-extension.d.ts +58 -0
- package/dist/authority-extension.d.ts.map +1 -0
- package/dist/authority-extension.js +70 -0
- package/dist/authority-extension.js.map +1 -0
- package/dist/card-audience.d.ts +87 -0
- package/dist/card-audience.d.ts.map +1 -0
- package/dist/card-audience.js +135 -0
- package/dist/card-audience.js.map +1 -0
- package/dist/client.d.ts +23 -8
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +74 -13
- package/dist/client.js.map +1 -1
- package/dist/cloudflare/index.d.ts.map +1 -1
- package/dist/cloudflare/index.js +10 -2
- package/dist/cloudflare/index.js.map +1 -1
- package/dist/conform/index.d.ts +36 -0
- package/dist/conform/index.d.ts.map +1 -0
- package/dist/conform/index.js +232 -0
- package/dist/conform/index.js.map +1 -0
- package/dist/discovery.d.ts +4 -0
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +6 -2
- package/dist/discovery.js.map +1 -1
- package/dist/engagement.d.ts +37 -0
- package/dist/engagement.d.ts.map +1 -0
- package/dist/engagement.js +83 -0
- package/dist/engagement.js.map +1 -0
- package/dist/grant.d.ts +4 -2
- package/dist/grant.d.ts.map +1 -1
- package/dist/grant.js +21 -13
- package/dist/grant.js.map +1 -1
- package/dist/handoff.d.ts +42 -0
- package/dist/handoff.d.ts.map +1 -0
- package/dist/handoff.js +28 -0
- package/dist/handoff.js.map +1 -0
- package/dist/index.d.ts +25 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -3
- package/dist/index.js.map +1 -1
- package/dist/payment-gate.d.ts +17 -2
- package/dist/payment-gate.d.ts.map +1 -1
- package/dist/payment-gate.js +4 -2
- package/dist/payment-gate.js.map +1 -1
- package/dist/run-provenance-extension.d.ts +22 -0
- package/dist/run-provenance-extension.d.ts.map +1 -0
- package/dist/run-provenance-extension.js +23 -0
- package/dist/run-provenance-extension.js.map +1 -0
- package/dist/runtime.d.ts +13 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +35 -10
- package/dist/runtime.js.map +1 -1
- package/dist/session-wire.d.ts +90 -0
- package/dist/session-wire.d.ts.map +1 -0
- package/dist/session-wire.js +226 -0
- package/dist/session-wire.js.map +1 -0
- package/dist/skill-handler.d.ts +21 -1
- package/dist/skill-handler.d.ts.map +1 -1
- package/dist/skill-handler.js.map +1 -1
- package/dist/standard/app-delegation.d.ts +60 -0
- package/dist/standard/app-delegation.d.ts.map +1 -0
- package/dist/standard/app-delegation.js +43 -0
- package/dist/standard/app-delegation.js.map +1 -0
- package/dist/standard/caller.d.ts +107 -0
- package/dist/standard/caller.d.ts.map +1 -0
- package/dist/standard/caller.js +176 -0
- package/dist/standard/caller.js.map +1 -0
- package/dist/standard/client.d.ts +53 -0
- package/dist/standard/client.d.ts.map +1 -0
- package/dist/standard/client.js +88 -0
- package/dist/standard/client.js.map +1 -0
- package/dist/standard/delegated.d.ts +121 -0
- package/dist/standard/delegated.d.ts.map +1 -0
- package/dist/standard/delegated.js +170 -0
- package/dist/standard/delegated.js.map +1 -0
- package/dist/standard/errors.d.ts +31 -0
- package/dist/standard/errors.d.ts.map +1 -0
- package/dist/standard/errors.js +75 -0
- package/dist/standard/errors.js.map +1 -0
- package/dist/standard/from-agent.d.ts +41 -0
- package/dist/standard/from-agent.d.ts.map +1 -0
- package/dist/standard/from-agent.js +133 -0
- package/dist/standard/from-agent.js.map +1 -0
- package/dist/standard/index.d.ts +10 -0
- package/dist/standard/index.d.ts.map +1 -0
- package/dist/standard/index.js +14 -0
- package/dist/standard/index.js.map +1 -0
- package/dist/standard/mount.d.ts +94 -0
- package/dist/standard/mount.d.ts.map +1 -0
- package/dist/standard/mount.js +186 -0
- package/dist/standard/mount.js.map +1 -0
- package/dist/standard/server.d.ts +130 -0
- package/dist/standard/server.d.ts.map +1 -0
- package/dist/standard/server.js +497 -0
- package/dist/standard/server.js.map +1 -0
- package/dist/standard/types.d.ts +186 -0
- package/dist/standard/types.d.ts.map +1 -0
- package/dist/standard/types.js +14 -0
- package/dist/standard/types.js.map +1 -0
- package/dist/subject-ask.d.ts +96 -0
- package/dist/subject-ask.d.ts.map +1 -0
- package/dist/subject-ask.js +45 -0
- package/dist/subject-ask.js.map +1 -0
- package/dist/subject-hop.d.ts +117 -0
- package/dist/subject-hop.d.ts.map +1 -0
- package/dist/subject-hop.js +187 -0
- package/dist/subject-hop.js.map +1 -0
- package/dist/task-store.d.ts +9 -3
- package/dist/task-store.d.ts.map +1 -1
- package/dist/task-store.js +12 -6
- package/dist/task-store.js.map +1 -1
- package/dist/wire-verify.d.ts +35 -0
- package/dist/wire-verify.d.ts.map +1 -0
- package/dist/wire-verify.js +51 -0
- package/dist/wire-verify.js.map +1 -0
- package/package.json +35 -12
- package/dist/jsonrpc.d.ts +0 -25
- package/dist/jsonrpc.d.ts.map +0 -1
- package/dist/jsonrpc.js +0 -52
- package/dist/jsonrpc.js.map +0 -1
package/dist/auth.d.ts
CHANGED
|
@@ -1,16 +1,41 @@
|
|
|
1
1
|
import { type Address, type Hex } from 'viem';
|
|
2
|
+
import { decodeTimestampTerms, decodeAllowedTargetsTerms, decodeAllowedMethodsTerms } from '@agenticprimitives/delegation';
|
|
2
3
|
import type { Delegation } from '@agenticprimitives/delegation';
|
|
3
4
|
import type { A2aMessage } from './types.js';
|
|
4
5
|
import { type A2aEnforcers } from './grant.js';
|
|
5
|
-
|
|
6
|
-
export declare function decodeTimestampTerms(terms: Hex): {
|
|
7
|
-
validAfter: bigint;
|
|
8
|
-
validUntil: bigint;
|
|
9
|
-
};
|
|
10
|
-
export declare function decodeAllowedTargetsTerms(terms: Hex): readonly Address[];
|
|
11
|
-
export declare function decodeAllowedMethodsTerms(terms: Hex): readonly Hex[];
|
|
6
|
+
export { decodeTimestampTerms, decodeAllowedTargetsTerms, decodeAllowedMethodsTerms };
|
|
12
7
|
/** Canonical hash the SENDER signs for an inbound message (A2A-INV-01). Binds id + sender + skill + body. */
|
|
13
8
|
export declare function hashA2aMessage(m: Pick<A2aMessage, 'messageId' | 'sender' | 'skill' | 'bodyHash' | 'createdAt'>): Hex;
|
|
9
|
+
/** AUDIT NEW-A2A-2: canonical digest the CALLER signs for a read/control request (tasks/get | cancel |
|
|
10
|
+
* pushNotificationConfig/set). Authorization for these ops previously trusted a `caller` param the client
|
|
11
|
+
* supplied — any attacker who knew a taskId could impersonate a party. The caller now PROVES control of
|
|
12
|
+
* `caller` by signing this digest (verified via `OnChainChecks.verifyCallerSignature`). Bound to the
|
|
13
|
+
* method (can't replay a `get` signature as a `cancel`), the taskId, the verifying agent, and the chain
|
|
14
|
+
* (no cross-deployment replay). The agent re-derives it server-side — a client-supplied digest is never
|
|
15
|
+
* trusted. */
|
|
16
|
+
export declare function hashA2aTaskRequest(r: {
|
|
17
|
+
method: string;
|
|
18
|
+
taskId: Hex;
|
|
19
|
+
agentSA: Address;
|
|
20
|
+
chainId: number;
|
|
21
|
+
/** N-1 (seam audit): hash of the method-specific parameters that MUST be bound into the signature so
|
|
22
|
+
* they can't be swapped on replay. For `pushNotificationConfig/set` this is
|
|
23
|
+
* `hashPushConfigParams(url, token)` — without it a captured `set` signature could be replayed with an
|
|
24
|
+
* ATTACKER url, redirecting the task's terminal push envelope (leaking taskId/state/artifacts + the push
|
|
25
|
+
* token). Absent (zero) for parameter-less ops (tasks/get, tasks/cancel). */
|
|
26
|
+
paramsHash?: Hex;
|
|
27
|
+
/** N-2 (seam audit): the caller's signing time (unix seconds), folded into the digest so a captured
|
|
28
|
+
* read/control signature is bounded to a freshness window (the agent rejects a stale one) instead of
|
|
29
|
+
* being replayable for the life of the task. Absent (0) preserves the legacy unbounded digest. */
|
|
30
|
+
issuedAt?: number;
|
|
31
|
+
}): Hex;
|
|
32
|
+
/** Bind the requested ARTIFACT into the `tasks/artifact` signature, so a captured signature cannot be
|
|
33
|
+
* replayed to fetch a DIFFERENT artifact of the same task. Same role `hashPushConfigParams` plays
|
|
34
|
+
* for the push url. */
|
|
35
|
+
export declare function hashArtifactParams(artifactId: Hex): Hex;
|
|
36
|
+
/** N-1: bind the push-notification target (url + optional token) into the `set` request signature so it
|
|
37
|
+
* cannot be replayed with a different url. The server re-derives this from the presented url/token. */
|
|
38
|
+
export declare function hashPushConfigParams(url: string, token?: string): Hex;
|
|
14
39
|
/** On-chain + crypto verdicts injected by the consumer (viem-backed). All fail-closed. */
|
|
15
40
|
export interface OnChainChecks {
|
|
16
41
|
/** DelegationManager.isRevoked(hashDelegation(d)) — MUST throw or return true to deny. */
|
|
@@ -19,18 +44,54 @@ export interface OnChainChecks {
|
|
|
19
44
|
verifyDelegationSignature(delegation: Delegation): Promise<boolean>;
|
|
20
45
|
/** ERC-1271: the sender SA signed `hashA2aMessage(message)`. */
|
|
21
46
|
verifyMessageSignature(message: A2aMessage, digest: Hex): Promise<boolean>;
|
|
47
|
+
/** AUDIT NEW-A2A-2 — ERC-1271: `caller` SA signed `hashA2aTaskRequest(...)`. Proves the read/control
|
|
48
|
+
* caller controls the address it claims, so `caller` can no longer be spoofed. Fail-closed (throw/false). */
|
|
49
|
+
verifyCallerSignature(caller: Address, digest: Hex, signature: Hex): Promise<boolean>;
|
|
50
|
+
}
|
|
51
|
+
/** A reserved single-use message-id (spec 290 §3). Mirrors delegation's `JtiReservation`: a reservation is
|
|
52
|
+
* IN-FLIGHT (it still serves as the replay guard) until the caller either commits it (the task was durably
|
|
53
|
+
* created) or releases it (a pre-acceptance rejection). */
|
|
54
|
+
export interface MessageIdReservation {
|
|
55
|
+
/** Finalize — the id is permanently consumed; a later replay is rejected. */
|
|
56
|
+
commit(): Promise<void>;
|
|
57
|
+
/** Free the IN-FLIGHT reservation WITHOUT consuming the id, so a legitimate retry of the SAME message
|
|
58
|
+
* may re-reserve. Called on any pre-durable-acceptance rejection so a failed submit never burns the
|
|
59
|
+
* nonce (spec 290 §3 — a post-verify reject must not consume the authorization). */
|
|
60
|
+
release(): Promise<void>;
|
|
22
61
|
}
|
|
23
|
-
/** Single-use reservation seam (the TaskStore provides this; FR-4.3). */
|
|
62
|
+
/** Single-use reservation seam (the TaskStore provides this; FR-4.3). Two-phase per spec 290 §3. */
|
|
24
63
|
export interface MessageIdReserver {
|
|
25
|
-
|
|
64
|
+
/** Reserve the id IN-FLIGHT (replay guard). Returns a reservation to `commit` (durable accept) or
|
|
65
|
+
* `release` (pre-acceptance reject); `null` if the id is already in-flight or consumed (replay → deny). */
|
|
66
|
+
reserveMessageId(messageId: Hex, ttlSec: number): Promise<MessageIdReservation | null>;
|
|
67
|
+
/** A2A-4 (seam audit): OPTIONAL cheap read-only "is this id already consumed/in-flight?" peek. When
|
|
68
|
+
* provided, authorizeA2aMessage checks it FIRST and rejects a definite replay BEFORE the 3 on-chain
|
|
69
|
+
* reads (isRevoked + two ERC-1271 verifies) — so replaying a consumed messageId can't amplify RPC load.
|
|
70
|
+
* It is only an optimization: the atomic reserve-LAST remains the source of truth for the genuine race
|
|
71
|
+
* (a false `false` here just falls through to the normal path). Omit ⇒ unchanged behavior. */
|
|
72
|
+
peekConsumed?(messageId: Hex): Promise<boolean>;
|
|
26
73
|
}
|
|
27
74
|
export type AuthorizeResult = {
|
|
28
75
|
ok: true;
|
|
29
76
|
principal: Address;
|
|
77
|
+
messageIdReservation: MessageIdReservation;
|
|
30
78
|
} | {
|
|
31
79
|
ok: false;
|
|
32
80
|
reason: string;
|
|
33
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* OPTIONAL scope-class resolver for FR-4.2 (spec 341 §5.1c). By default an `allowedTargets` entry is
|
|
84
|
+
* an exact agent address. A deployment may inject this to give SOME entries set semantics — e.g. "an
|
|
85
|
+
* org SA means its current members", "the naming registry means any named agent". Consulted ONLY when
|
|
86
|
+
* the exact-address check already failed, with the full target list, the delegation's delegator (the
|
|
87
|
+
* person the grant speaks for) and the requested skill — so the app can, and should, restrict class
|
|
88
|
+
* semantics to specific skills. Absent, throwing, or returning false all deny (fail-closed).
|
|
89
|
+
*/
|
|
90
|
+
export type ScopeCovers = (i: {
|
|
91
|
+
targets: readonly Address[];
|
|
92
|
+
sender: Address;
|
|
93
|
+
skill: string;
|
|
94
|
+
}) => Promise<boolean>;
|
|
34
95
|
/**
|
|
35
96
|
* Authorize an inbound A2A message. Returns the principal (delegation.delegator) the sender acts for, or
|
|
36
97
|
* a rejection reason. Fail-closed throughout; no task is created on `ok: false`. The message-id is
|
|
@@ -51,5 +112,7 @@ export declare function authorizeA2aMessage(args: {
|
|
|
51
112
|
now: number;
|
|
52
113
|
/** Message-id reservation TTL (seconds). Default 600. */
|
|
53
114
|
replayTtlSec?: number;
|
|
115
|
+
/** FR-4.2 scope classes — see `ScopeCovers`. Omit ⇒ exact-address matching only. */
|
|
116
|
+
scopeCovers?: ScopeCovers;
|
|
54
117
|
}): Promise<AuthorizeResult>;
|
|
55
118
|
//# sourceMappingURL=auth.d.ts.map
|
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAQA,OAAO,EAAgE,KAAK,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,MAAM,CAAC;AAC5G,OAAO,KAAK,EAAE,UAAU,EAAU,MAAM,+BAA+B,CAAC;AACxE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAgC,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAQA,OAAO,EAAgE,KAAK,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,MAAM,CAAC;AAC5G,OAAO,EACL,oBAAoB,EACpB,yBAAyB,EACzB,yBAAyB,EAC1B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,UAAU,EAAU,MAAM,+BAA+B,CAAC;AACxE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAgC,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAK7E,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,yBAAyB,EAAE,CAAC;AAEtF,6GAA6G;AAC7G,wBAAgB,cAAc,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,WAAW,CAAC,GAAG,GAAG,CAOpH;AAED;;;;;;eAMe;AACf,wBAAgB,kBAAkB,CAAC,CAAC,EAAE;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,GAAG,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;kFAI8E;IAC9E,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB;;uGAEmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,GAAG,CAcN;AAED;;wBAEwB;AACxB,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,GAAG,GAAG,GAAG,CAEvD;AAED;wGACwG;AACxG,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,GAAG,CAErE;AAED,0FAA0F;AAC1F,MAAM,WAAW,aAAa;IAC5B,0FAA0F;IAC1F,SAAS,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD,yDAAyD;IACzD,yBAAyB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpE,gEAAgE;IAChE,sBAAsB,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3E;kHAC8G;IAC9G,qBAAqB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACvF;AAED;;4DAE4D;AAC5D,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB;;yFAEqF;IACrF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,oGAAoG;AACpG,MAAM,WAAW,iBAAiB;IAChC;gHAC4G;IAC5G,gBAAgB,CAAC,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACvF;;;;mGAI+F;IAC/F,YAAY,CAAC,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjD;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,oBAAoB,EAAE,oBAAoB,CAAA;CAAE,GAC5E;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,EAAE;IAC5B,OAAO,EAAE,SAAS,OAAO,EAAE,CAAC;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAMvB;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,UAAU,EAAE,UAAU,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,UAAU,CAAC;IACpB,2EAA2E;IAC3E,WAAW,EAAE,OAAO,CAAC;IACrB,wFAAwF;IACxF,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,YAAY,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oFAAoF;IACpF,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B,GAAG,OAAO,CAAC,eAAe,CAAC,CAgF3B"}
|
package/dist/auth.js
CHANGED
|
@@ -6,25 +6,45 @@
|
|
|
6
6
|
// FR-4.3 single-use message-id (inbound replay guard).
|
|
7
7
|
// FR-4.4 the inbound message is signed by the sender.
|
|
8
8
|
// On-chain reads are INJECTED (the package stays transport-agnostic); the consumer wires viem.
|
|
9
|
-
import {
|
|
9
|
+
import { keccak256, encodeAbiParameters, toBytes } from 'viem';
|
|
10
|
+
import { decodeTimestampTerms, decodeAllowedTargetsTerms, decodeAllowedMethodsTerms, } from '@agenticprimitives/delegation';
|
|
10
11
|
import { skillSelector, A2A_ANY_SKILL } from './grant.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
export function decodeAllowedTargetsTerms(terms) {
|
|
17
|
-
const [targets] = decodeAbiParameters([{ type: 'address[]' }], terms);
|
|
18
|
-
return targets;
|
|
19
|
-
}
|
|
20
|
-
export function decodeAllowedMethodsTerms(terms) {
|
|
21
|
-
const [selectors] = decodeAbiParameters([{ type: 'bytes4[]' }], terms);
|
|
22
|
-
return selectors;
|
|
23
|
-
}
|
|
12
|
+
// Term decoders now live in `delegation`, beside the encoders that produce the bytes. Imported for
|
|
13
|
+
// local use AND re-exported so existing a2a consumers are unaffected — one implementation, and it
|
|
14
|
+
// cannot drift from the encoder without the compiler noticing.
|
|
15
|
+
export { decodeTimestampTerms, decodeAllowedTargetsTerms, decodeAllowedMethodsTerms };
|
|
24
16
|
/** Canonical hash the SENDER signs for an inbound message (A2A-INV-01). Binds id + sender + skill + body. */
|
|
25
17
|
export function hashA2aMessage(m) {
|
|
26
18
|
return keccak256(encodeAbiParameters([{ type: 'bytes32' }, { type: 'address' }, { type: 'bytes32' }, { type: 'bytes32' }, { type: 'uint256' }], [m.messageId, m.sender, keccak256(toBytes(m.skill)), m.bodyHash, BigInt(m.createdAt)]));
|
|
27
19
|
}
|
|
20
|
+
/** AUDIT NEW-A2A-2: canonical digest the CALLER signs for a read/control request (tasks/get | cancel |
|
|
21
|
+
* pushNotificationConfig/set). Authorization for these ops previously trusted a `caller` param the client
|
|
22
|
+
* supplied — any attacker who knew a taskId could impersonate a party. The caller now PROVES control of
|
|
23
|
+
* `caller` by signing this digest (verified via `OnChainChecks.verifyCallerSignature`). Bound to the
|
|
24
|
+
* method (can't replay a `get` signature as a `cancel`), the taskId, the verifying agent, and the chain
|
|
25
|
+
* (no cross-deployment replay). The agent re-derives it server-side — a client-supplied digest is never
|
|
26
|
+
* trusted. */
|
|
27
|
+
export function hashA2aTaskRequest(r) {
|
|
28
|
+
return keccak256(encodeAbiParameters([{ type: 'bytes32' }, { type: 'bytes32' }, { type: 'address' }, { type: 'uint256' }, { type: 'bytes32' }, { type: 'uint256' }], [
|
|
29
|
+
keccak256(toBytes(`a2a:request:${r.method}`)),
|
|
30
|
+
r.taskId,
|
|
31
|
+
r.agentSA,
|
|
32
|
+
BigInt(r.chainId),
|
|
33
|
+
r.paramsHash ?? `0x${'00'.repeat(32)}`,
|
|
34
|
+
BigInt(r.issuedAt ?? 0),
|
|
35
|
+
]));
|
|
36
|
+
}
|
|
37
|
+
/** Bind the requested ARTIFACT into the `tasks/artifact` signature, so a captured signature cannot be
|
|
38
|
+
* replayed to fetch a DIFFERENT artifact of the same task. Same role `hashPushConfigParams` plays
|
|
39
|
+
* for the push url. */
|
|
40
|
+
export function hashArtifactParams(artifactId) {
|
|
41
|
+
return keccak256(encodeAbiParameters([{ type: 'bytes32' }], [artifactId]));
|
|
42
|
+
}
|
|
43
|
+
/** N-1: bind the push-notification target (url + optional token) into the `set` request signature so it
|
|
44
|
+
* cannot be replayed with a different url. The server re-derives this from the presented url/token. */
|
|
45
|
+
export function hashPushConfigParams(url, token) {
|
|
46
|
+
return keccak256(encodeAbiParameters([{ type: 'string' }, { type: 'string' }], [url, token ?? '']));
|
|
47
|
+
}
|
|
28
48
|
const eq = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
29
49
|
const findCaveat = (caveats, enforcer) => caveats.find((c) => eq(c.enforcer, enforcer));
|
|
30
50
|
/**
|
|
@@ -40,6 +60,12 @@ export async function authorizeA2aMessage(args) {
|
|
|
40
60
|
return { ok: false, reason: 'delegate != requester' };
|
|
41
61
|
if (!eq(message.sender, args.requester))
|
|
42
62
|
return { ok: false, reason: 'message sender != requester' };
|
|
63
|
+
// A2A-4 — cheap replay peek BEFORE the on-chain reads: a replayed (already-consumed) messageId is
|
|
64
|
+
// rejected here rather than after isRevoked + two ERC-1271 verifies, bounding RPC amplification. The
|
|
65
|
+
// atomic reserve-last below still handles the genuine race; this only short-circuits definite replays.
|
|
66
|
+
if (args.store.peekConsumed && (await args.store.peekConsumed(message.messageId))) {
|
|
67
|
+
return { ok: false, reason: 'message replay (already consumed)' };
|
|
68
|
+
}
|
|
43
69
|
// FR-4.1 — timestamp window (off-chain decode; the enforcer would check the same on-chain).
|
|
44
70
|
const tsCav = findCaveat(d.caveats, enforcers.timestamp);
|
|
45
71
|
if (!tsCav)
|
|
@@ -54,7 +80,9 @@ export async function authorizeA2aMessage(args) {
|
|
|
54
80
|
const nowSec = BigInt(Math.floor(args.now / 1000));
|
|
55
81
|
if (nowSec < win.validAfter || nowSec >= win.validUntil)
|
|
56
82
|
return { ok: false, reason: 'grant outside timestamp window' };
|
|
57
|
-
// FR-4.2 — recipient scoping: allowedTargets MUST
|
|
83
|
+
// FR-4.2 — recipient scoping: allowedTargets MUST cover this agent. Exact address first; then, and
|
|
84
|
+
// only if the deployment injected class semantics, the scope resolver. One caveat, one verdict —
|
|
85
|
+
// this is set membership in a single authorization, not a second mechanism after a failed first.
|
|
58
86
|
const atCav = findCaveat(d.caveats, enforcers.allowedTargets);
|
|
59
87
|
if (!atCav)
|
|
60
88
|
return { ok: false, reason: 'missing allowedTargets caveat (FR-4.2)' };
|
|
@@ -65,8 +93,19 @@ export async function authorizeA2aMessage(args) {
|
|
|
65
93
|
catch {
|
|
66
94
|
return { ok: false, reason: 'bad allowedTargets terms' };
|
|
67
95
|
}
|
|
68
|
-
if (!targets.some((t) => eq(t, args.thisAgentSA)))
|
|
69
|
-
|
|
96
|
+
if (!targets.some((t) => eq(t, args.thisAgentSA))) {
|
|
97
|
+
let classCovered = false;
|
|
98
|
+
if (args.scopeCovers) {
|
|
99
|
+
try {
|
|
100
|
+
classCovered = await args.scopeCovers({ targets, sender: d.delegator, skill: args.skill });
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
classCovered = false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!classCovered)
|
|
107
|
+
return { ok: false, reason: 'grant not scoped to this agent (allowedTargets)' };
|
|
108
|
+
}
|
|
70
109
|
// FR-4.2 — skill scoping: allowedMethods MUST include the skill selector (or the any-sentinel).
|
|
71
110
|
const amCav = findCaveat(d.caveats, enforcers.allowedMethods);
|
|
72
111
|
if (!amCav)
|
|
@@ -108,9 +147,13 @@ export async function authorizeA2aMessage(args) {
|
|
|
108
147
|
return { ok: false, reason: `message signature check failed: ${e instanceof Error ? e.message : e}` };
|
|
109
148
|
}
|
|
110
149
|
// FR-4.3 — single-use message id (reserve last; a replay of a valid message hits the reserved id).
|
|
111
|
-
|
|
150
|
+
// Two-phase (spec 290 §3): the reservation marks the id IN-FLIGHT; the caller commits it ONLY after the
|
|
151
|
+
// task is durably created, and releases it on any pre-acceptance rejection — so a post-verify failure
|
|
152
|
+
// (bad bodyHash, budget exhausted, a throwing store) never burns the nonce.
|
|
153
|
+
const messageIdReservation = await args.store.reserveMessageId(message.messageId, args.replayTtlSec ?? 600);
|
|
154
|
+
if (!messageIdReservation) {
|
|
112
155
|
return { ok: false, reason: 'message id already used (replay)' };
|
|
113
156
|
}
|
|
114
|
-
return { ok: true, principal: d.delegator };
|
|
157
|
+
return { ok: true, principal: d.delegator, messageIdReservation };
|
|
115
158
|
}
|
|
116
159
|
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,6FAA6F;AAC7F,yGAAyG;AACzG,+FAA+F;AAC/F,iGAAiG;AACjG,yDAAyD;AACzD,wDAAwD;AACxD,+FAA+F;AAC/F,OAAO,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** The extension URI. Versioned in the path: a change to what the authority model REQUIRES is a new
|
|
2
|
+
* URI, not a silent redefinition of this one — a peer must be able to detect the difference. */
|
|
3
|
+
export declare const AP_AUTHORITY_EXTENSION_URI = "https://agenticprimitives.org/a2a/authority/v1";
|
|
4
|
+
/** The activation header stock A2A defines for extensions; clients opt in by naming the URI. */
|
|
5
|
+
export declare const A2A_EXTENSIONS_HEADER = "X-A2A-Extensions";
|
|
6
|
+
/**
|
|
7
|
+
* How a caller proves it may invoke a skill. Both forms resolve to the SAME question — "does the
|
|
8
|
+
* principal SA authorize this actor for this selector, right now?" — and neither is a bearer grant.
|
|
9
|
+
*/
|
|
10
|
+
export type ApAuthorityMethod =
|
|
11
|
+
/** A signed, caveated delegation naming the acting agent, verified against the principal SA
|
|
12
|
+
* (ERC-1271 / 6492 / ECDSA / WebAuthn via UniversalSignatureValidator). */
|
|
13
|
+
'delegation'
|
|
14
|
+
/** A Home-issued session wire bound to the person SA — custody-scoped, not a peer grant. */
|
|
15
|
+
| 'session-wire'
|
|
16
|
+
/** A signed mandate: a scoped delegation plus an explicit, signed statement of intent. Used where a
|
|
17
|
+
* human decision must be attributable, not merely permitted. */
|
|
18
|
+
| 'mandate';
|
|
19
|
+
export interface ApAuthorityExtensionParams {
|
|
20
|
+
/** Which proof forms this deployment accepts. Order is not precedence — the server decides. */
|
|
21
|
+
methods: readonly ApAuthorityMethod[];
|
|
22
|
+
/** CAIP-2 chain the principal SA and its delegations live on, so a peer resolves against the right
|
|
23
|
+
* chain rather than guessing from an address. */
|
|
24
|
+
chain: string;
|
|
25
|
+
/** Whether activation is required to call ANY skill. Almost always false: peer-callable skills are
|
|
26
|
+
* the point, and a required extension excludes clients that could otherwise interoperate. */
|
|
27
|
+
required?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface ApAuthorityExtension {
|
|
30
|
+
uri: string;
|
|
31
|
+
required: boolean;
|
|
32
|
+
description: string;
|
|
33
|
+
/** Machine-readable params. A peer MUST be able to act on this without parsing prose. */
|
|
34
|
+
params: {
|
|
35
|
+
methods: readonly ApAuthorityMethod[];
|
|
36
|
+
chain: string;
|
|
37
|
+
/** Stated explicitly so no client infers otherwise from the presence of a bearer envelope. */
|
|
38
|
+
bearerIsEnvelopeOnly: true;
|
|
39
|
+
/** Authority is re-checked at the moment of acting; a prior success is not a durable capability. */
|
|
40
|
+
reEvaluatedPerCall: true;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build the Card's authority extension.
|
|
45
|
+
*
|
|
46
|
+
* `bearerIsEnvelopeOnly` and `reEvaluatedPerCall` are literal `true` in the type, not booleans a
|
|
47
|
+
* deployment can set. They are not configuration — a deployment that wanted to say otherwise would be
|
|
48
|
+
* describing a different system, and the type refuses to let it say so on our Card (ADR-0041).
|
|
49
|
+
*/
|
|
50
|
+
export declare function apAuthorityExtension(params: ApAuthorityExtensionParams): ApAuthorityExtension;
|
|
51
|
+
/** Whether a client activated the extension, per the stock `X-A2A-Extensions` header.
|
|
52
|
+
*
|
|
53
|
+
* NOTE: activation is a CLIENT HINT and nothing more. It reports which extensions the caller
|
|
54
|
+
* understands; it does not authorize, and a server MUST NOT treat activation as a reason to relax any
|
|
55
|
+
* check. A caller that activates nothing still faces the same authority decision — the extension is
|
|
56
|
+
* documentation of the question, not a way to change the answer. */
|
|
57
|
+
export declare function activatesApAuthority(header: string | null | undefined): boolean;
|
|
58
|
+
//# sourceMappingURL=authority-extension.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authority-extension.d.ts","sourceRoot":"","sources":["../src/authority-extension.ts"],"names":[],"mappings":"AAuBA;iGACiG;AACjG,eAAO,MAAM,0BAA0B,mDAAmD,CAAC;AAE3F,gGAAgG;AAChG,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AAExD;;;GAGG;AACH,MAAM,MAAM,iBAAiB;AAC3B;4EAC4E;AAC1E,YAAY;AACd,4FAA4F;GAC1F,cAAc;AAChB;iEACiE;GAC/D,SAAS,CAAC;AAEd,MAAM,WAAW,0BAA0B;IACzC,+FAA+F;IAC/F,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACtC;sDACkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd;kGAC8F;IAC9F,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,MAAM,EAAE;QACN,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;QACtC,KAAK,EAAE,MAAM,CAAC;QACd,8FAA8F;QAC9F,oBAAoB,EAAE,IAAI,CAAC;QAC3B,oGAAoG;QACpG,kBAAkB,EAAE,IAAI,CAAC;KAC1B,CAAC;CACH;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,0BAA0B,GAAG,oBAAoB,CAqB7F;AAED;;;;;qEAKqE;AACrE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAM/E"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// The AP authority extension for the Agent Card (ADR-0059 / spec 341 §2.1).
|
|
2
|
+
//
|
|
3
|
+
// THE PROBLEM IT SOLVES. The Card declares no `securitySchemes` — not a wrong one, none. A peer reading
|
|
4
|
+
// it learns which skills exist and nothing about how to be allowed to call them. Every caller therefore
|
|
5
|
+
// needs out-of-band knowledge, which is the definition of a private RPC surface wearing a protocol's
|
|
6
|
+
// clothes. Declaring the authority model is what makes the difference between a protocol and skill-RPC
|
|
7
|
+
// cosplay, and it is the thing that lets an EXTERNAL agent use the same skills the Home uses — spec
|
|
8
|
+
// 341's actual success test.
|
|
9
|
+
//
|
|
10
|
+
// WHY AN EXTENSION AND NOT A `securityScheme`. Stock A2A's security schemes are OAuth2 / OIDC / API-key
|
|
11
|
+
// / mTLS — all of them bearer-shaped, all of them "the issuer says you may". Declaring one of those
|
|
12
|
+
// would assert something false about this substrate: here the grant is an ERC-4337-SA-bound,
|
|
13
|
+
// signature-verified, on-chain-revocable **delegation**, re-evaluated at the moment of acting, and a
|
|
14
|
+
// bearer token is at most an envelope carrying a reference to it (ADR-0041). Picking the nearest stock
|
|
15
|
+
// scheme to look compliant would invert the exact property that distinguishes this system, so the
|
|
16
|
+
// authority model is declared as a first-class `AgentExtension` instead — the same mechanism this
|
|
17
|
+
// package already ships for x402 pricing.
|
|
18
|
+
//
|
|
19
|
+
// WHAT THIS EXTENSION IS NOT. It is not authority. Advertising it grants nothing, and a caller that
|
|
20
|
+
// activates it is not thereby authorized; it tells a peer WHICH question will be asked of it, never
|
|
21
|
+
// what the answer will be. The decision still happens server-side against the principal SA, at the
|
|
22
|
+
// moment of acting, every time (ADR-0041, ADR-0043).
|
|
23
|
+
/** The extension URI. Versioned in the path: a change to what the authority model REQUIRES is a new
|
|
24
|
+
* URI, not a silent redefinition of this one — a peer must be able to detect the difference. */
|
|
25
|
+
export const AP_AUTHORITY_EXTENSION_URI = 'https://agenticprimitives.org/a2a/authority/v1';
|
|
26
|
+
/** The activation header stock A2A defines for extensions; clients opt in by naming the URI. */
|
|
27
|
+
export const A2A_EXTENSIONS_HEADER = 'X-A2A-Extensions';
|
|
28
|
+
/**
|
|
29
|
+
* Build the Card's authority extension.
|
|
30
|
+
*
|
|
31
|
+
* `bearerIsEnvelopeOnly` and `reEvaluatedPerCall` are literal `true` in the type, not booleans a
|
|
32
|
+
* deployment can set. They are not configuration — a deployment that wanted to say otherwise would be
|
|
33
|
+
* describing a different system, and the type refuses to let it say so on our Card (ADR-0041).
|
|
34
|
+
*/
|
|
35
|
+
export function apAuthorityExtension(params) {
|
|
36
|
+
if (params.methods.length === 0) {
|
|
37
|
+
// An authority extension that accepts nothing is not a lean Card, it is a broken one: it tells a
|
|
38
|
+
// peer to authorize and gives it no way to. Fail loudly at build time (ADR-0013 — empty is an
|
|
39
|
+
// answer, and here it is the wrong one).
|
|
40
|
+
throw new Error('apAuthorityExtension: at least one authority method must be declared');
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
uri: AP_AUTHORITY_EXTENSION_URI,
|
|
44
|
+
required: params.required ?? false,
|
|
45
|
+
description: 'Authorization is an on-chain delegation decision against the principal Smart Agent, verified by ' +
|
|
46
|
+
'signature and re-evaluated at the moment of acting. A bearer token is a transport envelope only ' +
|
|
47
|
+
'and never the grant.',
|
|
48
|
+
params: {
|
|
49
|
+
methods: params.methods,
|
|
50
|
+
chain: params.chain,
|
|
51
|
+
bearerIsEnvelopeOnly: true,
|
|
52
|
+
reEvaluatedPerCall: true,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** Whether a client activated the extension, per the stock `X-A2A-Extensions` header.
|
|
57
|
+
*
|
|
58
|
+
* NOTE: activation is a CLIENT HINT and nothing more. It reports which extensions the caller
|
|
59
|
+
* understands; it does not authorize, and a server MUST NOT treat activation as a reason to relax any
|
|
60
|
+
* check. A caller that activates nothing still faces the same authority decision — the extension is
|
|
61
|
+
* documentation of the question, not a way to change the answer. */
|
|
62
|
+
export function activatesApAuthority(header) {
|
|
63
|
+
if (!header)
|
|
64
|
+
return false;
|
|
65
|
+
return header
|
|
66
|
+
.split(',')
|
|
67
|
+
.map((s) => s.trim())
|
|
68
|
+
.includes(AP_AUTHORITY_EXTENSION_URI);
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=authority-extension.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authority-extension.js","sourceRoot":"","sources":["../src/authority-extension.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,EAAE;AACF,wGAAwG;AACxG,wGAAwG;AACxG,qGAAqG;AACrG,uGAAuG;AACvG,oGAAoG;AACpG,6BAA6B;AAC7B,EAAE;AACF,wGAAwG;AACxG,oGAAoG;AACpG,6FAA6F;AAC7F,qGAAqG;AACrG,uGAAuG;AACvG,kGAAkG;AAClG,kGAAkG;AAClG,0CAA0C;AAC1C,EAAE;AACF,oGAAoG;AACpG,oGAAoG;AACpG,mGAAmG;AACnG,qDAAqD;AAErD;iGACiG;AACjG,MAAM,CAAC,MAAM,0BAA0B,GAAG,gDAAgD,CAAC;AAE3F,gGAAgG;AAChG,MAAM,CAAC,MAAM,qBAAqB,GAAG,kBAAkB,CAAC;AA0CxD;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAkC;IACrE,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,iGAAiG;QACjG,8FAA8F;QAC9F,yCAAyC;QACzC,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;IAC1F,CAAC;IACD,OAAO;QACL,GAAG,EAAE,0BAA0B;QAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,KAAK;QAClC,WAAW,EACT,kGAAkG;YAClG,kGAAkG;YAClG,sBAAsB;QACxB,MAAM,EAAE;YACN,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,oBAAoB,EAAE,IAAI;YAC1B,kBAAkB,EAAE,IAAI;SACzB;KACF,CAAC;AACJ,CAAC;AAED;;;;;qEAKqE;AACrE,MAAM,UAAU,oBAAoB,CAAC,MAAiC;IACpE,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,OAAO,MAAM;SACV,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,QAAQ,CAAC,0BAA0B,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type VerifiedPeerContextV1 } from '@agenticprimitives/types';
|
|
2
|
+
import type { AgentCardAudience } from './agent.js';
|
|
3
|
+
/** What a requester presents to claim a wider card audience. All fields required to get past `public`. */
|
|
4
|
+
export interface CardAudienceClaim {
|
|
5
|
+
/** Claimed canonical id (CAIP-10) or address. Unverified until the signature checks out. */
|
|
6
|
+
requester?: string;
|
|
7
|
+
/** A nonce issued by THIS host. Single-use. */
|
|
8
|
+
challengeNonce?: string;
|
|
9
|
+
signature?: `0x${string}`;
|
|
10
|
+
issuedAt?: string;
|
|
11
|
+
/** The view being asked for. Never granted on its own — this is a request, not a grant. */
|
|
12
|
+
requestedView?: string;
|
|
13
|
+
}
|
|
14
|
+
/** The body a requester signs. Binds the host and the view so a proof is not portable. */
|
|
15
|
+
export interface CardChallengeBody {
|
|
16
|
+
requester: string;
|
|
17
|
+
challengeNonce: string;
|
|
18
|
+
/** This host's origin — a proof for one agent's card must not work at another's. */
|
|
19
|
+
cardOrigin: string;
|
|
20
|
+
requestedView: 'authenticated' | 'private';
|
|
21
|
+
issuedAt: string;
|
|
22
|
+
}
|
|
23
|
+
/** Injected signature verification — satisfied by a `chain-state` reader wrapper. */
|
|
24
|
+
export type CardSignatureVerifier = (args: {
|
|
25
|
+
requester: string;
|
|
26
|
+
body: CardChallengeBody;
|
|
27
|
+
signature: `0x${string}`;
|
|
28
|
+
}) => Promise<boolean>;
|
|
29
|
+
/** Single-use nonce consumption. Returns false if already spent or unknown to this host. */
|
|
30
|
+
export type CardNonceConsumer = (nonce: string, cardOrigin: string) => Promise<boolean>;
|
|
31
|
+
export interface CardAudienceResolverOptions {
|
|
32
|
+
/** This host's canonical origin. A proof bound to another origin is refused. */
|
|
33
|
+
cardOrigin: string;
|
|
34
|
+
verifySignature: CardSignatureVerifier;
|
|
35
|
+
consumeNonce: CardNonceConsumer;
|
|
36
|
+
now: () => string;
|
|
37
|
+
/** Maximum age of a claim. Default 2 minutes. */
|
|
38
|
+
maxClaimAgeMs?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Decides whether a VERIFIED requester may see the `private` view, and is the only place a private
|
|
41
|
+
* audience can be granted. Omit ⇒ a verified requester reaches `authenticated` and no further.
|
|
42
|
+
*
|
|
43
|
+
* Kept separate from signature verification because they answer different questions: the signature
|
|
44
|
+
* says *who* is asking; this says *what that party is entitled to see*.
|
|
45
|
+
*/
|
|
46
|
+
privateViewPolicy?: (requester: string) => Promise<boolean>;
|
|
47
|
+
/** Local-only diagnostics. Never surfaced to the requester. */
|
|
48
|
+
observe?: (o: {
|
|
49
|
+
granted: AgentCardAudience['audience'];
|
|
50
|
+
reason?: string;
|
|
51
|
+
}) => void;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* When the TRANSPORT already authenticated the peer (spec 338 W5-b), a separate card challenge is
|
|
55
|
+
* redundant — the connection itself proved who is asking.
|
|
56
|
+
*
|
|
57
|
+
* Accepted ONLY at `strength: 'agent'` (`isAgentAuthenticatedPeer`). Web PKI authenticates a
|
|
58
|
+
* hostname and SPIFFE a workload; neither is a Smart Agent, and treating either as one is exactly
|
|
59
|
+
* the conflation `VerifiedPeerContextV1` exists to prevent.
|
|
60
|
+
*
|
|
61
|
+
* NOTE this does not imply relay resistance: without a channel binding an active relay could be the
|
|
62
|
+
* party on the connection. Hosts that need that guarantee should require a channel-bound peer at the
|
|
63
|
+
* edge rather than loosening anything here.
|
|
64
|
+
*/
|
|
65
|
+
export interface PeerBackedAudienceInput {
|
|
66
|
+
peerContext?: VerifiedPeerContextV1;
|
|
67
|
+
}
|
|
68
|
+
/** The public audience — the floor, and the answer to every failed claim. */
|
|
69
|
+
export declare const PUBLIC_AUDIENCE: AgentCardAudience;
|
|
70
|
+
/**
|
|
71
|
+
* Build the resolver a card route calls to turn a request into an audience.
|
|
72
|
+
*
|
|
73
|
+
* Never throws. Never returns an audience wider than the claim proved.
|
|
74
|
+
*/
|
|
75
|
+
export declare function createCardAudienceResolver(opts: CardAudienceResolverOptions): (claim: CardAudienceClaim, peer?: PeerBackedAudienceInput) => Promise<AgentCardAudience>;
|
|
76
|
+
/**
|
|
77
|
+
* Read a claim from standard request inputs.
|
|
78
|
+
*
|
|
79
|
+
* The signature and nonce come from HEADERS, never the query string: a query parameter lands in
|
|
80
|
+
* access logs, `Referer` headers, and browser history, and a proof that leaks that way is a replay
|
|
81
|
+
* waiting to happen. `view` stays in the query because it is a request, not a secret.
|
|
82
|
+
*/
|
|
83
|
+
export declare function readCardAudienceClaim(input: {
|
|
84
|
+
query: (k: string) => string | undefined;
|
|
85
|
+
header: (k: string) => string | undefined;
|
|
86
|
+
}): CardAudienceClaim;
|
|
87
|
+
//# sourceMappingURL=card-audience.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card-audience.d.ts","sourceRoot":"","sources":["../src/card-audience.ts"],"names":[],"mappings":"AAoBA,OAAO,EAA4B,KAAK,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAChG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,0GAA0G;AAC1G,MAAM,WAAW,iBAAiB;IAChC,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2FAA2F;IAC3F,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,0FAA0F;AAC1F,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,oFAAoF;IACpF,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,eAAe,GAAG,SAAS,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qFAAqF;AACrF,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,iBAAiB,CAAC;IACxB,SAAS,EAAE,KAAK,MAAM,EAAE,CAAC;CAC1B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEvB,4FAA4F;AAC5F,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAExF,MAAM,WAAW,2BAA2B;IAC1C,gFAAgF;IAChF,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,qBAAqB,CAAC;IACvC,YAAY,EAAE,iBAAiB,CAAC;IAChC,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5D,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,OAAO,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACpF;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,uBAAuB;IACtC,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACrC;AAID,6EAA6E;AAC7E,eAAO,MAAM,eAAe,EAAE,iBAA0C,CAAC;AAMzE;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,2BAA2B,WAEjE,iBAAiB,SACjB,uBAAuB,KAC7B,OAAO,CAAC,iBAAiB,CAAC,CAsF9B;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACzC,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CAC3C,GAAG,iBAAiB,CAQpB"}
|