@aamp/protocol 1.1.7 → 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 CHANGED
@@ -1,9 +1,15 @@
1
1
  {
2
2
  "name": "@aamp/protocol",
3
- "version": "1.1.7",
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
@@ -352,7 +352,7 @@ export class AAMPPublisher {
352
352
  }
353
353
 
354
354
  // Return verified status so handleAgentStrict can proceed to Policy Check
355
- return { allowed: true, reason: 'OK', identityVerified: true };
355
+ return { allowed: true, reason: 'OK', identityVerified: identityVerified };
356
356
  }
357
357
 
358
358
  private async verifyDnsBinding(domain: string, requestKeySpki: string): Promise<boolean> {
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
- }
@@ -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
- }
package/dist/express.js DELETED
@@ -1,64 +0,0 @@
1
- /**
2
- * Layer 3: Framework Adapters
3
- * Zero-friction integration for Express/Node.js.
4
- */
5
- import { AAMPPublisher } from './publisher.js';
6
- import { ContentOrigin } from './types.js';
7
- import { generateKeyPair } from './crypto.js';
8
- export class AAMP {
9
- constructor(config) {
10
- this.publisher = new AAMPPublisher({ version: '1.1', ...config.policy }, config.strategy || 'PASSIVE', config.cache);
11
- this.origin = ContentOrigin[config.meta.origin];
12
- this.ready = generateKeyPair().then(keys => {
13
- return this.publisher.initialize(keys);
14
- });
15
- }
16
- static init(config) {
17
- return new AAMP(config);
18
- }
19
- /**
20
- * Express Middleware
21
- */
22
- middleware() {
23
- return async (req, res, next) => {
24
- await this.ready;
25
- // Normalize headers to lowercase dictionary
26
- const headers = {};
27
- Object.keys(req.headers).forEach(key => {
28
- headers[key.toLowerCase()] = req.headers[key];
29
- });
30
- // Retrieve Raw Payload if available (optional but good for crypto)
31
- // Note: Express body parsing might interfere, so we usually rely on the header content.
32
- const rawPayload = headers['x-aamp-payload'];
33
- // Evaluate Visitor
34
- const result = await this.publisher.evaluateVisitor(headers, rawPayload);
35
- // Enforce Decision
36
- if (!result.allowed) {
37
- res.status(result.status).json({
38
- error: result.reason,
39
- visitor_type: result.visitorType,
40
- proof_used: result.proofUsed
41
- });
42
- return;
43
- }
44
- // Inject Provenance Headers (For the humans/agents that got through)
45
- const respHeaders = await this.publisher.generateResponseHeaders(this.origin);
46
- Object.entries(respHeaders).forEach(([k, v]) => {
47
- res.setHeader(k, v);
48
- });
49
- // Attach metadata to request for downstream use
50
- req.aamp = {
51
- verified: result.visitorType === 'VERIFIED_AGENT',
52
- type: result.visitorType,
53
- ...result.metadata
54
- };
55
- next();
56
- };
57
- }
58
- discoveryHandler() {
59
- return (req, res) => {
60
- res.setHeader('Content-Type', 'application/json');
61
- res.send(JSON.stringify(this.publisher.getPolicy(), null, 2));
62
- };
63
- }
64
- }
package/dist/index.d.ts DELETED
@@ -1,12 +0,0 @@
1
- /**
2
- * AAMP SDK Public API
3
- *
4
- * This is the main entry point for the library.
5
- */
6
- export * from './types.js';
7
- export * from './constants.js';
8
- export * from './agent.js';
9
- export * from './publisher.js';
10
- export * from './crypto.js';
11
- export * from './express.js';
12
- export { AAMPNext } from './nextjs.js';
package/dist/index.js DELETED
@@ -1,12 +0,0 @@
1
- /**
2
- * AAMP SDK Public API
3
- *
4
- * This is the main entry point for the library.
5
- */
6
- export * from './types.js';
7
- export * from './constants.js';
8
- export * from './agent.js';
9
- export * from './publisher.js';
10
- export * from './crypto.js';
11
- export * from './express.js'; // Node.js / Express Adapter
12
- export { AAMPNext } from './nextjs.js'; // Serverless / Next.js Adapter
package/dist/nextjs.d.ts DELETED
@@ -1,25 +0,0 @@
1
- import { AccessPolicy, ContentOrigin, UnauthenticatedStrategy, IdentityCache } from './types.js';
2
- type NextRequest = any;
3
- type NextResponse = any;
4
- export interface AAMPConfig {
5
- policy: Omit<AccessPolicy, 'version'>;
6
- meta: {
7
- origin: keyof typeof ContentOrigin;
8
- paymentPointer?: string;
9
- };
10
- strategy?: UnauthenticatedStrategy;
11
- cache?: IdentityCache;
12
- }
13
- export declare class AAMPNext {
14
- private publisher;
15
- private origin;
16
- private ready;
17
- private constructor();
18
- static init(config: AAMPConfig): AAMPNext;
19
- /**
20
- * Serverless Route Wrapper
21
- */
22
- withProtection(handler: (req: NextRequest) => Promise<NextResponse>): (req: NextRequest) => Promise<any>;
23
- discoveryHandler(): () => Promise<Response>;
24
- }
25
- export {};
package/dist/nextjs.js DELETED
@@ -1,60 +0,0 @@
1
- /**
2
- * Layer 3: Framework Adapters
3
- * Serverless integration for Next.js (App Router & API Routes).
4
- */
5
- import { AAMPPublisher } from './publisher.js';
6
- import { ContentOrigin } from './types.js';
7
- import { generateKeyPair } from './crypto.js';
8
- const createJsonResponse = (body, status = 200) => {
9
- return new Response(JSON.stringify(body), {
10
- status,
11
- headers: { 'Content-Type': 'application/json' }
12
- });
13
- };
14
- export class AAMPNext {
15
- constructor(config) {
16
- this.publisher = new AAMPPublisher({ version: '1.1', ...config.policy }, config.strategy || 'PASSIVE', config.cache);
17
- this.origin = ContentOrigin[config.meta.origin];
18
- this.ready = generateKeyPair().then(keys => this.publisher.initialize(keys));
19
- }
20
- static init(config) {
21
- return new AAMPNext(config);
22
- }
23
- /**
24
- * Serverless Route Wrapper
25
- */
26
- withProtection(handler) {
27
- return async (req) => {
28
- await this.ready;
29
- // Extract Headers map
30
- const headers = {};
31
- req.headers.forEach((value, key) => {
32
- headers[key.toLowerCase()] = value;
33
- });
34
- // Evaluate
35
- const result = await this.publisher.evaluateVisitor(headers, headers['x-aamp-payload']);
36
- if (!result.allowed) {
37
- return createJsonResponse({
38
- error: result.reason,
39
- visitor_type: result.visitorType,
40
- proof_used: result.proofUsed
41
- }, result.status);
42
- }
43
- // Execute Handler
44
- const response = await handler(req);
45
- // Inject Provenance
46
- const aampHeaders = await this.publisher.generateResponseHeaders(this.origin);
47
- if (response && response.headers) {
48
- Object.entries(aampHeaders).forEach(([k, v]) => {
49
- response.headers.set(k, v);
50
- });
51
- }
52
- return response;
53
- };
54
- }
55
- discoveryHandler() {
56
- return async () => {
57
- return createJsonResponse(this.publisher.getPolicy());
58
- };
59
- }
60
- }
package/dist/proof.d.ts DELETED
@@ -1,9 +0,0 @@
1
- /**
2
- * Verifies a JWT (Proof Token or Payment Credential) using JWKS.
3
- *
4
- * @param token The JWT string
5
- * @param jwksUrl The URL to fetch Public Keys
6
- * @param issuer The expected issuer
7
- * @param audience The expected audience range
8
- */
9
- export declare function verifyJwt(token: string, jwksUrl: string, issuer: string, audience?: string): Promise<boolean>;
package/dist/proof.js DELETED
@@ -1,27 +0,0 @@
1
- import { createRemoteJWKSet, jwtVerify } from 'jose';
2
- // In-memory cache for JWKS to avoid repeated fetches
3
- // Jose's createRemoteJWKSet handles caching/cooldowns internally.
4
- /**
5
- * Verifies a JWT (Proof Token or Payment Credential) using JWKS.
6
- *
7
- * @param token The JWT string
8
- * @param jwksUrl The URL to fetch Public Keys
9
- * @param issuer The expected issuer
10
- * @param audience The expected audience range
11
- */
12
- export async function verifyJwt(token, jwksUrl, issuer, audience) {
13
- try {
14
- const JWKS = createRemoteJWKSet(new URL(jwksUrl));
15
- const { payload } = await jwtVerify(token, JWKS, {
16
- issuer: issuer,
17
- audience: audience // specific audience check if provided
18
- });
19
- // Check specific AAMP claims if we standardize them
20
- // if (payload.type !== 'AD_IMPRESSION') return false;
21
- return true;
22
- }
23
- catch (error) {
24
- // console.error("Ad Proof Verification Failed:", error);
25
- return false;
26
- }
27
- }
@@ -1,43 +0,0 @@
1
- import { AccessPolicy, ContentOrigin, EvaluationResult, IdentityCache, UnauthenticatedStrategy } from './types.js';
2
- export declare class AAMPPublisher {
3
- private policy;
4
- private keyPair;
5
- private unauthenticatedStrategy;
6
- private cache;
7
- private readonly CACHE_TTL_SECONDS;
8
- constructor(policy: AccessPolicy, strategy?: UnauthenticatedStrategy, cacheImpl?: IdentityCache);
9
- initialize(keyPair: CryptoKeyPair): Promise<void>;
10
- getPolicy(): AccessPolicy;
11
- /**
12
- * Main Entry Point: Evaluate ANY visitor (Human, Bot, or Agent)
13
- * STAGE 1: IDENTITY (Strict)
14
- * STAGE 2: POLICY (Permissions)
15
- * STAGE 3: ACCESS (HQ Content)
16
- */
17
- evaluateVisitor(reqHeaders: Record<string, string | undefined>, rawPayload?: string): Promise<EvaluationResult>;
18
- /**
19
- * Browser Heuristics (Hardened)
20
- * 1. Checks Known Bot Signatures (Fast Fail)
21
- * 2. Checks Trusted Upstream Signals (Cloudflare/Vercel)
22
- * 3. Checks Browser Header Consistency
23
- */
24
- private performBrowserHeuristics;
25
- /**
26
- * Handle AAMP Protocol Logic (Strict Mode)
27
- */
28
- private handleAgentStrict;
29
- private handleAgent;
30
- /**
31
- * STAGE 2: POLICY ENFORCEMENT CHECK
32
- */
33
- private checkPolicyStrict;
34
- private verifyRequestLogic;
35
- private verifyDnsBinding;
36
- private isDomain;
37
- generateResponseHeaders(origin: ContentOrigin): Promise<Record<string, string>>;
38
- /**
39
- * Handling Quality Feedback (The "Dispute" Layer)
40
- * This runs when an Agent sends 'x-aamp-feedback'.
41
- */
42
- private handleFeedback;
43
- }
package/dist/publisher.js DELETED
@@ -1,350 +0,0 @@
1
- /**
2
- * Layer 2: Publisher Middleware
3
- * Used by content owners to enforce policy, log access, and filter bots.
4
- */
5
- import { HEADERS, MAX_CLOCK_SKEW_MS, WELL_KNOWN_AGENT_PATH } from './constants.js';
6
- import { exportPublicKey, signData, verifySignature } from './crypto.js';
7
- import { AccessPurpose } from './types.js';
8
- import { verifyJwt } from './proof.js';
9
- /**
10
- * Default In-Memory Cache (Fallback only)
11
- * NOT recommended for high-traffic Serverless production.
12
- */
13
- class MemoryCache {
14
- constructor() {
15
- this.store = new Map();
16
- }
17
- async get(key) {
18
- const item = this.store.get(key);
19
- if (!item)
20
- return null;
21
- if (Date.now() > item.exp) {
22
- this.store.delete(key);
23
- return null;
24
- }
25
- return item.val;
26
- }
27
- async set(key, value, ttlSeconds) {
28
- this.store.set(key, {
29
- val: value,
30
- exp: Date.now() + (ttlSeconds * 1000)
31
- });
32
- }
33
- }
34
- export class AAMPPublisher {
35
- constructor(policy, strategy = 'PASSIVE', cacheImpl) {
36
- this.keyPair = null;
37
- // Default TTL: 1 Hour
38
- this.CACHE_TTL_SECONDS = 3600;
39
- this.policy = policy;
40
- this.unauthenticatedStrategy = strategy;
41
- this.cache = cacheImpl || new MemoryCache();
42
- }
43
- async initialize(keyPair) {
44
- this.keyPair = keyPair;
45
- }
46
- getPolicy() {
47
- return this.policy;
48
- }
49
- /**
50
- * Main Entry Point: Evaluate ANY visitor (Human, Bot, or Agent)
51
- * STAGE 1: IDENTITY (Strict)
52
- * STAGE 2: POLICY (Permissions)
53
- * STAGE 3: ACCESS (HQ Content)
54
- */
55
- async evaluateVisitor(reqHeaders, rawPayload) {
56
- console.log(`\n--- [AAMP LOG START] New Request ---`);
57
- // --- STAGE 1: IDENTITY VERIFICATION ---
58
- console.log(`[IDENTITY] šŸ” Checking Identity Headers...`);
59
- const hasAamp = reqHeaders[HEADERS.PAYLOAD] && reqHeaders[HEADERS.SIGNATURE] && reqHeaders[HEADERS.PUBLIC_KEY];
60
- if (hasAamp) {
61
- // It claims to be an Agent. Verify it STRICTLY.
62
- return await this.handleAgentStrict(reqHeaders, rawPayload);
63
- }
64
- // If NO AAMP Headers -> FAIL IDENTITY immediately.
65
- console.log(`[IDENTITY] āŒ FAILED. No AAMP Headers found.`);
66
- // For now, retaining the legacy "Passive/Hybrid" switch just to avoid breaking browser demos completely
67
- // BUT logging it as a specific "Identity Fail" flow.
68
- if (this.unauthenticatedStrategy === 'STRICT') {
69
- console.log(`[IDENTITY] ā›” BLOCKING. Strategy is STRICT.`);
70
- return {
71
- allowed: false,
72
- status: 401,
73
- reason: "IDENTITY_REQUIRED: Missing AAMP Headers.",
74
- visitorType: 'UNIDENTIFIED_BOT'
75
- };
76
- }
77
- console.log(`[IDENTITY] āš ļø SKIPPED (Legacy Mode). Checking Browser Heuristics...`);
78
- const isHuman = this.performBrowserHeuristics(reqHeaders);
79
- if (isHuman) {
80
- console.log(`[POLICY] šŸ‘¤ ALLOWED. Browser Heuristics Passed.`);
81
- return { allowed: true, status: 200, reason: "BROWSER_VERIFIED", visitorType: 'LIKELY_HUMAN' };
82
- }
83
- console.log(`[IDENTITY] āŒ FAILED. Not a Browser, No Headers.`);
84
- console.log(`[ACCESS] ā›” BLOCKED.`);
85
- return {
86
- allowed: false,
87
- status: 403,
88
- reason: "IDENTITY_FAIL: No Identity, No Browser.",
89
- visitorType: 'UNIDENTIFIED_BOT'
90
- };
91
- }
92
- /**
93
- * Browser Heuristics (Hardened)
94
- * 1. Checks Known Bot Signatures (Fast Fail)
95
- * 2. Checks Trusted Upstream Signals (Cloudflare/Vercel)
96
- * 3. Checks Browser Header Consistency
97
- */
98
- performBrowserHeuristics(headers) {
99
- const userAgent = headers['user-agent'] || '';
100
- // A. The "Obvious Bot" Blocklist (Fast Fail)
101
- const botSignatures = ['python-requests', 'curl', 'wget', 'scrapy', 'bot', 'crawler', 'spider'];
102
- if (botSignatures.some(sig => userAgent.toLowerCase().includes(sig))) {
103
- return false;
104
- }
105
- // B. Trusted Infrastructure Signals (The Real World Solution)
106
- if (headers['cf-visitor'] || headers['cf-ray'])
107
- return true;
108
- if (headers['x-vercel-id'])
109
- return true;
110
- if (headers['cloudfront-viewer-address'])
111
- return true;
112
- // C. The "Browser Fingerprint" (Fallback for direct connections)
113
- const hasAcceptLanguage = !!headers['accept-language'];
114
- const hasSecFetchDest = !!headers['sec-fetch-dest'];
115
- const hasUpgradeInsecure = !!headers['upgrade-insecure-requests'];
116
- if (hasAcceptLanguage && (hasSecFetchDest || hasUpgradeInsecure)) {
117
- return true;
118
- }
119
- return false;
120
- }
121
- /**
122
- * Handle AAMP Protocol Logic (Strict Mode)
123
- */
124
- async handleAgentStrict(reqHeaders, rawPayload) {
125
- let agentId = "UNKNOWN";
126
- try {
127
- // 1. Decode Headers
128
- const payloadHeader = reqHeaders[HEADERS.PAYLOAD];
129
- const sigHeader = reqHeaders[HEADERS.SIGNATURE];
130
- const keyHeader = reqHeaders[HEADERS.PUBLIC_KEY];
131
- const headerJson = atob(payloadHeader);
132
- const requestHeader = JSON.parse(headerJson);
133
- agentId = requestHeader.agent_id;
134
- console.log(`[IDENTITY] šŸ†” Claimed ID: ${agentId}`);
135
- // 2. Crypto & DNS Verification
136
- const signedRequest = {
137
- header: requestHeader,
138
- signature: sigHeader,
139
- publicKey: keyHeader
140
- };
141
- const agentKey = await crypto.subtle.importKey("spki", new Uint8Array(atob(keyHeader).split('').map(c => c.charCodeAt(0))), { name: "ECDSA", namedCurve: "P-256" }, true, ["verify"]);
142
- // Verify Core Logic (DNS + Crypto)
143
- const verification = await this.verifyRequestLogic(signedRequest, agentKey);
144
- if (!verification.identityVerified) {
145
- console.log(`[IDENTITY] āŒ FAILED. Reason: ${verification.reason}`);
146
- console.log(`[ACCESS] ā›” BLOCKED.`);
147
- return { allowed: false, status: 403, reason: verification.reason, visitorType: 'UNIDENTIFIED_BOT' };
148
- }
149
- console.log(`[IDENTITY] āœ… PASSED. DNS Binding Verified.`);
150
- // --- STAGE 2: POLICY ENFORCEMENT ---
151
- console.log(`[POLICY] šŸ“œ Checking Permissions for ${agentId}...`);
152
- const proofToken = reqHeaders[HEADERS.PROOF_TOKEN];
153
- const paymentCredential = reqHeaders[HEADERS.PAYMENT_CREDENTIAL];
154
- const policyResult = await this.checkPolicyStrict(requestHeader, proofToken, paymentCredential);
155
- if (!policyResult.allowed) {
156
- console.log(`[POLICY] ā›” DENIED. Reason: ${policyResult.reason}`);
157
- console.log(`[ACCESS] ā›” BLOCKED.`);
158
- return policyResult;
159
- }
160
- // --- STAGE 3: ACCESS GRANT ---
161
- console.log(`[POLICY] āœ… PASSED. Requirements Met.`);
162
- console.log(`[ACCESS] šŸ”“ GRANTED. Unlocking HQ Content.`);
163
- return {
164
- allowed: true,
165
- status: 200,
166
- reason: "AAMP_VERIFIED",
167
- visitorType: 'VERIFIED_AGENT',
168
- metadata: requestHeader,
169
- proofUsed: policyResult.proofUsed
170
- };
171
- }
172
- catch (e) {
173
- console.error(`[AAMP ERROR]`, e);
174
- return { allowed: false, status: 400, reason: "INVALID_SIGNATURE", visitorType: 'UNIDENTIFIED_BOT' };
175
- }
176
- }
177
- // Legacy handler kept for interface compatibility (deprecated)
178
- async handleAgent(reqHeaders, rawPayload) {
179
- return this.handleAgentStrict(reqHeaders, rawPayload);
180
- }
181
- /**
182
- * STAGE 2: POLICY ENFORCEMENT CHECK
183
- */
184
- async checkPolicyStrict(requestHeader, proofToken, paymentCredential) {
185
- // 1. Policy Check: Purpose Ban (e.g. No Training)
186
- if (requestHeader.purpose === AccessPurpose.CRAWL_TRAINING && !this.policy.allowTraining) {
187
- return { allowed: false, status: 403, reason: 'POLICY_DENIED: Training not allowed.', visitorType: 'VERIFIED_AGENT' };
188
- }
189
- if (requestHeader.purpose === AccessPurpose.RAG_RETRIEVAL && !this.policy.allowRAG) {
190
- return { allowed: false, status: 403, reason: 'POLICY_DENIED: RAG not allowed.', visitorType: 'VERIFIED_AGENT' };
191
- }
192
- // 2. Policy Check: Economics (v1.2) - Payment & Ads
193
- if (this.policy.requiresPayment) {
194
- let paymentSatisfied = false;
195
- // Method A: Flexible Payment Callback (DB / Custom Logic)
196
- if (this.policy.monetization?.checkPayment) {
197
- const isPaid = await this.policy.monetization.checkPayment(requestHeader.agent_id, requestHeader.purpose);
198
- if (isPaid) {
199
- console.log(`[POLICY] šŸ’° Payment Verified via Callback.`);
200
- return { allowed: true, status: 200, reason: 'OK', visitorType: 'VERIFIED_AGENT', proofUsed: 'WHITELIST_CALLBACK' };
201
- }
202
- }
203
- // Method B: Payment Credentials (Unified JWT)
204
- if (!paymentSatisfied && this.policy.monetization?.paymentConfig && paymentCredential) {
205
- const { jwksUrl, issuer } = this.policy.monetization.paymentConfig;
206
- console.log(`[POLICY] šŸ” Verifying Payment Credential (Issuer: ${issuer})...`);
207
- const isValidCredential = await verifyJwt(paymentCredential, jwksUrl, issuer);
208
- if (isValidCredential) {
209
- console.log(`[POLICY] āœ… Credential Signature VALID.`);
210
- return { allowed: true, status: 200, reason: 'OK', visitorType: 'VERIFIED_AGENT', proofUsed: 'PAYMENT_CREDENTIAL_JWT' };
211
- }
212
- else {
213
- console.log(`[POLICY] āŒ Credential Signature INVALID.`);
214
- }
215
- }
216
- // Method C: Ad-Supported (Proof Verification)
217
- if (!paymentSatisfied && this.policy.allowAdSupportedAccess && requestHeader.context?.ads_displayed) {
218
- if (proofToken && this.policy.monetization?.adNetwork) {
219
- const { jwksUrl, issuer } = this.policy.monetization.adNetwork;
220
- console.log(`[POLICY] šŸ“ŗ Verifying Ad Proof (Issuer: ${issuer})...`);
221
- const isValidProof = await verifyJwt(proofToken, jwksUrl, issuer);
222
- if (isValidProof) {
223
- console.log(`[POLICY] āœ… Ad Proof Signature VALID.`);
224
- return { allowed: true, status: 200, reason: 'OK', visitorType: 'VERIFIED_AGENT', proofUsed: 'AD_PROOF_JWT' };
225
- }
226
- else {
227
- console.log(`[POLICY] āŒ Ad Proof Signature INVALID.`);
228
- }
229
- }
230
- else {
231
- console.log(`[POLICY] āš ļø Ad Proof MISSING.`);
232
- }
233
- }
234
- return {
235
- allowed: false,
236
- status: 402,
237
- reason: 'PAYMENT_REQUIRED: Whitelist, Credential, and Ad Proof checks ALL failed.',
238
- visitorType: 'VERIFIED_AGENT',
239
- proofUsed: 'NONE'
240
- };
241
- }
242
- // If no payment required, allow.
243
- return { allowed: true, status: 200, reason: 'OK', visitorType: 'VERIFIED_AGENT' };
244
- }
245
- async verifyRequestLogic(request, requestPublicKey) {
246
- // 1. Replay Attack Prevention
247
- const requestTime = new Date(request.header.ts).getTime();
248
- if (Math.abs(Date.now() - requestTime) > MAX_CLOCK_SKEW_MS) {
249
- return { allowed: false, reason: 'TIMESTAMP_INVALID: Clock skew too large.', identityVerified: false };
250
- }
251
- // 2. Crypto Verification
252
- const signableString = JSON.stringify(request.header);
253
- const isCryptoValid = await verifySignature(requestPublicKey, signableString, request.signature);
254
- if (!isCryptoValid)
255
- return { allowed: false, reason: 'CRYPTO_FAIL: Signature invalid.', identityVerified: false };
256
- // 3. Identity Verification (DNS Binding) with Cache
257
- let identityVerified = false;
258
- const claimedDomain = request.header.agent_id;
259
- const pubKeyString = await exportPublicKey(requestPublicKey);
260
- console.log(`[IDENTITY] šŸ” Verifying DNS Binding for: ${claimedDomain}`);
261
- // Check Cache First
262
- const cachedKey = await this.cache.get(claimedDomain);
263
- if (cachedKey === pubKeyString) {
264
- console.log("[IDENTITY] ⚔ Cache Hit. Identity Verified.");
265
- identityVerified = true;
266
- }
267
- else if (this.isDomain(claimedDomain)) {
268
- // Cache Miss: Perform DNS Fetch
269
- identityVerified = await this.verifyDnsBinding(claimedDomain, pubKeyString);
270
- if (identityVerified) {
271
- await this.cache.set(claimedDomain, pubKeyString, this.CACHE_TTL_SECONDS);
272
- }
273
- }
274
- if (this.policy.requireIdentityBinding && !identityVerified) {
275
- return { allowed: false, reason: 'IDENTITY_FAIL: DNS Binding could not be verified.', identityVerified: false };
276
- }
277
- // Return verified status so handleAgentStrict can proceed to Policy Check
278
- return { allowed: true, reason: 'OK', identityVerified: true };
279
- }
280
- async verifyDnsBinding(domain, requestKeySpki) {
281
- try {
282
- // Allow HTTP for localhost testing
283
- const protocol = (domain.includes('localhost') || domain.match(/:\d+$/)) ? 'http' : 'https';
284
- const url = `${protocol}://${domain}${WELL_KNOWN_AGENT_PATH}`;
285
- console.log(` šŸŒ [AAMP DNS] Fetching Manifest: ${url} ...`);
286
- // In production, we need a short timeout to prevent hanging
287
- const controller = new AbortController();
288
- const timeoutId = setTimeout(() => controller.abort(), 1500); // 1.5s max for DNS check
289
- const response = await fetch(url, { signal: controller.signal });
290
- clearTimeout(timeoutId);
291
- if (!response.ok) {
292
- console.log(` āŒ [AAMP DNS] Fetch Failed: ${response.status}`);
293
- return false;
294
- }
295
- const manifest = await response.json();
296
- console.log(` šŸ“„ [AAMP DNS] Manifest received. Agent ID: ${manifest.agent_id}`);
297
- // CHECK 1: Does the manifest actually belong to the domain?
298
- if (manifest.agent_id !== domain) {
299
- console.log(` āŒ [AAMP DNS] Mismatch: Manifest ID ${manifest.agent_id} != Claimed ${domain}`);
300
- return false;
301
- }
302
- // CHECK 2: Does the key match?
303
- if (manifest.public_key !== requestKeySpki) {
304
- console.log(` āŒ [AAMP DNS] Key Mismatch: DNS Key != Request Key`);
305
- return false;
306
- }
307
- console.log(` āœ… [AAMP DNS] Identity Confirmed.`);
308
- return true;
309
- }
310
- catch (e) {
311
- console.log(` āŒ [AAMP DNS] Error: ${e.message}`);
312
- return false;
313
- }
314
- }
315
- isDomain(s) {
316
- // Basic regex, allows localhost with ports
317
- return /^[a-zA-Z0-9.-]+(:\d+)?$/.test(s) || /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(s);
318
- }
319
- async generateResponseHeaders(origin) {
320
- if (!this.keyPair)
321
- throw new Error("Publisher keys not initialized");
322
- const payload = JSON.stringify({ origin, ts: Date.now() });
323
- const signature = await signData(this.keyPair.privateKey, payload);
324
- return {
325
- [HEADERS.CONTENT_ORIGIN]: origin,
326
- [HEADERS.PROVENANCE_SIG]: signature
327
- };
328
- }
329
- /**
330
- * Handling Quality Feedback (The "Dispute" Layer)
331
- * This runs when an Agent sends 'x-aamp-feedback'.
332
- */
333
- async handleFeedback(token, headers) {
334
- // NOTE: In production, you would fetch the Agent's specific key.
335
- // For now, we assume standard Discovery or a centralized Key Set (like adNetwork).
336
- // Ideally, the SDK config should have a 'qualityOracle' key set.
337
- // 1. We just Decode it to Log it (Verification is optional but recommended)
338
- try {
339
- const parts = token.split('.');
340
- const payload = JSON.parse(atob(parts[1]));
341
- console.log(`\nšŸ“¢ [AAMP QUALITY ALERT] Feedback Received from ${payload.agent_id}`);
342
- console.log(` Reason: ${payload.reason} | Score: ${payload.quality_score}`);
343
- console.log(` Resource: ${payload.url}`);
344
- console.log(` (Signature available for dispute evidence)`);
345
- }
346
- catch (e) {
347
- console.log(` āš ļø [AAMP Warning] Malformed Feedback Token.`);
348
- }
349
- }
350
- }
package/dist/types.d.ts DELETED
@@ -1,113 +0,0 @@
1
- /**
2
- * Layer 1: Protocol Definitions
3
- * Shared types used by both Agent and Publisher.
4
- */
5
- export declare enum AccessPurpose {
6
- CRAWL_TRAINING = "CRAWL_TRAINING",
7
- RAG_RETRIEVAL = "RAG_RETRIEVAL",
8
- SUMMARY = "SUMMARY",
9
- QUOTATION = "QUOTATION",
10
- EMBEDDING = "EMBEDDING"
11
- }
12
- export declare enum ContentOrigin {
13
- HUMAN = "HUMAN",// Created by humans. High training value.
14
- SYNTHETIC = "SYNTHETIC",// Created by AI. Risk of model collapse.
15
- HYBRID = "HYBRID"
16
- }
17
- export declare enum QualityFlag {
18
- SEO_SPAM = "SEO_SPAM",
19
- INACCURATE = "INACCURATE",
20
- HATE_SPEECH = "HATE_SPEECH",
21
- HIGH_QUALITY = "HIGH_QUALITY"
22
- }
23
- /**
24
- * DNS Identity Manifest
25
- * Hosted at: https://{agent_id}/.well-known/aamp-agent.json
26
- */
27
- export interface AgentIdentityManifest {
28
- agent_id: string;
29
- public_key: string;
30
- contact_email?: string;
31
- }
32
- /**
33
- * PRODUCTION INFRASTRUCTURE: Cache Interface
34
- * Required for Serverless/Edge environments to prevent repeated DNS fetches.
35
- */
36
- export interface IdentityCache {
37
- get(key: string): Promise<string | null>;
38
- set(key: string, value: string, ttlSeconds: number): Promise<void>;
39
- }
40
- /**
41
- * Optional Monetization (The Settlement Layer)
42
- */
43
- /**
44
- * Optional Monetization (The Settlement Layer)
45
- */
46
- export interface MonetizationConfig {
47
- checkPayment?: (agentId: string, purpose: string) => boolean | Promise<boolean>;
48
- adNetwork?: {
49
- jwksUrl: string;
50
- issuer: string;
51
- };
52
- paymentConfig?: {
53
- jwksUrl: string;
54
- issuer: string;
55
- };
56
- }
57
- /**
58
- * Handling Non-AAMP Visitors
59
- *
60
- * PASSIVE: Allow everyone (Legacy web behavior).
61
- * HYBRID: Allow verified Agents AND likely Humans (Browser Heuristics). Block bots.
62
- * STRICT: Allow ONLY verified AAMP Agents. (API Mode).
63
- */
64
- export type UnauthenticatedStrategy = 'PASSIVE' | 'HYBRID' | 'STRICT';
65
- export interface AccessPolicy {
66
- version: '1.1';
67
- allowTraining: boolean;
68
- allowRAG: boolean;
69
- attributionRequired: boolean;
70
- allowAdSupportedAccess: boolean;
71
- requiresPayment: boolean;
72
- paymentPointer?: string;
73
- requireIdentityBinding?: boolean;
74
- monetization?: MonetizationConfig;
75
- }
76
- export interface ProtocolHeader {
77
- v: '1.1';
78
- ts: string;
79
- agent_id: string;
80
- resource: string;
81
- purpose: AccessPurpose;
82
- context: {
83
- ads_displayed: boolean;
84
- };
85
- }
86
- export interface SignedAccessRequest {
87
- header: ProtocolHeader;
88
- signature: string;
89
- publicKey?: string;
90
- }
91
- export interface FeedbackSignal {
92
- target_resource: string;
93
- agent_id: string;
94
- quality_score: number;
95
- flags: QualityFlag[];
96
- timestamp: string;
97
- }
98
- export interface EvaluationResult {
99
- allowed: boolean;
100
- status: 200 | 400 | 401 | 402 | 403;
101
- reason: string;
102
- visitorType: 'VERIFIED_AGENT' | 'LIKELY_HUMAN' | 'UNIDENTIFIED_BOT';
103
- metadata?: any;
104
- payment_status?: 'PAID_SUBSCRIBER' | 'AD_FUNDED' | 'UNPAID';
105
- proofUsed?: string;
106
- }
107
- export interface FeedbackSignalToken {
108
- url: string;
109
- agent_id: string;
110
- quality_score: number;
111
- reason: string;
112
- timestamp: number;
113
- }
package/dist/types.js DELETED
@@ -1,25 +0,0 @@
1
- /**
2
- * Layer 1: Protocol Definitions
3
- * Shared types used by both Agent and Publisher.
4
- */
5
- export var AccessPurpose;
6
- (function (AccessPurpose) {
7
- AccessPurpose["CRAWL_TRAINING"] = "CRAWL_TRAINING";
8
- AccessPurpose["RAG_RETRIEVAL"] = "RAG_RETRIEVAL";
9
- AccessPurpose["SUMMARY"] = "SUMMARY";
10
- AccessPurpose["QUOTATION"] = "QUOTATION";
11
- AccessPurpose["EMBEDDING"] = "EMBEDDING";
12
- })(AccessPurpose || (AccessPurpose = {}));
13
- export var ContentOrigin;
14
- (function (ContentOrigin) {
15
- ContentOrigin["HUMAN"] = "HUMAN";
16
- ContentOrigin["SYNTHETIC"] = "SYNTHETIC";
17
- ContentOrigin["HYBRID"] = "HYBRID"; // Edited by humans, drafted by AI.
18
- })(ContentOrigin || (ContentOrigin = {}));
19
- export var QualityFlag;
20
- (function (QualityFlag) {
21
- QualityFlag["SEO_SPAM"] = "SEO_SPAM";
22
- QualityFlag["INACCURATE"] = "INACCURATE";
23
- QualityFlag["HATE_SPEECH"] = "HATE_SPEECH";
24
- QualityFlag["HIGH_QUALITY"] = "HIGH_QUALITY";
25
- })(QualityFlag || (QualityFlag = {}));