@aamp/protocol 1.1.6 ā 1.1.8
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/package.json +7 -1
- package/src/publisher.ts +147 -125
- package/src/types.ts +1 -1
- package/aamp-protocol-1.1.5.tgz +0 -0
- package/dist/agent.d.ts +0 -30
- package/dist/agent.js +0 -65
- package/dist/constants.d.ts +0 -25
- package/dist/constants.js +0 -38
- package/dist/crypto.d.ts +0 -9
- package/dist/crypto.js +0 -44
- package/dist/express.d.ts +0 -22
- package/dist/express.js +0 -64
- package/dist/index.d.ts +0 -12
- package/dist/index.js +0 -12
- package/dist/nextjs.d.ts +0 -25
- package/dist/nextjs.js +0 -60
- package/dist/proof.d.ts +0 -9
- package/dist/proof.js +0 -27
- package/dist/publisher.d.ts +0 -35
- package/dist/publisher.js +0 -338
- package/dist/types.d.ts +0 -113
- package/dist/types.js +0 -25
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aamp/protocol",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"description": "TypeScript reference implementation of AAMP v1.1",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./dist/nextjs": "./dist/nextjs.js",
|
|
10
|
+
"./dist/agent": "./dist/agent.js",
|
|
11
|
+
"./dist/types": "./dist/types.js"
|
|
12
|
+
},
|
|
7
13
|
"type": "module",
|
|
8
14
|
"scripts": {
|
|
9
15
|
"build": "tsc",
|
package/src/publisher.ts
CHANGED
|
@@ -69,63 +69,56 @@ export class AAMPPublisher {
|
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
71
|
* Main Entry Point: Evaluate ANY visitor (Human, Bot, or Agent)
|
|
72
|
+
* STAGE 1: IDENTITY (Strict)
|
|
73
|
+
* STAGE 2: POLICY (Permissions)
|
|
74
|
+
* STAGE 3: ACCESS (HQ Content)
|
|
72
75
|
*/
|
|
73
76
|
async evaluateVisitor(
|
|
74
77
|
reqHeaders: Record<string, string | undefined>,
|
|
75
78
|
rawPayload?: string
|
|
76
79
|
): Promise<EvaluationResult> {
|
|
80
|
+
console.log(`\n--- [AAMP LOG START] New Request ---`);
|
|
77
81
|
|
|
78
|
-
// 1
|
|
79
|
-
|
|
82
|
+
// --- STAGE 1: IDENTITY VERIFICATION ---
|
|
83
|
+
console.log(`[IDENTITY] š Checking Identity Headers...`);
|
|
80
84
|
|
|
81
|
-
const
|
|
82
|
-
if (feedbackToken) {
|
|
83
|
-
await this.handleFeedback(feedbackToken, reqHeaders);
|
|
84
|
-
}
|
|
85
|
+
const hasAamp = reqHeaders[HEADERS.PAYLOAD] && reqHeaders[HEADERS.SIGNATURE] && reqHeaders[HEADERS.PUBLIC_KEY];
|
|
85
86
|
|
|
86
87
|
if (hasAamp) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return await this.handleAgent(reqHeaders, rawPayload);
|
|
88
|
+
// It claims to be an Agent. Verify it STRICTLY.
|
|
89
|
+
return await this.handleAgentStrict(reqHeaders, rawPayload);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
//
|
|
92
|
+
// If NO AAMP Headers -> FAIL IDENTITY immediately.
|
|
93
|
+
console.log(`[IDENTITY] ā FAILED. No AAMP Headers found.`);
|
|
94
|
+
|
|
95
|
+
// For now, retaining the legacy "Passive/Hybrid" switch just to avoid breaking browser demos completely
|
|
96
|
+
// BUT logging it as a specific "Identity Fail" flow.
|
|
93
97
|
if (this.unauthenticatedStrategy === 'STRICT') {
|
|
98
|
+
console.log(`[IDENTITY] ā BLOCKING. Strategy is STRICT.`);
|
|
94
99
|
return {
|
|
95
100
|
allowed: false,
|
|
96
101
|
status: 401,
|
|
97
|
-
reason: "
|
|
102
|
+
reason: "IDENTITY_REQUIRED: Missing AAMP Headers.",
|
|
98
103
|
visitorType: 'UNIDENTIFIED_BOT'
|
|
99
104
|
};
|
|
100
105
|
}
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
allowed: true,
|
|
105
|
-
status: 200,
|
|
106
|
-
reason: "PASSIVE_MODE: Allowed without verification.",
|
|
107
|
-
visitorType: 'LIKELY_HUMAN'
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// 3. HYBRID MODE: Heuristic Analysis (The "Lazy Bot" Filter)
|
|
107
|
+
console.log(`[IDENTITY] ā ļø SKIPPED (Legacy Mode). Checking Browser Heuristics...`);
|
|
112
108
|
const isHuman = this.performBrowserHeuristics(reqHeaders);
|
|
113
|
-
|
|
114
109
|
if (isHuman) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
status: 200,
|
|
118
|
-
reason: "BROWSER_VERIFIED: Heuristics passed.",
|
|
119
|
-
visitorType: 'LIKELY_HUMAN'
|
|
120
|
-
};
|
|
121
|
-
} else {
|
|
122
|
-
return {
|
|
123
|
-
allowed: false,
|
|
124
|
-
status: 403,
|
|
125
|
-
reason: "BOT_DETECTED: Request lacks browser signatures and AAMP headers.",
|
|
126
|
-
visitorType: 'UNIDENTIFIED_BOT'
|
|
127
|
-
};
|
|
110
|
+
console.log(`[POLICY] š¤ ALLOWED. Browser Heuristics Passed.`);
|
|
111
|
+
return { allowed: true, status: 200, reason: "BROWSER_VERIFIED", visitorType: 'LIKELY_HUMAN' };
|
|
128
112
|
}
|
|
113
|
+
|
|
114
|
+
console.log(`[IDENTITY] ā FAILED. Not a Browser, No Headers.`);
|
|
115
|
+
console.log(`[ACCESS] ā BLOCKED.`);
|
|
116
|
+
return {
|
|
117
|
+
allowed: false,
|
|
118
|
+
status: 403,
|
|
119
|
+
reason: "IDENTITY_FAIL: No Identity, No Browser.",
|
|
120
|
+
visitorType: 'UNIDENTIFIED_BOT'
|
|
121
|
+
};
|
|
129
122
|
}
|
|
130
123
|
|
|
131
124
|
/**
|
|
@@ -161,17 +154,24 @@ export class AAMPPublisher {
|
|
|
161
154
|
}
|
|
162
155
|
|
|
163
156
|
/**
|
|
164
|
-
* Handle AAMP Protocol Logic
|
|
157
|
+
* Handle AAMP Protocol Logic (Strict Mode)
|
|
165
158
|
*/
|
|
166
|
-
private async
|
|
159
|
+
private async handleAgentStrict(reqHeaders: Record<string, string | undefined>, rawPayload?: string): Promise<EvaluationResult> {
|
|
160
|
+
let agentId = "UNKNOWN";
|
|
161
|
+
|
|
167
162
|
try {
|
|
163
|
+
// 1. Decode Headers
|
|
168
164
|
const payloadHeader = reqHeaders[HEADERS.PAYLOAD]!;
|
|
169
165
|
const sigHeader = reqHeaders[HEADERS.SIGNATURE]!;
|
|
170
166
|
const keyHeader = reqHeaders[HEADERS.PUBLIC_KEY]!;
|
|
171
167
|
|
|
172
168
|
const headerJson = atob(payloadHeader);
|
|
173
169
|
const requestHeader = JSON.parse(headerJson);
|
|
170
|
+
agentId = requestHeader.agent_id;
|
|
174
171
|
|
|
172
|
+
console.log(`[IDENTITY] š Claimed ID: ${agentId}`);
|
|
173
|
+
|
|
174
|
+
// 2. Crypto & DNS Verification
|
|
175
175
|
const signedRequest: SignedAccessRequest = {
|
|
176
176
|
header: requestHeader,
|
|
177
177
|
signature: sigHeader,
|
|
@@ -186,151 +186,173 @@ export class AAMPPublisher {
|
|
|
186
186
|
["verify"]
|
|
187
187
|
);
|
|
188
188
|
|
|
189
|
+
// Verify Core Logic (DNS + Crypto)
|
|
190
|
+
const verification = await this.verifyRequestLogic(signedRequest, agentKey);
|
|
191
|
+
|
|
192
|
+
if (!verification.identityVerified) {
|
|
193
|
+
console.log(`[IDENTITY] ā FAILED. Reason: ${verification.reason}`);
|
|
194
|
+
console.log(`[ACCESS] ā BLOCKED.`);
|
|
195
|
+
return { allowed: false, status: 403, reason: verification.reason, visitorType: 'UNIDENTIFIED_BOT' };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
console.log(`[IDENTITY] ā
PASSED. DNS Binding Verified.`);
|
|
199
|
+
|
|
200
|
+
// --- STAGE 2: POLICY ENFORCEMENT ---
|
|
201
|
+
console.log(`[POLICY] š Checking Permissions for ${agentId}...`);
|
|
202
|
+
|
|
189
203
|
const proofToken = reqHeaders[HEADERS.PROOF_TOKEN];
|
|
190
204
|
const paymentCredential = reqHeaders[HEADERS.PAYMENT_CREDENTIAL];
|
|
191
205
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
console.log(
|
|
197
|
-
|
|
198
|
-
Reason: ${result.reason}
|
|
199
|
-
VisitorType: ${result.visitorType}
|
|
200
|
-
Proof: ${result.proofUsed || 'None'}
|
|
201
|
-
Identity Verified: ${result.identityVerified}`);
|
|
202
|
-
|
|
203
|
-
return {
|
|
204
|
-
allowed: false,
|
|
205
|
-
status: 403,
|
|
206
|
-
reason: result.reason,
|
|
207
|
-
visitorType: 'VERIFIED_AGENT'
|
|
208
|
-
};
|
|
206
|
+
const policyResult = await this.checkPolicyStrict(requestHeader, proofToken, paymentCredential);
|
|
207
|
+
|
|
208
|
+
if (!policyResult.allowed) {
|
|
209
|
+
console.log(`[POLICY] ā DENIED. Reason: ${policyResult.reason}`);
|
|
210
|
+
console.log(`[ACCESS] ā BLOCKED.`);
|
|
211
|
+
return policyResult;
|
|
209
212
|
}
|
|
210
213
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
Payment Method: ${result.proofUsed}
|
|
215
|
-
Identity Verified: ${result.identityVerified}`);
|
|
214
|
+
// --- STAGE 3: ACCESS GRANT ---
|
|
215
|
+
console.log(`[POLICY] ā
PASSED. Requirements Met.`);
|
|
216
|
+
console.log(`[ACCESS] š GRANTED. Unlocking HQ Content.`);
|
|
216
217
|
|
|
217
218
|
return {
|
|
218
219
|
allowed: true,
|
|
219
220
|
status: 200,
|
|
220
221
|
reason: "AAMP_VERIFIED",
|
|
221
222
|
visitorType: 'VERIFIED_AGENT',
|
|
222
|
-
metadata: requestHeader
|
|
223
|
+
metadata: requestHeader,
|
|
224
|
+
proofUsed: policyResult.proofUsed
|
|
223
225
|
};
|
|
224
226
|
|
|
225
227
|
} catch (e) {
|
|
226
|
-
console.error(e);
|
|
228
|
+
console.error(`[AAMP ERROR]`, e);
|
|
227
229
|
return { allowed: false, status: 400, reason: "INVALID_SIGNATURE", visitorType: 'UNIDENTIFIED_BOT' };
|
|
228
230
|
}
|
|
229
231
|
}
|
|
230
232
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
paymentCredential?: string,
|
|
236
|
-
rawPayload?: string
|
|
237
|
-
): Promise<VerificationResult> {
|
|
238
|
-
|
|
239
|
-
// 1. Replay Attack Prevention
|
|
240
|
-
const requestTime = new Date(request.header.ts).getTime();
|
|
241
|
-
if (Math.abs(Date.now() - requestTime) > MAX_CLOCK_SKEW_MS) {
|
|
242
|
-
return { allowed: false, reason: 'TIMESTAMP_INVALID', identityVerified: false };
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// 2. Crypto Verification
|
|
246
|
-
const signableString = rawPayload || JSON.stringify(request.header);
|
|
247
|
-
const isCryptoValid = await verifySignature(requestPublicKey, signableString, request.signature);
|
|
248
|
-
if (!isCryptoValid) return { allowed: false, reason: 'CRYPTO_FAIL', identityVerified: false };
|
|
249
|
-
|
|
250
|
-
// 3. Identity Verification (DNS Binding) with Cache
|
|
251
|
-
let identityVerified = false;
|
|
252
|
-
const claimedDomain = request.header.agent_id;
|
|
253
|
-
const pubKeyString = await exportPublicKey(requestPublicKey);
|
|
254
|
-
|
|
255
|
-
console.log(` š [AAMP Identity] Verifying DNS Binding for: ${claimedDomain}`);
|
|
256
|
-
|
|
257
|
-
// Check Cache First
|
|
258
|
-
const cachedKey = await this.cache.get(claimedDomain);
|
|
259
|
-
|
|
260
|
-
if (cachedKey === pubKeyString) {
|
|
261
|
-
console.log(" ā” [AAMP Cache] Identity found in cache.");
|
|
262
|
-
identityVerified = true;
|
|
263
|
-
} else if (this.isDomain(claimedDomain)) {
|
|
264
|
-
// Cache Miss: Perform DNS Fetch
|
|
265
|
-
identityVerified = await this.verifyDnsBinding(claimedDomain, pubKeyString);
|
|
266
|
-
if (identityVerified) {
|
|
267
|
-
await this.cache.set(claimedDomain, pubKeyString, this.CACHE_TTL_SECONDS);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
233
|
+
// Legacy handler kept for interface compatibility (deprecated)
|
|
234
|
+
private async handleAgent(reqHeaders: Record<string, string | undefined>, rawPayload?: string): Promise<EvaluationResult> {
|
|
235
|
+
return this.handleAgentStrict(reqHeaders, rawPayload);
|
|
236
|
+
}
|
|
270
237
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
238
|
+
/**
|
|
239
|
+
* STAGE 2: POLICY ENFORCEMENT CHECK
|
|
240
|
+
*/
|
|
241
|
+
private async checkPolicyStrict(
|
|
242
|
+
requestHeader: any,
|
|
243
|
+
proofToken?: string,
|
|
244
|
+
paymentCredential?: string
|
|
245
|
+
): Promise<EvaluationResult> {
|
|
274
246
|
|
|
275
|
-
//
|
|
276
|
-
if (
|
|
277
|
-
return { allowed: false, reason: 'POLICY_DENIED: Training not allowed.',
|
|
247
|
+
// 1. Policy Check: Purpose Ban (e.g. No Training)
|
|
248
|
+
if (requestHeader.purpose === AccessPurpose.CRAWL_TRAINING && !this.policy.allowTraining) {
|
|
249
|
+
return { allowed: false, status: 403, reason: 'POLICY_DENIED: Training not allowed.', visitorType: 'VERIFIED_AGENT' };
|
|
278
250
|
}
|
|
279
|
-
if (
|
|
280
|
-
return { allowed: false, reason: 'POLICY_DENIED: RAG not allowed.',
|
|
251
|
+
if (requestHeader.purpose === AccessPurpose.RAG_RETRIEVAL && !this.policy.allowRAG) {
|
|
252
|
+
return { allowed: false, status: 403, reason: 'POLICY_DENIED: RAG not allowed.', visitorType: 'VERIFIED_AGENT' };
|
|
281
253
|
}
|
|
282
254
|
|
|
283
|
-
//
|
|
255
|
+
// 2. Policy Check: Economics (v1.2) - Payment & Ads
|
|
284
256
|
if (this.policy.requiresPayment) {
|
|
285
257
|
let paymentSatisfied = false;
|
|
286
258
|
|
|
287
259
|
// Method A: Flexible Payment Callback (DB / Custom Logic)
|
|
288
260
|
if (this.policy.monetization?.checkPayment) {
|
|
289
|
-
const isPaid = await this.policy.monetization.checkPayment(
|
|
261
|
+
const isPaid = await this.policy.monetization.checkPayment(requestHeader.agent_id, requestHeader.purpose);
|
|
290
262
|
if (isPaid) {
|
|
291
|
-
console.log(`
|
|
292
|
-
return { allowed: true, reason: 'OK',
|
|
263
|
+
console.log(`[POLICY] š° Payment Verified via Callback.`);
|
|
264
|
+
return { allowed: true, status: 200, reason: 'OK', visitorType: 'VERIFIED_AGENT', proofUsed: 'WHITELIST_CALLBACK' };
|
|
293
265
|
}
|
|
294
266
|
}
|
|
295
267
|
|
|
296
268
|
// Method B: Payment Credentials (Unified JWT)
|
|
297
269
|
if (!paymentSatisfied && this.policy.monetization?.paymentConfig && paymentCredential) {
|
|
298
270
|
const { jwksUrl, issuer } = this.policy.monetization.paymentConfig;
|
|
299
|
-
console.log(`
|
|
271
|
+
console.log(`[POLICY] š Verifying Payment Credential (Issuer: ${issuer})...`);
|
|
300
272
|
|
|
301
273
|
const isValidCredential = await verifyJwt(paymentCredential, jwksUrl, issuer);
|
|
302
274
|
if (isValidCredential) {
|
|
303
|
-
console.log(`
|
|
304
|
-
return { allowed: true, reason: 'OK',
|
|
275
|
+
console.log(`[POLICY] ā
Credential Signature VALID.`);
|
|
276
|
+
return { allowed: true, status: 200, reason: 'OK', visitorType: 'VERIFIED_AGENT', proofUsed: 'PAYMENT_CREDENTIAL_JWT' };
|
|
305
277
|
} else {
|
|
306
|
-
console.log(`
|
|
278
|
+
console.log(`[POLICY] ā Credential Signature INVALID.`);
|
|
307
279
|
}
|
|
308
280
|
}
|
|
309
281
|
|
|
310
282
|
// Method C: Ad-Supported (Proof Verification)
|
|
311
|
-
if (!paymentSatisfied && this.policy.allowAdSupportedAccess &&
|
|
283
|
+
if (!paymentSatisfied && this.policy.allowAdSupportedAccess && requestHeader.context?.ads_displayed) {
|
|
312
284
|
if (proofToken && this.policy.monetization?.adNetwork) {
|
|
313
285
|
const { jwksUrl, issuer } = this.policy.monetization.adNetwork;
|
|
314
|
-
console.log(`
|
|
286
|
+
console.log(`[POLICY] šŗ Verifying Ad Proof (Issuer: ${issuer})...`);
|
|
315
287
|
|
|
316
288
|
const isValidProof = await verifyJwt(proofToken, jwksUrl, issuer);
|
|
317
289
|
if (isValidProof) {
|
|
318
|
-
console.log(`
|
|
319
|
-
return { allowed: true, reason: 'OK',
|
|
290
|
+
console.log(`[POLICY] ā
Ad Proof Signature VALID.`);
|
|
291
|
+
return { allowed: true, status: 200, reason: 'OK', visitorType: 'VERIFIED_AGENT', proofUsed: 'AD_PROOF_JWT' };
|
|
320
292
|
} else {
|
|
321
|
-
console.log(`
|
|
293
|
+
console.log(`[POLICY] ā Ad Proof Signature INVALID.`);
|
|
322
294
|
}
|
|
323
295
|
} else {
|
|
324
|
-
console.log(`
|
|
296
|
+
console.log(`[POLICY] ā ļø Ad Proof MISSING.`);
|
|
325
297
|
}
|
|
326
298
|
}
|
|
327
299
|
|
|
328
|
-
|
|
329
|
-
|
|
300
|
+
return {
|
|
301
|
+
allowed: false,
|
|
302
|
+
status: 402,
|
|
303
|
+
reason: 'PAYMENT_REQUIRED: Whitelist, Credential, and Ad Proof checks ALL failed.',
|
|
304
|
+
visitorType: 'VERIFIED_AGENT',
|
|
305
|
+
proofUsed: 'NONE'
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// If no payment required, allow.
|
|
310
|
+
return { allowed: true, status: 200, reason: 'OK', visitorType: 'VERIFIED_AGENT' };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private async verifyRequestLogic(
|
|
314
|
+
request: SignedAccessRequest,
|
|
315
|
+
requestPublicKey: CryptoKey,
|
|
316
|
+
): Promise<VerificationResult> {
|
|
317
|
+
|
|
318
|
+
// 1. Replay Attack Prevention
|
|
319
|
+
const requestTime = new Date(request.header.ts).getTime();
|
|
320
|
+
if (Math.abs(Date.now() - requestTime) > MAX_CLOCK_SKEW_MS) {
|
|
321
|
+
return { allowed: false, reason: 'TIMESTAMP_INVALID: Clock skew too large.', identityVerified: false };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// 2. Crypto Verification
|
|
325
|
+
const signableString = JSON.stringify(request.header);
|
|
326
|
+
const isCryptoValid = await verifySignature(requestPublicKey, signableString, request.signature);
|
|
327
|
+
if (!isCryptoValid) return { allowed: false, reason: 'CRYPTO_FAIL: Signature invalid.', identityVerified: false };
|
|
328
|
+
|
|
329
|
+
// 3. Identity Verification (DNS Binding) with Cache
|
|
330
|
+
let identityVerified = false;
|
|
331
|
+
const claimedDomain = request.header.agent_id;
|
|
332
|
+
const pubKeyString = await exportPublicKey(requestPublicKey);
|
|
333
|
+
|
|
334
|
+
console.log(`[IDENTITY] š Verifying DNS Binding for: ${claimedDomain}`);
|
|
335
|
+
|
|
336
|
+
// Check Cache First
|
|
337
|
+
const cachedKey = await this.cache.get(claimedDomain);
|
|
338
|
+
|
|
339
|
+
if (cachedKey === pubKeyString) {
|
|
340
|
+
console.log("[IDENTITY] ā” Cache Hit. Identity Verified.");
|
|
341
|
+
identityVerified = true;
|
|
342
|
+
} else if (this.isDomain(claimedDomain)) {
|
|
343
|
+
// Cache Miss: Perform DNS Fetch
|
|
344
|
+
identityVerified = await this.verifyDnsBinding(claimedDomain, pubKeyString);
|
|
345
|
+
if (identityVerified) {
|
|
346
|
+
await this.cache.set(claimedDomain, pubKeyString, this.CACHE_TTL_SECONDS);
|
|
330
347
|
}
|
|
331
348
|
}
|
|
332
349
|
|
|
333
|
-
|
|
350
|
+
if (this.policy.requireIdentityBinding && !identityVerified) {
|
|
351
|
+
return { allowed: false, reason: 'IDENTITY_FAIL: DNS Binding could not be verified.', identityVerified: false };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Return verified status so handleAgentStrict can proceed to Policy Check
|
|
355
|
+
return { allowed: true, reason: 'OK', identityVerified: identityVerified };
|
|
334
356
|
}
|
|
335
357
|
|
|
336
358
|
private async verifyDnsBinding(domain: string, requestKeySpki: string): Promise<boolean> {
|
package/src/types.ts
CHANGED
|
@@ -128,7 +128,7 @@ export interface FeedbackSignal {
|
|
|
128
128
|
// Result of the full evaluation pipeline
|
|
129
129
|
export interface EvaluationResult {
|
|
130
130
|
allowed: boolean;
|
|
131
|
-
status: 200 | 400 | 401 | 403;
|
|
131
|
+
status: 200 | 400 | 401 | 402 | 403;
|
|
132
132
|
reason: string;
|
|
133
133
|
visitorType: 'VERIFIED_AGENT' | 'LIKELY_HUMAN' | 'UNIDENTIFIED_BOT';
|
|
134
134
|
metadata?: any;
|
package/aamp-protocol-1.1.5.tgz
DELETED
|
Binary file
|
package/dist/agent.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layer 2: Agent SDK
|
|
3
|
-
*/
|
|
4
|
-
import { AccessPurpose, SignedAccessRequest, FeedbackSignal, QualityFlag, AgentIdentityManifest } from './types.js';
|
|
5
|
-
export interface AccessOptions {
|
|
6
|
-
adsDisplayed?: boolean;
|
|
7
|
-
}
|
|
8
|
-
export declare class AAMPAgent {
|
|
9
|
-
private keyPair;
|
|
10
|
-
agentId: string;
|
|
11
|
-
/**
|
|
12
|
-
* Initialize the Agent Identity (Ephemeral or Persisted)
|
|
13
|
-
* @param customAgentId For PRODUCTION, this should be your domain (e.g., "bot.openai.com")
|
|
14
|
-
*/
|
|
15
|
-
initialize(customAgentId?: string): Promise<void>;
|
|
16
|
-
createAccessRequest(resource: string, purpose: AccessPurpose, options?: AccessOptions): Promise<SignedAccessRequest>;
|
|
17
|
-
/**
|
|
18
|
-
* Helper: Generate the JSON file you must host on your domain
|
|
19
|
-
* Host this at: https://{agentId}/.well-known/aamp-agent.json
|
|
20
|
-
*/
|
|
21
|
-
getIdentityManifest(contactEmail?: string): Promise<AgentIdentityManifest>;
|
|
22
|
-
/**
|
|
23
|
-
* NEW IN V1.1: Quality Feedback Loop
|
|
24
|
-
* Allows the Agent to report spam or verify quality of a resource.
|
|
25
|
-
*/
|
|
26
|
-
generateFeedback(resource: string, score: number, flags: QualityFlag[]): Promise<{
|
|
27
|
-
signal: FeedbackSignal;
|
|
28
|
-
signature: string;
|
|
29
|
-
}>;
|
|
30
|
-
}
|
package/dist/agent.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { generateKeyPair, signData, exportPublicKey } from './crypto.js';
|
|
2
|
-
import { AAMP_VERSION } from './constants.js';
|
|
3
|
-
export class AAMPAgent {
|
|
4
|
-
constructor() {
|
|
5
|
-
this.keyPair = null;
|
|
6
|
-
this.agentId = "pending";
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Initialize the Agent Identity (Ephemeral or Persisted)
|
|
10
|
-
* @param customAgentId For PRODUCTION, this should be your domain (e.g., "bot.openai.com")
|
|
11
|
-
*/
|
|
12
|
-
async initialize(customAgentId) {
|
|
13
|
-
this.keyPair = await generateKeyPair();
|
|
14
|
-
// Use the provided ID (authentic) or generate a session ID (ephemeral)
|
|
15
|
-
this.agentId = customAgentId || "agent_" + Math.random().toString(36).substring(7);
|
|
16
|
-
}
|
|
17
|
-
async createAccessRequest(resource, purpose, options = {}) {
|
|
18
|
-
if (!this.keyPair)
|
|
19
|
-
throw new Error("Agent not initialized. Call initialize() first.");
|
|
20
|
-
const header = {
|
|
21
|
-
v: AAMP_VERSION,
|
|
22
|
-
ts: new Date().toISOString(),
|
|
23
|
-
agent_id: this.agentId,
|
|
24
|
-
resource,
|
|
25
|
-
purpose,
|
|
26
|
-
context: {
|
|
27
|
-
ads_displayed: options.adsDisplayed || false
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
const signature = await signData(this.keyPair.privateKey, JSON.stringify(header));
|
|
31
|
-
const publicKeyExport = await exportPublicKey(this.keyPair.publicKey);
|
|
32
|
-
return { header, signature, publicKey: publicKeyExport };
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Helper: Generate the JSON file you must host on your domain
|
|
36
|
-
* Host this at: https://{agentId}/.well-known/aamp-agent.json
|
|
37
|
-
*/
|
|
38
|
-
async getIdentityManifest(contactEmail) {
|
|
39
|
-
if (!this.keyPair)
|
|
40
|
-
throw new Error("Agent not initialized.");
|
|
41
|
-
const publicKey = await exportPublicKey(this.keyPair.publicKey);
|
|
42
|
-
return {
|
|
43
|
-
agent_id: this.agentId,
|
|
44
|
-
public_key: publicKey,
|
|
45
|
-
contact_email: contactEmail
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* NEW IN V1.1: Quality Feedback Loop
|
|
50
|
-
* Allows the Agent to report spam or verify quality of a resource.
|
|
51
|
-
*/
|
|
52
|
-
async generateFeedback(resource, score, flags) {
|
|
53
|
-
if (!this.keyPair)
|
|
54
|
-
throw new Error("Agent not initialized.");
|
|
55
|
-
const signal = {
|
|
56
|
-
target_resource: resource,
|
|
57
|
-
agent_id: this.agentId,
|
|
58
|
-
quality_score: Math.max(0, Math.min(1, score)),
|
|
59
|
-
flags,
|
|
60
|
-
timestamp: new Date().toISOString()
|
|
61
|
-
};
|
|
62
|
-
const signature = await signData(this.keyPair.privateKey, JSON.stringify(signal));
|
|
63
|
-
return { signal, signature };
|
|
64
|
-
}
|
|
65
|
-
}
|
package/dist/constants.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layer 1: Protocol Constants
|
|
3
|
-
* These values are immutable and defined by the AAMP Specification.
|
|
4
|
-
*/
|
|
5
|
-
export declare const AAMP_VERSION = "1.1";
|
|
6
|
-
export declare const WELL_KNOWN_AGENT_PATH = "/.well-known/aamp-agent.json";
|
|
7
|
-
export declare const HEADERS: {
|
|
8
|
-
readonly PAYLOAD: "x-aamp-payload";
|
|
9
|
-
readonly SIGNATURE: "x-aamp-signature";
|
|
10
|
-
readonly PUBLIC_KEY: "x-aamp-public-key";
|
|
11
|
-
readonly AGENT_ID: "x-aamp-agent-id";
|
|
12
|
-
readonly TIMESTAMP: "x-aamp-timestamp";
|
|
13
|
-
readonly ALGORITHM: "x-aamp-alg";
|
|
14
|
-
readonly CONTENT_ORIGIN: "x-aamp-content-origin";
|
|
15
|
-
readonly PROVENANCE_SIG: "x-aamp-provenance-sig";
|
|
16
|
-
readonly PROOF_TOKEN: "x-aamp-proof";
|
|
17
|
-
readonly PAYMENT_CREDENTIAL: "x-aamp-credential";
|
|
18
|
-
readonly FEEDBACK: "x-aamp-feedback";
|
|
19
|
-
};
|
|
20
|
-
export declare const CRYPTO_CONFIG: {
|
|
21
|
-
readonly ALGORITHM_NAME: "ECDSA";
|
|
22
|
-
readonly CURVE: "P-256";
|
|
23
|
-
readonly HASH: "SHA-256";
|
|
24
|
-
};
|
|
25
|
-
export declare const MAX_CLOCK_SKEW_MS: number;
|
package/dist/constants.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layer 1: Protocol Constants
|
|
3
|
-
* These values are immutable and defined by the AAMP Specification.
|
|
4
|
-
*/
|
|
5
|
-
export const AAMP_VERSION = '1.1';
|
|
6
|
-
// The path where Agents MUST host their public key to prove identity.
|
|
7
|
-
// Example: https://bot.openai.com/.well-known/aamp-agent.json
|
|
8
|
-
export const WELL_KNOWN_AGENT_PATH = '/.well-known/aamp-agent.json';
|
|
9
|
-
// HTTP Headers used for the handshake
|
|
10
|
-
export const HEADERS = {
|
|
11
|
-
// Transport: The signed payload (Base64 encoded JSON of ProtocolHeader)
|
|
12
|
-
PAYLOAD: 'x-aamp-payload',
|
|
13
|
-
// Transport: The cryptographic signature (Hex)
|
|
14
|
-
SIGNATURE: 'x-aamp-signature',
|
|
15
|
-
// Transport: The Agent's Public Key (Base64 SPKI)
|
|
16
|
-
PUBLIC_KEY: 'x-aamp-public-key',
|
|
17
|
-
// Informational / Legacy (Optional if Payload is present)
|
|
18
|
-
AGENT_ID: 'x-aamp-agent-id',
|
|
19
|
-
TIMESTAMP: 'x-aamp-timestamp',
|
|
20
|
-
ALGORITHM: 'x-aamp-alg',
|
|
21
|
-
// v1.1 Addition: Provenance (Server to Agent)
|
|
22
|
-
CONTENT_ORIGIN: 'x-aamp-content-origin',
|
|
23
|
-
PROVENANCE_SIG: 'x-aamp-provenance-sig',
|
|
24
|
-
// v1.2 Proof of Value
|
|
25
|
-
PROOF_TOKEN: 'x-aamp-proof',
|
|
26
|
-
// v1.2 Payment Credential (The "Digital Receipt")
|
|
27
|
-
PAYMENT_CREDENTIAL: 'x-aamp-credential',
|
|
28
|
-
// v1.2 Quality Feedback (The "Dispute Token")
|
|
29
|
-
FEEDBACK: 'x-aamp-feedback'
|
|
30
|
-
};
|
|
31
|
-
// Cryptographic Settings
|
|
32
|
-
export const CRYPTO_CONFIG = {
|
|
33
|
-
ALGORITHM_NAME: 'ECDSA',
|
|
34
|
-
CURVE: 'P-256',
|
|
35
|
-
HASH: 'SHA-256',
|
|
36
|
-
};
|
|
37
|
-
// Tolerance
|
|
38
|
-
export const MAX_CLOCK_SKEW_MS = 5 * 60 * 1000; // 5 minutes
|
package/dist/crypto.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layer 1: Cryptographic Primitives
|
|
3
|
-
* Implementation of ECDSA P-256 signing/verification.
|
|
4
|
-
*/
|
|
5
|
-
export declare function generateKeyPair(): Promise<CryptoKeyPair>;
|
|
6
|
-
export declare function signData(privateKey: CryptoKey, data: string): Promise<string>;
|
|
7
|
-
export declare function verifySignature(publicKey: CryptoKey, data: string, signatureHex: string): Promise<boolean>;
|
|
8
|
-
export declare function exportPublicKey(key: CryptoKey): Promise<string>;
|
|
9
|
-
export declare function importPublicKey(keyData: string): Promise<CryptoKey>;
|
package/dist/crypto.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layer 1: Cryptographic Primitives
|
|
3
|
-
* Implementation of ECDSA P-256 signing/verification.
|
|
4
|
-
*/
|
|
5
|
-
export async function generateKeyPair() {
|
|
6
|
-
// Uses standard Web Crypto API (Node 19+ compatible)
|
|
7
|
-
return await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]);
|
|
8
|
-
}
|
|
9
|
-
export async function signData(privateKey, data) {
|
|
10
|
-
const encoder = new TextEncoder();
|
|
11
|
-
const encoded = encoder.encode(data);
|
|
12
|
-
const signature = await crypto.subtle.sign({ name: "ECDSA", hash: { name: "SHA-256" } }, privateKey, encoded);
|
|
13
|
-
return bufToHex(signature);
|
|
14
|
-
}
|
|
15
|
-
export async function verifySignature(publicKey, data, signatureHex) {
|
|
16
|
-
const encoder = new TextEncoder();
|
|
17
|
-
const encodedData = encoder.encode(data);
|
|
18
|
-
const signatureBytes = hexToBuf(signatureHex);
|
|
19
|
-
console.log(" š [AAMP Crypto] Verifying ECDSA P-256 Signature...");
|
|
20
|
-
const isValid = await crypto.subtle.verify({ name: "ECDSA", hash: { name: "SHA-256" } }, publicKey, signatureBytes, encodedData);
|
|
21
|
-
console.log(` ${isValid ? "ā
" : "ā"} [AAMP Crypto] Signature Result: ${isValid ? "VALID" : "INVALID"}`);
|
|
22
|
-
return isValid;
|
|
23
|
-
}
|
|
24
|
-
export async function exportPublicKey(key) {
|
|
25
|
-
const exported = await crypto.subtle.exportKey("spki", key);
|
|
26
|
-
return btoa(String.fromCharCode(...new Uint8Array(exported)));
|
|
27
|
-
}
|
|
28
|
-
export async function importPublicKey(keyData) {
|
|
29
|
-
const binaryString = atob(keyData);
|
|
30
|
-
const bytes = new Uint8Array(binaryString.length);
|
|
31
|
-
for (let i = 0; i < binaryString.length; i++) {
|
|
32
|
-
bytes[i] = binaryString.charCodeAt(i);
|
|
33
|
-
}
|
|
34
|
-
return await crypto.subtle.importKey("spki", bytes, { name: "ECDSA", namedCurve: "P-256" }, true, ["verify"]);
|
|
35
|
-
}
|
|
36
|
-
// Helpers
|
|
37
|
-
function bufToHex(buffer) {
|
|
38
|
-
return Array.from(new Uint8Array(buffer))
|
|
39
|
-
.map(b => b.toString(16).padStart(2, '0'))
|
|
40
|
-
.join('');
|
|
41
|
-
}
|
|
42
|
-
function hexToBuf(hex) {
|
|
43
|
-
return new Uint8Array(hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
|
|
44
|
-
}
|
package/dist/express.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { AccessPolicy, ContentOrigin, UnauthenticatedStrategy, IdentityCache } from './types.js';
|
|
2
|
-
export interface AAMPConfig {
|
|
3
|
-
policy: Omit<AccessPolicy, 'version'>;
|
|
4
|
-
meta: {
|
|
5
|
-
origin: keyof typeof ContentOrigin;
|
|
6
|
-
paymentPointer?: string;
|
|
7
|
-
};
|
|
8
|
-
strategy?: UnauthenticatedStrategy;
|
|
9
|
-
cache?: IdentityCache;
|
|
10
|
-
}
|
|
11
|
-
export declare class AAMP {
|
|
12
|
-
private publisher;
|
|
13
|
-
private origin;
|
|
14
|
-
private ready;
|
|
15
|
-
private constructor();
|
|
16
|
-
static init(config: AAMPConfig): AAMP;
|
|
17
|
-
/**
|
|
18
|
-
* Express Middleware
|
|
19
|
-
*/
|
|
20
|
-
middleware(): (req: any, res: any, next: any) => Promise<void>;
|
|
21
|
-
discoveryHandler(): (req: any, res: any) => void;
|
|
22
|
-
}
|