@agent-id/express 0.1.0 → 0.1.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @agent-id/express
2
2
 
3
- Express.js middleware that automatically detects AI-agent traffic and requires a valid [AgentID](https://agentpass.vercel.app) JWT. Human browser traffic always passes through untouched.
3
+ Express.js middleware that automatically detects AI-agent traffic and requires a valid [AgentID](https://agentidapp.vercel.app) JWT. Human browser traffic always passes through untouched.
4
4
 
5
5
  ## Install
6
6
 
@@ -58,7 +58,7 @@ Agents add one header to every request:
58
58
  Authorization: Bearer <agentid-jwt>
59
59
  ```
60
60
 
61
- The JWT is obtained by completing a BankID flow at [agentpass.vercel.app](https://agentpass.vercel.app). Tokens are valid for 1 hour.
61
+ The JWT is obtained by completing a BankID flow at [agentidapp.vercel.app](https://agentidapp.vercel.app). Tokens are valid for 1 hour.
62
62
 
63
63
  ## Options
64
64
 
package/dist/index.d.mts CHANGED
@@ -38,7 +38,7 @@ interface VerifierOptions {
38
38
  * URL of the AgentID JWKS endpoint.
39
39
  * Must use HTTPS (except `localhost` / `127.0.0.1` in development).
40
40
  *
41
- * @default 'https://agentpass.vercel.app/api/jwks'
41
+ * @default 'https://agentidapp.vercel.app/api/jwks'
42
42
  */
43
43
  jwksUrl?: string;
44
44
  /**
package/dist/index.d.ts CHANGED
@@ -38,7 +38,7 @@ interface VerifierOptions {
38
38
  * URL of the AgentID JWKS endpoint.
39
39
  * Must use HTTPS (except `localhost` / `127.0.0.1` in development).
40
40
  *
41
- * @default 'https://agentpass.vercel.app/api/jwks'
41
+ * @default 'https://agentidapp.vercel.app/api/jwks'
42
42
  */
43
43
  jwksUrl?: string;
44
44
  /**
package/dist/index.js CHANGED
@@ -99,7 +99,7 @@ function extractToken(headers) {
99
99
 
100
100
  // src/verify.ts
101
101
  var import_jose = require("jose");
102
- var DEFAULT_JWKS_URL = "https://agentpass.vercel.app/api/jwks";
102
+ var DEFAULT_JWKS_URL = "https://agentidapp.vercel.app/api/jwks";
103
103
  var jwksSets = /* @__PURE__ */ new Map();
104
104
  function getJwks(rawUrl) {
105
105
  if (!jwksSets.has(rawUrl)) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/detect.ts","../src/verify.ts","../src/middleware.ts"],"sourcesContent":["export { agentID } from \"./middleware.js\";\nexport type { AgentIDMiddlewareOptions } from \"./middleware.js\";\n\nexport { verifyAgentIDToken } from \"./verify.js\";\nexport type { VerifyTokenOptions } from \"./verify.js\";\n\nexport { isAgentRequest, extractToken } from \"./detect.js\";\n\nexport type {\n AgentIDClaims,\n AgentIDResult,\n AgentIDVerified,\n AgentIDUnverified,\n VerifierOptions,\n} from \"./types.js\";\n","/**\n * Bot / AI-agent detection for Express.js (Node.js).\n *\n * Mirrors the logic in @agent-id/nextjs but uses `IncomingHttpHeaders`\n * from Node's `http` module instead of the Web `Headers` API.\n */\nimport type { IncomingHttpHeaders } from \"node:http\";\n\nconst BOT_UA_PATTERNS: RegExp[] = [\n // OpenAI\n /GPTBot/i,\n /ChatGPT-User/i,\n /OAI-SearchBot/i,\n // Anthropic / Claude\n /ClaudeBot/i,\n /Claude-Web/i,\n /anthropic-ai/i,\n /Claude-User/i,\n // Google\n /Googlebot/i,\n /Google-Extended/i,\n /AdsBot-Google/i,\n // Microsoft / Bing\n /bingbot/i,\n /msnbot/i,\n // AI search engines\n /PerplexityBot/i,\n /YouBot/i,\n // Common HTTP automation libraries\n /python-requests/i,\n /node-fetch/i,\n /\\baxios\\b/i,\n /\\bgot\\b\\//i,\n /\\bundici\\b/i,\n /\\bcurl\\b/i,\n /\\bwget\\b/i,\n /\\bhttpie\\b/i,\n // Generic crawler signals\n /\\bbot\\b/i,\n /\\bcrawler\\b/i,\n /\\bspider\\b/i,\n /\\bscraper\\b/i,\n /\\bfetcher\\b/i,\n // MCP / AgentID clients\n /mcp-client/i,\n /agentid-client/i,\n];\n\nconst BROWSER_UA_RE = /Mozilla\\/5\\.0/i;\n\n/** Normalise a potentially multi-value header to a single string. */\nfunction single(\n value: string | string[] | undefined\n): string | undefined {\n return Array.isArray(value) ? value[0] : value;\n}\n\n/**\n * Returns `true` if the request appears to originate from an automated\n * agent or bot rather than a human browser.\n *\n * @param headers - `req.headers` from an Express `Request`\n */\nexport function isAgentRequest(headers: IncomingHttpHeaders): boolean {\n const ua = single(headers[\"user-agent\"]) ?? \"\";\n\n if (!ua) return true;\n\n if (BOT_UA_PATTERNS.some((p) => p.test(ua))) return true;\n\n // Cloudflare Bot Management score\n const cfRaw = single(headers[\"cf-bot-management\"]);\n if (cfRaw) {\n try {\n const parsed = JSON.parse(cfRaw) as { score?: unknown };\n if (typeof parsed.score === \"number\" && parsed.score < 30) return true;\n } catch {\n // Malformed header — fail open\n }\n }\n\n // Non-browser UA + no Accept-Language → likely automated\n if (!BROWSER_UA_RE.test(ua) && !headers[\"accept-language\"]) return true;\n\n return false;\n}\n\n/**\n * Extract the AgentID JWT from request headers.\n *\n * Checks:\n * 1. `Authorization: Bearer <token>` (preferred)\n * 2. `X-AgentID-Token` (fallback)\n *\n * @returns Raw JWT string or `null`.\n */\nexport function extractToken(headers: IncomingHttpHeaders): string | null {\n const auth = single(headers[\"authorization\"]) ?? \"\";\n if (auth.startsWith(\"Bearer \")) {\n const token = auth.slice(7).trim();\n if (token) return token;\n }\n\n const custom = single(headers[\"x-agentid-token\"])?.trim();\n if (custom) return custom;\n\n return null;\n}\n","import { createRemoteJWKSet, jwtVerify } from \"jose\";\nimport type { AgentIDClaims } from \"./types.js\";\n\nconst DEFAULT_JWKS_URL = \"https://agentpass.vercel.app/api/jwks\";\n\n/**\n * Module-level JWKS cache — one RemoteJWKSet instance per unique URL.\n * jose caches the fetched key material internally; re-fetches when the\n * cache TTL (1 h) expires or a new `kid` is seen.\n */\nconst jwksSets = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n\nfunction getJwks(rawUrl: string): ReturnType<typeof createRemoteJWKSet> {\n if (!jwksSets.has(rawUrl)) {\n const url = new URL(rawUrl); // throws on malformed URL\n\n // Security: HTTPS is mandatory to prevent MITM on the public-key fetch.\n // Localhost is whitelisted for local development / CI.\n const isLocal =\n url.hostname === \"localhost\" ||\n url.hostname === \"127.0.0.1\" ||\n url.hostname === \"::1\";\n\n if (url.protocol !== \"https:\" && !isLocal) {\n throw new Error(\n `[agent-id] jwksUrl must use HTTPS, received: ${rawUrl}`\n );\n }\n\n jwksSets.set(\n rawUrl,\n createRemoteJWKSet(url, {\n cacheMaxAge: 60 * 60 * 1_000, // 1 hour in ms\n })\n );\n }\n\n return jwksSets.get(rawUrl)!;\n}\n\nexport interface VerifyTokenOptions {\n jwksUrl?: string;\n clockTolerance?: number;\n}\n\n/**\n * Verify an AgentID JWT and return its decoded claims.\n *\n * Security guarantees\n * ───────────────────\n * • Signature — RS256, verified against the live JWKS public key.\n * • Algorithm — strict allowlist `['RS256']`; alg:none and HS256 are\n * rejected before signature verification even begins.\n * • Issuer — must be exactly \"agentid\".\n * • Expiry — enforced; configurable clock tolerance (default 30 s).\n * • kid — jose matches the JWT `kid` header to the JWKS automatically.\n * • auth_method — validated at runtime; must equal \"bankid\".\n *\n * @throws if the token is invalid, expired, or fails any check.\n */\nexport async function verifyAgentIDToken(\n token: string,\n options: VerifyTokenOptions = {}\n): Promise<AgentIDClaims> {\n const jwksUrl = options.jwksUrl ?? DEFAULT_JWKS_URL;\n const JWKS = getJwks(jwksUrl);\n\n const { payload } = await jwtVerify(token, JWKS, {\n issuer: \"agentid\",\n // ↓ Critical — explicit allowlist prevents algorithm-confusion attacks.\n // Any token claiming alg:\"none\", alg:\"HS256\", or anything else is\n // rejected before signature verification.\n algorithms: [\"RS256\"],\n clockTolerance: options.clockTolerance ?? 30,\n });\n\n // Runtime validation of AgentID-specific claims.\n if (payload.auth_method !== \"bankid\") {\n throw new Error(\n `[agent-id] JWT has invalid auth_method: expected \"bankid\", got \"${\n payload.auth_method ?? \"undefined\"\n }\"`\n );\n }\n if (typeof payload.sub !== \"string\" || payload.sub.length === 0) {\n throw new Error(\"[agent-id] JWT is missing the sub claim\");\n }\n\n return payload as unknown as AgentIDClaims;\n}\n","import type { Request, Response, NextFunction } from \"express\";\nimport { isAgentRequest, extractToken } from \"./detect.js\";\nimport { verifyAgentIDToken } from \"./verify.js\";\nimport type { VerifierOptions, AgentIDResult, AgentIDClaims } from \"./types.js\";\n\n// ── TypeScript augmentation ──────────────────────────────────────────────────\n// Adds `req.agentId` to Express's Request interface.\n// Consumers get full type-safety without any extra setup.\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n /** AgentID verification result set by the agentID() middleware. */\n agentId?: AgentIDResult;\n }\n }\n}\n\nexport type AgentIDMiddlewareOptions = VerifierOptions & {\n /**\n * Custom handler invoked when an agent is detected but has no valid token.\n * Call `next()` to pass through, or `res.status(403).json(...)` to block.\n * If omitted, the default behaviour is controlled by `blockUnauthorizedAgents`.\n */\n onUnauthorizedAgent?: (\n req: Request,\n res: Response,\n next: NextFunction,\n reason: string\n ) => void;\n};\n\n/** Headers managed by this middleware — stripped from incoming requests. */\nconst MANAGED_HEADERS = [\n \"x-agentid-verified\",\n \"x-agentid-sub\",\n \"x-agentid-claims\",\n] as const;\n\n/**\n * Express middleware that detects AI-agent traffic and enforces AgentID\n * JWT authentication.\n *\n * Sets `req.agentId` on every request:\n * - `{ verified: false, reason: 'not_agent' }` — human traffic (always passes through)\n * - `{ verified: true, claims }` — agent with valid JWT\n * - `{ verified: false, reason: 'no_token' | 'invalid_token' }` — blocked agent\n * (returns 403 when `blockUnauthorizedAgents: true`)\n *\n * @example\n * ```ts\n * import express from 'express';\n * import { agentID } from '@agent-id/express';\n *\n * const app = express();\n * app.use(agentID({ blockUnauthorizedAgents: true }));\n *\n * app.get('/api/data', (req, res) => {\n * if (req.agentId?.verified) {\n * res.json({ sub: req.agentId.claims.sub });\n * } else {\n * res.status(403).json({ error: 'Unauthorized' });\n * }\n * });\n * ```\n */\nexport function agentID(options: AgentIDMiddlewareOptions = {}) {\n const {\n jwksUrl,\n blockUnauthorizedAgents = true,\n clockTolerance = 30,\n onUnauthorizedAgent,\n } = options;\n\n return async function agentIDMiddleware(\n req: Request,\n res: Response,\n next: NextFunction\n ): Promise<void> {\n // ── Security: strip client-supplied AgentID headers ──────────────────\n // Prevents a malicious client from pre-setting x-agentid-verified: true\n // to fool any downstream code that checks the header directly.\n for (const header of MANAGED_HEADERS) {\n delete req.headers[header];\n }\n\n // ── Non-agent traffic ──────────────────────────────────────────────────\n if (!isAgentRequest(req.headers)) {\n req.agentId = { verified: false, reason: \"not_agent\" };\n return next();\n }\n\n // ── Agent detected — require a valid JWT ───────────────────────────────\n const token = extractToken(req.headers);\n\n if (!token) {\n return handleUnauthorized(\n req,\n res,\n next,\n \"no_token\",\n 'Agent request missing AgentID token. Provide \"Authorization: Bearer <token>\".',\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verify the JWT ─────────────────────────────────────────────────────\n let claims: AgentIDClaims;\n try {\n claims = await verifyAgentIDToken(token, {\n ...(jwksUrl !== undefined && { jwksUrl }),\n clockTolerance,\n });\n } catch (err) {\n console.warn(\n \"[agent-id] Token verification failed:\",\n err instanceof Error ? err.message : String(err)\n );\n return handleUnauthorized(\n req,\n res,\n next,\n \"invalid_token\",\n \"Invalid or expired AgentID token.\",\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verified ───────────────────────────────────────────────────────────\n req.agentId = { verified: true, claims };\n return next();\n };\n}\n\n// ── Internal helper ──────────────────────────────────────────────────────────\n\nfunction handleUnauthorized(\n req: Request,\n res: Response,\n next: NextFunction,\n reason: \"no_token\" | \"invalid_token\",\n message: string,\n block: boolean,\n onUnauthorized: AgentIDMiddlewareOptions[\"onUnauthorizedAgent\"]\n): void {\n req.agentId = { verified: false, reason };\n\n if (onUnauthorized) {\n onUnauthorized(req, res, next, message);\n return;\n }\n\n if (block) {\n res.status(403).json({ error: \"AGENT_UNAUTHORIZED\", message });\n return;\n }\n\n next();\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/detect.ts","../src/verify.ts","../src/middleware.ts"],"sourcesContent":["export { agentID } from \"./middleware.js\";\nexport type { AgentIDMiddlewareOptions } from \"./middleware.js\";\n\nexport { verifyAgentIDToken } from \"./verify.js\";\nexport type { VerifyTokenOptions } from \"./verify.js\";\n\nexport { isAgentRequest, extractToken } from \"./detect.js\";\n\nexport type {\n AgentIDClaims,\n AgentIDResult,\n AgentIDVerified,\n AgentIDUnverified,\n VerifierOptions,\n} from \"./types.js\";\n","/**\n * Bot / AI-agent detection for Express.js (Node.js).\n *\n * Mirrors the logic in @agent-id/nextjs but uses `IncomingHttpHeaders`\n * from Node's `http` module instead of the Web `Headers` API.\n */\nimport type { IncomingHttpHeaders } from \"node:http\";\n\nconst BOT_UA_PATTERNS: RegExp[] = [\n // OpenAI\n /GPTBot/i,\n /ChatGPT-User/i,\n /OAI-SearchBot/i,\n // Anthropic / Claude\n /ClaudeBot/i,\n /Claude-Web/i,\n /anthropic-ai/i,\n /Claude-User/i,\n // Google\n /Googlebot/i,\n /Google-Extended/i,\n /AdsBot-Google/i,\n // Microsoft / Bing\n /bingbot/i,\n /msnbot/i,\n // AI search engines\n /PerplexityBot/i,\n /YouBot/i,\n // Common HTTP automation libraries\n /python-requests/i,\n /node-fetch/i,\n /\\baxios\\b/i,\n /\\bgot\\b\\//i,\n /\\bundici\\b/i,\n /\\bcurl\\b/i,\n /\\bwget\\b/i,\n /\\bhttpie\\b/i,\n // Generic crawler signals\n /\\bbot\\b/i,\n /\\bcrawler\\b/i,\n /\\bspider\\b/i,\n /\\bscraper\\b/i,\n /\\bfetcher\\b/i,\n // MCP / AgentID clients\n /mcp-client/i,\n /agentid-client/i,\n];\n\nconst BROWSER_UA_RE = /Mozilla\\/5\\.0/i;\n\n/** Normalise a potentially multi-value header to a single string. */\nfunction single(\n value: string | string[] | undefined\n): string | undefined {\n return Array.isArray(value) ? value[0] : value;\n}\n\n/**\n * Returns `true` if the request appears to originate from an automated\n * agent or bot rather than a human browser.\n *\n * @param headers - `req.headers` from an Express `Request`\n */\nexport function isAgentRequest(headers: IncomingHttpHeaders): boolean {\n const ua = single(headers[\"user-agent\"]) ?? \"\";\n\n if (!ua) return true;\n\n if (BOT_UA_PATTERNS.some((p) => p.test(ua))) return true;\n\n // Cloudflare Bot Management score\n const cfRaw = single(headers[\"cf-bot-management\"]);\n if (cfRaw) {\n try {\n const parsed = JSON.parse(cfRaw) as { score?: unknown };\n if (typeof parsed.score === \"number\" && parsed.score < 30) return true;\n } catch {\n // Malformed header — fail open\n }\n }\n\n // Non-browser UA + no Accept-Language → likely automated\n if (!BROWSER_UA_RE.test(ua) && !headers[\"accept-language\"]) return true;\n\n return false;\n}\n\n/**\n * Extract the AgentID JWT from request headers.\n *\n * Checks:\n * 1. `Authorization: Bearer <token>` (preferred)\n * 2. `X-AgentID-Token` (fallback)\n *\n * @returns Raw JWT string or `null`.\n */\nexport function extractToken(headers: IncomingHttpHeaders): string | null {\n const auth = single(headers[\"authorization\"]) ?? \"\";\n if (auth.startsWith(\"Bearer \")) {\n const token = auth.slice(7).trim();\n if (token) return token;\n }\n\n const custom = single(headers[\"x-agentid-token\"])?.trim();\n if (custom) return custom;\n\n return null;\n}\n","import { createRemoteJWKSet, jwtVerify } from \"jose\";\nimport type { AgentIDClaims } from \"./types.js\";\n\nconst DEFAULT_JWKS_URL = \"https://agentidapp.vercel.app/api/jwks\";\n\n/**\n * Module-level JWKS cache — one RemoteJWKSet instance per unique URL.\n * jose caches the fetched key material internally; re-fetches when the\n * cache TTL (1 h) expires or a new `kid` is seen.\n */\nconst jwksSets = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n\nfunction getJwks(rawUrl: string): ReturnType<typeof createRemoteJWKSet> {\n if (!jwksSets.has(rawUrl)) {\n const url = new URL(rawUrl); // throws on malformed URL\n\n // Security: HTTPS is mandatory to prevent MITM on the public-key fetch.\n // Localhost is whitelisted for local development / CI.\n const isLocal =\n url.hostname === \"localhost\" ||\n url.hostname === \"127.0.0.1\" ||\n url.hostname === \"::1\";\n\n if (url.protocol !== \"https:\" && !isLocal) {\n throw new Error(\n `[agent-id] jwksUrl must use HTTPS, received: ${rawUrl}`\n );\n }\n\n jwksSets.set(\n rawUrl,\n createRemoteJWKSet(url, {\n cacheMaxAge: 60 * 60 * 1_000, // 1 hour in ms\n })\n );\n }\n\n return jwksSets.get(rawUrl)!;\n}\n\nexport interface VerifyTokenOptions {\n jwksUrl?: string;\n clockTolerance?: number;\n}\n\n/**\n * Verify an AgentID JWT and return its decoded claims.\n *\n * Security guarantees\n * ───────────────────\n * • Signature — RS256, verified against the live JWKS public key.\n * • Algorithm — strict allowlist `['RS256']`; alg:none and HS256 are\n * rejected before signature verification even begins.\n * • Issuer — must be exactly \"agentid\".\n * • Expiry — enforced; configurable clock tolerance (default 30 s).\n * • kid — jose matches the JWT `kid` header to the JWKS automatically.\n * • auth_method — validated at runtime; must equal \"bankid\".\n *\n * @throws if the token is invalid, expired, or fails any check.\n */\nexport async function verifyAgentIDToken(\n token: string,\n options: VerifyTokenOptions = {}\n): Promise<AgentIDClaims> {\n const jwksUrl = options.jwksUrl ?? DEFAULT_JWKS_URL;\n const JWKS = getJwks(jwksUrl);\n\n const { payload } = await jwtVerify(token, JWKS, {\n issuer: \"agentid\",\n // ↓ Critical — explicit allowlist prevents algorithm-confusion attacks.\n // Any token claiming alg:\"none\", alg:\"HS256\", or anything else is\n // rejected before signature verification.\n algorithms: [\"RS256\"],\n clockTolerance: options.clockTolerance ?? 30,\n });\n\n // Runtime validation of AgentID-specific claims.\n if (payload.auth_method !== \"bankid\") {\n throw new Error(\n `[agent-id] JWT has invalid auth_method: expected \"bankid\", got \"${\n payload.auth_method ?? \"undefined\"\n }\"`\n );\n }\n if (typeof payload.sub !== \"string\" || payload.sub.length === 0) {\n throw new Error(\"[agent-id] JWT is missing the sub claim\");\n }\n\n return payload as unknown as AgentIDClaims;\n}\n","import type { Request, Response, NextFunction } from \"express\";\nimport { isAgentRequest, extractToken } from \"./detect.js\";\nimport { verifyAgentIDToken } from \"./verify.js\";\nimport type { VerifierOptions, AgentIDResult, AgentIDClaims } from \"./types.js\";\n\n// ── TypeScript augmentation ──────────────────────────────────────────────────\n// Adds `req.agentId` to Express's Request interface.\n// Consumers get full type-safety without any extra setup.\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n /** AgentID verification result set by the agentID() middleware. */\n agentId?: AgentIDResult;\n }\n }\n}\n\nexport type AgentIDMiddlewareOptions = VerifierOptions & {\n /**\n * Custom handler invoked when an agent is detected but has no valid token.\n * Call `next()` to pass through, or `res.status(403).json(...)` to block.\n * If omitted, the default behaviour is controlled by `blockUnauthorizedAgents`.\n */\n onUnauthorizedAgent?: (\n req: Request,\n res: Response,\n next: NextFunction,\n reason: string\n ) => void;\n};\n\n/** Headers managed by this middleware — stripped from incoming requests. */\nconst MANAGED_HEADERS = [\n \"x-agentid-verified\",\n \"x-agentid-sub\",\n \"x-agentid-claims\",\n] as const;\n\n/**\n * Express middleware that detects AI-agent traffic and enforces AgentID\n * JWT authentication.\n *\n * Sets `req.agentId` on every request:\n * - `{ verified: false, reason: 'not_agent' }` — human traffic (always passes through)\n * - `{ verified: true, claims }` — agent with valid JWT\n * - `{ verified: false, reason: 'no_token' | 'invalid_token' }` — blocked agent\n * (returns 403 when `blockUnauthorizedAgents: true`)\n *\n * @example\n * ```ts\n * import express from 'express';\n * import { agentID } from '@agent-id/express';\n *\n * const app = express();\n * app.use(agentID({ blockUnauthorizedAgents: true }));\n *\n * app.get('/api/data', (req, res) => {\n * if (req.agentId?.verified) {\n * res.json({ sub: req.agentId.claims.sub });\n * } else {\n * res.status(403).json({ error: 'Unauthorized' });\n * }\n * });\n * ```\n */\nexport function agentID(options: AgentIDMiddlewareOptions = {}) {\n const {\n jwksUrl,\n blockUnauthorizedAgents = true,\n clockTolerance = 30,\n onUnauthorizedAgent,\n } = options;\n\n return async function agentIDMiddleware(\n req: Request,\n res: Response,\n next: NextFunction\n ): Promise<void> {\n // ── Security: strip client-supplied AgentID headers ──────────────────\n // Prevents a malicious client from pre-setting x-agentid-verified: true\n // to fool any downstream code that checks the header directly.\n for (const header of MANAGED_HEADERS) {\n delete req.headers[header];\n }\n\n // ── Non-agent traffic ──────────────────────────────────────────────────\n if (!isAgentRequest(req.headers)) {\n req.agentId = { verified: false, reason: \"not_agent\" };\n return next();\n }\n\n // ── Agent detected — require a valid JWT ───────────────────────────────\n const token = extractToken(req.headers);\n\n if (!token) {\n return handleUnauthorized(\n req,\n res,\n next,\n \"no_token\",\n 'Agent request missing AgentID token. Provide \"Authorization: Bearer <token>\".',\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verify the JWT ─────────────────────────────────────────────────────\n let claims: AgentIDClaims;\n try {\n claims = await verifyAgentIDToken(token, {\n ...(jwksUrl !== undefined && { jwksUrl }),\n clockTolerance,\n });\n } catch (err) {\n console.warn(\n \"[agent-id] Token verification failed:\",\n err instanceof Error ? err.message : String(err)\n );\n return handleUnauthorized(\n req,\n res,\n next,\n \"invalid_token\",\n \"Invalid or expired AgentID token.\",\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verified ───────────────────────────────────────────────────────────\n req.agentId = { verified: true, claims };\n return next();\n };\n}\n\n// ── Internal helper ──────────────────────────────────────────────────────────\n\nfunction handleUnauthorized(\n req: Request,\n res: Response,\n next: NextFunction,\n reason: \"no_token\" | \"invalid_token\",\n message: string,\n block: boolean,\n onUnauthorized: AgentIDMiddlewareOptions[\"onUnauthorizedAgent\"]\n): void {\n req.agentId = { verified: false, reason };\n\n if (onUnauthorized) {\n onUnauthorized(req, res, next, message);\n return;\n }\n\n if (block) {\n res.status(403).json({ error: \"AGENT_UNAUTHORIZED\", message });\n return;\n }\n\n next();\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -70,7 +70,7 @@ function extractToken(headers) {
70
70
 
71
71
  // src/verify.ts
72
72
  import { createRemoteJWKSet, jwtVerify } from "jose";
73
- var DEFAULT_JWKS_URL = "https://agentpass.vercel.app/api/jwks";
73
+ var DEFAULT_JWKS_URL = "https://agentidapp.vercel.app/api/jwks";
74
74
  var jwksSets = /* @__PURE__ */ new Map();
75
75
  function getJwks(rawUrl) {
76
76
  if (!jwksSets.has(rawUrl)) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/detect.ts","../src/verify.ts","../src/middleware.ts"],"sourcesContent":["/**\n * Bot / AI-agent detection for Express.js (Node.js).\n *\n * Mirrors the logic in @agent-id/nextjs but uses `IncomingHttpHeaders`\n * from Node's `http` module instead of the Web `Headers` API.\n */\nimport type { IncomingHttpHeaders } from \"node:http\";\n\nconst BOT_UA_PATTERNS: RegExp[] = [\n // OpenAI\n /GPTBot/i,\n /ChatGPT-User/i,\n /OAI-SearchBot/i,\n // Anthropic / Claude\n /ClaudeBot/i,\n /Claude-Web/i,\n /anthropic-ai/i,\n /Claude-User/i,\n // Google\n /Googlebot/i,\n /Google-Extended/i,\n /AdsBot-Google/i,\n // Microsoft / Bing\n /bingbot/i,\n /msnbot/i,\n // AI search engines\n /PerplexityBot/i,\n /YouBot/i,\n // Common HTTP automation libraries\n /python-requests/i,\n /node-fetch/i,\n /\\baxios\\b/i,\n /\\bgot\\b\\//i,\n /\\bundici\\b/i,\n /\\bcurl\\b/i,\n /\\bwget\\b/i,\n /\\bhttpie\\b/i,\n // Generic crawler signals\n /\\bbot\\b/i,\n /\\bcrawler\\b/i,\n /\\bspider\\b/i,\n /\\bscraper\\b/i,\n /\\bfetcher\\b/i,\n // MCP / AgentID clients\n /mcp-client/i,\n /agentid-client/i,\n];\n\nconst BROWSER_UA_RE = /Mozilla\\/5\\.0/i;\n\n/** Normalise a potentially multi-value header to a single string. */\nfunction single(\n value: string | string[] | undefined\n): string | undefined {\n return Array.isArray(value) ? value[0] : value;\n}\n\n/**\n * Returns `true` if the request appears to originate from an automated\n * agent or bot rather than a human browser.\n *\n * @param headers - `req.headers` from an Express `Request`\n */\nexport function isAgentRequest(headers: IncomingHttpHeaders): boolean {\n const ua = single(headers[\"user-agent\"]) ?? \"\";\n\n if (!ua) return true;\n\n if (BOT_UA_PATTERNS.some((p) => p.test(ua))) return true;\n\n // Cloudflare Bot Management score\n const cfRaw = single(headers[\"cf-bot-management\"]);\n if (cfRaw) {\n try {\n const parsed = JSON.parse(cfRaw) as { score?: unknown };\n if (typeof parsed.score === \"number\" && parsed.score < 30) return true;\n } catch {\n // Malformed header — fail open\n }\n }\n\n // Non-browser UA + no Accept-Language → likely automated\n if (!BROWSER_UA_RE.test(ua) && !headers[\"accept-language\"]) return true;\n\n return false;\n}\n\n/**\n * Extract the AgentID JWT from request headers.\n *\n * Checks:\n * 1. `Authorization: Bearer <token>` (preferred)\n * 2. `X-AgentID-Token` (fallback)\n *\n * @returns Raw JWT string or `null`.\n */\nexport function extractToken(headers: IncomingHttpHeaders): string | null {\n const auth = single(headers[\"authorization\"]) ?? \"\";\n if (auth.startsWith(\"Bearer \")) {\n const token = auth.slice(7).trim();\n if (token) return token;\n }\n\n const custom = single(headers[\"x-agentid-token\"])?.trim();\n if (custom) return custom;\n\n return null;\n}\n","import { createRemoteJWKSet, jwtVerify } from \"jose\";\nimport type { AgentIDClaims } from \"./types.js\";\n\nconst DEFAULT_JWKS_URL = \"https://agentpass.vercel.app/api/jwks\";\n\n/**\n * Module-level JWKS cache — one RemoteJWKSet instance per unique URL.\n * jose caches the fetched key material internally; re-fetches when the\n * cache TTL (1 h) expires or a new `kid` is seen.\n */\nconst jwksSets = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n\nfunction getJwks(rawUrl: string): ReturnType<typeof createRemoteJWKSet> {\n if (!jwksSets.has(rawUrl)) {\n const url = new URL(rawUrl); // throws on malformed URL\n\n // Security: HTTPS is mandatory to prevent MITM on the public-key fetch.\n // Localhost is whitelisted for local development / CI.\n const isLocal =\n url.hostname === \"localhost\" ||\n url.hostname === \"127.0.0.1\" ||\n url.hostname === \"::1\";\n\n if (url.protocol !== \"https:\" && !isLocal) {\n throw new Error(\n `[agent-id] jwksUrl must use HTTPS, received: ${rawUrl}`\n );\n }\n\n jwksSets.set(\n rawUrl,\n createRemoteJWKSet(url, {\n cacheMaxAge: 60 * 60 * 1_000, // 1 hour in ms\n })\n );\n }\n\n return jwksSets.get(rawUrl)!;\n}\n\nexport interface VerifyTokenOptions {\n jwksUrl?: string;\n clockTolerance?: number;\n}\n\n/**\n * Verify an AgentID JWT and return its decoded claims.\n *\n * Security guarantees\n * ───────────────────\n * • Signature — RS256, verified against the live JWKS public key.\n * • Algorithm — strict allowlist `['RS256']`; alg:none and HS256 are\n * rejected before signature verification even begins.\n * • Issuer — must be exactly \"agentid\".\n * • Expiry — enforced; configurable clock tolerance (default 30 s).\n * • kid — jose matches the JWT `kid` header to the JWKS automatically.\n * • auth_method — validated at runtime; must equal \"bankid\".\n *\n * @throws if the token is invalid, expired, or fails any check.\n */\nexport async function verifyAgentIDToken(\n token: string,\n options: VerifyTokenOptions = {}\n): Promise<AgentIDClaims> {\n const jwksUrl = options.jwksUrl ?? DEFAULT_JWKS_URL;\n const JWKS = getJwks(jwksUrl);\n\n const { payload } = await jwtVerify(token, JWKS, {\n issuer: \"agentid\",\n // ↓ Critical — explicit allowlist prevents algorithm-confusion attacks.\n // Any token claiming alg:\"none\", alg:\"HS256\", or anything else is\n // rejected before signature verification.\n algorithms: [\"RS256\"],\n clockTolerance: options.clockTolerance ?? 30,\n });\n\n // Runtime validation of AgentID-specific claims.\n if (payload.auth_method !== \"bankid\") {\n throw new Error(\n `[agent-id] JWT has invalid auth_method: expected \"bankid\", got \"${\n payload.auth_method ?? \"undefined\"\n }\"`\n );\n }\n if (typeof payload.sub !== \"string\" || payload.sub.length === 0) {\n throw new Error(\"[agent-id] JWT is missing the sub claim\");\n }\n\n return payload as unknown as AgentIDClaims;\n}\n","import type { Request, Response, NextFunction } from \"express\";\nimport { isAgentRequest, extractToken } from \"./detect.js\";\nimport { verifyAgentIDToken } from \"./verify.js\";\nimport type { VerifierOptions, AgentIDResult, AgentIDClaims } from \"./types.js\";\n\n// ── TypeScript augmentation ──────────────────────────────────────────────────\n// Adds `req.agentId` to Express's Request interface.\n// Consumers get full type-safety without any extra setup.\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n /** AgentID verification result set by the agentID() middleware. */\n agentId?: AgentIDResult;\n }\n }\n}\n\nexport type AgentIDMiddlewareOptions = VerifierOptions & {\n /**\n * Custom handler invoked when an agent is detected but has no valid token.\n * Call `next()` to pass through, or `res.status(403).json(...)` to block.\n * If omitted, the default behaviour is controlled by `blockUnauthorizedAgents`.\n */\n onUnauthorizedAgent?: (\n req: Request,\n res: Response,\n next: NextFunction,\n reason: string\n ) => void;\n};\n\n/** Headers managed by this middleware — stripped from incoming requests. */\nconst MANAGED_HEADERS = [\n \"x-agentid-verified\",\n \"x-agentid-sub\",\n \"x-agentid-claims\",\n] as const;\n\n/**\n * Express middleware that detects AI-agent traffic and enforces AgentID\n * JWT authentication.\n *\n * Sets `req.agentId` on every request:\n * - `{ verified: false, reason: 'not_agent' }` — human traffic (always passes through)\n * - `{ verified: true, claims }` — agent with valid JWT\n * - `{ verified: false, reason: 'no_token' | 'invalid_token' }` — blocked agent\n * (returns 403 when `blockUnauthorizedAgents: true`)\n *\n * @example\n * ```ts\n * import express from 'express';\n * import { agentID } from '@agent-id/express';\n *\n * const app = express();\n * app.use(agentID({ blockUnauthorizedAgents: true }));\n *\n * app.get('/api/data', (req, res) => {\n * if (req.agentId?.verified) {\n * res.json({ sub: req.agentId.claims.sub });\n * } else {\n * res.status(403).json({ error: 'Unauthorized' });\n * }\n * });\n * ```\n */\nexport function agentID(options: AgentIDMiddlewareOptions = {}) {\n const {\n jwksUrl,\n blockUnauthorizedAgents = true,\n clockTolerance = 30,\n onUnauthorizedAgent,\n } = options;\n\n return async function agentIDMiddleware(\n req: Request,\n res: Response,\n next: NextFunction\n ): Promise<void> {\n // ── Security: strip client-supplied AgentID headers ──────────────────\n // Prevents a malicious client from pre-setting x-agentid-verified: true\n // to fool any downstream code that checks the header directly.\n for (const header of MANAGED_HEADERS) {\n delete req.headers[header];\n }\n\n // ── Non-agent traffic ──────────────────────────────────────────────────\n if (!isAgentRequest(req.headers)) {\n req.agentId = { verified: false, reason: \"not_agent\" };\n return next();\n }\n\n // ── Agent detected — require a valid JWT ───────────────────────────────\n const token = extractToken(req.headers);\n\n if (!token) {\n return handleUnauthorized(\n req,\n res,\n next,\n \"no_token\",\n 'Agent request missing AgentID token. Provide \"Authorization: Bearer <token>\".',\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verify the JWT ─────────────────────────────────────────────────────\n let claims: AgentIDClaims;\n try {\n claims = await verifyAgentIDToken(token, {\n ...(jwksUrl !== undefined && { jwksUrl }),\n clockTolerance,\n });\n } catch (err) {\n console.warn(\n \"[agent-id] Token verification failed:\",\n err instanceof Error ? err.message : String(err)\n );\n return handleUnauthorized(\n req,\n res,\n next,\n \"invalid_token\",\n \"Invalid or expired AgentID token.\",\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verified ───────────────────────────────────────────────────────────\n req.agentId = { verified: true, claims };\n return next();\n };\n}\n\n// ── Internal helper ──────────────────────────────────────────────────────────\n\nfunction handleUnauthorized(\n req: Request,\n res: Response,\n next: NextFunction,\n reason: \"no_token\" | \"invalid_token\",\n message: string,\n block: boolean,\n onUnauthorized: AgentIDMiddlewareOptions[\"onUnauthorizedAgent\"]\n): void {\n req.agentId = { verified: false, reason };\n\n if (onUnauthorized) {\n onUnauthorized(req, res, next, message);\n return;\n }\n\n if (block) {\n res.status(403).json({ error: \"AGENT_UNAUTHORIZED\", message });\n return;\n }\n\n next();\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/detect.ts","../src/verify.ts","../src/middleware.ts"],"sourcesContent":["/**\n * Bot / AI-agent detection for Express.js (Node.js).\n *\n * Mirrors the logic in @agent-id/nextjs but uses `IncomingHttpHeaders`\n * from Node's `http` module instead of the Web `Headers` API.\n */\nimport type { IncomingHttpHeaders } from \"node:http\";\n\nconst BOT_UA_PATTERNS: RegExp[] = [\n // OpenAI\n /GPTBot/i,\n /ChatGPT-User/i,\n /OAI-SearchBot/i,\n // Anthropic / Claude\n /ClaudeBot/i,\n /Claude-Web/i,\n /anthropic-ai/i,\n /Claude-User/i,\n // Google\n /Googlebot/i,\n /Google-Extended/i,\n /AdsBot-Google/i,\n // Microsoft / Bing\n /bingbot/i,\n /msnbot/i,\n // AI search engines\n /PerplexityBot/i,\n /YouBot/i,\n // Common HTTP automation libraries\n /python-requests/i,\n /node-fetch/i,\n /\\baxios\\b/i,\n /\\bgot\\b\\//i,\n /\\bundici\\b/i,\n /\\bcurl\\b/i,\n /\\bwget\\b/i,\n /\\bhttpie\\b/i,\n // Generic crawler signals\n /\\bbot\\b/i,\n /\\bcrawler\\b/i,\n /\\bspider\\b/i,\n /\\bscraper\\b/i,\n /\\bfetcher\\b/i,\n // MCP / AgentID clients\n /mcp-client/i,\n /agentid-client/i,\n];\n\nconst BROWSER_UA_RE = /Mozilla\\/5\\.0/i;\n\n/** Normalise a potentially multi-value header to a single string. */\nfunction single(\n value: string | string[] | undefined\n): string | undefined {\n return Array.isArray(value) ? value[0] : value;\n}\n\n/**\n * Returns `true` if the request appears to originate from an automated\n * agent or bot rather than a human browser.\n *\n * @param headers - `req.headers` from an Express `Request`\n */\nexport function isAgentRequest(headers: IncomingHttpHeaders): boolean {\n const ua = single(headers[\"user-agent\"]) ?? \"\";\n\n if (!ua) return true;\n\n if (BOT_UA_PATTERNS.some((p) => p.test(ua))) return true;\n\n // Cloudflare Bot Management score\n const cfRaw = single(headers[\"cf-bot-management\"]);\n if (cfRaw) {\n try {\n const parsed = JSON.parse(cfRaw) as { score?: unknown };\n if (typeof parsed.score === \"number\" && parsed.score < 30) return true;\n } catch {\n // Malformed header — fail open\n }\n }\n\n // Non-browser UA + no Accept-Language → likely automated\n if (!BROWSER_UA_RE.test(ua) && !headers[\"accept-language\"]) return true;\n\n return false;\n}\n\n/**\n * Extract the AgentID JWT from request headers.\n *\n * Checks:\n * 1. `Authorization: Bearer <token>` (preferred)\n * 2. `X-AgentID-Token` (fallback)\n *\n * @returns Raw JWT string or `null`.\n */\nexport function extractToken(headers: IncomingHttpHeaders): string | null {\n const auth = single(headers[\"authorization\"]) ?? \"\";\n if (auth.startsWith(\"Bearer \")) {\n const token = auth.slice(7).trim();\n if (token) return token;\n }\n\n const custom = single(headers[\"x-agentid-token\"])?.trim();\n if (custom) return custom;\n\n return null;\n}\n","import { createRemoteJWKSet, jwtVerify } from \"jose\";\nimport type { AgentIDClaims } from \"./types.js\";\n\nconst DEFAULT_JWKS_URL = \"https://agentidapp.vercel.app/api/jwks\";\n\n/**\n * Module-level JWKS cache — one RemoteJWKSet instance per unique URL.\n * jose caches the fetched key material internally; re-fetches when the\n * cache TTL (1 h) expires or a new `kid` is seen.\n */\nconst jwksSets = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n\nfunction getJwks(rawUrl: string): ReturnType<typeof createRemoteJWKSet> {\n if (!jwksSets.has(rawUrl)) {\n const url = new URL(rawUrl); // throws on malformed URL\n\n // Security: HTTPS is mandatory to prevent MITM on the public-key fetch.\n // Localhost is whitelisted for local development / CI.\n const isLocal =\n url.hostname === \"localhost\" ||\n url.hostname === \"127.0.0.1\" ||\n url.hostname === \"::1\";\n\n if (url.protocol !== \"https:\" && !isLocal) {\n throw new Error(\n `[agent-id] jwksUrl must use HTTPS, received: ${rawUrl}`\n );\n }\n\n jwksSets.set(\n rawUrl,\n createRemoteJWKSet(url, {\n cacheMaxAge: 60 * 60 * 1_000, // 1 hour in ms\n })\n );\n }\n\n return jwksSets.get(rawUrl)!;\n}\n\nexport interface VerifyTokenOptions {\n jwksUrl?: string;\n clockTolerance?: number;\n}\n\n/**\n * Verify an AgentID JWT and return its decoded claims.\n *\n * Security guarantees\n * ───────────────────\n * • Signature — RS256, verified against the live JWKS public key.\n * • Algorithm — strict allowlist `['RS256']`; alg:none and HS256 are\n * rejected before signature verification even begins.\n * • Issuer — must be exactly \"agentid\".\n * • Expiry — enforced; configurable clock tolerance (default 30 s).\n * • kid — jose matches the JWT `kid` header to the JWKS automatically.\n * • auth_method — validated at runtime; must equal \"bankid\".\n *\n * @throws if the token is invalid, expired, or fails any check.\n */\nexport async function verifyAgentIDToken(\n token: string,\n options: VerifyTokenOptions = {}\n): Promise<AgentIDClaims> {\n const jwksUrl = options.jwksUrl ?? DEFAULT_JWKS_URL;\n const JWKS = getJwks(jwksUrl);\n\n const { payload } = await jwtVerify(token, JWKS, {\n issuer: \"agentid\",\n // ↓ Critical — explicit allowlist prevents algorithm-confusion attacks.\n // Any token claiming alg:\"none\", alg:\"HS256\", or anything else is\n // rejected before signature verification.\n algorithms: [\"RS256\"],\n clockTolerance: options.clockTolerance ?? 30,\n });\n\n // Runtime validation of AgentID-specific claims.\n if (payload.auth_method !== \"bankid\") {\n throw new Error(\n `[agent-id] JWT has invalid auth_method: expected \"bankid\", got \"${\n payload.auth_method ?? \"undefined\"\n }\"`\n );\n }\n if (typeof payload.sub !== \"string\" || payload.sub.length === 0) {\n throw new Error(\"[agent-id] JWT is missing the sub claim\");\n }\n\n return payload as unknown as AgentIDClaims;\n}\n","import type { Request, Response, NextFunction } from \"express\";\nimport { isAgentRequest, extractToken } from \"./detect.js\";\nimport { verifyAgentIDToken } from \"./verify.js\";\nimport type { VerifierOptions, AgentIDResult, AgentIDClaims } from \"./types.js\";\n\n// ── TypeScript augmentation ──────────────────────────────────────────────────\n// Adds `req.agentId` to Express's Request interface.\n// Consumers get full type-safety without any extra setup.\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n /** AgentID verification result set by the agentID() middleware. */\n agentId?: AgentIDResult;\n }\n }\n}\n\nexport type AgentIDMiddlewareOptions = VerifierOptions & {\n /**\n * Custom handler invoked when an agent is detected but has no valid token.\n * Call `next()` to pass through, or `res.status(403).json(...)` to block.\n * If omitted, the default behaviour is controlled by `blockUnauthorizedAgents`.\n */\n onUnauthorizedAgent?: (\n req: Request,\n res: Response,\n next: NextFunction,\n reason: string\n ) => void;\n};\n\n/** Headers managed by this middleware — stripped from incoming requests. */\nconst MANAGED_HEADERS = [\n \"x-agentid-verified\",\n \"x-agentid-sub\",\n \"x-agentid-claims\",\n] as const;\n\n/**\n * Express middleware that detects AI-agent traffic and enforces AgentID\n * JWT authentication.\n *\n * Sets `req.agentId` on every request:\n * - `{ verified: false, reason: 'not_agent' }` — human traffic (always passes through)\n * - `{ verified: true, claims }` — agent with valid JWT\n * - `{ verified: false, reason: 'no_token' | 'invalid_token' }` — blocked agent\n * (returns 403 when `blockUnauthorizedAgents: true`)\n *\n * @example\n * ```ts\n * import express from 'express';\n * import { agentID } from '@agent-id/express';\n *\n * const app = express();\n * app.use(agentID({ blockUnauthorizedAgents: true }));\n *\n * app.get('/api/data', (req, res) => {\n * if (req.agentId?.verified) {\n * res.json({ sub: req.agentId.claims.sub });\n * } else {\n * res.status(403).json({ error: 'Unauthorized' });\n * }\n * });\n * ```\n */\nexport function agentID(options: AgentIDMiddlewareOptions = {}) {\n const {\n jwksUrl,\n blockUnauthorizedAgents = true,\n clockTolerance = 30,\n onUnauthorizedAgent,\n } = options;\n\n return async function agentIDMiddleware(\n req: Request,\n res: Response,\n next: NextFunction\n ): Promise<void> {\n // ── Security: strip client-supplied AgentID headers ──────────────────\n // Prevents a malicious client from pre-setting x-agentid-verified: true\n // to fool any downstream code that checks the header directly.\n for (const header of MANAGED_HEADERS) {\n delete req.headers[header];\n }\n\n // ── Non-agent traffic ──────────────────────────────────────────────────\n if (!isAgentRequest(req.headers)) {\n req.agentId = { verified: false, reason: \"not_agent\" };\n return next();\n }\n\n // ── Agent detected — require a valid JWT ───────────────────────────────\n const token = extractToken(req.headers);\n\n if (!token) {\n return handleUnauthorized(\n req,\n res,\n next,\n \"no_token\",\n 'Agent request missing AgentID token. Provide \"Authorization: Bearer <token>\".',\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verify the JWT ─────────────────────────────────────────────────────\n let claims: AgentIDClaims;\n try {\n claims = await verifyAgentIDToken(token, {\n ...(jwksUrl !== undefined && { jwksUrl }),\n clockTolerance,\n });\n } catch (err) {\n console.warn(\n \"[agent-id] Token verification failed:\",\n err instanceof Error ? err.message : String(err)\n );\n return handleUnauthorized(\n req,\n res,\n next,\n \"invalid_token\",\n \"Invalid or expired AgentID token.\",\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verified ───────────────────────────────────────────────────────────\n req.agentId = { verified: true, claims };\n return next();\n };\n}\n\n// ── Internal helper ──────────────────────────────────────────────────────────\n\nfunction handleUnauthorized(\n req: Request,\n res: Response,\n next: NextFunction,\n reason: \"no_token\" | \"invalid_token\",\n message: string,\n block: boolean,\n onUnauthorized: AgentIDMiddlewareOptions[\"onUnauthorizedAgent\"]\n): void {\n req.agentId = { verified: false, reason };\n\n if (onUnauthorized) {\n onUnauthorized(req, res, next, message);\n return;\n }\n\n if (block) {\n res.status(403).json({ error: \"AGENT_UNAUTHORIZED\", message });\n return;\n }\n\n next();\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@agent-id/express",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Agent-ID verifier middleware for Express.js — blocks unauthorized AI agents from your API routes",
5
- "keywords": ["agent-id", "agentpass", "bankid", "jwt", "express", "middleware", "ai-agent", "mcp", "bot-detection"],
5
+ "keywords": ["agent-id", "bankid", "jwt", "express", "middleware", "ai-agent", "mcp", "bot-detection"],
6
6
  "license": "MIT",
7
7
  "main": "./dist/index.cjs",
8
8
  "module": "./dist/index.js",