@agent-native/core 0.212.0 → 0.213.1
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/dist/a2a/anthropic-managed-agents.js +1 -0
- package/dist/a2a/caller-auth.js +7 -2
- package/dist/a2a/client.d.ts +1 -0
- package/dist/a2a/client.js +27 -0
- package/dist/a2a/handlers.js +15 -3
- package/dist/a2a/organization-identity.d.ts +4 -1
- package/dist/a2a/organization-identity.js +3 -1
- package/dist/a2a/server.d.ts +6 -5
- package/dist/a2a/server.js +79 -6
- package/dist/a2a-claims.d.ts +1 -0
- package/dist/a2a-claims.js +1 -0
- package/dist/agent/actions/manage-provider-key-policy.js +4 -2
- package/dist/agent/app-model-defaults.js +2 -10
- package/dist/agent/loop-settings.js +2 -10
- package/dist/audit/actions/export-audit-events.js +1 -0
- package/dist/audit/attribution.d.ts +5 -4
- package/dist/audit/attribution.js +2 -1
- package/dist/audit/index.d.ts +1 -1
- package/dist/audit/ocsf.js +1 -0
- package/dist/audit/read-scope.js +2 -12
- package/dist/audit/record.js +1 -0
- package/dist/audit/store.js +12 -3
- package/dist/audit/types.d.ts +4 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/automations/service.js +3 -10
- package/dist/client/org/hooks.d.ts +3 -0
- package/dist/client/org/hooks.js +12 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/connections/catalog.d.ts +1 -1
- package/dist/connections/catalog.js +7 -1
- package/dist/credentials/index.js +18 -2
- package/dist/db/schema-snapshot.generated.js +5 -2
- package/dist/feature-flags/a2a-action-route.js +1 -0
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/file-upload/storage-settings.js +2 -9
- package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
- package/dist/jobs/tools.js +2 -12
- package/dist/mcp/actions/service-token-access.js +15 -11
- package/dist/mcp/credential-issuance.js +9 -2
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +6 -6
- package/dist/org/actions.d.ts +1 -1
- package/dist/org/actions.js +3 -7
- package/dist/org/app-roles.js +10 -1
- package/dist/org/context.d.ts +1 -0
- package/dist/org/context.js +122 -24
- package/dist/org/federation.d.ts +1 -1
- package/dist/org/federation.js +2 -2
- package/dist/org/handlers.d.ts +4 -0
- package/dist/org/handlers.js +142 -44
- package/dist/org/index.d.ts +2 -1
- package/dist/org/membership.d.ts +6 -4
- package/dist/org/membership.js +7 -5
- package/dist/org/migrations.js +6 -0
- package/dist/org/plugin.d.ts +1 -0
- package/dist/org/plugin.js +9 -1
- package/dist/org/private-icon-handlers.js +4 -10
- package/dist/org/types.d.ts +2 -0
- package/dist/org/workspace-app-access-outcome.d.ts +1 -1
- package/dist/org/workspace-app-access.js +77 -14
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
- package/dist/provider-api/actions/provider-api.d.ts +3 -3
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +10 -10
- package/dist/server/action-routes.d.ts +1 -0
- package/dist/server/action-routes.js +13 -1
- package/dist/server/agent-chat/prompt-resources.js +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/org-admin.js +3 -2
- package/dist/server/personal-provider-key-holders.d.ts +1 -1
- package/dist/server/personal-provider-key-holders.js +7 -2
- package/dist/server/personal-provider-key-policy.d.ts +2 -0
- package/dist/server/personal-provider-key-policy.js +21 -5
- package/dist/server/request-context.d.ts +2 -0
- package/dist/server/request-context.js +7 -0
- package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -13
- package/dist/triggers/actions/manage-automation.d.ts +5 -5
- package/dist/triggers/routes.js +2 -9
- package/dist/usage/alerts-store.js +2 -9
- package/dist/usage/metrics-store.js +2 -9
- package/dist/workspace-connections/actions/list-workspace-user-groups.d.ts +1 -1
- package/dist/workspace-connections/groups.js +2 -11
- package/package.json +3 -3
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -13
|
@@ -183,6 +183,7 @@ async function verifyContinuationToken(token, context, options) {
|
|
|
183
183
|
verified = await verifyA2AToken(token, context.event, {
|
|
184
184
|
includeClaims: true,
|
|
185
185
|
globalSecretOnly: true,
|
|
186
|
+
allowAnthropicManagedContinuation: true,
|
|
186
187
|
});
|
|
187
188
|
}
|
|
188
189
|
catch (cause) {
|
package/dist/a2a/caller-auth.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
|
|
2
|
-
import { getGlobalA2ASecret, signA2AOrganizationToken, signA2AToken, } from "./client.js";
|
|
1
|
+
import { getRequestContext, getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
|
|
2
|
+
import { getGlobalA2ASecret, signA2AOrgMemberDelegationToken, signA2AOrganizationToken, signA2AToken, } from "./client.js";
|
|
3
3
|
const DEFAULT_A2A_CALLER_TOKEN_TTL = "5m";
|
|
4
4
|
export async function resolveA2ACallerAuth(options) {
|
|
5
5
|
const userEmail = getRequestUserEmail();
|
|
6
|
+
const isOrgMemberDelegation = getRequestContext()?.a2aIdentityAssurance === "org-member";
|
|
6
7
|
const globalSecret = getGlobalA2ASecret();
|
|
7
8
|
const metadata = {};
|
|
8
9
|
if (userEmail)
|
|
@@ -24,6 +25,7 @@ export async function resolveA2ACallerAuth(options) {
|
|
|
24
25
|
apiKeyAttempts.push(token);
|
|
25
26
|
};
|
|
26
27
|
if (userEmail &&
|
|
28
|
+
!isOrgMemberDelegation &&
|
|
27
29
|
options?.audience &&
|
|
28
30
|
globalSecret &&
|
|
29
31
|
(options?.userIdentityOnly || !orgId || orgDomain?.trim())) {
|
|
@@ -40,6 +42,9 @@ export async function resolveA2ACallerAuth(options) {
|
|
|
40
42
|
orgDomain &&
|
|
41
43
|
options?.audience &&
|
|
42
44
|
(orgSecret || globalSecret)) {
|
|
45
|
+
if (userEmail && orgSecret) {
|
|
46
|
+
addApiKeyAttempt(await signA2AOrgMemberDelegationToken(userEmail, orgDomain, orgSecret, options.audience));
|
|
47
|
+
}
|
|
43
48
|
addApiKeyAttempt(await signA2AOrganizationToken(orgDomain, orgSecret, undefined, {
|
|
44
49
|
expiresIn: options?.expiresIn ?? DEFAULT_A2A_CALLER_TOKEN_TTL,
|
|
45
50
|
audience: options?.audience,
|
package/dist/a2a/client.d.ts
CHANGED
|
@@ -64,6 +64,7 @@ export declare function signA2AOrganizationToken(orgDomain: string | undefined,
|
|
|
64
64
|
preferGlobalSecret?: boolean;
|
|
65
65
|
extraClaims?: Record<string, unknown>;
|
|
66
66
|
}): Promise<string>;
|
|
67
|
+
export declare function signA2AOrgMemberDelegationToken(email: string, orgDomain: string, orgSecret: string, audience: string | string[]): Promise<string>;
|
|
67
68
|
export declare function shouldPreferGlobalA2ASecret(orgSecret?: string): boolean;
|
|
68
69
|
export declare class A2AClient {
|
|
69
70
|
private baseUrl;
|
package/dist/a2a/client.js
CHANGED
|
@@ -8,6 +8,7 @@ import { getRequestContext } from "../server/request-context.js";
|
|
|
8
8
|
import { SYNTHETIC_TRAFFIC_BETA_E2E, SYNTHETIC_TRAFFIC_HEADER, } from "../shared/test-traffic.js";
|
|
9
9
|
import { canonicalA2AAudience } from "./audience.js";
|
|
10
10
|
import { sanitizeA2ACorrelationMetadata } from "./correlation.js";
|
|
11
|
+
import { A2A_ORG_MEMBER_DELEGATION_TYPE } from "./organization-identity.js";
|
|
11
12
|
import { RemoteAgentCredentialRejectedError } from "./remote-agent-auth.js";
|
|
12
13
|
import { workspacePrivateOrigins } from "./workspace-private-origins.js";
|
|
13
14
|
const DEFAULT_A2A_POLL_REQUEST_TIMEOUT_MS = 15_000;
|
|
@@ -172,6 +173,27 @@ export async function signA2AOrganizationToken(orgDomain, orgSecret, orgId, opti
|
|
|
172
173
|
jwt.setAudience(options.audience);
|
|
173
174
|
return jwt.sign(new TextEncoder().encode(secret));
|
|
174
175
|
}
|
|
176
|
+
export async function signA2AOrgMemberDelegationToken(email, orgDomain, orgSecret, audience) {
|
|
177
|
+
const normalizedEmail = email.trim().toLowerCase();
|
|
178
|
+
const normalizedDomain = orgDomain.trim().toLowerCase();
|
|
179
|
+
const secret = orgSecret.trim();
|
|
180
|
+
if (!normalizedEmail || !normalizedDomain || !secret || !audience) {
|
|
181
|
+
throw new Error("A member email, organization domain, organization secret, and audience are required.");
|
|
182
|
+
}
|
|
183
|
+
const appUrl = getAppConfig().app.url ?? "http://localhost:3000";
|
|
184
|
+
return new jose.SignJWT({
|
|
185
|
+
typ: A2A_ORG_MEMBER_DELEGATION_TYPE,
|
|
186
|
+
sub: normalizedEmail,
|
|
187
|
+
org_domain: normalizedDomain,
|
|
188
|
+
})
|
|
189
|
+
.setProtectedHeader({ alg: "HS256" })
|
|
190
|
+
.setIssuer(appUrl)
|
|
191
|
+
.setIssuedAt()
|
|
192
|
+
.setJti(randomUUID())
|
|
193
|
+
.setExpirationTime("5m")
|
|
194
|
+
.setAudience(audience)
|
|
195
|
+
.sign(new TextEncoder().encode(secret));
|
|
196
|
+
}
|
|
175
197
|
export function shouldPreferGlobalA2ASecret(orgSecret) {
|
|
176
198
|
return !!getGlobalA2ASecret() || !orgSecret;
|
|
177
199
|
}
|
|
@@ -1517,7 +1539,9 @@ async function buildA2AApiKeyAttempts(opts, audience) {
|
|
|
1517
1539
|
attempts.push(token);
|
|
1518
1540
|
};
|
|
1519
1541
|
const globalSecret = getGlobalA2ASecret();
|
|
1542
|
+
const isOrgMemberDelegation = getRequestContext()?.a2aIdentityAssurance === "org-member";
|
|
1520
1543
|
if (opts?.userEmail &&
|
|
1544
|
+
!isOrgMemberDelegation &&
|
|
1521
1545
|
globalSecret &&
|
|
1522
1546
|
(!opts.orgId || opts.orgDomain?.trim())) {
|
|
1523
1547
|
add(await signA2AToken(opts.userEmail, opts.orgDomain, undefined, {
|
|
@@ -1525,6 +1549,9 @@ async function buildA2AApiKeyAttempts(opts, audience) {
|
|
|
1525
1549
|
audience,
|
|
1526
1550
|
}));
|
|
1527
1551
|
}
|
|
1552
|
+
if (opts?.userEmail && opts.orgDomain && opts.orgSecret && audience) {
|
|
1553
|
+
add(await signA2AOrgMemberDelegationToken(opts.userEmail, opts.orgDomain, opts.orgSecret, audience));
|
|
1554
|
+
}
|
|
1528
1555
|
if (opts?.orgDomain && (opts.orgSecret || globalSecret)) {
|
|
1529
1556
|
add(await signA2AOrganizationToken(opts.orgDomain, opts.orgSecret, undefined, { audience }));
|
|
1530
1557
|
}
|
package/dist/a2a/handlers.js
CHANGED
|
@@ -302,9 +302,11 @@ async function processClaimedA2ATask(taskId, claimed, config, signal, event) {
|
|
|
302
302
|
const verifiedEmail = processorMeta.verifiedEmail;
|
|
303
303
|
const identityAssurance = processorMeta.identityAssurance === "organization"
|
|
304
304
|
? "organization"
|
|
305
|
-
: processorMeta.identityAssurance === "
|
|
306
|
-
? "
|
|
307
|
-
:
|
|
305
|
+
: processorMeta.identityAssurance === "org-member"
|
|
306
|
+
? "org-member"
|
|
307
|
+
: processorMeta.identityAssurance === "user"
|
|
308
|
+
? "user"
|
|
309
|
+
: undefined;
|
|
308
310
|
const orgDomainHint = processorMeta.orgDomainHint;
|
|
309
311
|
const verifiedOrgId = typeof processorMeta.verifiedOrgId === "string"
|
|
310
312
|
? processorMeta.verifiedOrgId.trim()
|
|
@@ -367,6 +369,9 @@ async function processClaimedA2ATask(taskId, claimed, config, signal, event) {
|
|
|
367
369
|
const { runWithRequestContext } = await import("../server/request-context.js");
|
|
368
370
|
await runWithRequestContext({
|
|
369
371
|
userEmail: verifiedEmail,
|
|
372
|
+
...(identityAssurance
|
|
373
|
+
? { a2aIdentityAssurance: identityAssurance }
|
|
374
|
+
: {}),
|
|
370
375
|
...(resolvedOrgId
|
|
371
376
|
? { orgId: resolvedOrgId }
|
|
372
377
|
: verifiedEmail
|
|
@@ -547,12 +552,19 @@ async function withA2ARequestContext(metadata, event, fn) {
|
|
|
547
552
|
const { runWithRequestContext } = await import("../server/request-context.js");
|
|
548
553
|
const verifiedEmail = event?.context?.__a2aVerifiedEmail ?? undefined;
|
|
549
554
|
const verifiedOrgId = event?.context?.__a2aVerifiedOrgId ?? undefined;
|
|
555
|
+
const rawIdentityAssurance = event?.context?.__a2aIdentityAssurance;
|
|
556
|
+
const identityAssurance = rawIdentityAssurance === "user" ||
|
|
557
|
+
rawIdentityAssurance === "organization" ||
|
|
558
|
+
rawIdentityAssurance === "org-member"
|
|
559
|
+
? rawIdentityAssurance
|
|
560
|
+
: undefined;
|
|
550
561
|
const requestOrigin = requestOriginForContext(metadata, event);
|
|
551
562
|
if (event?.context && verifiedEmail && !verifiedOrgId) {
|
|
552
563
|
markExplicitPersonalOrgScope(event);
|
|
553
564
|
}
|
|
554
565
|
return runWithRequestContext({
|
|
555
566
|
userEmail: verifiedEmail,
|
|
567
|
+
...(identityAssurance ? { a2aIdentityAssurance: identityAssurance } : {}),
|
|
556
568
|
...(verifiedOrgId
|
|
557
569
|
? { orgId: verifiedOrgId }
|
|
558
570
|
: verifiedEmail
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { JWTPayload } from "jose";
|
|
2
|
+
export declare const A2A_ORG_MEMBER_DELEGATION_TYPE = "agent-native-org-member-delegation";
|
|
2
3
|
export interface VerifiedA2AOrganizationIdentity {
|
|
3
4
|
orgId: string;
|
|
4
5
|
orgDomain: string | null;
|
|
@@ -6,10 +7,12 @@ export interface VerifiedA2AOrganizationIdentity {
|
|
|
6
7
|
export interface A2AOrganizationCredential extends VerifiedA2AOrganizationIdentity {
|
|
7
8
|
orgDomain: string;
|
|
8
9
|
secret: string;
|
|
10
|
+
a2aMemberDelegationEnabled?: boolean;
|
|
9
11
|
}
|
|
10
12
|
/**
|
|
11
13
|
* An organization secret authenticates an organization principal. Its JWT
|
|
12
|
-
* subject is caller-controlled data
|
|
14
|
+
* subject is caller-controlled data unless it is an opted-in, audience-bound
|
|
15
|
+
* member delegation verified against the receiver's current roster.
|
|
13
16
|
*/
|
|
14
17
|
export declare function organizationPrincipalClaims(payload: JWTPayload, organization: A2AOrganizationCredential): JWTPayload | null;
|
|
15
18
|
/**
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
export const A2A_ORG_MEMBER_DELEGATION_TYPE = "agent-native-org-member-delegation";
|
|
1
2
|
/**
|
|
2
3
|
* An organization secret authenticates an organization principal. Its JWT
|
|
3
|
-
* subject is caller-controlled data
|
|
4
|
+
* subject is caller-controlled data unless it is an opted-in, audience-bound
|
|
5
|
+
* member delegation verified against the receiver's current roster.
|
|
4
6
|
*/
|
|
5
7
|
export function organizationPrincipalClaims(payload, organization) {
|
|
6
8
|
const orgDomain = typeof payload.org_domain === "string"
|
package/dist/a2a/server.d.ts
CHANGED
|
@@ -2,16 +2,15 @@ import * as jose from "jose";
|
|
|
2
2
|
import "../authorization/check-action.js";
|
|
3
3
|
import type { A2AConfig, AgentSkill } from "./types.js";
|
|
4
4
|
/**
|
|
5
|
-
* Result of verifying an inbound A2A JWT. `email` is present
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `orgId` are bound to local organization metadata when present.
|
|
5
|
+
* Result of verifying an inbound A2A JWT. `email` is present for deployment
|
|
6
|
+
* user assertions or an explicitly opted-in org-member delegation. `orgDomain`
|
|
7
|
+
* and `orgId` are bound to local organization metadata when present.
|
|
9
8
|
*/
|
|
10
9
|
export interface A2ATokenPayload {
|
|
11
10
|
email: string | null;
|
|
12
11
|
orgDomain: string | null;
|
|
13
12
|
orgId?: string;
|
|
14
|
-
identityAssurance?: "user" | "organization";
|
|
13
|
+
identityAssurance?: "user" | "organization" | "org-member";
|
|
15
14
|
claims?: jose.JWTPayload;
|
|
16
15
|
}
|
|
17
16
|
export declare class A2AIdentityVerificationUnavailableError extends Error {
|
|
@@ -47,6 +46,8 @@ export declare function verifyA2AToken(token: string, event?: any, audienceOptio
|
|
|
47
46
|
globalSecretOnly?: boolean;
|
|
48
47
|
/** Only use this explicitly configured credential; never fall back. */
|
|
49
48
|
verificationSecret?: string;
|
|
49
|
+
/** Dedicated verifier for the typed Anthropic continuation credential. */
|
|
50
|
+
allowAnthropicManagedContinuation?: boolean;
|
|
50
51
|
requireAudience?: boolean;
|
|
51
52
|
}): Promise<A2ATokenPayload>;
|
|
52
53
|
export type A2ATokenFailureCause = "malformed" | "secret-missing" | "audience-unresolvable" | "audience" | "issuer" | "expired" | "signature" | "invalid-claims" | "mcp-credential" | "missing-subject" | "org-binding" | "org-lookup-failed" | "undetermined";
|
package/dist/a2a/server.js
CHANGED
|
@@ -19,7 +19,7 @@ import { addAgentProfileExamples } from "./app-profile.js";
|
|
|
19
19
|
import { canonicalA2AAudience } from "./audience.js";
|
|
20
20
|
import { hasConfiguredA2ASecret, isA2AProductionRuntime, } from "./auth-policy.js";
|
|
21
21
|
import { handleJsonRpcH3, processA2ATaskFromQueue } from "./handlers.js";
|
|
22
|
-
import { organizationPrincipalClaims, verifyA2AOrganizationIdentity, } from "./organization-identity.js";
|
|
22
|
+
import { A2A_ORG_MEMBER_DELEGATION_TYPE, organizationPrincipalClaims, verifyA2AOrganizationIdentity, } from "./organization-identity.js";
|
|
23
23
|
import { claimA2AApproval, a2aTaskLeaseThreadId, getA2AApprovalForOwner, settleA2AApproval, } from "./task-store.js";
|
|
24
24
|
let _warnedUnauthA2A = false;
|
|
25
25
|
function warnA2AUnauthOnce() {
|
|
@@ -59,7 +59,7 @@ export function isA2AIdentityVerificationUnavailableError(error) {
|
|
|
59
59
|
(error instanceof Error &&
|
|
60
60
|
error.name === "A2AIdentityVerificationUnavailableError"));
|
|
61
61
|
}
|
|
62
|
-
function identityPayload(payload, audienceOptions, verifiedOrganization) {
|
|
62
|
+
function identityPayload(payload, audienceOptions, verifiedOrganization, identityAssurance) {
|
|
63
63
|
const email = typeof payload.sub === "string" && payload.sub.trim()
|
|
64
64
|
? payload.sub.trim()
|
|
65
65
|
: null;
|
|
@@ -79,9 +79,34 @@ function identityPayload(payload, audienceOptions, verifiedOrganization) {
|
|
|
79
79
|
...(!email && verifiedOrganization
|
|
80
80
|
? { identityAssurance: "organization" }
|
|
81
81
|
: {}),
|
|
82
|
+
...(identityAssurance ? { identityAssurance } : {}),
|
|
82
83
|
...(audienceOptions?.includeClaims ? { claims } : {}),
|
|
83
84
|
};
|
|
84
85
|
}
|
|
86
|
+
function validOrgMemberDelegationClaims(payload) {
|
|
87
|
+
const email = typeof payload.sub === "string" ? payload.sub.trim() : "";
|
|
88
|
+
const jti = typeof payload.jti === "string" ? payload.jti.trim() : "";
|
|
89
|
+
const issuedAt = payload.iat;
|
|
90
|
+
const expiresAt = payload.exp;
|
|
91
|
+
const now = Math.floor(Date.now() / 1_000);
|
|
92
|
+
const audiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
|
|
93
|
+
return (payload.typ === A2A_ORG_MEMBER_DELEGATION_TYPE &&
|
|
94
|
+
email.length <= 320 &&
|
|
95
|
+
email.includes("@") &&
|
|
96
|
+
!/[\u0000-\u001f\u007f]/.test(email) &&
|
|
97
|
+
jti.length > 0 &&
|
|
98
|
+
jti.length <= 128 &&
|
|
99
|
+
!/[\u0000-\u001f\u007f]/.test(jti) &&
|
|
100
|
+
typeof issuedAt === "number" &&
|
|
101
|
+
Number.isSafeInteger(issuedAt) &&
|
|
102
|
+
typeof expiresAt === "number" &&
|
|
103
|
+
Number.isSafeInteger(expiresAt) &&
|
|
104
|
+
issuedAt <= now + 60 &&
|
|
105
|
+
expiresAt > now &&
|
|
106
|
+
expiresAt > issuedAt &&
|
|
107
|
+
expiresAt - issuedAt <= 15 * 60 &&
|
|
108
|
+
audiences.some((audience) => typeof audience === "string" && audience.trim()));
|
|
109
|
+
}
|
|
85
110
|
async function verifyJwtWithSecret(token, secret, options) {
|
|
86
111
|
try {
|
|
87
112
|
const { payload } = await jose.jwtVerify(token, new TextEncoder().encode(secret), options);
|
|
@@ -327,6 +352,17 @@ async function verifyA2ATokenInternal(token, event, audienceOptions) {
|
|
|
327
352
|
if (globalSecret) {
|
|
328
353
|
const payload = await verifyJwtWithSecret(token, globalSecret, verifyOptions);
|
|
329
354
|
if (payload) {
|
|
355
|
+
// Typed credentials have their own verifier. Only the managed-agent
|
|
356
|
+
// confirmation verifier may accept its continuation token here.
|
|
357
|
+
if (typeof payload.typ !== "undefined") {
|
|
358
|
+
const managedContinuation = payload.typ === "anthropic-managed-continuation" &&
|
|
359
|
+
audienceOptions?.globalSecretOnly === true &&
|
|
360
|
+
audienceOptions.allowAnthropicManagedContinuation === true &&
|
|
361
|
+
audienceOptions.includeClaims === true;
|
|
362
|
+
if (!managedContinuation) {
|
|
363
|
+
return { payload: { email: null, orgDomain: null } };
|
|
364
|
+
}
|
|
365
|
+
}
|
|
330
366
|
if (typeof payload.org_domain === "string" && payload.org_domain.trim()) {
|
|
331
367
|
let organization;
|
|
332
368
|
try {
|
|
@@ -388,12 +424,49 @@ async function verifyA2ATokenInternal(token, event, audienceOptions) {
|
|
|
388
424
|
}
|
|
389
425
|
if (!organization)
|
|
390
426
|
return { payload: { email: null, orgDomain: null } };
|
|
391
|
-
const
|
|
427
|
+
const orgVerifyOptions = unverifiedPayload?.typ === A2A_ORG_MEMBER_DELEGATION_TYPE
|
|
428
|
+
? { ...verifyOptions, algorithms: ["HS256"] }
|
|
429
|
+
: verifyOptions;
|
|
430
|
+
const payload = await verifyJwtWithSecret(token, organization.secret, orgVerifyOptions);
|
|
392
431
|
if (!payload)
|
|
393
432
|
return { payload: { email: null, orgDomain: null } };
|
|
394
433
|
const claims = organizationPrincipalClaims(payload, organization);
|
|
395
434
|
if (!claims)
|
|
396
435
|
return { payload: { email: null, orgDomain: null } };
|
|
436
|
+
if (payload.typ === A2A_ORG_MEMBER_DELEGATION_TYPE) {
|
|
437
|
+
if (!validOrgMemberDelegationClaims(payload)) {
|
|
438
|
+
return { payload: { email: null, orgDomain: null } };
|
|
439
|
+
}
|
|
440
|
+
if (organization.secret.trim() === globalSecret) {
|
|
441
|
+
return { payload: { email: null, orgDomain: null } };
|
|
442
|
+
}
|
|
443
|
+
let isCurrentMember = false;
|
|
444
|
+
if (organization.a2aMemberDelegationEnabled) {
|
|
445
|
+
try {
|
|
446
|
+
const { isOrgMemberForA2A } = await import("../org/membership.js");
|
|
447
|
+
isCurrentMember = await isOrgMemberForA2A(organization.orgId, String(payload.sub).trim(), event);
|
|
448
|
+
}
|
|
449
|
+
catch (cause) {
|
|
450
|
+
throw new A2AIdentityVerificationUnavailableError(cause);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (isCurrentMember) {
|
|
454
|
+
return {
|
|
455
|
+
payload: identityPayload(payload, audienceOptions, organization, "org-member"),
|
|
456
|
+
verifiedOrgId: organization.orgId,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
payload: {
|
|
461
|
+
email: null,
|
|
462
|
+
orgDomain: organization.orgDomain,
|
|
463
|
+
orgId: organization.orgId,
|
|
464
|
+
identityAssurance: "organization",
|
|
465
|
+
...(audienceOptions?.includeClaims ? { claims } : {}),
|
|
466
|
+
},
|
|
467
|
+
verifiedOrgId: organization.orgId,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
397
470
|
warnUserAssertionDemotedOnce(organization.orgId, payload, "user token verified only with org secret");
|
|
398
471
|
return {
|
|
399
472
|
payload: {
|
|
@@ -742,9 +815,9 @@ export function mountA2A(nitroApp, config, routePrefix = "/_agent-native") {
|
|
|
742
815
|
}
|
|
743
816
|
verifiedCallerEmail = tokenPayload.email;
|
|
744
817
|
verifiedOrgDomain = tokenPayload.orgDomain;
|
|
745
|
-
verifiedIdentityAssurance =
|
|
746
|
-
|
|
747
|
-
|
|
818
|
+
verifiedIdentityAssurance =
|
|
819
|
+
tokenPayload.identityAssurance ??
|
|
820
|
+
(tokenPayload.email ? "user" : undefined);
|
|
748
821
|
bearerTokenVerified = verifiedIdentityAssurance !== undefined;
|
|
749
822
|
verifiedAudienceBound =
|
|
750
823
|
bearerTokenVerified && tokenHasAudienceClaim(bearerToken);
|
package/dist/a2a-claims.d.ts
CHANGED
package/dist/a2a-claims.js
CHANGED
|
@@ -92,7 +92,7 @@ export default defineAction({
|
|
|
92
92
|
return {
|
|
93
93
|
...policy,
|
|
94
94
|
canManage,
|
|
95
|
-
affectedMembers: await listPersonalProviderKeyHolders(orgId),
|
|
95
|
+
affectedMembers: await listPersonalProviderKeyHolders(orgId, email),
|
|
96
96
|
changed,
|
|
97
97
|
};
|
|
98
98
|
}
|
|
@@ -101,7 +101,9 @@ export default defineAction({
|
|
|
101
101
|
...policy,
|
|
102
102
|
canManage,
|
|
103
103
|
...(canManage
|
|
104
|
-
? {
|
|
104
|
+
? {
|
|
105
|
+
affectedMembers: await listPersonalProviderKeyHolders(orgId, email),
|
|
106
|
+
}
|
|
105
107
|
: {}),
|
|
106
108
|
};
|
|
107
109
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOrgMemberRole } from "../server/personal-provider-key-policy.js";
|
|
2
2
|
import { getRequestContext, getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
|
|
3
3
|
import { deleteOrgSetting, deleteUserSetting, getOrgSetting, getUserSetting, putOrgSetting, putUserSetting, } from "../settings/index.js";
|
|
4
4
|
export const AGENT_APP_MODEL_DEFAULT_KEY_PREFIX = "agent-app-model-default";
|
|
@@ -116,15 +116,7 @@ export async function canUpdateAgentAppModelDefaultSettings(userEmail, orgId, or
|
|
|
116
116
|
if (!orgId)
|
|
117
117
|
return orgScope === "personal";
|
|
118
118
|
try {
|
|
119
|
-
const
|
|
120
|
-
const { rows } = await exec.execute({
|
|
121
|
-
sql: `SELECT role FROM org_members
|
|
122
|
-
WHERE org_id = ? AND LOWER(email) = ?
|
|
123
|
-
AND federation_removal_pending_at IS NULL
|
|
124
|
-
LIMIT 1`,
|
|
125
|
-
args: [orgId, userEmail.toLowerCase()],
|
|
126
|
-
});
|
|
127
|
-
const role = String(rows[0]?.role ?? "");
|
|
119
|
+
const role = await readOrgMemberRole(orgId, userEmail);
|
|
128
120
|
return role === "owner" || role === "admin";
|
|
129
121
|
}
|
|
130
122
|
catch {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOrgMemberRole } from "../server/personal-provider-key-policy.js";
|
|
2
2
|
import { getOrgSetting, putOrgSetting, deleteOrgSetting, getUserSetting, putUserSetting, deleteUserSetting, } from "../settings/index.js";
|
|
3
3
|
export const AGENT_LOOP_SETTINGS_KEY = "agent-loop";
|
|
4
4
|
export const DEFAULT_AGENT_MAX_ITERATIONS = 400;
|
|
@@ -169,15 +169,7 @@ export async function canUpdateAgentLoopSettings(userEmail, orgId) {
|
|
|
169
169
|
if (!orgId)
|
|
170
170
|
return true;
|
|
171
171
|
try {
|
|
172
|
-
const
|
|
173
|
-
const { rows } = await exec.execute({
|
|
174
|
-
sql: `SELECT role FROM org_members
|
|
175
|
-
WHERE org_id = ? AND LOWER(email) = ?
|
|
176
|
-
AND federation_removal_pending_at IS NULL
|
|
177
|
-
LIMIT 1`,
|
|
178
|
-
args: [orgId, userEmail.toLowerCase()],
|
|
179
|
-
});
|
|
180
|
-
const role = String(rows[0]?.role ?? "");
|
|
172
|
+
const role = await readOrgMemberRole(orgId, userEmail);
|
|
181
173
|
return role === "owner" || role === "admin";
|
|
182
174
|
}
|
|
183
175
|
catch {
|
|
@@ -26,6 +26,7 @@ const CSV_COLUMNS = [
|
|
|
26
26
|
["reportedClientVersion", "reported_client_version"],
|
|
27
27
|
["oauthClientId", "oauth_client_id"],
|
|
28
28
|
["approval", "approval"],
|
|
29
|
+
["identityAssurance", "identity_assurance"],
|
|
29
30
|
];
|
|
30
31
|
function csvField(value) {
|
|
31
32
|
if (value === null || value === undefined)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AuditApproval } from "./types.js";
|
|
1
|
+
import type { AuditApproval, AuditIdentityAssurance } from "./types.js";
|
|
2
2
|
declare const auditAttributionBrand: unique symbol;
|
|
3
3
|
interface AuditAttributionFields {
|
|
4
4
|
caller: string;
|
|
@@ -16,11 +16,12 @@ interface AuditAttributionFields {
|
|
|
16
16
|
};
|
|
17
17
|
mcpClientAccepted?: true;
|
|
18
18
|
approval?: AuditApproval;
|
|
19
|
+
identityAssurance?: AuditIdentityAssurance;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
22
|
* Where an audited call came from and what vouched for it: its source, the
|
|
22
|
-
* MCP client it reported, the OAuth client of its credential,
|
|
23
|
-
* signal, and its run and network ids. `recordActionAudit` requires one and
|
|
23
|
+
* MCP client it reported, the OAuth client of its credential, verified A2A
|
|
24
|
+
* identity assurance, the approval signal, and its run and network ids. `recordActionAudit` requires one and
|
|
24
25
|
* only {@link auditAttribution} makes one, so a recorder cannot build its own
|
|
25
26
|
* context and store nulls for a field it forgot.
|
|
26
27
|
*/
|
|
@@ -38,7 +39,7 @@ export type AuditOverride = Partial<Pick<AuditAttributionFields, "threadId" | "t
|
|
|
38
39
|
* The run context attribution reads from; an `ActionRunContext` is one.
|
|
39
40
|
* Approval is not read from it: a context can be handed to another action.
|
|
40
41
|
*/
|
|
41
|
-
export type AuditAttributionSource = Partial<Omit<AuditAttributionFields, "approval" | "approvedToolCallKey" | "mcpClientAccepted">>;
|
|
42
|
+
export type AuditAttributionSource = Partial<Omit<AuditAttributionFields, "approval" | "approvedToolCallKey" | "mcpClientAccepted" | "identityAssurance">>;
|
|
42
43
|
/** A recorder was given no attribution, or one `auditAttribution` did not make. */
|
|
43
44
|
export declare class AuditAttributionError extends Error {
|
|
44
45
|
constructor(recorder: string);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { currentInvocationEvidence } from "../action-frame.js";
|
|
2
|
-
import { getVerifiedMcpCredentialFromEvent } from "../server/request-context.js";
|
|
2
|
+
import { getRequestContext, getVerifiedMcpCredentialFromEvent, } from "../server/request-context.js";
|
|
3
3
|
// Registered, so an attribution made by another loaded copy of this module
|
|
4
4
|
// still passes the check.
|
|
5
5
|
const auditAttributionBrand = Symbol.for("@agent-native/core/audit-attribution");
|
|
@@ -40,6 +40,7 @@ export function auditAttribution(ctx) {
|
|
|
40
40
|
mcpClient: ctx?.mcpClient,
|
|
41
41
|
mcpClientAccepted: evidence?.mcpClientAccepted,
|
|
42
42
|
approval: override?.approval,
|
|
43
|
+
identityAssurance: getRequestContext()?.a2aIdentityAssurance,
|
|
43
44
|
};
|
|
44
45
|
return { ...fields, [auditAttributionBrand]: true };
|
|
45
46
|
}
|
package/dist/audit/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { ActionAuditConfig, AuditActorKind, AuditApproval, AuditCallMeta, AuditEvent, AuditQueryFilters, AuditStatus, AuditTarget, AuditVisibility, } from "./types.js";
|
|
1
|
+
export type { ActionAuditConfig, AuditActorKind, AuditApproval, AuditCallMeta, AuditEvent, AuditIdentityAssurance, AuditQueryFilters, AuditStatus, AuditTarget, AuditVisibility, } from "./types.js";
|
|
2
2
|
export { AGENT_AUDIT_CALLERS, deriveActorKind, isAuditDisabled, normalizeAuditConfig, resolveAuditAttach, shouldRecordAudit, } from "./config.js";
|
|
3
3
|
export { redactArgsToJson } from "./redact.js";
|
|
4
4
|
export { ensureAuditTables, insertAuditEvent, queryAuditEvents, queryAuditEventPage, queryAuditApps, getAuditEventById, deleteOldAuditEvents, type AuditEventPage, type AuditReadScope, type AuditTrail, } from "./store.js";
|
package/dist/audit/ocsf.js
CHANGED
|
@@ -177,6 +177,7 @@ export function auditEventToOcsf(event) {
|
|
|
177
177
|
keep("reported_client_version", event.reportedClientVersion);
|
|
178
178
|
keep("oauth_client_id", event.oauthClientId);
|
|
179
179
|
keep("approval", event.approval);
|
|
180
|
+
keep("identity_assurance", event.identityAssurance);
|
|
180
181
|
const resource = event.targetType || event.targetId
|
|
181
182
|
? [
|
|
182
183
|
{
|
package/dist/audit/read-scope.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* decided on the server from `org_members`, never from anything the caller
|
|
5
5
|
* sends.
|
|
6
6
|
*/
|
|
7
|
-
import { getDbExec } from "../db/client.js";
|
|
8
7
|
import { isMissingOrganizationTableError } from "../org/membership.js";
|
|
9
8
|
import { canManageOrg } from "../org/permissions.js";
|
|
9
|
+
import { readOrgMemberRole } from "../server/personal-provider-key-policy.js";
|
|
10
10
|
export class AuditAccessError extends Error {
|
|
11
11
|
statusCode = 403;
|
|
12
12
|
constructor(message) {
|
|
@@ -16,17 +16,7 @@ export class AuditAccessError extends Error {
|
|
|
16
16
|
}
|
|
17
17
|
async function readOrgRole(orgId, email) {
|
|
18
18
|
try {
|
|
19
|
-
|
|
20
|
-
sql: `SELECT role FROM org_members
|
|
21
|
-
WHERE org_id = ? AND LOWER(email) = ?
|
|
22
|
-
AND federation_removal_pending_at IS NULL
|
|
23
|
-
LIMIT 1`,
|
|
24
|
-
args: [orgId, email.toLowerCase()],
|
|
25
|
-
});
|
|
26
|
-
const role = rows[0]?.role;
|
|
27
|
-
return role === "owner" || role === "admin" || role === "member"
|
|
28
|
-
? role
|
|
29
|
-
: null;
|
|
19
|
+
return await readOrgMemberRole(orgId, email);
|
|
30
20
|
}
|
|
31
21
|
catch (error) {
|
|
32
22
|
// No org tables means no organization to administer. Any other failure
|
package/dist/audit/record.js
CHANGED
|
@@ -170,6 +170,7 @@ export async function recordActionAudit(input) {
|
|
|
170
170
|
reportedClientVersion: reportedLabel(attribution.mcpClient?.version),
|
|
171
171
|
oauthClientId: attribution.mcpClient?.oauthClientId ?? null,
|
|
172
172
|
approval: approvalEvidence(attribution),
|
|
173
|
+
identityAssurance: attribution.identityAssurance ?? null,
|
|
173
174
|
};
|
|
174
175
|
if (integration) {
|
|
175
176
|
if (ctx?.orgId &&
|
package/dist/audit/store.js
CHANGED
|
@@ -78,6 +78,12 @@ export const auditStore = defineStore({
|
|
|
78
78
|
"ALTER TABLE agent_audit_log ADD COLUMN IF NOT EXISTS approval TEXT",
|
|
79
79
|
],
|
|
80
80
|
},
|
|
81
|
+
{
|
|
82
|
+
name: "a2a-identity-assurance",
|
|
83
|
+
sql: [
|
|
84
|
+
"ALTER TABLE agent_audit_log ADD COLUMN IF NOT EXISTS identity_assurance TEXT",
|
|
85
|
+
],
|
|
86
|
+
},
|
|
81
87
|
],
|
|
82
88
|
});
|
|
83
89
|
export function ensureAuditTables() {
|
|
@@ -100,8 +106,8 @@ export async function insertAuditEvent(event) {
|
|
|
100
106
|
error_code, owner_email, visibility, run_id, task_id, parent_task_id,
|
|
101
107
|
source_kind, source_platform, source_id, source_url, network_protocol,
|
|
102
108
|
network_id, network_peer, app, reported_client_name,
|
|
103
|
-
reported_client_version, oauth_client_id, approval)
|
|
104
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
109
|
+
reported_client_version, oauth_client_id, approval, identity_assurance)
|
|
110
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
105
111
|
args: [
|
|
106
112
|
event.id,
|
|
107
113
|
event.createdAt,
|
|
@@ -135,6 +141,7 @@ export async function insertAuditEvent(event) {
|
|
|
135
141
|
event.reportedClientVersion ?? null,
|
|
136
142
|
event.oauthClientId ?? null,
|
|
137
143
|
event.approval ?? null,
|
|
144
|
+
event.identityAssurance ?? null,
|
|
138
145
|
].map(storableText),
|
|
139
146
|
});
|
|
140
147
|
}
|
|
@@ -203,6 +210,8 @@ function mapRow(row) {
|
|
|
203
210
|
reportedClientVersion: row.reported_client_version ?? null,
|
|
204
211
|
oauthClientId: row.oauth_client_id ?? null,
|
|
205
212
|
approval: (row.approval ?? null),
|
|
213
|
+
identityAssurance: (row.identity_assurance ??
|
|
214
|
+
null),
|
|
206
215
|
};
|
|
207
216
|
}
|
|
208
217
|
function sharedVisibilities(scope) {
|
|
@@ -252,7 +261,7 @@ const LIST_COLUMNS = "id, created_at, action, caller, actor_kind, actor_email, o
|
|
|
252
261
|
"error_code, owner_email, visibility, app, run_id, task_id, " +
|
|
253
262
|
"parent_task_id, source_kind, source_platform, source_id, source_url, " +
|
|
254
263
|
"network_protocol, network_id, network_peer, reported_client_name, " +
|
|
255
|
-
"reported_client_version, oauth_client_id, approval";
|
|
264
|
+
"reported_client_version, oauth_client_id, approval, identity_assurance";
|
|
256
265
|
function clampLimit(limit) {
|
|
257
266
|
return Math.min(Math.max(1, Math.floor(limit ?? DEFAULT_LIMIT)), MAX_LIMIT);
|
|
258
267
|
}
|
package/dist/audit/types.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export type AuditActorKind = "agent" | "human" | "service" | "system";
|
|
|
7
7
|
* accepted MCP elicitation response; the server cannot verify who responded.
|
|
8
8
|
*/
|
|
9
9
|
export type AuditApproval = "agent-chat" | "a2a-review" | "mcp-client-accepted";
|
|
10
|
+
/** Verified identity provenance for a request authenticated through A2A. */
|
|
11
|
+
export type AuditIdentityAssurance = "user" | "organization" | "org-member";
|
|
10
12
|
/**
|
|
11
13
|
* Who can read an event besides its owner.
|
|
12
14
|
* - `private` — the owner only (the default; personal content stays here).
|
|
@@ -80,6 +82,8 @@ export interface AuditEvent {
|
|
|
80
82
|
* not verify who responded. Rows written before the column existed are null.
|
|
81
83
|
*/
|
|
82
84
|
approval?: AuditApproval | null;
|
|
85
|
+
/** Verified identity provenance; null on older rows or non-A2A calls. */
|
|
86
|
+
identityAssurance?: AuditIdentityAssurance | null;
|
|
83
87
|
}
|
|
84
88
|
export interface AuditQueryFilters {
|
|
85
89
|
targetType?: string;
|
|
@@ -139,8 +139,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
|
|
|
139
139
|
duplicate?: undefined;
|
|
140
140
|
eventId?: undefined;
|
|
141
141
|
} | {
|
|
142
|
+
error?: undefined;
|
|
142
143
|
accepted: boolean;
|
|
143
144
|
duplicate: boolean;
|
|
144
145
|
eventId: string;
|
|
145
|
-
error?: undefined;
|
|
146
146
|
}>;
|