@astrasyncai/adapter-lambda 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -100,10 +100,17 @@ function collectConnectionMetadata(request) {
100
100
  put("country", "cloudfront-viewer-country");
101
101
  put("countryName", "cloudfront-viewer-country-name");
102
102
  put("region", "cloudfront-viewer-country-region");
103
+ put("countryRegionName", "cloudfront-viewer-country-region-name");
103
104
  put("city", "cloudfront-viewer-city");
104
105
  put("asn", "cloudfront-viewer-asn");
105
106
  put("tlsVersion", "cloudfront-viewer-tls");
106
107
  put("httpVersion", "cloudfront-viewer-http-version");
108
+ put("timeZone", "cloudfront-viewer-time-zone");
109
+ put("metroCode", "cloudfront-viewer-metro-code");
110
+ put("tlsFingerprint", "cloudfront-viewer-ja3-fingerprint");
111
+ put("ja4", "cloudfront-viewer-ja4-fingerprint");
112
+ put("headerOrder", "cloudfront-viewer-header-order", (v) => v.slice(0, 256));
113
+ put("headerCount", "cloudfront-viewer-header-count");
107
114
  const devices = [];
108
115
  for (const [label, header] of [
109
116
  ["desktop", "cloudfront-is-desktop-viewer"],
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/handler.ts","../src/cf.ts","../src/config.ts","../src/classify.ts","../src/detect.ts","../src/pipeline.ts","../src/enforce.ts","../src/messaging.ts","../src/events.ts"],"sourcesContent":["/**\n * @astrasyncai/adapter-lambda — AstraSync Trusted Agent Gateway,\n * CloudFront/Lambda@Edge adapter.\n *\n * Library surface: `createEdgeHandler(options)` builds a Lambda@Edge\n * origin-request handler. The deployable single-file bundle lives at\n * `dist/lambda/index.js` (see `src/lambda-entry.ts` + `infra/`).\n */\n\nexport { createEdgeHandler, type EdgeHandler } from './handler';\nexport { classify, resolvePolicy } from './classify';\nexport { detectCommerce, type DetectedCommerce } from './detect';\nexport { runTier } from './pipeline';\nexport { applyOutcome, ASAT_HEADERS } from './enforce';\nexport { buildDenyBody, buildManualReviewBody } from './messaging';\nexport { emitBeacon, withBudget, sampled } from './events';\nexport { resolveApiKey, loadEdgeConfig, _resetConfigCaches } from './config';\nexport {\n getRequest,\n getHeader,\n flattenHeaders,\n decodeBody,\n requestUrl,\n setHeader,\n stripHeadersByPrefix,\n jsonResponse,\n} from './cf';\nexport type {\n EdgeAdapterOptions,\n Classification,\n TrafficTier,\n TierOutcome,\n EdgeDecision,\n EffectivePolicy,\n} from './types';\n","/**\n * Trusted Agent Gateway edge shell — Lambda@Edge origin-request handler\n * factory.\n *\n * Composition per request:\n * config (cached) → edge core evaluation (path policy → classify → tier\n * pipeline) → apply outcome onto the CloudFront request\n *\n * THE invariant (tested): outside enforce mode the handler ALWAYS returns\n * the origin-bound request — any error, timeout, or misconfiguration\n * results in pass-through. A top-level catch guarantees it even against\n * bugs in the shell itself.\n */\n\nimport type { CloudFrontRequestEvent, CloudFrontRequestResult } from 'aws-lambda';\nimport { evaluateEdgeRequest } from '@astrasyncai/verification-gateway/edge-core';\nimport { getRequest, normalizeOriginHost, toNormalizedRequest, applyCoreOutcome } from './cf';\nimport { resolveApiKey, loadEdgeConfig } from './config';\nimport type { EdgeAdapterOptions } from './types';\n\nexport type EdgeHandler = (event: CloudFrontRequestEvent) => Promise<CloudFrontRequestResult>;\n\nexport function createEdgeHandler(options: EdgeAdapterOptions): EdgeHandler {\n const log =\n options.log ??\n ((level, message, data) => {\n // Lambda@Edge writes console output to the regional CloudWatch log\n // group of whichever edge location served the request.\n console[level === 'info' ? 'log' : level](`[astrasync-edge] ${message}`, data ?? '');\n });\n\n return async function handler(event) {\n const request = getRequest(event);\n try {\n // Own the Host rewrite (metadata capture): the CloudFront-Viewer-* capture\n // requires an origin-request policy that also forwards the viewer Host,\n // so the shell restores origin-domain Host + X-Forwarded-Host itself.\n // Runs first so EVERY origin-bound return (skip / human / allow) is\n // correctly routed — and so the normalized request the core sees\n // already carries the viewer host in X-Forwarded-Host.\n normalizeOriginHost(request);\n\n const apiKey = await resolveApiKey({ ...options, log });\n const config = await loadEdgeConfig(options, apiKey);\n\n const { application } = await evaluateEdgeRequest(\n { ...options, log },\n toNormalizedRequest(request),\n config,\n apiKey\n );\n\n return applyCoreOutcome(request, application);\n } catch (err) {\n // Fail-open, unconditionally: the shell must never take the site down.\n log('error', 'edge handler error — passing request through', {\n error: err instanceof Error ? err.message : String(err),\n });\n return request;\n }\n };\n}\n","/**\n * CloudFront platform I/O — the Lambda@Edge side of the edge core's\n * PlatformIo contract, plus origin-request event helpers.\n *\n * The Lambda@Edge event shape puts headers in `{ key, value }[]` arrays\n * keyed by LOWERCASED header name, and (with IncludeBody) the body arrives\n * base64-encoded with an `inputTruncated` flag when it exceeded\n * CloudFront's ~1MB exposure cap. `toNormalizedRequest` converts that into\n * the core's NormalizedRequest; `applyCoreOutcome` executes the core's\n * abstract outcome application against the CloudFront request/response\n * primitives.\n */\n\nimport type {\n CloudFrontRequest,\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n CloudFrontHeaders,\n CloudFrontResultResponse,\n} from 'aws-lambda';\nimport type {\n NormalizedRequest,\n OutcomeApplication,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { buildObservedMetadata as buildObservedMetadataCore } from '@astrasyncai/verification-gateway/edge-core';\nimport type { ObservedMetadata } from '@astrasyncai/verification-gateway/metadata-capture';\n\nexport function getRequest(event: CloudFrontRequestEvent): CloudFrontRequest {\n return event.Records[0].cf.request;\n}\n\n/** First value of a header (lowercase name), or undefined. */\nexport function getHeader(request: CloudFrontRequest, name: string): string | undefined {\n return request.headers[name.toLowerCase()]?.[0]?.value;\n}\n\n/**\n * Flatten CloudFront headers into the `Record<string, string | string[]>`\n * shape the SDK extractors and verify-access artifacts expect.\n */\nexport function flattenHeaders(headers: CloudFrontHeaders): Record<string, string | string[]> {\n const out: Record<string, string | string[]> = {};\n for (const [name, entries] of Object.entries(headers)) {\n if (!entries?.length) continue;\n out[name] = entries.length === 1 ? entries[0].value : entries.map((e) => e.value);\n }\n return out;\n}\n\n/**\n * Flatten CloudFront headers to single string values (multi-valued headers\n * joined with `\", \"`, standard HTTP field-value concatenation) — the\n * NormalizedRequest header shape.\n */\nfunction flattenHeadersSingle(headers: CloudFrontHeaders): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [name, entries] of Object.entries(headers)) {\n if (!entries?.length) continue;\n out[name] = entries.map((e) => e.value).join(', ');\n }\n return out;\n}\n\nexport interface DecodedBody {\n /** UTF-8 body text, undefined when there is no body. */\n text?: string;\n /** True when CloudFront truncated the body before handing it to the trigger. */\n truncated: boolean;\n}\n\nexport function decodeBody(request: CloudFrontRequest): DecodedBody {\n const body = request.body;\n if (!body?.data) return { truncated: false };\n const text =\n body.encoding === 'base64' ? Buffer.from(body.data, 'base64').toString('utf8') : body.data;\n return { text, truncated: body.inputTruncated === true };\n}\n\n/**\n * Connection-layer signals the edge can see but the SDK cannot. CloudFront\n * injects the `CloudFront-Viewer-*` header family (IP, ASN, country, TLS\n * version/cipher, HTTP version, device class) when the OriginRequestPolicy\n * whitelists them (see infra/template.yaml). Absent values are simply\n * omitted — a partial connection block is fine.\n */\nexport function collectConnectionMetadata(request: CloudFrontRequest): Record<string, string> {\n const out: Record<string, string> = {};\n const put = (key: string, headerName: string, transform?: (v: string) => string) => {\n const v = getHeader(request, headerName);\n if (v) out[key] = transform ? transform(v) : v;\n };\n // `cloudfront-viewer-address` is `ip:port` (IPv6 keeps its brackets); take the\n // address portion the same way the beacon/pipeline already do.\n const addr = getHeader(request, 'cloudfront-viewer-address');\n if (addr) out.ip = addr.replace(/:\\d+$/, '');\n put('country', 'cloudfront-viewer-country');\n put('countryName', 'cloudfront-viewer-country-name');\n put('region', 'cloudfront-viewer-country-region');\n put('city', 'cloudfront-viewer-city');\n put('asn', 'cloudfront-viewer-asn');\n put('tlsVersion', 'cloudfront-viewer-tls');\n put('httpVersion', 'cloudfront-viewer-http-version');\n // Device-class booleans → a single compact device string.\n const devices: string[] = [];\n for (const [label, header] of [\n ['desktop', 'cloudfront-is-desktop-viewer'],\n ['mobile', 'cloudfront-is-mobile-viewer'],\n ['tablet', 'cloudfront-is-tablet-viewer'],\n ['smarttv', 'cloudfront-is-smarttv-viewer'],\n ] as const) {\n if (getHeader(request, header) === 'true') devices.push(label);\n }\n if (devices.length) out.device = devices.join(',');\n return out;\n}\n\n/**\n * CloudFront PlatformIo normalization: origin-request event → the edge\n * core's NormalizedRequest. `clientIp` keeps the pipeline's historical\n * viewer-address derivation (address portion before the first `:`) so\n * verify-access/beacon telemetry is byte-identical to earlier releases.\n */\nexport function toNormalizedRequest(request: CloudFrontRequest): NormalizedRequest {\n const body = decodeBody(request);\n const connection = collectConnectionMetadata(request);\n const clientIp = getHeader(request, 'cloudfront-viewer-address')?.split(':')[0];\n return {\n method: request.method,\n uri: request.uri,\n querystring: request.querystring ?? '',\n headers: flattenHeadersSingle(request.headers),\n ...(body.text !== undefined && { bodyText: body.text }),\n ...(body.truncated && { bodyTruncated: true }),\n ...(clientIp && { clientIp }),\n ...(Object.keys(connection).length > 0 && { connection }),\n };\n}\n\n/**\n * Execute the core's abstract outcome application against the CloudFront\n * request: `pass` mutates the origin-bound request (strip spoofable\n * prefixes, inject trust headers) and returns it; `respond` short-circuits\n * with a CloudFront JSON response.\n */\nexport function applyCoreOutcome(\n request: CloudFrontRequest,\n application: OutcomeApplication\n): CloudFrontRequestResult {\n if (application.action === 'respond') {\n return jsonResponse(application.status, application.bodyJson, application.headers);\n }\n for (const prefix of application.stripHeaderPrefixes) {\n stripHeadersByPrefix(request, prefix);\n }\n for (const [name, value] of Object.entries(application.injectHeaders)) {\n setHeader(request, name, value);\n }\n return request;\n}\n\n/**\n * The FULL sanitised metadata capture for one request: every inbound header\n * (secrets stripped, credential headers reduced to a safe format prefix)\n * plus the connection-layer block. Forwarded on both the verify-access call\n * and the beacon.\n */\nexport function buildObservedMetadata(request: CloudFrontRequest): ObservedMetadata {\n return buildObservedMetadataCore(toNormalizedRequest(request));\n}\n\n/**\n * Normalize the origin-bound `Host` header.\n *\n * To receive the `CloudFront-Viewer-*` connection headers (IP/ASN/TLS/country)\n * we must move off the managed `AllViewerExceptHostHeader` origin-request policy\n * onto one that whitelists those headers — but every such policy ALSO forwards\n * the viewer `Host`, which would break vhost / PaaS origin routing that expects\n * the origin's own hostname. So the Lambda takes ownership of the Host rewrite:\n * stash the public (viewer) host into `X-Forwarded-Host` for downstream use and\n * detection, then set `Host` to the origin's domain (available on the event as\n * `request.origin.custom.domainName`). No-ops when the origin domain is absent\n * (synthetic test events) or already matches — so it is safe on every path,\n * including static-asset pass-through.\n */\nexport function normalizeOriginHost(request: CloudFrontRequest): void {\n const originDomain =\n request.origin?.custom?.domainName ?? request.origin?.s3?.domainName ?? undefined;\n if (!originDomain) return;\n const currentHost = getHeader(request, 'host');\n if (!currentHost || currentHost === originDomain) return;\n if (!getHeader(request, 'x-forwarded-host')) {\n setHeader(request, 'X-Forwarded-Host', currentHost);\n }\n setHeader(request, 'Host', originDomain);\n}\n\n/** Full request URL as the origin will see it (scheme fixed to https). */\nexport function requestUrl(request: CloudFrontRequest, host: string): string {\n const qs = request.querystring ? `?${request.querystring}` : '';\n return `https://${host}${request.uri}${qs}`;\n}\n\n/**\n * Set a header on the origin-bound request (overwrites all prior values).\n */\nexport function setHeader(request: CloudFrontRequest, name: string, value: string): void {\n request.headers[name.toLowerCase()] = [{ key: name, value }];\n}\n\n/**\n * Strip every inbound header matching a prefix (lowercase compare). Used to\n * drop spoofed `x-astrasync-*` trust headers before the shell injects its\n * own — in BOTH modes, so a caller can never smuggle trust past the origin.\n */\nexport function stripHeadersByPrefix(request: CloudFrontRequest, prefix: string): void {\n const p = prefix.toLowerCase();\n for (const name of Object.keys(request.headers)) {\n if (name.startsWith(p)) delete request.headers[name];\n }\n}\n\nexport function jsonResponse(\n status: number,\n body: unknown,\n extraHeaders?: Record<string, string>\n): CloudFrontResultResponse {\n const headers: CloudFrontResultResponse['headers'] = {\n 'content-type': [{ key: 'Content-Type', value: 'application/json' }],\n };\n for (const [name, value] of Object.entries(extraHeaders ?? {})) {\n headers[name.toLowerCase()] = [{ key: name, value }];\n }\n return {\n status: String(status),\n statusDescription: status === 403 ? 'Forbidden' : status === 202 ? 'Accepted' : 'OK',\n headers,\n body: JSON.stringify(body),\n };\n}\n","/**\n * Runtime configuration for the edge shell.\n *\n * Lambda@Edge forbids environment variables, so configuration is layered:\n * 1. Build-time constants baked into the bundle (apiBaseUrl,\n * counterpartyId, counterpartyUrl, apiKeySecretName) — see\n * `tsup.config.ts` define block and `infra/deploy.sh`.\n * 2. The merchant API key from AWS Secrets Manager (us-east-1), fetched\n * lazily once per container and cached with a 15-minute TTL so\n * rotation needs no redeploy. Observe mode works fully without it —\n * the beacon endpoint is unauthenticated and verify() degrades\n * keyless — so a Secrets Manager outage can never take the shell down.\n * 3. The dashboard EdgeConfig via the SDK's getEdgeConfig (60s TTL,\n * stale-while-revalidate, enforce→observe degradation after 24h\n * staleness, DEFAULT_EDGE_CONFIG fallback — all in the SDK).\n */\n\nimport {\n getEdgeConfig,\n DEFAULT_EDGE_CONFIG,\n type EdgeConfig,\n} from '@astrasyncai/verification-gateway/edge-config';\nimport type { EdgeAdapterOptions } from './types';\n\nconst SECRET_TTL_MS = 15 * 60 * 1000;\n\ninterface SecretCacheEntry {\n value: string | undefined;\n fetchedAt: number;\n}\n\nconst secretCache = new Map<string, SecretCacheEntry>();\n\nasync function defaultFetchSecret(secretName: string): Promise<string | undefined> {\n // Lazy import so tests (and the library build) never touch the AWS SDK\n // unless a secret name is actually configured.\n const { SecretsManagerClient, GetSecretValueCommand } =\n await import('@aws-sdk/client-secrets-manager');\n // Lambda@Edge replicates globally but secrets live in us-east-1 (the\n // Lambda@Edge home region).\n const client = new SecretsManagerClient({ region: 'us-east-1' });\n const result = await client.send(new GetSecretValueCommand({ SecretId: secretName }));\n return result.SecretString ?? undefined;\n}\n\n/**\n * Resolve the merchant API key. Never throws — a missing/unreachable secret\n * degrades to keyless operation (observe mode is fully functional without\n * it; enforce mode without a key cannot fetch a config that enforces, so it\n * degrades to observe by construction).\n */\nexport async function resolveApiKey(options: EdgeAdapterOptions): Promise<string | undefined> {\n if (options.apiKey) return options.apiKey;\n const secretName = options.apiKeySecretName;\n if (!secretName) return undefined;\n\n const cached = secretCache.get(secretName);\n if (cached && Date.now() - cached.fetchedAt < SECRET_TTL_MS) return cached.value;\n\n try {\n const fetcher = options.fetchSecret ?? defaultFetchSecret;\n const value = await fetcher(secretName);\n secretCache.set(secretName, { value, fetchedAt: Date.now() });\n return value;\n } catch (err) {\n options.log?.('warn', 'Secrets Manager fetch failed — continuing keyless', {\n secretName,\n error: err instanceof Error ? err.message : String(err),\n });\n // Serve a stale value if we ever had one; otherwise keyless.\n return cached?.value;\n }\n}\n\n/**\n * Load the effective EdgeConfig. Delegates caching/fallback/degradation to\n * the SDK; adds the compile-time fallback override and never throws.\n */\nexport async function loadEdgeConfig(\n options: EdgeAdapterOptions,\n apiKey: string | undefined\n): Promise<EdgeConfig> {\n try {\n return await getEdgeConfig({ apiBaseUrl: options.apiBaseUrl, apiKey }, options.counterpartyId, {\n ttlMs: options.configTtlMs,\n });\n } catch {\n return options.fallbackConfig ?? DEFAULT_EDGE_CONFIG;\n }\n}\n\n/** Reset module caches — tests only. */\nexport function _resetConfigCaches(): void {\n secretCache.clear();\n}\n","/**\n * Three-tier traffic classification — CloudFront wrapper over\n * the edge core's classifier. The classification logic itself (tier order,\n * UA patterns, path-rule resolution) lives in\n * `@astrasyncai/verification-gateway/edge-core` and is shared with every\n * edge platform adapter.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport {\n classify as classifyCore,\n resolvePolicy,\n _patterns,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest } from './cf';\nimport type { Classification } from './types';\n\nexport { resolvePolicy, _patterns };\n\n/** Classify one CloudFront origin-request. See the edge core for the tier order. */\nexport function classify(\n request: CloudFrontRequest,\n host: string,\n depth: 'classify' | 'authenticate' | 'authorize'\n): Classification {\n return classifyCore(toNormalizedRequest(request), host, depth);\n}\n","/**\n * Crypto-free commerce-protocol detection (fixed detection order) —\n * CloudFront wrapper over the edge core's detector. The sniffing logic\n * (header shapes, compact-JWT patterns, body field names) lives in\n * `@astrasyncai/verification-gateway/edge-core`; this module adapts the\n * CloudFront event shape (separately-decoded body) onto it.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport {\n detectCommerce as detectCommerceCore,\n type DetectedCommerce,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest, type DecodedBody } from './cf';\n\nexport type { DetectedCommerce };\n\n/**\n * Detect commerce-protocol credentials on a CloudFront origin-request.\n * `body` is the caller-decoded request body (see `decodeBody`) — kept as an\n * explicit parameter so callers that already decoded it don't pay twice.\n */\nexport function detectCommerce(\n request: CloudFrontRequest,\n host: string,\n body: DecodedBody\n): DetectedCommerce | undefined {\n const normalized = toNormalizedRequest(request);\n return detectCommerceCore(\n {\n ...normalized,\n bodyText: body.text,\n bodyTruncated: body.truncated,\n },\n host\n );\n}\n","/**\n * Depth-controlled verification per tier — CloudFront wrapper over the edge\n * core's tier pipeline. The verification semantics (what each depth looks\n * at and forwards, the fail-open rules, the verify-access request shape)\n * live in `@astrasyncai/verification-gateway/edge-core`; the edge never\n * verifies credentials itself — verify-access is the canonical sole\n * verification sink.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport { runTier as runTierCore } from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest } from './cf';\nimport type { Classification, EdgeAdapterOptions, EffectivePolicy, TierOutcome } from './types';\n\n/**\n * Run the tier-appropriate verification/telemetry for one classified\n * request. NEVER throws; on any failure returns an `allow` outcome with the\n * failure recorded in `reasons` (infra failures fail open — a presented\n * credential the BACKEND rejected is a deny, but an unreachable backend is\n * not a rejection).\n */\nexport async function runTier(\n options: EdgeAdapterOptions,\n request: CloudFrontRequest,\n classification: Classification,\n policy: EffectivePolicy,\n apiKey: string | undefined,\n sampling: { anonymousBeaconRate: number }\n): Promise<TierOutcome> {\n return runTierCore(\n options,\n toNormalizedRequest(request),\n classification,\n policy,\n apiKey,\n sampling\n );\n}\n","/**\n * Decision → CloudFront result wiring (enforcement + ASAT trust-attestation\n * headers). The decision logic (observe shadow stamps, misconfig fail-open,\n * enforce-mode response bodies) lives in the edge core; this module\n * executes the core's abstract outcome application against CloudFront\n * request/response primitives.\n */\n\nimport type { CloudFrontRequest, CloudFrontRequestResult } from 'aws-lambda';\nimport {\n applyOutcome as resolveOutcomeApplication,\n ASAT_HEADERS,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { applyCoreOutcome } from './cf';\nimport type { EdgeAdapterOptions, EffectivePolicy, TierOutcome } from './types';\n\nexport { ASAT_HEADERS };\n\n/**\n * Apply the outcome. Always strips inbound spoofed `x-astrasync-*` headers\n * first (both modes — trust headers must only ever originate here).\n *\n * Observe mode: returns the request untouched apart from header hygiene and\n * an `X-AstraSync-Observed-Decision` stamp for origin-side log correlation.\n *\n * Enforce mode: ALLOW → request + ASAT headers; DENY → 403 machine-readable\n * body; MANUAL_REVIEW → 202 with step-up poll info.\n */\nexport function applyOutcome(\n options: EdgeAdapterOptions,\n request: CloudFrontRequest,\n outcome: TierOutcome,\n policy: EffectivePolicy\n): CloudFrontRequestResult {\n return applyCoreOutcome(request, resolveOutcomeApplication(options, outcome, policy));\n}\n","/**\n * Protocol-specific machine-readable messaging for enforce-mode\n * responses. The message catalogue and body builders live in the edge core\n * (shared across every edge platform adapter); re-exported here so the\n * adapter's public API is unchanged.\n */\n\nexport {\n buildDenyBody,\n buildManualReviewBody,\n type DenyBody,\n type ManualReviewBody,\n} from '@astrasyncai/verification-gateway/edge-core';\n","/**\n * Time-budgeted event emission — CloudFront wrapper over the edge core's\n * telemetry. Lambda@Edge has no `waitUntil`, so every network call is\n * awaited under an explicit budget and DROPPED on timeout (the core's\n * default posture): latency to the customer's traffic is bounded, and a\n * slow AstraSync API costs telemetry, never page loads.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport {\n emitBeacon as emitBeaconCore,\n withBudget,\n sampled,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest } from './cf';\nimport type { Classification, EdgeAdapterOptions } from './types';\n\nexport { withBudget, sampled };\n\n/**\n * Emit an unregistered-attempt beacon for `identified` (non-crypto depths)\n * and sampled `anonymous-bot` traffic. Unauthenticated by design\n * (CSRF-exempt on the backend); the backend fingerprints the UA and\n * find-or-provisions orphan platform agents server-side.\n */\nexport async function emitBeacon(\n options: EdgeAdapterOptions,\n request: CloudFrontRequest,\n classification: Classification,\n budgetMs: number\n): Promise<void> {\n await emitBeaconCore(options, toNormalizedRequest(request), classification, budgetMs);\n}\n"],"mappings":"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","names":["import_edge_core","import_edge_core","classifyCore","import_edge_core","detectCommerceCore","import_edge_core","runTierCore","import_edge_core","resolveOutcomeApplication","import_edge_core","import_edge_core","emitBeaconCore"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/handler.ts","../src/cf.ts","../src/config.ts","../src/classify.ts","../src/detect.ts","../src/pipeline.ts","../src/enforce.ts","../src/messaging.ts","../src/events.ts"],"sourcesContent":["/**\n * @astrasyncai/adapter-lambda — AstraSync Trusted Agent Gateway,\n * CloudFront/Lambda@Edge adapter.\n *\n * Library surface: `createEdgeHandler(options)` builds a Lambda@Edge\n * origin-request handler. The deployable single-file bundle lives at\n * `dist/lambda/index.js` (see `src/lambda-entry.ts` + `infra/`).\n */\n\nexport { createEdgeHandler, type EdgeHandler } from './handler';\nexport { classify, resolvePolicy } from './classify';\nexport { detectCommerce, type DetectedCommerce } from './detect';\nexport { runTier } from './pipeline';\nexport { applyOutcome, ASAT_HEADERS } from './enforce';\nexport { buildDenyBody, buildManualReviewBody } from './messaging';\nexport { emitBeacon, withBudget, sampled } from './events';\nexport { resolveApiKey, loadEdgeConfig, _resetConfigCaches } from './config';\nexport {\n getRequest,\n getHeader,\n flattenHeaders,\n decodeBody,\n requestUrl,\n setHeader,\n stripHeadersByPrefix,\n jsonResponse,\n} from './cf';\nexport type {\n EdgeAdapterOptions,\n Classification,\n TrafficTier,\n TierOutcome,\n EdgeDecision,\n EffectivePolicy,\n} from './types';\n","/**\n * Trusted Agent Gateway edge shell — Lambda@Edge origin-request handler\n * factory.\n *\n * Composition per request:\n * config (cached) → edge core evaluation (path policy → classify → tier\n * pipeline) → apply outcome onto the CloudFront request\n *\n * THE invariant (tested): outside enforce mode the handler ALWAYS returns\n * the origin-bound request — any error, timeout, or misconfiguration\n * results in pass-through. A top-level catch guarantees it even against\n * bugs in the shell itself.\n */\n\nimport type { CloudFrontRequestEvent, CloudFrontRequestResult } from 'aws-lambda';\nimport { evaluateEdgeRequest } from '@astrasyncai/verification-gateway/edge-core';\nimport { getRequest, normalizeOriginHost, toNormalizedRequest, applyCoreOutcome } from './cf';\nimport { resolveApiKey, loadEdgeConfig } from './config';\nimport type { EdgeAdapterOptions } from './types';\n\nexport type EdgeHandler = (event: CloudFrontRequestEvent) => Promise<CloudFrontRequestResult>;\n\nexport function createEdgeHandler(options: EdgeAdapterOptions): EdgeHandler {\n const log =\n options.log ??\n ((level, message, data) => {\n // Lambda@Edge writes console output to the regional CloudWatch log\n // group of whichever edge location served the request.\n console[level === 'info' ? 'log' : level](`[astrasync-edge] ${message}`, data ?? '');\n });\n\n return async function handler(event) {\n const request = getRequest(event);\n try {\n // Own the Host rewrite (metadata capture): the CloudFront-Viewer-* capture\n // requires an origin-request policy that also forwards the viewer Host,\n // so the shell restores origin-domain Host + X-Forwarded-Host itself.\n // Runs first so EVERY origin-bound return (skip / human / allow) is\n // correctly routed — and so the normalized request the core sees\n // already carries the viewer host in X-Forwarded-Host.\n normalizeOriginHost(request);\n\n const apiKey = await resolveApiKey({ ...options, log });\n const config = await loadEdgeConfig(options, apiKey);\n\n const { application } = await evaluateEdgeRequest(\n { ...options, log },\n toNormalizedRequest(request),\n config,\n apiKey\n );\n\n return applyCoreOutcome(request, application);\n } catch (err) {\n // Fail-open, unconditionally: the shell must never take the site down.\n log('error', 'edge handler error — passing request through', {\n error: err instanceof Error ? err.message : String(err),\n });\n return request;\n }\n };\n}\n","/**\n * CloudFront platform I/O — the Lambda@Edge side of the edge core's\n * PlatformIo contract, plus origin-request event helpers.\n *\n * The Lambda@Edge event shape puts headers in `{ key, value }[]` arrays\n * keyed by LOWERCASED header name, and (with IncludeBody) the body arrives\n * base64-encoded with an `inputTruncated` flag when it exceeded\n * CloudFront's ~1MB exposure cap. `toNormalizedRequest` converts that into\n * the core's NormalizedRequest; `applyCoreOutcome` executes the core's\n * abstract outcome application against the CloudFront request/response\n * primitives.\n */\n\nimport type {\n CloudFrontRequest,\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n CloudFrontHeaders,\n CloudFrontResultResponse,\n} from 'aws-lambda';\nimport type {\n NormalizedRequest,\n OutcomeApplication,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { buildObservedMetadata as buildObservedMetadataCore } from '@astrasyncai/verification-gateway/edge-core';\nimport type { ObservedMetadata } from '@astrasyncai/verification-gateway/metadata-capture';\n\nexport function getRequest(event: CloudFrontRequestEvent): CloudFrontRequest {\n return event.Records[0].cf.request;\n}\n\n/** First value of a header (lowercase name), or undefined. */\nexport function getHeader(request: CloudFrontRequest, name: string): string | undefined {\n return request.headers[name.toLowerCase()]?.[0]?.value;\n}\n\n/**\n * Flatten CloudFront headers into the `Record<string, string | string[]>`\n * shape the SDK extractors and verify-access artifacts expect.\n */\nexport function flattenHeaders(headers: CloudFrontHeaders): Record<string, string | string[]> {\n const out: Record<string, string | string[]> = {};\n for (const [name, entries] of Object.entries(headers)) {\n if (!entries?.length) continue;\n out[name] = entries.length === 1 ? entries[0].value : entries.map((e) => e.value);\n }\n return out;\n}\n\n/**\n * Flatten CloudFront headers to single string values (multi-valued headers\n * joined with `\", \"`, standard HTTP field-value concatenation) — the\n * NormalizedRequest header shape.\n */\nfunction flattenHeadersSingle(headers: CloudFrontHeaders): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [name, entries] of Object.entries(headers)) {\n if (!entries?.length) continue;\n out[name] = entries.map((e) => e.value).join(', ');\n }\n return out;\n}\n\nexport interface DecodedBody {\n /** UTF-8 body text, undefined when there is no body. */\n text?: string;\n /** True when CloudFront truncated the body before handing it to the trigger. */\n truncated: boolean;\n}\n\nexport function decodeBody(request: CloudFrontRequest): DecodedBody {\n const body = request.body;\n if (!body?.data) return { truncated: false };\n const text =\n body.encoding === 'base64' ? Buffer.from(body.data, 'base64').toString('utf8') : body.data;\n return { text, truncated: body.inputTruncated === true };\n}\n\n/**\n * Connection-layer signals the edge can see but the SDK cannot. CloudFront\n * injects the `CloudFront-Viewer-*` header family (IP, ASN, country, TLS\n * version/cipher, HTTP version, device class) when the OriginRequestPolicy\n * whitelists them (see infra/template.yaml). Absent values are simply\n * omitted — a partial connection block is fine.\n */\nexport function collectConnectionMetadata(request: CloudFrontRequest): Record<string, string> {\n const out: Record<string, string> = {};\n const put = (key: string, headerName: string, transform?: (v: string) => string) => {\n const v = getHeader(request, headerName);\n if (v) out[key] = transform ? transform(v) : v;\n };\n // `cloudfront-viewer-address` is `ip:port` (IPv6 keeps its brackets); take the\n // address portion the same way the beacon/pipeline already do.\n const addr = getHeader(request, 'cloudfront-viewer-address');\n if (addr) out.ip = addr.replace(/:\\d+$/, '');\n put('country', 'cloudfront-viewer-country');\n put('countryName', 'cloudfront-viewer-country-name');\n put('region', 'cloudfront-viewer-country-region');\n put('countryRegionName', 'cloudfront-viewer-country-region-name');\n put('city', 'cloudfront-viewer-city');\n put('asn', 'cloudfront-viewer-asn');\n put('tlsVersion', 'cloudfront-viewer-tls');\n put('httpVersion', 'cloudfront-viewer-http-version');\n put('timeZone', 'cloudfront-viewer-time-zone');\n put('metroCode', 'cloudfront-viewer-metro-code');\n // JA3/JA4 client fingerprints (HTTPS viewers only; delivered solely via the\n // OriginRequestPolicy — see infra/template.yaml, kept in lockstep by test).\n put('tlsFingerprint', 'cloudfront-viewer-ja3-fingerprint');\n put('ja4', 'cloudfront-viewer-ja4-fingerprint');\n // Header-structure fingerprint. headerOrder can exceed the platform's\n // 256-char connection-value bound — truncate at the emitter.\n put('headerOrder', 'cloudfront-viewer-header-order', (v) => v.slice(0, 256));\n put('headerCount', 'cloudfront-viewer-header-count');\n // Device-class booleans → a single compact device string.\n const devices: string[] = [];\n for (const [label, header] of [\n ['desktop', 'cloudfront-is-desktop-viewer'],\n ['mobile', 'cloudfront-is-mobile-viewer'],\n ['tablet', 'cloudfront-is-tablet-viewer'],\n ['smarttv', 'cloudfront-is-smarttv-viewer'],\n ] as const) {\n if (getHeader(request, header) === 'true') devices.push(label);\n }\n if (devices.length) out.device = devices.join(',');\n return out;\n}\n\n/**\n * CloudFront PlatformIo normalization: origin-request event → the edge\n * core's NormalizedRequest. `clientIp` keeps the pipeline's historical\n * viewer-address derivation (address portion before the first `:`) so\n * verify-access/beacon telemetry is byte-identical to earlier releases.\n */\nexport function toNormalizedRequest(request: CloudFrontRequest): NormalizedRequest {\n const body = decodeBody(request);\n const connection = collectConnectionMetadata(request);\n const clientIp = getHeader(request, 'cloudfront-viewer-address')?.split(':')[0];\n return {\n method: request.method,\n uri: request.uri,\n querystring: request.querystring ?? '',\n headers: flattenHeadersSingle(request.headers),\n ...(body.text !== undefined && { bodyText: body.text }),\n ...(body.truncated && { bodyTruncated: true }),\n ...(clientIp && { clientIp }),\n ...(Object.keys(connection).length > 0 && { connection }),\n };\n}\n\n/**\n * Execute the core's abstract outcome application against the CloudFront\n * request: `pass` mutates the origin-bound request (strip spoofable\n * prefixes, inject trust headers) and returns it; `respond` short-circuits\n * with a CloudFront JSON response.\n */\nexport function applyCoreOutcome(\n request: CloudFrontRequest,\n application: OutcomeApplication\n): CloudFrontRequestResult {\n if (application.action === 'respond') {\n return jsonResponse(application.status, application.bodyJson, application.headers);\n }\n for (const prefix of application.stripHeaderPrefixes) {\n stripHeadersByPrefix(request, prefix);\n }\n for (const [name, value] of Object.entries(application.injectHeaders)) {\n setHeader(request, name, value);\n }\n return request;\n}\n\n/**\n * The FULL sanitised metadata capture for one request: every inbound header\n * (secrets stripped, credential headers reduced to a safe format prefix)\n * plus the connection-layer block. Forwarded on both the verify-access call\n * and the beacon.\n */\nexport function buildObservedMetadata(request: CloudFrontRequest): ObservedMetadata {\n return buildObservedMetadataCore(toNormalizedRequest(request));\n}\n\n/**\n * Normalize the origin-bound `Host` header.\n *\n * To receive the `CloudFront-Viewer-*` connection headers (IP/ASN/TLS/country)\n * we must move off the managed `AllViewerExceptHostHeader` origin-request policy\n * onto one that whitelists those headers — but every such policy ALSO forwards\n * the viewer `Host`, which would break vhost / PaaS origin routing that expects\n * the origin's own hostname. So the Lambda takes ownership of the Host rewrite:\n * stash the public (viewer) host into `X-Forwarded-Host` for downstream use and\n * detection, then set `Host` to the origin's domain (available on the event as\n * `request.origin.custom.domainName`). No-ops when the origin domain is absent\n * (synthetic test events) or already matches — so it is safe on every path,\n * including static-asset pass-through.\n */\nexport function normalizeOriginHost(request: CloudFrontRequest): void {\n const originDomain =\n request.origin?.custom?.domainName ?? request.origin?.s3?.domainName ?? undefined;\n if (!originDomain) return;\n const currentHost = getHeader(request, 'host');\n if (!currentHost || currentHost === originDomain) return;\n if (!getHeader(request, 'x-forwarded-host')) {\n setHeader(request, 'X-Forwarded-Host', currentHost);\n }\n setHeader(request, 'Host', originDomain);\n}\n\n/** Full request URL as the origin will see it (scheme fixed to https). */\nexport function requestUrl(request: CloudFrontRequest, host: string): string {\n const qs = request.querystring ? `?${request.querystring}` : '';\n return `https://${host}${request.uri}${qs}`;\n}\n\n/**\n * Set a header on the origin-bound request (overwrites all prior values).\n */\nexport function setHeader(request: CloudFrontRequest, name: string, value: string): void {\n request.headers[name.toLowerCase()] = [{ key: name, value }];\n}\n\n/**\n * Strip every inbound header matching a prefix (lowercase compare). Used to\n * drop spoofed `x-astrasync-*` trust headers before the shell injects its\n * own — in BOTH modes, so a caller can never smuggle trust past the origin.\n */\nexport function stripHeadersByPrefix(request: CloudFrontRequest, prefix: string): void {\n const p = prefix.toLowerCase();\n for (const name of Object.keys(request.headers)) {\n if (name.startsWith(p)) delete request.headers[name];\n }\n}\n\nexport function jsonResponse(\n status: number,\n body: unknown,\n extraHeaders?: Record<string, string>\n): CloudFrontResultResponse {\n const headers: CloudFrontResultResponse['headers'] = {\n 'content-type': [{ key: 'Content-Type', value: 'application/json' }],\n };\n for (const [name, value] of Object.entries(extraHeaders ?? {})) {\n headers[name.toLowerCase()] = [{ key: name, value }];\n }\n return {\n status: String(status),\n statusDescription: status === 403 ? 'Forbidden' : status === 202 ? 'Accepted' : 'OK',\n headers,\n body: JSON.stringify(body),\n };\n}\n","/**\n * Runtime configuration for the edge shell.\n *\n * Lambda@Edge forbids environment variables, so configuration is layered:\n * 1. Build-time constants baked into the bundle (apiBaseUrl,\n * counterpartyId, counterpartyUrl, apiKeySecretName) — see\n * `tsup.config.ts` define block and `infra/deploy.sh`.\n * 2. The merchant API key from AWS Secrets Manager (us-east-1), fetched\n * lazily once per container and cached with a 15-minute TTL so\n * rotation needs no redeploy. Observe mode works fully without it —\n * the beacon endpoint is unauthenticated and verify() degrades\n * keyless — so a Secrets Manager outage can never take the shell down.\n * 3. The dashboard EdgeConfig via the SDK's getEdgeConfig (60s TTL,\n * stale-while-revalidate, enforce→observe degradation after 24h\n * staleness, DEFAULT_EDGE_CONFIG fallback — all in the SDK).\n */\n\nimport {\n getEdgeConfig,\n DEFAULT_EDGE_CONFIG,\n type EdgeConfig,\n} from '@astrasyncai/verification-gateway/edge-config';\nimport type { EdgeAdapterOptions } from './types';\n\nconst SECRET_TTL_MS = 15 * 60 * 1000;\n\ninterface SecretCacheEntry {\n value: string | undefined;\n fetchedAt: number;\n}\n\nconst secretCache = new Map<string, SecretCacheEntry>();\n\nasync function defaultFetchSecret(secretName: string): Promise<string | undefined> {\n // Lazy import so tests (and the library build) never touch the AWS SDK\n // unless a secret name is actually configured.\n const { SecretsManagerClient, GetSecretValueCommand } =\n await import('@aws-sdk/client-secrets-manager');\n // Lambda@Edge replicates globally but secrets live in us-east-1 (the\n // Lambda@Edge home region).\n const client = new SecretsManagerClient({ region: 'us-east-1' });\n const result = await client.send(new GetSecretValueCommand({ SecretId: secretName }));\n return result.SecretString ?? undefined;\n}\n\n/**\n * Resolve the merchant API key. Never throws — a missing/unreachable secret\n * degrades to keyless operation (observe mode is fully functional without\n * it; enforce mode without a key cannot fetch a config that enforces, so it\n * degrades to observe by construction).\n */\nexport async function resolveApiKey(options: EdgeAdapterOptions): Promise<string | undefined> {\n if (options.apiKey) return options.apiKey;\n const secretName = options.apiKeySecretName;\n if (!secretName) return undefined;\n\n const cached = secretCache.get(secretName);\n if (cached && Date.now() - cached.fetchedAt < SECRET_TTL_MS) return cached.value;\n\n try {\n const fetcher = options.fetchSecret ?? defaultFetchSecret;\n const value = await fetcher(secretName);\n secretCache.set(secretName, { value, fetchedAt: Date.now() });\n return value;\n } catch (err) {\n options.log?.('warn', 'Secrets Manager fetch failed — continuing keyless', {\n secretName,\n error: err instanceof Error ? err.message : String(err),\n });\n // Serve a stale value if we ever had one; otherwise keyless.\n return cached?.value;\n }\n}\n\n/**\n * Load the effective EdgeConfig. Delegates caching/fallback/degradation to\n * the SDK; adds the compile-time fallback override and never throws.\n */\nexport async function loadEdgeConfig(\n options: EdgeAdapterOptions,\n apiKey: string | undefined\n): Promise<EdgeConfig> {\n try {\n return await getEdgeConfig({ apiBaseUrl: options.apiBaseUrl, apiKey }, options.counterpartyId, {\n ttlMs: options.configTtlMs,\n });\n } catch {\n return options.fallbackConfig ?? DEFAULT_EDGE_CONFIG;\n }\n}\n\n/** Reset module caches — tests only. */\nexport function _resetConfigCaches(): void {\n secretCache.clear();\n}\n","/**\n * Three-tier traffic classification — CloudFront wrapper over\n * the edge core's classifier. The classification logic itself (tier order,\n * UA patterns, path-rule resolution) lives in\n * `@astrasyncai/verification-gateway/edge-core` and is shared with every\n * edge platform adapter.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport {\n classify as classifyCore,\n resolvePolicy,\n _patterns,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest } from './cf';\nimport type { Classification } from './types';\n\nexport { resolvePolicy, _patterns };\n\n/** Classify one CloudFront origin-request. See the edge core for the tier order. */\nexport function classify(\n request: CloudFrontRequest,\n host: string,\n depth: 'classify' | 'authenticate' | 'authorize'\n): Classification {\n return classifyCore(toNormalizedRequest(request), host, depth);\n}\n","/**\n * Crypto-free commerce-protocol detection (fixed detection order) —\n * CloudFront wrapper over the edge core's detector. The sniffing logic\n * (header shapes, compact-JWT patterns, body field names) lives in\n * `@astrasyncai/verification-gateway/edge-core`; this module adapts the\n * CloudFront event shape (separately-decoded body) onto it.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport {\n detectCommerce as detectCommerceCore,\n type DetectedCommerce,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest, type DecodedBody } from './cf';\n\nexport type { DetectedCommerce };\n\n/**\n * Detect commerce-protocol credentials on a CloudFront origin-request.\n * `body` is the caller-decoded request body (see `decodeBody`) — kept as an\n * explicit parameter so callers that already decoded it don't pay twice.\n */\nexport function detectCommerce(\n request: CloudFrontRequest,\n host: string,\n body: DecodedBody\n): DetectedCommerce | undefined {\n const normalized = toNormalizedRequest(request);\n return detectCommerceCore(\n {\n ...normalized,\n bodyText: body.text,\n bodyTruncated: body.truncated,\n },\n host\n );\n}\n","/**\n * Depth-controlled verification per tier — CloudFront wrapper over the edge\n * core's tier pipeline. The verification semantics (what each depth looks\n * at and forwards, the fail-open rules, the verify-access request shape)\n * live in `@astrasyncai/verification-gateway/edge-core`; the edge never\n * verifies credentials itself — verify-access is the canonical sole\n * verification sink.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport { runTier as runTierCore } from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest } from './cf';\nimport type { Classification, EdgeAdapterOptions, EffectivePolicy, TierOutcome } from './types';\n\n/**\n * Run the tier-appropriate verification/telemetry for one classified\n * request. NEVER throws; on any failure returns an `allow` outcome with the\n * failure recorded in `reasons` (infra failures fail open — a presented\n * credential the BACKEND rejected is a deny, but an unreachable backend is\n * not a rejection).\n */\nexport async function runTier(\n options: EdgeAdapterOptions,\n request: CloudFrontRequest,\n classification: Classification,\n policy: EffectivePolicy,\n apiKey: string | undefined,\n sampling: { anonymousBeaconRate: number }\n): Promise<TierOutcome> {\n return runTierCore(\n options,\n toNormalizedRequest(request),\n classification,\n policy,\n apiKey,\n sampling\n );\n}\n","/**\n * Decision → CloudFront result wiring (enforcement + ASAT trust-attestation\n * headers). The decision logic (observe shadow stamps, misconfig fail-open,\n * enforce-mode response bodies) lives in the edge core; this module\n * executes the core's abstract outcome application against CloudFront\n * request/response primitives.\n */\n\nimport type { CloudFrontRequest, CloudFrontRequestResult } from 'aws-lambda';\nimport {\n applyOutcome as resolveOutcomeApplication,\n ASAT_HEADERS,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { applyCoreOutcome } from './cf';\nimport type { EdgeAdapterOptions, EffectivePolicy, TierOutcome } from './types';\n\nexport { ASAT_HEADERS };\n\n/**\n * Apply the outcome. Always strips inbound spoofed `x-astrasync-*` headers\n * first (both modes — trust headers must only ever originate here).\n *\n * Observe mode: returns the request untouched apart from header hygiene and\n * an `X-AstraSync-Observed-Decision` stamp for origin-side log correlation.\n *\n * Enforce mode: ALLOW → request + ASAT headers; DENY → 403 machine-readable\n * body; MANUAL_REVIEW → 202 with step-up poll info.\n */\nexport function applyOutcome(\n options: EdgeAdapterOptions,\n request: CloudFrontRequest,\n outcome: TierOutcome,\n policy: EffectivePolicy\n): CloudFrontRequestResult {\n return applyCoreOutcome(request, resolveOutcomeApplication(options, outcome, policy));\n}\n","/**\n * Protocol-specific machine-readable messaging for enforce-mode\n * responses. The message catalogue and body builders live in the edge core\n * (shared across every edge platform adapter); re-exported here so the\n * adapter's public API is unchanged.\n */\n\nexport {\n buildDenyBody,\n buildManualReviewBody,\n type DenyBody,\n type ManualReviewBody,\n} from '@astrasyncai/verification-gateway/edge-core';\n","/**\n * Time-budgeted event emission — CloudFront wrapper over the edge core's\n * telemetry. Lambda@Edge has no `waitUntil`, so every network call is\n * awaited under an explicit budget and DROPPED on timeout (the core's\n * default posture): latency to the customer's traffic is bounded, and a\n * slow AstraSync API costs telemetry, never page loads.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport {\n emitBeacon as emitBeaconCore,\n withBudget,\n sampled,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest } from './cf';\nimport type { Classification, EdgeAdapterOptions } from './types';\n\nexport { withBudget, sampled };\n\n/**\n * Emit an unregistered-attempt beacon for `identified` (non-crypto depths)\n * and sampled `anonymous-bot` traffic. Unauthenticated by design\n * (CSRF-exempt on the backend); the backend fingerprints the UA and\n * find-or-provisions orphan platform agents server-side.\n */\nexport async function emitBeacon(\n options: EdgeAdapterOptions,\n request: CloudFrontRequest,\n classification: Classification,\n budgetMs: number\n): Promise<void> {\n await emitBeaconCore(options, toNormalizedRequest(request), classification, budgetMs);\n}\n"],"mappings":"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","names":["import_edge_core","import_edge_core","classifyCore","import_edge_core","detectCommerceCore","import_edge_core","runTierCore","import_edge_core","resolveOutcomeApplication","import_edge_core","import_edge_core","emitBeaconCore"]}
package/dist/index.mjs CHANGED
@@ -42,10 +42,17 @@ function collectConnectionMetadata(request) {
42
42
  put("country", "cloudfront-viewer-country");
43
43
  put("countryName", "cloudfront-viewer-country-name");
44
44
  put("region", "cloudfront-viewer-country-region");
45
+ put("countryRegionName", "cloudfront-viewer-country-region-name");
45
46
  put("city", "cloudfront-viewer-city");
46
47
  put("asn", "cloudfront-viewer-asn");
47
48
  put("tlsVersion", "cloudfront-viewer-tls");
48
49
  put("httpVersion", "cloudfront-viewer-http-version");
50
+ put("timeZone", "cloudfront-viewer-time-zone");
51
+ put("metroCode", "cloudfront-viewer-metro-code");
52
+ put("tlsFingerprint", "cloudfront-viewer-ja3-fingerprint");
53
+ put("ja4", "cloudfront-viewer-ja4-fingerprint");
54
+ put("headerOrder", "cloudfront-viewer-header-order", (v) => v.slice(0, 256));
55
+ put("headerCount", "cloudfront-viewer-header-count");
49
56
  const devices = [];
50
57
  for (const [label, header] of [
51
58
  ["desktop", "cloudfront-is-desktop-viewer"],
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/handler.ts","../src/cf.ts","../src/config.ts","../src/classify.ts","../src/detect.ts","../src/pipeline.ts","../src/enforce.ts","../src/messaging.ts","../src/events.ts"],"sourcesContent":["/**\n * Trusted Agent Gateway edge shell — Lambda@Edge origin-request handler\n * factory.\n *\n * Composition per request:\n * config (cached) → edge core evaluation (path policy → classify → tier\n * pipeline) → apply outcome onto the CloudFront request\n *\n * THE invariant (tested): outside enforce mode the handler ALWAYS returns\n * the origin-bound request — any error, timeout, or misconfiguration\n * results in pass-through. A top-level catch guarantees it even against\n * bugs in the shell itself.\n */\n\nimport type { CloudFrontRequestEvent, CloudFrontRequestResult } from 'aws-lambda';\nimport { evaluateEdgeRequest } from '@astrasyncai/verification-gateway/edge-core';\nimport { getRequest, normalizeOriginHost, toNormalizedRequest, applyCoreOutcome } from './cf';\nimport { resolveApiKey, loadEdgeConfig } from './config';\nimport type { EdgeAdapterOptions } from './types';\n\nexport type EdgeHandler = (event: CloudFrontRequestEvent) => Promise<CloudFrontRequestResult>;\n\nexport function createEdgeHandler(options: EdgeAdapterOptions): EdgeHandler {\n const log =\n options.log ??\n ((level, message, data) => {\n // Lambda@Edge writes console output to the regional CloudWatch log\n // group of whichever edge location served the request.\n console[level === 'info' ? 'log' : level](`[astrasync-edge] ${message}`, data ?? '');\n });\n\n return async function handler(event) {\n const request = getRequest(event);\n try {\n // Own the Host rewrite (metadata capture): the CloudFront-Viewer-* capture\n // requires an origin-request policy that also forwards the viewer Host,\n // so the shell restores origin-domain Host + X-Forwarded-Host itself.\n // Runs first so EVERY origin-bound return (skip / human / allow) is\n // correctly routed — and so the normalized request the core sees\n // already carries the viewer host in X-Forwarded-Host.\n normalizeOriginHost(request);\n\n const apiKey = await resolveApiKey({ ...options, log });\n const config = await loadEdgeConfig(options, apiKey);\n\n const { application } = await evaluateEdgeRequest(\n { ...options, log },\n toNormalizedRequest(request),\n config,\n apiKey\n );\n\n return applyCoreOutcome(request, application);\n } catch (err) {\n // Fail-open, unconditionally: the shell must never take the site down.\n log('error', 'edge handler error — passing request through', {\n error: err instanceof Error ? err.message : String(err),\n });\n return request;\n }\n };\n}\n","/**\n * CloudFront platform I/O — the Lambda@Edge side of the edge core's\n * PlatformIo contract, plus origin-request event helpers.\n *\n * The Lambda@Edge event shape puts headers in `{ key, value }[]` arrays\n * keyed by LOWERCASED header name, and (with IncludeBody) the body arrives\n * base64-encoded with an `inputTruncated` flag when it exceeded\n * CloudFront's ~1MB exposure cap. `toNormalizedRequest` converts that into\n * the core's NormalizedRequest; `applyCoreOutcome` executes the core's\n * abstract outcome application against the CloudFront request/response\n * primitives.\n */\n\nimport type {\n CloudFrontRequest,\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n CloudFrontHeaders,\n CloudFrontResultResponse,\n} from 'aws-lambda';\nimport type {\n NormalizedRequest,\n OutcomeApplication,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { buildObservedMetadata as buildObservedMetadataCore } from '@astrasyncai/verification-gateway/edge-core';\nimport type { ObservedMetadata } from '@astrasyncai/verification-gateway/metadata-capture';\n\nexport function getRequest(event: CloudFrontRequestEvent): CloudFrontRequest {\n return event.Records[0].cf.request;\n}\n\n/** First value of a header (lowercase name), or undefined. */\nexport function getHeader(request: CloudFrontRequest, name: string): string | undefined {\n return request.headers[name.toLowerCase()]?.[0]?.value;\n}\n\n/**\n * Flatten CloudFront headers into the `Record<string, string | string[]>`\n * shape the SDK extractors and verify-access artifacts expect.\n */\nexport function flattenHeaders(headers: CloudFrontHeaders): Record<string, string | string[]> {\n const out: Record<string, string | string[]> = {};\n for (const [name, entries] of Object.entries(headers)) {\n if (!entries?.length) continue;\n out[name] = entries.length === 1 ? entries[0].value : entries.map((e) => e.value);\n }\n return out;\n}\n\n/**\n * Flatten CloudFront headers to single string values (multi-valued headers\n * joined with `\", \"`, standard HTTP field-value concatenation) — the\n * NormalizedRequest header shape.\n */\nfunction flattenHeadersSingle(headers: CloudFrontHeaders): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [name, entries] of Object.entries(headers)) {\n if (!entries?.length) continue;\n out[name] = entries.map((e) => e.value).join(', ');\n }\n return out;\n}\n\nexport interface DecodedBody {\n /** UTF-8 body text, undefined when there is no body. */\n text?: string;\n /** True when CloudFront truncated the body before handing it to the trigger. */\n truncated: boolean;\n}\n\nexport function decodeBody(request: CloudFrontRequest): DecodedBody {\n const body = request.body;\n if (!body?.data) return { truncated: false };\n const text =\n body.encoding === 'base64' ? Buffer.from(body.data, 'base64').toString('utf8') : body.data;\n return { text, truncated: body.inputTruncated === true };\n}\n\n/**\n * Connection-layer signals the edge can see but the SDK cannot. CloudFront\n * injects the `CloudFront-Viewer-*` header family (IP, ASN, country, TLS\n * version/cipher, HTTP version, device class) when the OriginRequestPolicy\n * whitelists them (see infra/template.yaml). Absent values are simply\n * omitted — a partial connection block is fine.\n */\nexport function collectConnectionMetadata(request: CloudFrontRequest): Record<string, string> {\n const out: Record<string, string> = {};\n const put = (key: string, headerName: string, transform?: (v: string) => string) => {\n const v = getHeader(request, headerName);\n if (v) out[key] = transform ? transform(v) : v;\n };\n // `cloudfront-viewer-address` is `ip:port` (IPv6 keeps its brackets); take the\n // address portion the same way the beacon/pipeline already do.\n const addr = getHeader(request, 'cloudfront-viewer-address');\n if (addr) out.ip = addr.replace(/:\\d+$/, '');\n put('country', 'cloudfront-viewer-country');\n put('countryName', 'cloudfront-viewer-country-name');\n put('region', 'cloudfront-viewer-country-region');\n put('city', 'cloudfront-viewer-city');\n put('asn', 'cloudfront-viewer-asn');\n put('tlsVersion', 'cloudfront-viewer-tls');\n put('httpVersion', 'cloudfront-viewer-http-version');\n // Device-class booleans → a single compact device string.\n const devices: string[] = [];\n for (const [label, header] of [\n ['desktop', 'cloudfront-is-desktop-viewer'],\n ['mobile', 'cloudfront-is-mobile-viewer'],\n ['tablet', 'cloudfront-is-tablet-viewer'],\n ['smarttv', 'cloudfront-is-smarttv-viewer'],\n ] as const) {\n if (getHeader(request, header) === 'true') devices.push(label);\n }\n if (devices.length) out.device = devices.join(',');\n return out;\n}\n\n/**\n * CloudFront PlatformIo normalization: origin-request event → the edge\n * core's NormalizedRequest. `clientIp` keeps the pipeline's historical\n * viewer-address derivation (address portion before the first `:`) so\n * verify-access/beacon telemetry is byte-identical to earlier releases.\n */\nexport function toNormalizedRequest(request: CloudFrontRequest): NormalizedRequest {\n const body = decodeBody(request);\n const connection = collectConnectionMetadata(request);\n const clientIp = getHeader(request, 'cloudfront-viewer-address')?.split(':')[0];\n return {\n method: request.method,\n uri: request.uri,\n querystring: request.querystring ?? '',\n headers: flattenHeadersSingle(request.headers),\n ...(body.text !== undefined && { bodyText: body.text }),\n ...(body.truncated && { bodyTruncated: true }),\n ...(clientIp && { clientIp }),\n ...(Object.keys(connection).length > 0 && { connection }),\n };\n}\n\n/**\n * Execute the core's abstract outcome application against the CloudFront\n * request: `pass` mutates the origin-bound request (strip spoofable\n * prefixes, inject trust headers) and returns it; `respond` short-circuits\n * with a CloudFront JSON response.\n */\nexport function applyCoreOutcome(\n request: CloudFrontRequest,\n application: OutcomeApplication\n): CloudFrontRequestResult {\n if (application.action === 'respond') {\n return jsonResponse(application.status, application.bodyJson, application.headers);\n }\n for (const prefix of application.stripHeaderPrefixes) {\n stripHeadersByPrefix(request, prefix);\n }\n for (const [name, value] of Object.entries(application.injectHeaders)) {\n setHeader(request, name, value);\n }\n return request;\n}\n\n/**\n * The FULL sanitised metadata capture for one request: every inbound header\n * (secrets stripped, credential headers reduced to a safe format prefix)\n * plus the connection-layer block. Forwarded on both the verify-access call\n * and the beacon.\n */\nexport function buildObservedMetadata(request: CloudFrontRequest): ObservedMetadata {\n return buildObservedMetadataCore(toNormalizedRequest(request));\n}\n\n/**\n * Normalize the origin-bound `Host` header.\n *\n * To receive the `CloudFront-Viewer-*` connection headers (IP/ASN/TLS/country)\n * we must move off the managed `AllViewerExceptHostHeader` origin-request policy\n * onto one that whitelists those headers — but every such policy ALSO forwards\n * the viewer `Host`, which would break vhost / PaaS origin routing that expects\n * the origin's own hostname. So the Lambda takes ownership of the Host rewrite:\n * stash the public (viewer) host into `X-Forwarded-Host` for downstream use and\n * detection, then set `Host` to the origin's domain (available on the event as\n * `request.origin.custom.domainName`). No-ops when the origin domain is absent\n * (synthetic test events) or already matches — so it is safe on every path,\n * including static-asset pass-through.\n */\nexport function normalizeOriginHost(request: CloudFrontRequest): void {\n const originDomain =\n request.origin?.custom?.domainName ?? request.origin?.s3?.domainName ?? undefined;\n if (!originDomain) return;\n const currentHost = getHeader(request, 'host');\n if (!currentHost || currentHost === originDomain) return;\n if (!getHeader(request, 'x-forwarded-host')) {\n setHeader(request, 'X-Forwarded-Host', currentHost);\n }\n setHeader(request, 'Host', originDomain);\n}\n\n/** Full request URL as the origin will see it (scheme fixed to https). */\nexport function requestUrl(request: CloudFrontRequest, host: string): string {\n const qs = request.querystring ? `?${request.querystring}` : '';\n return `https://${host}${request.uri}${qs}`;\n}\n\n/**\n * Set a header on the origin-bound request (overwrites all prior values).\n */\nexport function setHeader(request: CloudFrontRequest, name: string, value: string): void {\n request.headers[name.toLowerCase()] = [{ key: name, value }];\n}\n\n/**\n * Strip every inbound header matching a prefix (lowercase compare). Used to\n * drop spoofed `x-astrasync-*` trust headers before the shell injects its\n * own — in BOTH modes, so a caller can never smuggle trust past the origin.\n */\nexport function stripHeadersByPrefix(request: CloudFrontRequest, prefix: string): void {\n const p = prefix.toLowerCase();\n for (const name of Object.keys(request.headers)) {\n if (name.startsWith(p)) delete request.headers[name];\n }\n}\n\nexport function jsonResponse(\n status: number,\n body: unknown,\n extraHeaders?: Record<string, string>\n): CloudFrontResultResponse {\n const headers: CloudFrontResultResponse['headers'] = {\n 'content-type': [{ key: 'Content-Type', value: 'application/json' }],\n };\n for (const [name, value] of Object.entries(extraHeaders ?? {})) {\n headers[name.toLowerCase()] = [{ key: name, value }];\n }\n return {\n status: String(status),\n statusDescription: status === 403 ? 'Forbidden' : status === 202 ? 'Accepted' : 'OK',\n headers,\n body: JSON.stringify(body),\n };\n}\n","/**\n * Runtime configuration for the edge shell.\n *\n * Lambda@Edge forbids environment variables, so configuration is layered:\n * 1. Build-time constants baked into the bundle (apiBaseUrl,\n * counterpartyId, counterpartyUrl, apiKeySecretName) — see\n * `tsup.config.ts` define block and `infra/deploy.sh`.\n * 2. The merchant API key from AWS Secrets Manager (us-east-1), fetched\n * lazily once per container and cached with a 15-minute TTL so\n * rotation needs no redeploy. Observe mode works fully without it —\n * the beacon endpoint is unauthenticated and verify() degrades\n * keyless — so a Secrets Manager outage can never take the shell down.\n * 3. The dashboard EdgeConfig via the SDK's getEdgeConfig (60s TTL,\n * stale-while-revalidate, enforce→observe degradation after 24h\n * staleness, DEFAULT_EDGE_CONFIG fallback — all in the SDK).\n */\n\nimport {\n getEdgeConfig,\n DEFAULT_EDGE_CONFIG,\n type EdgeConfig,\n} from '@astrasyncai/verification-gateway/edge-config';\nimport type { EdgeAdapterOptions } from './types';\n\nconst SECRET_TTL_MS = 15 * 60 * 1000;\n\ninterface SecretCacheEntry {\n value: string | undefined;\n fetchedAt: number;\n}\n\nconst secretCache = new Map<string, SecretCacheEntry>();\n\nasync function defaultFetchSecret(secretName: string): Promise<string | undefined> {\n // Lazy import so tests (and the library build) never touch the AWS SDK\n // unless a secret name is actually configured.\n const { SecretsManagerClient, GetSecretValueCommand } =\n await import('@aws-sdk/client-secrets-manager');\n // Lambda@Edge replicates globally but secrets live in us-east-1 (the\n // Lambda@Edge home region).\n const client = new SecretsManagerClient({ region: 'us-east-1' });\n const result = await client.send(new GetSecretValueCommand({ SecretId: secretName }));\n return result.SecretString ?? undefined;\n}\n\n/**\n * Resolve the merchant API key. Never throws — a missing/unreachable secret\n * degrades to keyless operation (observe mode is fully functional without\n * it; enforce mode without a key cannot fetch a config that enforces, so it\n * degrades to observe by construction).\n */\nexport async function resolveApiKey(options: EdgeAdapterOptions): Promise<string | undefined> {\n if (options.apiKey) return options.apiKey;\n const secretName = options.apiKeySecretName;\n if (!secretName) return undefined;\n\n const cached = secretCache.get(secretName);\n if (cached && Date.now() - cached.fetchedAt < SECRET_TTL_MS) return cached.value;\n\n try {\n const fetcher = options.fetchSecret ?? defaultFetchSecret;\n const value = await fetcher(secretName);\n secretCache.set(secretName, { value, fetchedAt: Date.now() });\n return value;\n } catch (err) {\n options.log?.('warn', 'Secrets Manager fetch failed — continuing keyless', {\n secretName,\n error: err instanceof Error ? err.message : String(err),\n });\n // Serve a stale value if we ever had one; otherwise keyless.\n return cached?.value;\n }\n}\n\n/**\n * Load the effective EdgeConfig. Delegates caching/fallback/degradation to\n * the SDK; adds the compile-time fallback override and never throws.\n */\nexport async function loadEdgeConfig(\n options: EdgeAdapterOptions,\n apiKey: string | undefined\n): Promise<EdgeConfig> {\n try {\n return await getEdgeConfig({ apiBaseUrl: options.apiBaseUrl, apiKey }, options.counterpartyId, {\n ttlMs: options.configTtlMs,\n });\n } catch {\n return options.fallbackConfig ?? DEFAULT_EDGE_CONFIG;\n }\n}\n\n/** Reset module caches — tests only. */\nexport function _resetConfigCaches(): void {\n secretCache.clear();\n}\n","/**\n * Three-tier traffic classification — CloudFront wrapper over\n * the edge core's classifier. The classification logic itself (tier order,\n * UA patterns, path-rule resolution) lives in\n * `@astrasyncai/verification-gateway/edge-core` and is shared with every\n * edge platform adapter.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport {\n classify as classifyCore,\n resolvePolicy,\n _patterns,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest } from './cf';\nimport type { Classification } from './types';\n\nexport { resolvePolicy, _patterns };\n\n/** Classify one CloudFront origin-request. See the edge core for the tier order. */\nexport function classify(\n request: CloudFrontRequest,\n host: string,\n depth: 'classify' | 'authenticate' | 'authorize'\n): Classification {\n return classifyCore(toNormalizedRequest(request), host, depth);\n}\n","/**\n * Crypto-free commerce-protocol detection (fixed detection order) —\n * CloudFront wrapper over the edge core's detector. The sniffing logic\n * (header shapes, compact-JWT patterns, body field names) lives in\n * `@astrasyncai/verification-gateway/edge-core`; this module adapts the\n * CloudFront event shape (separately-decoded body) onto it.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport {\n detectCommerce as detectCommerceCore,\n type DetectedCommerce,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest, type DecodedBody } from './cf';\n\nexport type { DetectedCommerce };\n\n/**\n * Detect commerce-protocol credentials on a CloudFront origin-request.\n * `body` is the caller-decoded request body (see `decodeBody`) — kept as an\n * explicit parameter so callers that already decoded it don't pay twice.\n */\nexport function detectCommerce(\n request: CloudFrontRequest,\n host: string,\n body: DecodedBody\n): DetectedCommerce | undefined {\n const normalized = toNormalizedRequest(request);\n return detectCommerceCore(\n {\n ...normalized,\n bodyText: body.text,\n bodyTruncated: body.truncated,\n },\n host\n );\n}\n","/**\n * Depth-controlled verification per tier — CloudFront wrapper over the edge\n * core's tier pipeline. The verification semantics (what each depth looks\n * at and forwards, the fail-open rules, the verify-access request shape)\n * live in `@astrasyncai/verification-gateway/edge-core`; the edge never\n * verifies credentials itself — verify-access is the canonical sole\n * verification sink.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport { runTier as runTierCore } from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest } from './cf';\nimport type { Classification, EdgeAdapterOptions, EffectivePolicy, TierOutcome } from './types';\n\n/**\n * Run the tier-appropriate verification/telemetry for one classified\n * request. NEVER throws; on any failure returns an `allow` outcome with the\n * failure recorded in `reasons` (infra failures fail open — a presented\n * credential the BACKEND rejected is a deny, but an unreachable backend is\n * not a rejection).\n */\nexport async function runTier(\n options: EdgeAdapterOptions,\n request: CloudFrontRequest,\n classification: Classification,\n policy: EffectivePolicy,\n apiKey: string | undefined,\n sampling: { anonymousBeaconRate: number }\n): Promise<TierOutcome> {\n return runTierCore(\n options,\n toNormalizedRequest(request),\n classification,\n policy,\n apiKey,\n sampling\n );\n}\n","/**\n * Decision → CloudFront result wiring (enforcement + ASAT trust-attestation\n * headers). The decision logic (observe shadow stamps, misconfig fail-open,\n * enforce-mode response bodies) lives in the edge core; this module\n * executes the core's abstract outcome application against CloudFront\n * request/response primitives.\n */\n\nimport type { CloudFrontRequest, CloudFrontRequestResult } from 'aws-lambda';\nimport {\n applyOutcome as resolveOutcomeApplication,\n ASAT_HEADERS,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { applyCoreOutcome } from './cf';\nimport type { EdgeAdapterOptions, EffectivePolicy, TierOutcome } from './types';\n\nexport { ASAT_HEADERS };\n\n/**\n * Apply the outcome. Always strips inbound spoofed `x-astrasync-*` headers\n * first (both modes — trust headers must only ever originate here).\n *\n * Observe mode: returns the request untouched apart from header hygiene and\n * an `X-AstraSync-Observed-Decision` stamp for origin-side log correlation.\n *\n * Enforce mode: ALLOW → request + ASAT headers; DENY → 403 machine-readable\n * body; MANUAL_REVIEW → 202 with step-up poll info.\n */\nexport function applyOutcome(\n options: EdgeAdapterOptions,\n request: CloudFrontRequest,\n outcome: TierOutcome,\n policy: EffectivePolicy\n): CloudFrontRequestResult {\n return applyCoreOutcome(request, resolveOutcomeApplication(options, outcome, policy));\n}\n","/**\n * Protocol-specific machine-readable messaging for enforce-mode\n * responses. The message catalogue and body builders live in the edge core\n * (shared across every edge platform adapter); re-exported here so the\n * adapter's public API is unchanged.\n */\n\nexport {\n buildDenyBody,\n buildManualReviewBody,\n type DenyBody,\n type ManualReviewBody,\n} from '@astrasyncai/verification-gateway/edge-core';\n","/**\n * Time-budgeted event emission — CloudFront wrapper over the edge core's\n * telemetry. Lambda@Edge has no `waitUntil`, so every network call is\n * awaited under an explicit budget and DROPPED on timeout (the core's\n * default posture): latency to the customer's traffic is bounded, and a\n * slow AstraSync API costs telemetry, never page loads.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport {\n emitBeacon as emitBeaconCore,\n withBudget,\n sampled,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest } from './cf';\nimport type { Classification, EdgeAdapterOptions } from './types';\n\nexport { withBudget, sampled };\n\n/**\n * Emit an unregistered-attempt beacon for `identified` (non-crypto depths)\n * and sampled `anonymous-bot` traffic. Unauthenticated by design\n * (CSRF-exempt on the backend); the backend fingerprints the UA and\n * find-or-provisions orphan platform agents server-side.\n */\nexport async function emitBeacon(\n options: EdgeAdapterOptions,\n request: CloudFrontRequest,\n classification: Classification,\n budgetMs: number\n): Promise<void> {\n await emitBeaconCore(options, toNormalizedRequest(request), classification, budgetMs);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/handler.ts","../src/cf.ts","../src/config.ts","../src/classify.ts","../src/detect.ts","../src/pipeline.ts","../src/enforce.ts","../src/messaging.ts","../src/events.ts"],"sourcesContent":["/**\n * Trusted Agent Gateway edge shell — Lambda@Edge origin-request handler\n * factory.\n *\n * Composition per request:\n * config (cached) → edge core evaluation (path policy → classify → tier\n * pipeline) → apply outcome onto the CloudFront request\n *\n * THE invariant (tested): outside enforce mode the handler ALWAYS returns\n * the origin-bound request — any error, timeout, or misconfiguration\n * results in pass-through. A top-level catch guarantees it even against\n * bugs in the shell itself.\n */\n\nimport type { CloudFrontRequestEvent, CloudFrontRequestResult } from 'aws-lambda';\nimport { evaluateEdgeRequest } from '@astrasyncai/verification-gateway/edge-core';\nimport { getRequest, normalizeOriginHost, toNormalizedRequest, applyCoreOutcome } from './cf';\nimport { resolveApiKey, loadEdgeConfig } from './config';\nimport type { EdgeAdapterOptions } from './types';\n\nexport type EdgeHandler = (event: CloudFrontRequestEvent) => Promise<CloudFrontRequestResult>;\n\nexport function createEdgeHandler(options: EdgeAdapterOptions): EdgeHandler {\n const log =\n options.log ??\n ((level, message, data) => {\n // Lambda@Edge writes console output to the regional CloudWatch log\n // group of whichever edge location served the request.\n console[level === 'info' ? 'log' : level](`[astrasync-edge] ${message}`, data ?? '');\n });\n\n return async function handler(event) {\n const request = getRequest(event);\n try {\n // Own the Host rewrite (metadata capture): the CloudFront-Viewer-* capture\n // requires an origin-request policy that also forwards the viewer Host,\n // so the shell restores origin-domain Host + X-Forwarded-Host itself.\n // Runs first so EVERY origin-bound return (skip / human / allow) is\n // correctly routed — and so the normalized request the core sees\n // already carries the viewer host in X-Forwarded-Host.\n normalizeOriginHost(request);\n\n const apiKey = await resolveApiKey({ ...options, log });\n const config = await loadEdgeConfig(options, apiKey);\n\n const { application } = await evaluateEdgeRequest(\n { ...options, log },\n toNormalizedRequest(request),\n config,\n apiKey\n );\n\n return applyCoreOutcome(request, application);\n } catch (err) {\n // Fail-open, unconditionally: the shell must never take the site down.\n log('error', 'edge handler error — passing request through', {\n error: err instanceof Error ? err.message : String(err),\n });\n return request;\n }\n };\n}\n","/**\n * CloudFront platform I/O — the Lambda@Edge side of the edge core's\n * PlatformIo contract, plus origin-request event helpers.\n *\n * The Lambda@Edge event shape puts headers in `{ key, value }[]` arrays\n * keyed by LOWERCASED header name, and (with IncludeBody) the body arrives\n * base64-encoded with an `inputTruncated` flag when it exceeded\n * CloudFront's ~1MB exposure cap. `toNormalizedRequest` converts that into\n * the core's NormalizedRequest; `applyCoreOutcome` executes the core's\n * abstract outcome application against the CloudFront request/response\n * primitives.\n */\n\nimport type {\n CloudFrontRequest,\n CloudFrontRequestEvent,\n CloudFrontRequestResult,\n CloudFrontHeaders,\n CloudFrontResultResponse,\n} from 'aws-lambda';\nimport type {\n NormalizedRequest,\n OutcomeApplication,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { buildObservedMetadata as buildObservedMetadataCore } from '@astrasyncai/verification-gateway/edge-core';\nimport type { ObservedMetadata } from '@astrasyncai/verification-gateway/metadata-capture';\n\nexport function getRequest(event: CloudFrontRequestEvent): CloudFrontRequest {\n return event.Records[0].cf.request;\n}\n\n/** First value of a header (lowercase name), or undefined. */\nexport function getHeader(request: CloudFrontRequest, name: string): string | undefined {\n return request.headers[name.toLowerCase()]?.[0]?.value;\n}\n\n/**\n * Flatten CloudFront headers into the `Record<string, string | string[]>`\n * shape the SDK extractors and verify-access artifacts expect.\n */\nexport function flattenHeaders(headers: CloudFrontHeaders): Record<string, string | string[]> {\n const out: Record<string, string | string[]> = {};\n for (const [name, entries] of Object.entries(headers)) {\n if (!entries?.length) continue;\n out[name] = entries.length === 1 ? entries[0].value : entries.map((e) => e.value);\n }\n return out;\n}\n\n/**\n * Flatten CloudFront headers to single string values (multi-valued headers\n * joined with `\", \"`, standard HTTP field-value concatenation) — the\n * NormalizedRequest header shape.\n */\nfunction flattenHeadersSingle(headers: CloudFrontHeaders): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [name, entries] of Object.entries(headers)) {\n if (!entries?.length) continue;\n out[name] = entries.map((e) => e.value).join(', ');\n }\n return out;\n}\n\nexport interface DecodedBody {\n /** UTF-8 body text, undefined when there is no body. */\n text?: string;\n /** True when CloudFront truncated the body before handing it to the trigger. */\n truncated: boolean;\n}\n\nexport function decodeBody(request: CloudFrontRequest): DecodedBody {\n const body = request.body;\n if (!body?.data) return { truncated: false };\n const text =\n body.encoding === 'base64' ? Buffer.from(body.data, 'base64').toString('utf8') : body.data;\n return { text, truncated: body.inputTruncated === true };\n}\n\n/**\n * Connection-layer signals the edge can see but the SDK cannot. CloudFront\n * injects the `CloudFront-Viewer-*` header family (IP, ASN, country, TLS\n * version/cipher, HTTP version, device class) when the OriginRequestPolicy\n * whitelists them (see infra/template.yaml). Absent values are simply\n * omitted — a partial connection block is fine.\n */\nexport function collectConnectionMetadata(request: CloudFrontRequest): Record<string, string> {\n const out: Record<string, string> = {};\n const put = (key: string, headerName: string, transform?: (v: string) => string) => {\n const v = getHeader(request, headerName);\n if (v) out[key] = transform ? transform(v) : v;\n };\n // `cloudfront-viewer-address` is `ip:port` (IPv6 keeps its brackets); take the\n // address portion the same way the beacon/pipeline already do.\n const addr = getHeader(request, 'cloudfront-viewer-address');\n if (addr) out.ip = addr.replace(/:\\d+$/, '');\n put('country', 'cloudfront-viewer-country');\n put('countryName', 'cloudfront-viewer-country-name');\n put('region', 'cloudfront-viewer-country-region');\n put('countryRegionName', 'cloudfront-viewer-country-region-name');\n put('city', 'cloudfront-viewer-city');\n put('asn', 'cloudfront-viewer-asn');\n put('tlsVersion', 'cloudfront-viewer-tls');\n put('httpVersion', 'cloudfront-viewer-http-version');\n put('timeZone', 'cloudfront-viewer-time-zone');\n put('metroCode', 'cloudfront-viewer-metro-code');\n // JA3/JA4 client fingerprints (HTTPS viewers only; delivered solely via the\n // OriginRequestPolicy — see infra/template.yaml, kept in lockstep by test).\n put('tlsFingerprint', 'cloudfront-viewer-ja3-fingerprint');\n put('ja4', 'cloudfront-viewer-ja4-fingerprint');\n // Header-structure fingerprint. headerOrder can exceed the platform's\n // 256-char connection-value bound — truncate at the emitter.\n put('headerOrder', 'cloudfront-viewer-header-order', (v) => v.slice(0, 256));\n put('headerCount', 'cloudfront-viewer-header-count');\n // Device-class booleans → a single compact device string.\n const devices: string[] = [];\n for (const [label, header] of [\n ['desktop', 'cloudfront-is-desktop-viewer'],\n ['mobile', 'cloudfront-is-mobile-viewer'],\n ['tablet', 'cloudfront-is-tablet-viewer'],\n ['smarttv', 'cloudfront-is-smarttv-viewer'],\n ] as const) {\n if (getHeader(request, header) === 'true') devices.push(label);\n }\n if (devices.length) out.device = devices.join(',');\n return out;\n}\n\n/**\n * CloudFront PlatformIo normalization: origin-request event → the edge\n * core's NormalizedRequest. `clientIp` keeps the pipeline's historical\n * viewer-address derivation (address portion before the first `:`) so\n * verify-access/beacon telemetry is byte-identical to earlier releases.\n */\nexport function toNormalizedRequest(request: CloudFrontRequest): NormalizedRequest {\n const body = decodeBody(request);\n const connection = collectConnectionMetadata(request);\n const clientIp = getHeader(request, 'cloudfront-viewer-address')?.split(':')[0];\n return {\n method: request.method,\n uri: request.uri,\n querystring: request.querystring ?? '',\n headers: flattenHeadersSingle(request.headers),\n ...(body.text !== undefined && { bodyText: body.text }),\n ...(body.truncated && { bodyTruncated: true }),\n ...(clientIp && { clientIp }),\n ...(Object.keys(connection).length > 0 && { connection }),\n };\n}\n\n/**\n * Execute the core's abstract outcome application against the CloudFront\n * request: `pass` mutates the origin-bound request (strip spoofable\n * prefixes, inject trust headers) and returns it; `respond` short-circuits\n * with a CloudFront JSON response.\n */\nexport function applyCoreOutcome(\n request: CloudFrontRequest,\n application: OutcomeApplication\n): CloudFrontRequestResult {\n if (application.action === 'respond') {\n return jsonResponse(application.status, application.bodyJson, application.headers);\n }\n for (const prefix of application.stripHeaderPrefixes) {\n stripHeadersByPrefix(request, prefix);\n }\n for (const [name, value] of Object.entries(application.injectHeaders)) {\n setHeader(request, name, value);\n }\n return request;\n}\n\n/**\n * The FULL sanitised metadata capture for one request: every inbound header\n * (secrets stripped, credential headers reduced to a safe format prefix)\n * plus the connection-layer block. Forwarded on both the verify-access call\n * and the beacon.\n */\nexport function buildObservedMetadata(request: CloudFrontRequest): ObservedMetadata {\n return buildObservedMetadataCore(toNormalizedRequest(request));\n}\n\n/**\n * Normalize the origin-bound `Host` header.\n *\n * To receive the `CloudFront-Viewer-*` connection headers (IP/ASN/TLS/country)\n * we must move off the managed `AllViewerExceptHostHeader` origin-request policy\n * onto one that whitelists those headers — but every such policy ALSO forwards\n * the viewer `Host`, which would break vhost / PaaS origin routing that expects\n * the origin's own hostname. So the Lambda takes ownership of the Host rewrite:\n * stash the public (viewer) host into `X-Forwarded-Host` for downstream use and\n * detection, then set `Host` to the origin's domain (available on the event as\n * `request.origin.custom.domainName`). No-ops when the origin domain is absent\n * (synthetic test events) or already matches — so it is safe on every path,\n * including static-asset pass-through.\n */\nexport function normalizeOriginHost(request: CloudFrontRequest): void {\n const originDomain =\n request.origin?.custom?.domainName ?? request.origin?.s3?.domainName ?? undefined;\n if (!originDomain) return;\n const currentHost = getHeader(request, 'host');\n if (!currentHost || currentHost === originDomain) return;\n if (!getHeader(request, 'x-forwarded-host')) {\n setHeader(request, 'X-Forwarded-Host', currentHost);\n }\n setHeader(request, 'Host', originDomain);\n}\n\n/** Full request URL as the origin will see it (scheme fixed to https). */\nexport function requestUrl(request: CloudFrontRequest, host: string): string {\n const qs = request.querystring ? `?${request.querystring}` : '';\n return `https://${host}${request.uri}${qs}`;\n}\n\n/**\n * Set a header on the origin-bound request (overwrites all prior values).\n */\nexport function setHeader(request: CloudFrontRequest, name: string, value: string): void {\n request.headers[name.toLowerCase()] = [{ key: name, value }];\n}\n\n/**\n * Strip every inbound header matching a prefix (lowercase compare). Used to\n * drop spoofed `x-astrasync-*` trust headers before the shell injects its\n * own — in BOTH modes, so a caller can never smuggle trust past the origin.\n */\nexport function stripHeadersByPrefix(request: CloudFrontRequest, prefix: string): void {\n const p = prefix.toLowerCase();\n for (const name of Object.keys(request.headers)) {\n if (name.startsWith(p)) delete request.headers[name];\n }\n}\n\nexport function jsonResponse(\n status: number,\n body: unknown,\n extraHeaders?: Record<string, string>\n): CloudFrontResultResponse {\n const headers: CloudFrontResultResponse['headers'] = {\n 'content-type': [{ key: 'Content-Type', value: 'application/json' }],\n };\n for (const [name, value] of Object.entries(extraHeaders ?? {})) {\n headers[name.toLowerCase()] = [{ key: name, value }];\n }\n return {\n status: String(status),\n statusDescription: status === 403 ? 'Forbidden' : status === 202 ? 'Accepted' : 'OK',\n headers,\n body: JSON.stringify(body),\n };\n}\n","/**\n * Runtime configuration for the edge shell.\n *\n * Lambda@Edge forbids environment variables, so configuration is layered:\n * 1. Build-time constants baked into the bundle (apiBaseUrl,\n * counterpartyId, counterpartyUrl, apiKeySecretName) — see\n * `tsup.config.ts` define block and `infra/deploy.sh`.\n * 2. The merchant API key from AWS Secrets Manager (us-east-1), fetched\n * lazily once per container and cached with a 15-minute TTL so\n * rotation needs no redeploy. Observe mode works fully without it —\n * the beacon endpoint is unauthenticated and verify() degrades\n * keyless — so a Secrets Manager outage can never take the shell down.\n * 3. The dashboard EdgeConfig via the SDK's getEdgeConfig (60s TTL,\n * stale-while-revalidate, enforce→observe degradation after 24h\n * staleness, DEFAULT_EDGE_CONFIG fallback — all in the SDK).\n */\n\nimport {\n getEdgeConfig,\n DEFAULT_EDGE_CONFIG,\n type EdgeConfig,\n} from '@astrasyncai/verification-gateway/edge-config';\nimport type { EdgeAdapterOptions } from './types';\n\nconst SECRET_TTL_MS = 15 * 60 * 1000;\n\ninterface SecretCacheEntry {\n value: string | undefined;\n fetchedAt: number;\n}\n\nconst secretCache = new Map<string, SecretCacheEntry>();\n\nasync function defaultFetchSecret(secretName: string): Promise<string | undefined> {\n // Lazy import so tests (and the library build) never touch the AWS SDK\n // unless a secret name is actually configured.\n const { SecretsManagerClient, GetSecretValueCommand } =\n await import('@aws-sdk/client-secrets-manager');\n // Lambda@Edge replicates globally but secrets live in us-east-1 (the\n // Lambda@Edge home region).\n const client = new SecretsManagerClient({ region: 'us-east-1' });\n const result = await client.send(new GetSecretValueCommand({ SecretId: secretName }));\n return result.SecretString ?? undefined;\n}\n\n/**\n * Resolve the merchant API key. Never throws — a missing/unreachable secret\n * degrades to keyless operation (observe mode is fully functional without\n * it; enforce mode without a key cannot fetch a config that enforces, so it\n * degrades to observe by construction).\n */\nexport async function resolveApiKey(options: EdgeAdapterOptions): Promise<string | undefined> {\n if (options.apiKey) return options.apiKey;\n const secretName = options.apiKeySecretName;\n if (!secretName) return undefined;\n\n const cached = secretCache.get(secretName);\n if (cached && Date.now() - cached.fetchedAt < SECRET_TTL_MS) return cached.value;\n\n try {\n const fetcher = options.fetchSecret ?? defaultFetchSecret;\n const value = await fetcher(secretName);\n secretCache.set(secretName, { value, fetchedAt: Date.now() });\n return value;\n } catch (err) {\n options.log?.('warn', 'Secrets Manager fetch failed — continuing keyless', {\n secretName,\n error: err instanceof Error ? err.message : String(err),\n });\n // Serve a stale value if we ever had one; otherwise keyless.\n return cached?.value;\n }\n}\n\n/**\n * Load the effective EdgeConfig. Delegates caching/fallback/degradation to\n * the SDK; adds the compile-time fallback override and never throws.\n */\nexport async function loadEdgeConfig(\n options: EdgeAdapterOptions,\n apiKey: string | undefined\n): Promise<EdgeConfig> {\n try {\n return await getEdgeConfig({ apiBaseUrl: options.apiBaseUrl, apiKey }, options.counterpartyId, {\n ttlMs: options.configTtlMs,\n });\n } catch {\n return options.fallbackConfig ?? DEFAULT_EDGE_CONFIG;\n }\n}\n\n/** Reset module caches — tests only. */\nexport function _resetConfigCaches(): void {\n secretCache.clear();\n}\n","/**\n * Three-tier traffic classification — CloudFront wrapper over\n * the edge core's classifier. The classification logic itself (tier order,\n * UA patterns, path-rule resolution) lives in\n * `@astrasyncai/verification-gateway/edge-core` and is shared with every\n * edge platform adapter.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport {\n classify as classifyCore,\n resolvePolicy,\n _patterns,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest } from './cf';\nimport type { Classification } from './types';\n\nexport { resolvePolicy, _patterns };\n\n/** Classify one CloudFront origin-request. See the edge core for the tier order. */\nexport function classify(\n request: CloudFrontRequest,\n host: string,\n depth: 'classify' | 'authenticate' | 'authorize'\n): Classification {\n return classifyCore(toNormalizedRequest(request), host, depth);\n}\n","/**\n * Crypto-free commerce-protocol detection (fixed detection order) —\n * CloudFront wrapper over the edge core's detector. The sniffing logic\n * (header shapes, compact-JWT patterns, body field names) lives in\n * `@astrasyncai/verification-gateway/edge-core`; this module adapts the\n * CloudFront event shape (separately-decoded body) onto it.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport {\n detectCommerce as detectCommerceCore,\n type DetectedCommerce,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest, type DecodedBody } from './cf';\n\nexport type { DetectedCommerce };\n\n/**\n * Detect commerce-protocol credentials on a CloudFront origin-request.\n * `body` is the caller-decoded request body (see `decodeBody`) — kept as an\n * explicit parameter so callers that already decoded it don't pay twice.\n */\nexport function detectCommerce(\n request: CloudFrontRequest,\n host: string,\n body: DecodedBody\n): DetectedCommerce | undefined {\n const normalized = toNormalizedRequest(request);\n return detectCommerceCore(\n {\n ...normalized,\n bodyText: body.text,\n bodyTruncated: body.truncated,\n },\n host\n );\n}\n","/**\n * Depth-controlled verification per tier — CloudFront wrapper over the edge\n * core's tier pipeline. The verification semantics (what each depth looks\n * at and forwards, the fail-open rules, the verify-access request shape)\n * live in `@astrasyncai/verification-gateway/edge-core`; the edge never\n * verifies credentials itself — verify-access is the canonical sole\n * verification sink.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport { runTier as runTierCore } from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest } from './cf';\nimport type { Classification, EdgeAdapterOptions, EffectivePolicy, TierOutcome } from './types';\n\n/**\n * Run the tier-appropriate verification/telemetry for one classified\n * request. NEVER throws; on any failure returns an `allow` outcome with the\n * failure recorded in `reasons` (infra failures fail open — a presented\n * credential the BACKEND rejected is a deny, but an unreachable backend is\n * not a rejection).\n */\nexport async function runTier(\n options: EdgeAdapterOptions,\n request: CloudFrontRequest,\n classification: Classification,\n policy: EffectivePolicy,\n apiKey: string | undefined,\n sampling: { anonymousBeaconRate: number }\n): Promise<TierOutcome> {\n return runTierCore(\n options,\n toNormalizedRequest(request),\n classification,\n policy,\n apiKey,\n sampling\n );\n}\n","/**\n * Decision → CloudFront result wiring (enforcement + ASAT trust-attestation\n * headers). The decision logic (observe shadow stamps, misconfig fail-open,\n * enforce-mode response bodies) lives in the edge core; this module\n * executes the core's abstract outcome application against CloudFront\n * request/response primitives.\n */\n\nimport type { CloudFrontRequest, CloudFrontRequestResult } from 'aws-lambda';\nimport {\n applyOutcome as resolveOutcomeApplication,\n ASAT_HEADERS,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { applyCoreOutcome } from './cf';\nimport type { EdgeAdapterOptions, EffectivePolicy, TierOutcome } from './types';\n\nexport { ASAT_HEADERS };\n\n/**\n * Apply the outcome. Always strips inbound spoofed `x-astrasync-*` headers\n * first (both modes — trust headers must only ever originate here).\n *\n * Observe mode: returns the request untouched apart from header hygiene and\n * an `X-AstraSync-Observed-Decision` stamp for origin-side log correlation.\n *\n * Enforce mode: ALLOW → request + ASAT headers; DENY → 403 machine-readable\n * body; MANUAL_REVIEW → 202 with step-up poll info.\n */\nexport function applyOutcome(\n options: EdgeAdapterOptions,\n request: CloudFrontRequest,\n outcome: TierOutcome,\n policy: EffectivePolicy\n): CloudFrontRequestResult {\n return applyCoreOutcome(request, resolveOutcomeApplication(options, outcome, policy));\n}\n","/**\n * Protocol-specific machine-readable messaging for enforce-mode\n * responses. The message catalogue and body builders live in the edge core\n * (shared across every edge platform adapter); re-exported here so the\n * adapter's public API is unchanged.\n */\n\nexport {\n buildDenyBody,\n buildManualReviewBody,\n type DenyBody,\n type ManualReviewBody,\n} from '@astrasyncai/verification-gateway/edge-core';\n","/**\n * Time-budgeted event emission — CloudFront wrapper over the edge core's\n * telemetry. Lambda@Edge has no `waitUntil`, so every network call is\n * awaited under an explicit budget and DROPPED on timeout (the core's\n * default posture): latency to the customer's traffic is bounded, and a\n * slow AstraSync API costs telemetry, never page loads.\n */\n\nimport type { CloudFrontRequest } from 'aws-lambda';\nimport {\n emitBeacon as emitBeaconCore,\n withBudget,\n sampled,\n} from '@astrasyncai/verification-gateway/edge-core';\nimport { toNormalizedRequest } from './cf';\nimport type { Classification, EdgeAdapterOptions } from './types';\n\nexport { withBudget, sampled };\n\n/**\n * Emit an unregistered-attempt beacon for `identified` (non-crypto depths)\n * and sampled `anonymous-bot` traffic. Unauthenticated by design\n * (CSRF-exempt on the backend); the backend fingerprints the UA and\n * find-or-provisions orphan platform agents server-side.\n */\nexport async function emitBeacon(\n options: EdgeAdapterOptions,\n request: CloudFrontRequest,\n classification: Classification,\n budgetMs: number\n): Promise<void> {\n await emitBeaconCore(options, toNormalizedRequest(request), classification, budgetMs);\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- "use strict";var ue=Object.create;var S=Object.defineProperty;var le=Object.getOwnPropertyDescriptor;var pe=Object.getOwnPropertyNames;var fe=Object.getPrototypeOf,ge=Object.prototype.hasOwnProperty;var ye=(e,t)=>{for(var r in t)S(e,r,{get:t[r],enumerable:!0})},k=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of pe(t))!ge.call(e,a)&&a!==r&&S(e,a,{get:()=>t[a],enumerable:!(n=le(t,a))||n.enumerable});return e};var me=(e,t,r)=>(r=e!=null?ue(fe(e)):{},k(t||!e||!e.__esModule?S(r,"default",{value:e,enumerable:!0}):r,e)),he=e=>k(S({},"__esModule",{value:!0}),e);var _t={};ye(_t,{handler:()=>Ut});module.exports=he(_t);var kt=Object.freeze({version:1,mode:"observe",depth:"classify",pathRules:[],sampling:Object.freeze({anonymousBeaconRate:.1}),failurePosture:"open"}),Ot=60*1e3,Mt=1440*60*1e3;function ve(e,t){for(let r of e)if(Ae(r.pattern).test(t))return r}function Ae(e){let t=e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${t}$`,"i")}var we=1,be=new Set(["cookie","set-cookie","x-csrf-token","proxy-authorization","x-astrasync-signature","x-astrasync-secret","x-hub-signature","x-hub-signature-256"]),Re=["x-astrasync-","kya-","kya_"],Se=new Set(["authorization","x-api-key","x-goog-api-key","api-key"]);function Te(e){return e==="signature"||e.endsWith("-signature")||e.endsWith("-signature-256")}var Ie=new Set(["openai-organization","openai-project","openai-version","openai-processing-ms","anthropic-version","anthropic-beta","x-goog-user-project","x-goog-api-client","user-agent","x-agent-card-url","x-agent-provider"]),Ce=["x-goog-","x-stainless-","x-anthropic-","x-openai-"],Ee=["sk-ant-api03","sk-ant-api","sk-ant","sk-proj","sk-svcacct","sk-or-v1","sk-or","sk-lf","sk-","gsk_","aiza","ya29","ghp_","gho_","github_pat_","xai-","pplx-","r8_","hf_","astra-","astrae-"],Ue=64,O=1024,_e=16*1024;function T(e){let t=0;for(let r=0;r<e.length;r++){let n=e.charCodeAt(r);n<128?t+=1:n<2048?t+=2:n>=55296&&n<=56319?(t+=4,r++):t+=3}return t}function xe(e){if(!e)return;let t=e.trim(),r=t.indexOf(" ");if(r>0){let a=t.slice(0,r).toLowerCase();(a==="bearer"||a==="basic"||a==="token")&&(t=t.slice(r+1).trim())}let n=t.toLowerCase();for(let a of Ee)if(n.startsWith(a))return a}function Pe(e){return!!(be.has(e)||Re.some(t=>e.startsWith(t))||Te(e))}function ke(e){return Ie.has(e)?!0:Ce.some(t=>e.startsWith(t))}function $(e){let t={},r={},n,a=0,i=0;if(!e)return{headers:t};for(let s of Object.keys(e)){if(i>=Ue)break;let c=s.toLowerCase(),f=e[s];if(f===void 0)continue;let p=Array.isArray(f)?f.join(", "):String(f);if(Se.has(c)){let h=xe(p);h&&!n&&(n=h);continue}if(Pe(c))continue;let u=p;T(u)>O&&(u=u.slice(0,O));let g=T(c)+T(u);a+g>_e||(a+=g,i++,t[c]=u,ke(c)&&(r[c]=u))}let o={headers:t};return n&&(o.apiKeyFormat=n),Object.keys(r).length>0&&(o.platformHeaders=r),o}var Oe=[{vendor:"claude",displayName:"Anthropic Claude",uaPatterns:[/claude/i,/anthropic/i],agentCardPatterns:[/anthropic\.com/i,/claude\.ai/i],apiKeyFormatPatterns:[/^sk-ant/i],headerPatterns:[/^anthropic-version/i,/^anthropic-beta/i,/^x-anthropic-/i]},{vendor:"chatgpt",displayName:"OpenAI ChatGPT",uaPatterns:[/chatgpt/i,/openai/i,/gpt-?\d/i],agentCardPatterns:[/openai\.com/i,/chatgpt\.com/i],apiKeyFormatPatterns:[/^sk-proj/i,/^sk-svcacct/i],headerPatterns:[/^openai-organization/i,/^openai-project/i,/^openai-version/i]},{vendor:"gemini",displayName:"Google Gemini",uaPatterns:[/gemini/i,/google-gemini/i,/bard/i],agentCardPatterns:[/gemini\.google\.com/i,/ai\.google\.dev/i],apiKeyFormatPatterns:[/^aiza/i],headerPatterns:[/^x-goog-user-project/i,/^x-goog-api-client/i]},{vendor:"cursor",displayName:"Cursor",uaPatterns:[/cursor/i],agentCardPatterns:[/cursor\.sh/i,/cursor\.com/i]},{vendor:"goose",displayName:"Block Goose",uaPatterns:[/goose/i,/block-goose/i],agentCardPatterns:[/block\.xyz\/goose/i,/goose\.tools/i]},{vendor:"perplexity",displayName:"Perplexity",uaPatterns:[/perplexity/i],agentCardPatterns:[/perplexity\.ai/i],apiKeyFormatPatterns:[/^pplx-/i]}];function Me(e,t){let r=t.userAgent??"",n=t.agentCardUrl??"",a=t.apiKeyFormat??"",i=[];if(t.platformHeaders)for(let[o,s]of Object.entries(t.platformHeaders))i.push(o,`${o}: ${s}`);for(let o of e){let s=o.uaPatterns?.some(u=>u.test(r)),c=o.agentCardPatterns?.some(u=>u.test(n)),f=a.length>0&&o.apiKeyFormatPatterns?.some(u=>u.test(a)),p=i.length>0&&o.headerPatterns?.some(u=>i.some(g=>u.test(g)));if(s||c||f||p){let u=r.split(/[\s/]/)[0]?.toLowerCase()??"",g=(()=>{try{return n?new URL(n).host:""}catch{return""}})(),h=u||g||a||"unknown";return{vendor:o.vendor,fingerprintKey:`${o.vendor}:${h}`,displayName:o.displayName}}}}function M(e){return Me(Oe,e)}function Fe(e){return e>=80?"PLATINUM":e>=60?"GOLD":e>=40?"SILVER":"BRONZE"}var De="4.1.0",Ft=3600*1e3,He=new Map;function Ne(e){return He.get(e)?.data}var K={apiBaseUrl:"https://astrasync.ai/api",defaultAccessLevel:"none",debug:!1},V=!1,F=!1;async function D(e,t,r,n){V=!0;try{let a=`${e}/agents/verify-access`,o=(await fetch(a,{method:"HEAD"})).headers.get("content-type")??"";if(o.startsWith("text/html")){let s=`[VerificationGateway] apiBaseUrl '${e}' returned HTML (content-type: ${o}). This usually means apiBaseUrl is pointing at a marketing site instead of the API. Expected: 'https://astrasync.ai/api' (prod) or 'https://staging.astrasync.ai/api' (staging).`;if(r)throw new Error(`${s} (strictInit=true)`);console.warn(`${s} Set disableInitChecks: true on GatewayConfig to silence.`)}else t&&console.log(`[VerificationGateway] init check passed for ${e} (content-type: ${o})`);if(n){let s=await fetch(`${e}/agents/verify-access/self-check`,{headers:{Authorization:`Bearer ${n}`}});if(s.status===401){let c=await s.json().catch(()=>({})),f=`[VerificationGateway] the configured AstraSync API key was REJECTED at startup (${c.code??"api_key_invalid"}: ${c.message??"invalid key"}). Every inbound agent will be turned away until the key is rotated`+(c.remediation?.manageUrl?` at ${c.remediation.manageUrl}`:"")+".";if(r)throw new Error(`${f} (strictInit=true)`);console.error(f)}else if(s.ok){let c=await s.json().catch(()=>({}));typeof c.daysToExpiry=="number"&&c.daysToExpiry<=7?console.warn(`[VerificationGateway] the configured AstraSync API key expires in ${c.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`):t&&console.log("[VerificationGateway] API key self-check passed")}}}catch(a){if(r)throw a;t&&console.log(`[VerificationGateway] init check failed (non-blocking): ${String(a)}`)}}var C=new Map;function z(e,t){let r=e.credentials;return[r.astraId||"",r.apiKey||"",r.jwt||"",e.purpose||"",e.action||"",e.resourceType||"",e.resource||"",e.jurisdiction||"",e.transactionValue??"",e.currency||"",t||"",e.counterpartyUrl||"",e.counterpartyType||"",e.isSubAgentRequest?"1":"0",e.parentAgentId||"",e.subAgentDepth??"",e.durationRequired??"",e.invocationProtocol||"",e.enableRuntimeChallenge?"1":"0",e.callerMetadata?.sourceIp||"",e.callerMetadata?.userAgent||"",e.callerMetadata?.forwardedFor||"",e.callerMetadata?.agentCardUrl||""].join("|")}function Le(e,t){let r=z(e,t),n=C.get(r);return n&&n.expiresAt>Date.now()?n.result:(n&&C.delete(r),null)}var Be=60;function je(e,t,r,n){let a=r&&r>0?r:Be,i=z(e,n);C.set(i,{result:t,expiresAt:Date.now()+a*1e3})}function G(e,t){let r={},n=/^ASTRAE?-[A-Za-z0-9_-]{1,64}$/,a=e["x-astra-id"]||e["X-Astra-Id"]||e["X-ASTRA-ID"]||e["x-astra-agentid"]||e["X-Astra-AgentId"]||e["x-astra-agent-id"]||e["X-Astra-Agent-Id"]||e["X-ASTRA-AGENT-ID"];if(a){let s=Array.isArray(a)?a[0]:typeof a=="string"?a:void 0;typeof s=="string"&&n.test(s)&&(r.astraId=s)}let i=e["x-api-key"]||e["X-Api-Key"]||e["X-API-KEY"];i&&(r.apiKey=Array.isArray(i)?i[0]:i);let o=e.authorization||e.Authorization;if(o){let s=Array.isArray(o)?o[0]:o;typeof s=="string"&&(r.authorizationHeader=s,s.startsWith("Bearer ")&&(r.jwt=s.slice(7)))}return t&&(t.astraId&&!r.astraId&&(r.astraId=t.astraId),t.apiKey&&!r.apiKey&&(r.apiKey=t.apiKey)),r}function $e(e,t,r={}){let n=r.source??"no_credentials",a=n==="api_error",i=n==="merchant_misconfig",o=r.urls,s=i?{message:"This site's AstraSync integration is misconfigured (its API key was revoked, expired, or rotated). Your identity and the AstraSync platform are fine \u2014 contact the site operator. Retrying will not help.",registrationUrl:o?.registrationUrl??"",documentationUrl:r.remediation?.docsUrl??o?.documentationUrl??"",steps:["Report the failure to the site operator \u2014 their AstraSync API key needs rotating","Do not retry: the failure is deterministic until the operator updates their key"]}:a?{message:"Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",registrationUrl:o?.registrationUrl??"",documentationUrl:o?.documentationUrl??"",steps:["Retry the request with exponential backoff","If failures persist, share the correlationId with support"]}:{message:"This service verifies AI agents before granting access. Please register your agent with AstraSync.",registrationUrl:o?.registrationUrl??"",documentationUrl:o?.documentationUrl??"",steps:["Register for an AstraSync account","Create and register your agent","Add your ASTRA-ID to request headers","Retry your request"]};return{identityVerified:!1,policyAllowed:!1,accessLevel:"none",guidance:s,denialReasons:t?[t]:["No valid agent credentials provided"],failures:i?[{dimension:"verify_access.merchant_misconfigured",message:t??"The integration API key was rejected by AstraSync",guidance:s.message}]:a?[{dimension:"verify_access.api_error",message:t??"Verification temporarily unavailable",guidance:s.message}]:void 0,...i&&{misconfigured:!0},correlationId:r.correlationId,attemptId:r.attemptId,verifiedAt:new Date}}async function Ke(e,t){let{credentials:r,...n}=t,a={...r.astraId&&{agentId:r.astraId},...n.purpose&&{purpose:n.purpose}};if(n.action&&(a.action=n.action),n.resourceType&&(a.resourceType=n.resourceType),n.resource&&(a.resource=n.resource),n.jurisdiction&&(a.jurisdiction=n.jurisdiction),n.transactionValue&&(a.transactionValue=n.transactionValue),n.currency&&(a.currency=n.currency),n.isSubAgentRequest&&(a.isSubAgentRequest=n.isSubAgentRequest),n.parentAgentId&&(a.parentAgentId=n.parentAgentId),n.subAgentDepth!==void 0&&(a.subAgentDepth=n.subAgentDepth),n.enableRuntimeChallenge&&(a.enableRuntimeChallenge=n.enableRuntimeChallenge),n.createSession&&(a.createSession=n.createSession),n.durationRequired&&(a.durationRequired=n.durationRequired),n.counterpartyType&&(a.counterpartyType=n.counterpartyType),n.counterpartyUrl&&(a.counterpartyUrl=n.counterpartyUrl),e.counterpartyId&&(a.counterpartyId=e.counterpartyId),n.runtimeChallengeOptions&&(a.runtimeChallengeOptions=n.runtimeChallengeOptions),n.invocationProtocol&&(a.invocationProtocol=n.invocationProtocol),n.commerceArtifacts&&(a.commerceArtifacts=n.commerceArtifacts),n.attemptId&&(a.attemptId=n.attemptId),n.considerationSet&&(a.considerationSet=n.considerationSet),a.sdkVersion=De,n.callerMetadata||n.clientIp||n.userAgent){let o={...n.clientIp&&{sourceIp:n.clientIp},...n.userAgent&&{userAgent:n.userAgent},...n.callerMetadata};Object.keys(o).length>0&&(a.callerMetadata=o)}let i={"Content-Type":"application/json",...e.customHeaders};e.apiKey&&(i.Authorization=`Bearer ${e.apiKey}`,i["X-API-Key"]=e.apiKey);try{let o=await fetch(`${e.apiBaseUrl}/agents/verify-access`,{method:"POST",headers:i,body:JSON.stringify(a),signal:AbortSignal.timeout(e.verifyTimeoutMs??1e4)}),s=await o.json();return o.status===410?{success:!0,attemptId:typeof s?.attemptId=="string"?s.attemptId:void 0,access:{allowed:!1,accessLevel:"none",reason:"endpoint_deactivated",failures:[{dimension:"endpoint.deactivated",message:typeof s?.message=="string"?s.message:"Endpoint has been deactivated",guidance:typeof s?.guidance=="string"?s.guidance:"Reactivate via POST /api/endpoints/{id}/reactivate, or update the URL on the calling agent."}]}}:o.ok?s:o.status===401?{success:!1,error:s.message||s.error||"Integration API key rejected",misconfig:!0,misconfigCode:typeof s?.code=="string"?s.code:void 0,remediation:s?.remediation&&typeof s.remediation=="object"?s.remediation:void 0,correlationId:typeof s?.correlationId=="string"?s.correlationId:void 0,attemptId:typeof s?.attemptId=="string"?s.attemptId:void 0}:{success:!1,error:s.message||s.error||`API returned ${o.status}`,correlationId:typeof s?.correlationId=="string"?s.correlationId:void 0,attemptId:typeof s?.attemptId=="string"?s.attemptId:void 0}}catch(o){return{success:!1,error:`Failed to call verify-access API: ${o instanceof Error?o.message:"Unknown error"}`}}}async function H(e,t,r){let n={...K,...e},a=n.apiBaseUrl?Ne(n.apiBaseUrl):void 0;if(!V&&!n.disableInitChecks&&n.apiBaseUrl&&(n.strictInit?await D(n.apiBaseUrl,n.debug,!0,n.apiKey):D(n.apiBaseUrl,n.debug,!1,n.apiKey)),!F&&(e.minTrustScore!==void 0||e.minTrustScoreForFull!==void 0)&&(F=!0,console.warn("[VerificationGateway] minTrustScore / minTrustScoreForFull are deprecated in v2.3.0 and have no effect. Server is now the single source of truth for access-level decisions (the SDK reads access.accessLevel from the verify-access response). To gate access to an endpoint, configure the endpoint's trust_score_requirement server-side.")),n.cacheTtl!==0&&!r?.bypassCache){let y=Le(t,n.counterpartyId);if(y){n.debug&&console.log("[VerificationGateway] Returning cached result");let b={...y};return t.attemptId?b.attemptId=t.attemptId:delete b.attemptId,b}}let i={...t};!i.counterpartyUrl&&n.counterpartyUrl&&(i.counterpartyUrl=n.counterpartyUrl),!i.counterpartyType&&n.counterpartyType&&(i.counterpartyType=n.counterpartyType),n.debug&&console.log("[VerificationGateway] Calling verify-access API");let o=await Ke(n,i),s=o.denialReasons,c=Array.isArray(s)&&s.length>0?s:void 0;if(!o.success)return $e(n,o.error,{source:o.misconfig?"merchant_misconfig":"api_error",correlationId:o.correlationId,attemptId:o.attemptId,remediation:o.remediation,urls:a});if(!o.access?.allowed){let y=o.access?.failures;return{identityVerified:o.verificationContext?.idVerified===!0,policyAllowed:!1,accessLevel:"none",denialReasons:c??(y&&y.length>0?y.map(de=>de.message):o.access?.reason?[o.access.reason]:["Access denied"]),failures:y,requiresStepUp:o.access?.requiresStepUp,requiresApproval:o.access?.requiresApproval,guidance:{message:o.access?.reason||"Access denied by PDLSS policy",registrationUrl:a?.registrationUrl??"",documentationUrl:a?.documentationUrl??""},verifiedAt:new Date,sessionId:o.sessionId,correlationId:o.correlationId,attemptId:o.attemptId,recommendation:o.recommendation,recommendationReasons:o.recommendationReasons,stepUpApproval:o.stepUpApproval,settlement:o.settlement}}let f=o.agent?{astraId:o.agent.astraId,name:o.agent.name,trustScore:o.agent.trustScore,trustLevel:Fe(o.agent.trustScore),blockchainVerified:o.agent.blockchainStatus==="verified",status:o.agent.agentStatus}:void 0,p=o.developer?{astradId:o.developer.kyaOwnerId,name:o.developer.fullName,trustScore:o.developer.trustScore||0,verified:o.developer.identityVerified}:void 0,u=o.organization?{name:o.organization.name,verified:o.organization.verified,trustScore:o.organization.trustScore}:void 0,g=o.verificationContext,h=o.access?.accessLevel??"standard",d={identityVerified:o.verificationContext?.idVerified!==!1,policyAllowed:!0,accessLevel:h,agent:f,developer:p,organization:u,appliedPolicy:o.access?.appliedPolicy,verificationContext:g,requiresStepUp:o.access?.requiresStepUp,requiresApproval:o.access?.requiresApproval,verifiedAt:new Date,cacheTtl:n.cacheTtl,sessionId:o.sessionId,correlationId:o.correlationId,attemptId:o.attemptId,runtimeChallenge:o.runtimeChallenge,tokenGuidance:o.tokenGuidance,recommendation:o.recommendation,recommendationReasons:o.recommendationReasons,warningHeader:o.warningHeader,stepUpApproval:o.stepUpApproval,settlement:o.settlement};d.recommendation==="deny"?(d.policyAllowed=!1,d.accessLevel="none",d.denialReasons=c??d.recommendationReasons??["Access denied by AstraSync recommendation"],d.guidance=d.runtimeChallenge?{message:`Verification failed: ${d.runtimeChallenge.reason||"runtime challenge failed"}`,registrationUrl:a?.registrationUrl??"",documentationUrl:a?.documentationUrl??""}:{message:d.recommendationReasons?.[0]||"Access denied by AstraSync recommendation",registrationUrl:a?.registrationUrl??"",documentationUrl:a?.documentationUrl??""}):d.recommendation==="step_up_required"&&(d.requiresStepUp=!0,d.denialReasons=c??d.recommendationReasons??["Step-up verification required"]);let m=d.requiresStepUp===!0||d.requiresApproval===!0||d.recommendation==="step_up_required";return n.cacheTtl!==0&&d.recommendation!=="deny"&&!m&&je(t,d,n.cacheTtl,n.counterpartyId),d}async function Ve(e,t){let r=e.apiBaseUrl||K.apiBaseUrl,{counterpartyUrl:n,...a}=t,i={...a,...n&&{counterpartyUrl:n},...e.counterpartyId&&{counterpartyId:e.counterpartyId}};await fetch(`${r}/verification-activity/unregistered-attempt`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)}).catch(()=>{})}function l(e,t){return e.headers[t.toLowerCase()]}function ze(e,t){let r=e.querystring?`?${e.querystring}`:"";return`https://${t}${e.uri}${r}`}function U(e){let{headers:t,apiKeyFormat:r,platformHeaders:n}=$(e.headers),a=e.connection??{};return{headers:t,...r&&{apiKeyFormat:r},...n&&{platformHeaders:n},...Object.keys(a).length>0&&{connection:a},schemaVersion:we}}var Ge=/^eyJ[\w-]+\.[\w-]*\.[\w-]+~/;function Xe(e){let t=e.replace(/-/g,"+").replace(/_/g,"/"),r=t+"=".repeat((4-t.length%4)%4),n=atob(r),a=new Uint8Array(n.length);for(let i=0;i<n.length;i++)a[i]=n.charCodeAt(i);return new TextDecoder().decode(a)}function Ye(e){try{let t=e.split(".")[0];return JSON.parse(Xe(t))}catch{return}}function Je(e){if(e)try{let t=JSON.parse(e);return typeof t=="object"&&t!==null?t:void 0}catch{return}}function We(e,t){let r=ze(e,t),n=e.method,a=e.headers,i=Je(e.bodyText),o=e.bodyTruncated?{bodyTruncated:!0}:{},s=l(e,"authorization"),c=s?.replace(/^Bearer\s+/i,""),p=[l(e,"x-payment-mandate")??l(e,"payment-mandate"),c].find(d=>d&&Ge.test(d));if(p){let d=Ye(p)?.typ;return{protocol:"vi-sd-jwt",artifacts:{viSdJwt:p},evidence:{typ:d,...o}}}let u=l(e,"signature"),g=l(e,"signature-input");if(u&&g){let d=l(e,"signature-agent"),m=l(e,"x-agent-registry"),y=g.toLowerCase();return{protocol:d?"rfc9421-web-bot-auth":m?.toLowerCase().includes("visa")||y.includes("visa")?"rfc9421-tap":m?.toLowerCase().includes("mastercard")||y.includes("mastercard")?"rfc9421-agent-pay":"rfc9421",artifacts:{rfc9421:{headers:a,method:n,url:r,...e.bodyText&&{body:e.bodyText}}},evidence:{...d&&{signatureAgent:d},...m&&{registryHint:m},keyid:g.match(/keyid="([^"]+)"/)?.[1],...o}}}let h=l(e,"timestamp");if(u&&!g&&(h||l(e,"idempotency-key")))return{protocol:"acp",artifacts:{acpRequest:{method:n,url:r,headers:a,...e.bodyText&&{rawBody:e.bodyText},...i&&{body:i}}},evidence:{hasTimestamp:!!h,...o}};if(/\/checkout[-_]sessions(\/|$)/.test(e.uri))return{protocol:"ucp",artifacts:{ucpRequest:{method:n,url:r,...i&&{body:i}}},evidence:{path:e.uri,...o}};if(i){let d=i.intent_mandate??i.intentMandate,m=i.cart_mandate??i.cartMandate,y=i.payment_mandate??i.paymentMandate;if(typeof d=="string"||typeof m=="string"||typeof y=="string")return{protocol:"ap2",artifacts:{ap2Mandates:{...typeof d=="string"&&{intent:d},...typeof m=="string"&&{cart:m},...typeof y=="string"&&{payment:y}}},evidence:{mandatesPresent:[typeof d=="string"&&"intent",typeof m=="string"&&"cart",typeof y=="string"&&"payment"].filter(Boolean),...o}}}if(s&&/^Payment\s+/i.test(s))return{protocol:"mpp",artifacts:{mppRequest:{method:n,url:r,headers:a,...e.bodyText&&{rawBody:e.bodyText},...i&&{body:i}}},evidence:o};if(l(e,"x-payment"))return{protocol:"x402",artifacts:{x402Request:{method:n,url:r,headers:a,...i&&{body:i}}},evidence:o}}var Ze=/\.(css|js|mjs|map|png|jpe?g|gif|webp|avif|svg|ico|woff2?|ttf|otf|eot|mp4|webm|pdf|txt|xml)$/i,Qe=/curl|wget|python-requests|python-urllib|node-fetch|axios|got|okhttp|go-http-client|libwww|httpie|java\/|ruby|php|scrapy|headless|phantomjs|selenium|playwright|puppeteer|bot|crawler|spider|slurp/i,qe=/mozilla\/\d/i;function et(e,t){if(Ze.test(t))return{mode:e.mode,depth:e.depth,skip:!0};let r=ve(e.pathRules,t);return r?{mode:r.mode??e.mode,depth:r.depth??e.depth,skip:r.skip===!0}:{mode:e.mode,depth:e.depth,skip:!1}}function tt(e,t,r){let n=l(e,"user-agent")??"",a=G(e.headers,{});if(a.astraId||a.apiKey||a.jwt)return{tier:"verified",protocol:"x-astra"};let{apiKeyFormat:i,platformHeaders:o}=$(e.headers),s={userAgent:n,apiKeyFormat:i,platformHeaders:o};if(r!=="classify"){let f=We(e,t);if(f)return{tier:"identified",protocol:f.protocol,artifacts:f.artifacts,evidence:f.evidence,fingerprint:M(s)}}let c=M(s);return c?{tier:"identified",protocol:"platform-ua",fingerprint:c}:!n||Qe.test(n)||!qe.test(n)?{tier:"anonymous-bot",evidence:{userAgent:n||"(none)"}}:!l(e,"sec-fetch-mode")&&!l(e,"accept-language")?{tier:"anonymous-bot",evidence:{userAgent:n,spoofedBrowser:!0}}:{tier:"human"}}var rt={"vi-sd-jwt":"This request presented Visa Intelligent Commerce credentials, but the agent is not verified with AstraSync. Register the agent to pair your VI credentials with a verified identity and mandate.","rfc9421-agent-pay":"This request carried a Mastercard Agent Pay signature, but the agent is not verified with AstraSync. Register the agent to pair Agent Pay credentials with a verified identity and mandate.","rfc9421-tap":"This request carried a Visa TAP signature, but the agent is not verified with AstraSync. Register the agent to pair TAP credentials with a verified identity and mandate.","rfc9421-web-bot-auth":"This request carried a Web Bot Auth signature, but the agent is not verified with AstraSync. Register the agent to link your signed-agent identity to a verified principal.",rfc9421:"This request carried an RFC 9421 HTTP message signature, but the agent is not verified with AstraSync. Register the agent to link the signing identity to a verified principal.",acp:"This request used the Agentic Commerce Protocol, but the agent is not verified with AstraSync. Register the agent to transact on this property.",ucp:"This request used a UCP checkout session, but the agent is not verified with AstraSync. Register the agent to transact on this property.",ap2:"This request presented AP2 mandates, but the agent is not verified with AstraSync. Register the agent to pair your mandates with a verified identity.",mpp:"This request presented MPP payment credentials, but the agent is not verified with AstraSync. Register the agent to transact on this property.",x402:"This request used x402 payment headers, but the agent is not verified with AstraSync. Register the agent to transact on this property.","platform-ua":"This traffic was identified as a platform AI agent without AstraSync credentials. Register the agent to access this property with a verified identity.","x-astra":"The presented AstraSync credentials failed verification or were denied by this property\u2019s access policy."};function nt(e,t){let r=t.replace(/\/api\/?$/,""),n=(e.protocol&&rt[e.protocol])??"This request could not be verified by AstraSync.";return{success:!1,error:{code:e.tier==="verified"?"INSUFFICIENT_ACCESS":"UNAUTHORIZED",message:n,...e.protocol&&{protocol:e.protocol},failures:e.reasons,...e.correlationId&&{correlationId:e.correlationId},guidance:{message:e.guidance?.message||"Register this agent with AstraSync to receive verifiable credentials.",registrationUrl:e.guidance?.registrationUrl||`${r}/register`,documentationUrl:e.guidance?.documentationUrl||`${r}/docs/edge-adapter`}}}}function at(e){return{success:!1,status:"manual_review",message:"This action requires human approval. Poll the step-up approval endpoint and retry once approved.",...e.stepUp&&{stepUpApproval:e.stepUp},...e.correlationId&&{correlationId:e.correlationId}}}var A={trustScore:"X-AstraSync-Trust-Score",agentId:"X-AstraSync-Agent-Id",developerId:"X-AstraSync-Developer-Id",permissions:"X-AstraSync-Permissions",observedDecision:"X-AstraSync-Observed-Decision"};function I(){return{action:"pass",injectHeaders:{},stripHeaderPrefixes:[]}}function ot(e,t,r){let n={};if(t.misconfig&&(n[A.observedDecision]="misconfig"),r.mode!=="enforce")return t.decision!=="allow"&&(n[A.observedDecision]=t.decision),Object.assign(n,N(t)),{action:"pass",injectHeaders:n,stripHeaderPrefixes:["x-astrasync-"]};switch(t.decision){case"deny":return{action:"respond",status:403,headers:{},bodyJson:nt(t,e.apiBaseUrl)};case"manual_review":return{action:"respond",status:202,headers:{"Retry-After":"30"},bodyJson:at(t)};default:return Object.assign(n,N(t)),{action:"pass",injectHeaders:n,stripHeaderPrefixes:["x-astrasync-"]}}}function N(e){if(!e.asat)return{};let{trustScore:t,agentId:r,developerId:n,accessLevel:a}=e.asat;return{...t!==void 0&&{[A.trustScore]:String(t)},...r&&{[A.agentId]:r},...n&&{[A.developerId]:n},...a&&{[A.permissions]:a}}}var it={GET:"data.read",HEAD:"data.read",OPTIONS:"data.read",POST:"data.write",PUT:"data.write",PATCH:"data.write",DELETE:"data.delete"},st="data.write",ct="data";function dt(e){return it[e.toUpperCase()]??st}function ut(e){let t=e.indexOf(":");if(t>=0)return{purpose:e.slice(0,t)};let r=e.indexOf(".");return r>0&&r<e.length-1?{purpose:e.slice(0,r),actionCandidate:e}:{purpose:e}}function lt(e){let t=e.astraPurpose?ut(e.astraPurpose):void 0,r,n;e.routeAction?(r=e.routeAction,n="route_config"):e.hasCustomActionExtractor&&e.customAction?(r=e.customAction,n="custom_extractor"):!e.hasCustomActionExtractor&&e.astraAction?(r=e.astraAction,n="header"):!e.hasCustomActionExtractor&&t?.actionCandidate?(r=t.actionCandidate,n="purpose_header_derived"):(r=dt(e.method),n="method_table");let a,i;if(e.routePurpose?(a=e.routePurpose,i="route_config"):e.hasCustomPurposeExtractor?e.customPurpose&&(a=e.customPurpose,i="custom_extractor"):t?(a=t.purpose,i="header"):e.legacyPurpose?(a=e.legacyPurpose,i="legacy_header"):e.queryPurpose&&(a=e.queryPurpose,i="query"),!a){let o=r.indexOf(".");o>0?(a=r.slice(0,o),i="action_derived"):(a=ct,i="transport_default")}return{purpose:a,action:r,purposeSource:i,actionSource:n}}async function E(e,t){let r;try{return await Promise.race([e,new Promise(n=>{r=setTimeout(()=>n(void 0),t)})])}catch{return}finally{r&&clearTimeout(r)}}async function L(e,t,r,n){let a=E(Ve({apiBaseUrl:e.apiBaseUrl,counterpartyId:e.counterpartyId},{counterpartyUrl:e.counterpartyUrl,counterpartyType:"website",sourceIp:t.clientIp,userAgent:l(t,"user-agent"),requestPath:t.uri,requestMethod:t.method,protocol:r.protocol,evidence:r.evidence,observedMetadata:U(t)}),n);if(e.waitUntil){e.waitUntil(a);return}await a}function pt(e,t=Math.random){return e<=0?!1:e>=1?!0:t()<e}function v(e){return{tier:e.tier,decision:"allow",reasons:[],protocol:e.protocol}}function B(e,t,r,n){let a=lt({method:t.method,astraPurpose:l(t,"x-astra-purpose"),astraAction:l(t,"x-astra-action")}),i=l(t,"x-astra-attempt-id");return{...i&&/^att_[0-9a-f]{32}$/.test(i)&&{attemptId:i},credentials:G(t.headers,{}),purpose:a.purpose,action:a.action,resource:t.uri,createSession:!0,counterpartyType:"website",counterpartyUrl:e.counterpartyUrl,callerMetadata:{sourceIp:t.clientIp,userAgent:l(t,"user-agent"),referer:l(t,"referer"),host:l(t,"host"),observedMetadata:U(t)},...n&&r.artifacts&&{commerceArtifacts:r.artifacts}}}function j(e,t){let r=v(e);return r.sessionId=t.sessionId,r.correlationId=t.correlationId,r.guidance=t.guidance,t.misconfigured?(r.reasons=["verification key misconfigured \u2014 fail-open (merchant_misconfig)"],r.misconfig=!0,r):t.requiresStepUp||t.stepUpApproval?(r.decision="manual_review",r.reasons=["step-up approval required"],r.stepUp={pollUrl:t.stepUpApproval?.pollUrl,expiresAt:t.stepUpApproval?.expiresAt},r):!t.identityVerified||!t.policyAllowed?(r.decision="deny",r.reasons=t.denialReasons?.length?t.denialReasons:[t.identityVerified?"denied by access policy":"identity not verified"],r):(r.asat={trustScore:t.agent?.trustScore,agentId:t.agent?.astraId,developerId:t.developer?.astradId,accessLevel:t.accessLevel},r)}async function ft(e,t,r,n,a,i){let o=e.budgets??{},s=o.beaconMs??300,c=n.mode==="enforce"?o.verifyEnforceMs??3e3:o.verifyObserveMs??1500,f={apiBaseUrl:e.apiBaseUrl,...a&&{apiKey:a},counterpartyId:e.counterpartyId,disableInitChecks:!0};try{switch(r.tier){case"verified":{if(n.depth==="classify")return v(r);let p=await E(H(f,B(e,t,r,n.depth==="authorize")),c);if(!p){let u=v(r);return u.reasons=["verify-access unreachable within budget \u2014 fail-open"],u}return j(r,p)}case"identified":{if(n.depth==="authorize"&&r.artifacts){let u=await E(H(f,B(e,t,r,!0)),c);if(!u){let g=v(r);return g.reasons=["verify-access unreachable within budget \u2014 fail-open"],g}return j(r,u)}await L(e,t,r,s);let p=v(r);return n.mode==="enforce"&&n.depth!=="classify"&&(p.decision="deny",p.reasons=["agent is not registered with AstraSync"]),p}case"anonymous-bot":return pt(i.anonymousBeaconRate,e.random)&&await L(e,t,{...r,protocol:r.protocol??"ua-heuristic"},s),v(r);default:return v(r)}}catch(p){let u=v(r);return u.reasons=[`edge pipeline error \u2014 fail-open: ${p instanceof Error?p.message:String(p)}`],u}}async function X(e,t,r,n){let a=et(r,t.uri);try{if(a.skip)return{policy:a,application:I()};let i=l(t,"x-forwarded-host")??l(t,"host")??new URL(e.counterpartyUrl).host,o=tt(t,i,a.depth);if(o.tier==="human")return{policy:a,classification:o,application:I()};let s=await ft(e,t,o,a,n,r.sampling);return s.decision!=="allow"&&e.log?.("info",a.mode==="enforce"?`${s.decision.toUpperCase()}: ${t.method} ${t.uri}`:`[OBSERVE] would have been ${s.decision.toUpperCase()}: ${t.method} ${t.uri}`,{tier:s.tier,protocol:s.protocol,reasons:s.reasons}),{policy:a,classification:o,outcome:s,application:ot(e,s,a)}}catch(i){return e.log?.("error","edge evaluation error \u2014 passing request through",{error:i instanceof Error?i.message:String(i)}),{policy:a,application:I()}}}function Y(e){return e.Records[0].cf.request}function w(e,t){return e.headers[t.toLowerCase()]?.[0]?.value}function gt(e){let t={};for(let[r,n]of Object.entries(e))n?.length&&(t[r]=n.map(a=>a.value).join(", "));return t}function yt(e){let t=e.body;return t?.data?{text:t.encoding==="base64"?Buffer.from(t.data,"base64").toString("utf8"):t.data,truncated:t.inputTruncated===!0}:{truncated:!1}}function mt(e){let t={},r=(i,o,s)=>{let c=w(e,o);c&&(t[i]=s?s(c):c)},n=w(e,"cloudfront-viewer-address");n&&(t.ip=n.replace(/:\d+$/,"")),r("country","cloudfront-viewer-country"),r("countryName","cloudfront-viewer-country-name"),r("region","cloudfront-viewer-country-region"),r("city","cloudfront-viewer-city"),r("asn","cloudfront-viewer-asn"),r("tlsVersion","cloudfront-viewer-tls"),r("httpVersion","cloudfront-viewer-http-version");let a=[];for(let[i,o]of[["desktop","cloudfront-is-desktop-viewer"],["mobile","cloudfront-is-mobile-viewer"],["tablet","cloudfront-is-tablet-viewer"],["smarttv","cloudfront-is-smarttv-viewer"]])w(e,o)==="true"&&a.push(i);return a.length&&(t.device=a.join(",")),t}function J(e){let t=yt(e),r=mt(e),n=w(e,"cloudfront-viewer-address")?.split(":")[0];return{method:e.method,uri:e.uri,querystring:e.querystring??"",headers:gt(e.headers),...t.text!==void 0&&{bodyText:t.text},...t.truncated&&{bodyTruncated:!0},...n&&{clientIp:n},...Object.keys(r).length>0&&{connection:r}}}function W(e,t){if(t.action==="respond")return vt(t.status,t.bodyJson,t.headers);for(let r of t.stripHeaderPrefixes)ht(e,r);for(let[r,n]of Object.entries(t.injectHeaders))_(e,r,n);return e}function Z(e){let t=e.origin?.custom?.domainName??e.origin?.s3?.domainName??void 0;if(!t)return;let r=w(e,"host");!r||r===t||(w(e,"x-forwarded-host")||_(e,"X-Forwarded-Host",r),_(e,"Host",t))}function _(e,t,r){e.headers[t.toLowerCase()]=[{key:t,value:r}]}function ht(e,t){let r=t.toLowerCase();for(let n of Object.keys(e.headers))n.startsWith(r)&&delete e.headers[n]}function vt(e,t,r){let n={"content-type":[{key:"Content-Type",value:"application/json"}]};for(let[a,i]of Object.entries(r??{}))n[a.toLowerCase()]=[{key:a,value:i}];return{status:String(e),statusDescription:e===403?"Forbidden":e===202?"Accepted":"OK",headers:n,body:JSON.stringify(t)}}var P=Object.freeze({version:1,mode:"observe",depth:"classify",pathRules:[],sampling:Object.freeze({anonymousBeaconRate:.1}),failurePosture:"open"}),Q=["observe","enforce"],q=["classify","authenticate","authorize"];function At(e){if(typeof e!="object"||e===null)return!1;let t=e;if(t.version!==1||!Q.includes(t.mode)||!q.includes(t.depth)||!Array.isArray(t.pathRules))return!1;for(let n of t.pathRules){if(typeof n!="object"||n===null)return!1;let a=n;if(typeof a.pattern!="string"||a.pattern.length===0||a.mode!==void 0&&!Q.includes(a.mode)||a.depth!==void 0&&!q.includes(a.depth))return!1}let r=t.sampling;return typeof r!="object"||r===null||typeof r.anonymousBeaconRate!="number"||r.anonymousBeaconRate<0||r.anonymousBeaconRate>1?!1:t.failurePosture==="open"}async function wt(e,t,r){if(!t)return null;let n={Accept:"application/json"};e.apiKey&&(n.Authorization=`Bearer ${e.apiKey}`,n["X-API-Key"]=e.apiKey),r?.etag&&(n["If-None-Match"]=r.etag);try{let a=await fetch(`${e.apiBaseUrl}/endpoints/${encodeURIComponent(t)}/edge-config`,{method:"GET",headers:n,signal:AbortSignal.timeout(r?.timeoutMs??5e3)});if(a.status===304)return{notModified:!0};if(!a.ok)return null;let o=(await a.json()).data?.edgeConfig;return At(o)?{edgeConfig:o,etag:a.headers.get("etag")??void 0}:null}catch{return null}}var bt=60*1e3,Rt=1440*60*1e3,R=new Map,x=new Map;function te(e,t){return`${e}|${t}`}function St(e){return e.mode!=="enforce"&&!e.pathRules.some(t=>t.mode==="enforce")?e:{...e,mode:"observe",pathRules:e.pathRules.map(t=>t.mode==="enforce"?{...t,mode:"observe"}:t)}}async function ee(e,t){let r=te(e.apiBaseUrl,t),n=x.get(r);if(n)return n;let a=wt(e,t,{etag:R.get(r)?.etag}).then(i=>{if(i===null)return;let o=Date.now();if("notModified"in i){let s=R.get(r);s&&(s.lastSuccessAt=o);return}R.set(r,{config:i.edgeConfig,etag:i.etag,lastSuccessAt:o})}).finally(()=>{x.delete(r)});return x.set(r,a),a}async function re(e,t,r){let n=r?.ttlMs??bt,a=r?.maxStaleMs??Rt,i=te(e.apiBaseUrl,t),o=R.get(i);if(o){let s=Date.now()-o.lastSuccessAt;return s>n&&ee(e,t).catch(()=>{}),s>a?St(o.config):o.config}return await ee(e,t).catch(()=>{}),R.get(i)?.config??P}var Tt=900*1e3,ne=new Map;async function It(e){let{SecretsManagerClient:t,GetSecretValueCommand:r}=await import("@aws-sdk/client-secrets-manager");return(await new t({region:"us-east-1"}).send(new r({SecretId:e}))).SecretString??void 0}async function ae(e){if(e.apiKey)return e.apiKey;let t=e.apiKeySecretName;if(!t)return;let r=ne.get(t);if(r&&Date.now()-r.fetchedAt<Tt)return r.value;try{let a=await(e.fetchSecret??It)(t);return ne.set(t,{value:a,fetchedAt:Date.now()}),a}catch(n){return e.log?.("warn","Secrets Manager fetch failed \u2014 continuing keyless",{secretName:t,error:n instanceof Error?n.message:String(n)}),r?.value}}async function oe(e,t){try{return await re({apiBaseUrl:e.apiBaseUrl,apiKey:t},e.counterpartyId,{ttlMs:e.configTtlMs})}catch{return e.fallbackConfig??P}}function ie(e){let t=e.log??((r,n,a)=>{console[r==="info"?"log":r](`[astrasync-edge] ${n}`,a??"")});return async function(n){let a=Y(n);try{Z(a);let i=await ae({...e,log:t}),o=await oe(e,i),{application:s}=await X({...e,log:t},J(a),o,i);return W(a,s)}catch(i){return t("error","edge handler error \u2014 passing request through",{error:i instanceof Error?i.message:String(i)}),a}}}var Ct="https://astrasync.ai/api",se="",ce="",Et=void 0;(!se||!ce)&&console.error("[astrasync-edge] ASTRASYNC_COUNTERPARTY_ID / ASTRASYNC_COUNTERPARTY_URL were not baked into the bundle \u2014 rebuild via infra/deploy.sh with the ASTRASYNC_* env vars set.");var Ut=ie({apiBaseUrl:Ct,counterpartyId:se,counterpartyUrl:ce,apiKeySecretName:Et});0&&(module.exports={handler});
1
+ "use strict";var pe=Object.create;var S=Object.defineProperty;var fe=Object.getOwnPropertyDescriptor;var ge=Object.getOwnPropertyNames;var ye=Object.getPrototypeOf,me=Object.prototype.hasOwnProperty;var he=(e,t)=>{for(var r in t)S(e,r,{get:t[r],enumerable:!0})},k=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of ge(t))!me.call(e,a)&&a!==r&&S(e,a,{get:()=>t[a],enumerable:!(n=fe(t,a))||n.enumerable});return e};var ve=(e,t,r)=>(r=e!=null?pe(ye(e)):{},k(t||!e||!e.__esModule?S(r,"default",{value:e,enumerable:!0}):r,e)),Ae=e=>k(S({},"__esModule",{value:!0}),e);var Ot={};he(Ot,{handler:()=>kt});module.exports=Ae(Ot);var Ht=Object.freeze({version:1,mode:"observe",depth:"classify",pathRules:[],sampling:Object.freeze({anonymousBeaconRate:.1}),failurePosture:"open"}),Dt=60*1e3,Nt=1440*60*1e3;function we(e,t){for(let r of e)if(Re(r.pattern).test(t))return r}function Re(e){let t=e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${t}$`,"i")}var be=1,Se=new Set(["cookie","set-cookie","x-csrf-token","proxy-authorization","x-astrasync-signature","x-astrasync-secret","x-hub-signature","x-hub-signature-256","x-astra-verified-hop"]),Ie=["x-astrasync-","kya-","kya_"],Te=new Set(["authorization","x-api-key","x-goog-api-key","api-key"]);function Ee(e){return e==="signature"||e.endsWith("-signature")||e.endsWith("-signature-256")}var Ce=new Set(["openai-organization","openai-project","openai-version","openai-processing-ms","anthropic-version","anthropic-beta","x-goog-user-project","x-goog-api-client","user-agent","x-agent-card-url","x-agent-provider"]),_e=["x-goog-","x-stainless-","x-anthropic-","x-openai-"],Ue=["sk-ant-api03","sk-ant-api","sk-ant","sk-proj","sk-svcacct","sk-or-v1","sk-or","sk-lf","sk-","gsk_","aiza","ya29","ghp_","gho_","github_pat_","xai-","pplx-","r8_","hf_","astra-","astrae-"],Pe=64,O=1024,xe=16*1024;function I(e){let t=0;for(let r=0;r<e.length;r++){let n=e.charCodeAt(r);n<128?t+=1:n<2048?t+=2:n>=55296&&n<=56319?(t+=4,r++):t+=3}return t}function ke(e){if(!e)return;let t=e.trim(),r=t.indexOf(" ");if(r>0){let a=t.slice(0,r).toLowerCase();(a==="bearer"||a==="basic"||a==="token")&&(t=t.slice(r+1).trim())}let n=t.toLowerCase();for(let a of Ue)if(n.startsWith(a))return a}function Oe(e){return!!(Se.has(e)||Ie.some(t=>e.startsWith(t))||Ee(e))}function Me(e){return Ce.has(e)?!0:_e.some(t=>e.startsWith(t))}function K(e){let t={},r={},n,a=0,i=0;if(!e)return{headers:t};for(let s of Object.keys(e)){if(i>=Pe)break;let c=s.toLowerCase(),f=e[s];if(f===void 0)continue;let p=Array.isArray(f)?f.join(", "):String(f);if(Te.has(c)){let h=ke(p);h&&!n&&(n=h);continue}if(Oe(c))continue;let u=p;I(u)>O&&(u=u.slice(0,O));let g=I(c)+I(u);a+g>xe||(a+=g,i++,t[c]=u,Me(c)&&(r[c]=u))}let o={headers:t};return n&&(o.apiKeyFormat=n),Object.keys(r).length>0&&(o.platformHeaders=r),o}var Fe=[{vendor:"claude",displayName:"Anthropic Claude",uaPatterns:[/claude/i,/anthropic/i],agentCardPatterns:[/anthropic\.com/i,/claude\.ai/i],apiKeyFormatPatterns:[/^sk-ant/i],headerPatterns:[/^anthropic-version/i,/^anthropic-beta/i,/^x-anthropic-/i]},{vendor:"chatgpt",displayName:"OpenAI ChatGPT",uaPatterns:[/chatgpt/i,/openai/i,/gpt-?\d/i],agentCardPatterns:[/openai\.com/i,/chatgpt\.com/i],apiKeyFormatPatterns:[/^sk-proj/i,/^sk-svcacct/i],headerPatterns:[/^openai-organization/i,/^openai-project/i,/^openai-version/i]},{vendor:"gemini",displayName:"Google Gemini",uaPatterns:[/gemini/i,/google-gemini/i,/bard/i],agentCardPatterns:[/gemini\.google\.com/i,/ai\.google\.dev/i],apiKeyFormatPatterns:[/^aiza/i],headerPatterns:[/^x-goog-user-project/i,/^x-goog-api-client/i]},{vendor:"cursor",displayName:"Cursor",uaPatterns:[/cursor/i],agentCardPatterns:[/cursor\.sh/i,/cursor\.com/i]},{vendor:"goose",displayName:"Block Goose",uaPatterns:[/goose/i,/block-goose/i],agentCardPatterns:[/block\.xyz\/goose/i,/goose\.tools/i]},{vendor:"perplexity",displayName:"Perplexity",uaPatterns:[/perplexity/i],agentCardPatterns:[/perplexity\.ai/i],apiKeyFormatPatterns:[/^pplx-/i]}];function He(e,t){let r=t.userAgent??"",n=t.agentCardUrl??"",a=t.apiKeyFormat??"",i=[];if(t.platformHeaders)for(let[o,s]of Object.entries(t.platformHeaders))i.push(o,`${o}: ${s}`);for(let o of e){let s=o.uaPatterns?.some(u=>u.test(r)),c=o.agentCardPatterns?.some(u=>u.test(n)),f=a.length>0&&o.apiKeyFormatPatterns?.some(u=>u.test(a)),p=i.length>0&&o.headerPatterns?.some(u=>i.some(g=>u.test(g)));if(s||c||f||p){let u=r.split(/[\s/]/)[0]?.toLowerCase()??"",g=(()=>{try{return n?new URL(n).host:""}catch{return""}})(),h=u||g||a||"unknown";return{vendor:o.vendor,fingerprintKey:`${o.vendor}:${h}`,displayName:o.displayName}}}}function M(e){return He(Fe,e)}function De(e){return e>=80?"PLATINUM":e>=60?"GOLD":e>=40?"SILVER":"BRONZE"}var Ne="4.1.0",jt=3600*1e3,je=new Map;function Le(e){return je.get(e)?.data}var z={apiBaseUrl:"https://astrasync.ai/api",defaultAccessLevel:"none",debug:!1},G=!1,F=!1;async function H(e,t,r,n){G=!0;try{let a=`${e}/agents/verify-access`,o=(await fetch(a,{method:"HEAD"})).headers.get("content-type")??"";if(o.startsWith("text/html")){let s=`[VerificationGateway] apiBaseUrl '${e}' returned HTML (content-type: ${o}). This usually means apiBaseUrl is pointing at a marketing site instead of the API. Expected: 'https://astrasync.ai/api' (prod) or 'https://staging.astrasync.ai/api' (staging).`;if(r)throw new Error(`${s} (strictInit=true)`);console.warn(`${s} Set disableInitChecks: true on GatewayConfig to silence.`)}else t&&console.log(`[VerificationGateway] init check passed for ${e} (content-type: ${o})`);if(n){let s=await fetch(`${e}/agents/verify-access/self-check`,{headers:{Authorization:`Bearer ${n}`}});if(s.status===401){let c=await s.json().catch(()=>({})),f=`[VerificationGateway] the configured AstraSync API key was REJECTED at startup (${c.code??"api_key_invalid"}: ${c.message??"invalid key"}). Every inbound agent will be turned away until the key is rotated`+(c.remediation?.manageUrl?` at ${c.remediation.manageUrl}`:"")+".";if(r)throw new Error(`${f} (strictInit=true)`);console.error(f)}else if(s.ok){let c=await s.json().catch(()=>({}));typeof c.daysToExpiry=="number"&&c.daysToExpiry<=7?console.warn(`[VerificationGateway] the configured AstraSync API key expires in ${c.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`):t&&console.log("[VerificationGateway] API key self-check passed")}}}catch(a){if(r)throw a;t&&console.log(`[VerificationGateway] init check failed (non-blocking): ${String(a)}`)}}var E=new Map;function X(e,t){let r=e.credentials;return[r.astraId||"",r.apiKey||"",r.jwt||"",e.purpose||"",e.action||"",e.resourceType||"",e.resource||"",e.jurisdiction||"",e.transactionValue??"",e.currency||"",t||"",e.counterpartyUrl||"",e.counterpartyType||"",e.isSubAgentRequest?"1":"0",e.parentAgentId||"",e.subAgentDepth??"",e.durationRequired??"",e.invocationProtocol||"",e.enableRuntimeChallenge?"1":"0",e.callerMetadata?.sourceIp||"",e.callerMetadata?.userAgent||"",e.callerMetadata?.forwardedFor||"",e.callerMetadata?.agentCardUrl||""].join("|")}function $e(e,t){let r=X(e,t),n=E.get(r);return n&&n.expiresAt>Date.now()?n.result:(n&&E.delete(r),null)}var Be=60;function Ve(e,t,r,n){let a=r&&r>0?r:Be,i=X(e,n);E.set(i,{result:t,expiresAt:Date.now()+a*1e3})}function Y(e,t){let r={},n=/^ASTRAE?-[A-Za-z0-9_-]{1,64}$/,a=e["x-astra-id"]||e["X-Astra-Id"]||e["X-ASTRA-ID"]||e["x-astra-agentid"]||e["X-Astra-AgentId"]||e["x-astra-agent-id"]||e["X-Astra-Agent-Id"]||e["X-ASTRA-AGENT-ID"];if(a){let s=Array.isArray(a)?a[0]:typeof a=="string"?a:void 0;typeof s=="string"&&n.test(s)&&(r.astraId=s)}let i=e["x-api-key"]||e["X-Api-Key"]||e["X-API-KEY"];i&&(r.apiKey=Array.isArray(i)?i[0]:i);let o=e.authorization||e.Authorization;if(o){let s=Array.isArray(o)?o[0]:o;typeof s=="string"&&(r.authorizationHeader=s,s.startsWith("Bearer ")&&(r.jwt=s.slice(7)))}return t&&(t.astraId&&!r.astraId&&(r.astraId=t.astraId),t.apiKey&&!r.apiKey&&(r.apiKey=t.apiKey)),r}function Ke(e,t,r={}){let n=r.source??"no_credentials",a=n==="api_error",i=n==="merchant_misconfig",o=r.urls,s=i?{message:"This site's AstraSync integration is misconfigured (its API key was revoked, expired, or rotated). Your identity and the AstraSync platform are fine \u2014 contact the site operator. Retrying will not help.",registrationUrl:o?.registrationUrl??"",documentationUrl:r.remediation?.docsUrl??o?.documentationUrl??"",steps:["Report the failure to the site operator \u2014 their AstraSync API key needs rotating","Do not retry: the failure is deterministic until the operator updates their key"]}:a?{message:"Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",registrationUrl:o?.registrationUrl??"",documentationUrl:o?.documentationUrl??"",steps:["Retry the request with exponential backoff","If failures persist, share the correlationId with support"]}:{message:"This service verifies AI agents before granting access. Please register your agent with AstraSync.",registrationUrl:o?.registrationUrl??"",documentationUrl:o?.documentationUrl??"",steps:["Register for an AstraSync account","Create and register your agent","Add your ASTRA-ID to request headers","Retry your request"]};return{identityVerified:!1,policyAllowed:!1,accessLevel:"none",guidance:s,denialReasons:t?[t]:["No valid agent credentials provided"],failures:i?[{dimension:"verify_access.merchant_misconfigured",message:t??"The integration API key was rejected by AstraSync",guidance:s.message}]:a?[{dimension:"verify_access.api_error",message:t??"Verification temporarily unavailable",guidance:s.message}]:void 0,...i&&{misconfigured:!0},correlationId:r.correlationId,attemptId:r.attemptId,verifiedAt:new Date}}async function ze(e,t){let{credentials:r,...n}=t,a={...r.astraId&&{agentId:r.astraId},...n.purpose&&{purpose:n.purpose}};if(n.action&&(a.action=n.action),n.resourceType&&(a.resourceType=n.resourceType),n.resource&&(a.resource=n.resource),n.jurisdiction&&(a.jurisdiction=n.jurisdiction),n.transactionValue&&(a.transactionValue=n.transactionValue),n.currency&&(a.currency=n.currency),n.isSubAgentRequest&&(a.isSubAgentRequest=n.isSubAgentRequest),n.parentAgentId&&(a.parentAgentId=n.parentAgentId),n.subAgentDepth!==void 0&&(a.subAgentDepth=n.subAgentDepth),n.enableRuntimeChallenge&&(a.enableRuntimeChallenge=n.enableRuntimeChallenge),n.createSession&&(a.createSession=n.createSession),n.durationRequired&&(a.durationRequired=n.durationRequired),n.counterpartyType&&(a.counterpartyType=n.counterpartyType),n.counterpartyUrl&&(a.counterpartyUrl=n.counterpartyUrl),e.counterpartyId&&(a.counterpartyId=e.counterpartyId),n.runtimeChallengeOptions&&(a.runtimeChallengeOptions=n.runtimeChallengeOptions),n.invocationProtocol&&(a.invocationProtocol=n.invocationProtocol),n.commerceArtifacts&&(a.commerceArtifacts=n.commerceArtifacts),n.attemptId&&(a.attemptId=n.attemptId),n.considerationSet&&(a.considerationSet=n.considerationSet),a.sdkVersion=Ne,n.callerMetadata||n.clientIp||n.userAgent){let o={...n.clientIp&&{sourceIp:n.clientIp},...n.userAgent&&{userAgent:n.userAgent},...n.callerMetadata};Object.keys(o).length>0&&(a.callerMetadata=o)}let i={"Content-Type":"application/json",...e.customHeaders};e.apiKey&&(i.Authorization=`Bearer ${e.apiKey}`,i["X-API-Key"]=e.apiKey);try{let o=await fetch(`${e.apiBaseUrl}/agents/verify-access`,{method:"POST",headers:i,body:JSON.stringify(a),signal:AbortSignal.timeout(e.verifyTimeoutMs??1e4)}),s=await o.json();return o.status===410?{success:!0,attemptId:typeof s?.attemptId=="string"?s.attemptId:void 0,access:{allowed:!1,accessLevel:"none",reason:"endpoint_deactivated",failures:[{dimension:"endpoint.deactivated",message:typeof s?.message=="string"?s.message:"Endpoint has been deactivated",guidance:typeof s?.guidance=="string"?s.guidance:"Reactivate via POST /api/endpoints/{id}/reactivate, or update the URL on the calling agent."}]}}:o.ok?s:o.status===401?{success:!1,error:s.message||s.error||"Integration API key rejected",misconfig:!0,misconfigCode:typeof s?.code=="string"?s.code:void 0,remediation:s?.remediation&&typeof s.remediation=="object"?s.remediation:void 0,correlationId:typeof s?.correlationId=="string"?s.correlationId:void 0,attemptId:typeof s?.attemptId=="string"?s.attemptId:void 0}:{success:!1,error:s.message||s.error||`API returned ${o.status}`,correlationId:typeof s?.correlationId=="string"?s.correlationId:void 0,attemptId:typeof s?.attemptId=="string"?s.attemptId:void 0}}catch(o){return{success:!1,error:`Failed to call verify-access API: ${o instanceof Error?o.message:"Unknown error"}`}}}async function D(e,t,r){let n={...z,...e},a=n.apiBaseUrl?Le(n.apiBaseUrl):void 0;if(!G&&!n.disableInitChecks&&n.apiBaseUrl&&(n.strictInit?await H(n.apiBaseUrl,n.debug,!0,n.apiKey):H(n.apiBaseUrl,n.debug,!1,n.apiKey)),!F&&(e.minTrustScore!==void 0||e.minTrustScoreForFull!==void 0)&&(F=!0,console.warn("[VerificationGateway] minTrustScore / minTrustScoreForFull are deprecated in v2.3.0 and have no effect. Server is now the single source of truth for access-level decisions (the SDK reads access.accessLevel from the verify-access response). To gate access to an endpoint, configure the endpoint's trust_score_requirement server-side.")),n.cacheTtl!==0&&!r?.bypassCache){let y=$e(t,n.counterpartyId);if(y){n.debug&&console.log("[VerificationGateway] Returning cached result");let R={...y};return t.attemptId?R.attemptId=t.attemptId:delete R.attemptId,R}}let i={...t};!i.counterpartyUrl&&n.counterpartyUrl&&(i.counterpartyUrl=n.counterpartyUrl),!i.counterpartyType&&n.counterpartyType&&(i.counterpartyType=n.counterpartyType),n.debug&&console.log("[VerificationGateway] Calling verify-access API");let o=await ze(n,i),s=o.denialReasons,c=Array.isArray(s)&&s.length>0?s:void 0;if(!o.success)return Ke(n,o.error,{source:o.misconfig?"merchant_misconfig":"api_error",correlationId:o.correlationId,attemptId:o.attemptId,remediation:o.remediation,urls:a});if(!o.access?.allowed){let y=o.access?.failures;return{identityVerified:o.verificationContext?.idVerified===!0,policyAllowed:!1,accessLevel:"none",denialReasons:c??(y&&y.length>0?y.map(le=>le.message):o.access?.reason?[o.access.reason]:["Access denied"]),failures:y,requiresStepUp:o.access?.requiresStepUp,requiresApproval:o.access?.requiresApproval,guidance:{message:o.access?.reason||"Access denied by PDLSS policy",registrationUrl:a?.registrationUrl??"",documentationUrl:a?.documentationUrl??""},verifiedAt:new Date,sessionId:o.sessionId,correlationId:o.correlationId,attemptId:o.attemptId,recommendation:o.recommendation,recommendationReasons:o.recommendationReasons,stepUpApproval:o.stepUpApproval,settlement:o.settlement}}let f=o.agent?{astraId:o.agent.astraId,name:o.agent.name,trustScore:o.agent.trustScore,trustLevel:De(o.agent.trustScore),blockchainVerified:o.agent.blockchainStatus==="verified",status:o.agent.agentStatus}:void 0,p=o.developer?{astradId:o.developer.kyaOwnerId,name:o.developer.fullName,trustScore:o.developer.trustScore||0,verified:o.developer.identityVerified}:void 0,u=o.organization?{name:o.organization.name,verified:o.organization.verified,trustScore:o.organization.trustScore}:void 0,g=o.verificationContext,h=o.access?.accessLevel??"standard",d={identityVerified:o.verificationContext?.idVerified!==!1,policyAllowed:!0,accessLevel:h,agent:f,developer:p,organization:u,appliedPolicy:o.access?.appliedPolicy,verificationContext:g,requiresStepUp:o.access?.requiresStepUp,requiresApproval:o.access?.requiresApproval,verifiedAt:new Date,cacheTtl:n.cacheTtl,sessionId:o.sessionId,correlationId:o.correlationId,attemptId:o.attemptId,runtimeChallenge:o.runtimeChallenge,tokenGuidance:o.tokenGuidance,recommendation:o.recommendation,recommendationReasons:o.recommendationReasons,warningHeader:o.warningHeader,stepUpApproval:o.stepUpApproval,settlement:o.settlement};d.recommendation==="deny"?(d.policyAllowed=!1,d.accessLevel="none",d.denialReasons=c??d.recommendationReasons??["Access denied by AstraSync recommendation"],d.guidance=d.runtimeChallenge?{message:`Verification failed: ${d.runtimeChallenge.reason||"runtime challenge failed"}`,registrationUrl:a?.registrationUrl??"",documentationUrl:a?.documentationUrl??""}:{message:d.recommendationReasons?.[0]||"Access denied by AstraSync recommendation",registrationUrl:a?.registrationUrl??"",documentationUrl:a?.documentationUrl??""}):d.recommendation==="step_up_required"&&(d.requiresStepUp=!0,d.denialReasons=c??d.recommendationReasons??["Step-up verification required"]);let m=d.requiresStepUp===!0||d.requiresApproval===!0||d.recommendation==="step_up_required";return n.cacheTtl!==0&&d.recommendation!=="deny"&&!m&&Ve(t,d,n.cacheTtl,n.counterpartyId),d}async function Ge(e,t){let r=e.apiBaseUrl||z.apiBaseUrl,{counterpartyUrl:n,...a}=t,i={...a,...n&&{counterpartyUrl:n},...e.counterpartyId&&{counterpartyId:e.counterpartyId}};await fetch(`${r}/verification-activity/unregistered-attempt`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)}).catch(()=>{})}function l(e,t){return e.headers[t.toLowerCase()]}function Xe(e,t){let r=e.querystring?`?${e.querystring}`:"";return`https://${t}${e.uri}${r}`}function _(e){let{headers:t,apiKeyFormat:r,platformHeaders:n}=K(e.headers),a=e.connection??{};return{headers:t,...r&&{apiKeyFormat:r},...n&&{platformHeaders:n},...Object.keys(a).length>0&&{connection:a},schemaVersion:be}}var Ye=/^eyJ[\w-]+\.[\w-]*\.[\w-]+~/;function Je(e){let t=e.replace(/-/g,"+").replace(/_/g,"/"),r=t+"=".repeat((4-t.length%4)%4),n=atob(r),a=new Uint8Array(n.length);for(let i=0;i<n.length;i++)a[i]=n.charCodeAt(i);return new TextDecoder().decode(a)}function We(e){try{let t=e.split(".")[0];return JSON.parse(Je(t))}catch{return}}function Ze(e){if(e)try{let t=JSON.parse(e);return typeof t=="object"&&t!==null?t:void 0}catch{return}}function Qe(e,t){let r=Xe(e,t),n=e.method,a=e.headers,i=Ze(e.bodyText),o=e.bodyTruncated?{bodyTruncated:!0}:{},s=l(e,"authorization"),c=s?.replace(/^Bearer\s+/i,""),p=[l(e,"x-payment-mandate")??l(e,"payment-mandate"),c].find(d=>d&&Ye.test(d));if(p){let d=We(p)?.typ;return{protocol:"vi-sd-jwt",artifacts:{viSdJwt:p},evidence:{typ:d,...o}}}let u=l(e,"signature"),g=l(e,"signature-input");if(u&&g){let d=l(e,"signature-agent"),m=l(e,"x-agent-registry"),y=g.toLowerCase();return{protocol:d?"rfc9421-web-bot-auth":m?.toLowerCase().includes("visa")||y.includes("visa")?"rfc9421-tap":m?.toLowerCase().includes("mastercard")||y.includes("mastercard")?"rfc9421-agent-pay":"rfc9421",artifacts:{rfc9421:{headers:a,method:n,url:r,...e.bodyText&&{body:e.bodyText}}},evidence:{...d&&{signatureAgent:d},...m&&{registryHint:m},keyid:g.match(/keyid="([^"]+)"/)?.[1],...o}}}let h=l(e,"timestamp");if(u&&!g&&(h||l(e,"idempotency-key")))return{protocol:"acp",artifacts:{acpRequest:{method:n,url:r,headers:a,...e.bodyText&&{rawBody:e.bodyText},...i&&{body:i}}},evidence:{hasTimestamp:!!h,...o}};if(/\/checkout[-_]sessions(\/|$)/.test(e.uri))return{protocol:"ucp",artifacts:{ucpRequest:{method:n,url:r,...i&&{body:i}}},evidence:{path:e.uri,...o}};if(i){let d=i.intent_mandate??i.intentMandate,m=i.cart_mandate??i.cartMandate,y=i.payment_mandate??i.paymentMandate;if(typeof d=="string"||typeof m=="string"||typeof y=="string")return{protocol:"ap2",artifacts:{ap2Mandates:{...typeof d=="string"&&{intent:d},...typeof m=="string"&&{cart:m},...typeof y=="string"&&{payment:y}}},evidence:{mandatesPresent:[typeof d=="string"&&"intent",typeof m=="string"&&"cart",typeof y=="string"&&"payment"].filter(Boolean),...o}}}if(s&&/^Payment\s+/i.test(s))return{protocol:"mpp",artifacts:{mppRequest:{method:n,url:r,headers:a,...e.bodyText&&{rawBody:e.bodyText},...i&&{body:i}}},evidence:o};if(l(e,"x-payment"))return{protocol:"x402",artifacts:{x402Request:{method:n,url:r,headers:a,...i&&{body:i}}},evidence:o}}var qe=/\.(css|js|mjs|map|png|jpe?g|gif|webp|avif|svg|ico|woff2?|ttf|otf|eot|mp4|webm|pdf|txt|xml)$/i,et=/curl|wget|python-requests|python-urllib|node-fetch|axios|got|okhttp|go-http-client|libwww|httpie|java\/|ruby|php|scrapy|headless|phantomjs|selenium|playwright|puppeteer|bot|crawler|spider|slurp/i,tt=/mozilla\/\d/i;function rt(e,t){if(qe.test(t))return{mode:e.mode,depth:e.depth,skip:!0};let r=we(e.pathRules,t);return r?{mode:r.mode??e.mode,depth:r.depth??e.depth,skip:r.skip===!0}:{mode:e.mode,depth:e.depth,skip:!1}}function nt(e,t,r){let n=l(e,"user-agent")??"",a=Y(e.headers,{});if(a.astraId||a.apiKey||a.jwt)return{tier:"verified",protocol:"x-astra"};let{apiKeyFormat:i,platformHeaders:o}=K(e.headers),s={userAgent:n,apiKeyFormat:i,platformHeaders:o};if(r!=="classify"){let f=Qe(e,t);if(f)return{tier:"identified",protocol:f.protocol,artifacts:f.artifacts,evidence:f.evidence,fingerprint:M(s)}}let c=M(s);return c?{tier:"identified",protocol:"platform-ua",fingerprint:c}:!n||et.test(n)||!tt.test(n)?{tier:"anonymous-bot",evidence:{userAgent:n||"(none)"}}:!l(e,"sec-fetch-mode")&&!l(e,"accept-language")?{tier:"anonymous-bot",evidence:{userAgent:n,spoofedBrowser:!0}}:{tier:"human"}}var at={"vi-sd-jwt":"This request presented Visa Intelligent Commerce credentials, but the agent is not verified with AstraSync. Register the agent to pair your VI credentials with a verified identity and mandate.","rfc9421-agent-pay":"This request carried a Mastercard Agent Pay signature, but the agent is not verified with AstraSync. Register the agent to pair Agent Pay credentials with a verified identity and mandate.","rfc9421-tap":"This request carried a Visa TAP signature, but the agent is not verified with AstraSync. Register the agent to pair TAP credentials with a verified identity and mandate.","rfc9421-web-bot-auth":"This request carried a Web Bot Auth signature, but the agent is not verified with AstraSync. Register the agent to link your signed-agent identity to a verified principal.",rfc9421:"This request carried an RFC 9421 HTTP message signature, but the agent is not verified with AstraSync. Register the agent to link the signing identity to a verified principal.",acp:"This request used the Agentic Commerce Protocol, but the agent is not verified with AstraSync. Register the agent to transact on this property.",ucp:"This request used a UCP checkout session, but the agent is not verified with AstraSync. Register the agent to transact on this property.",ap2:"This request presented AP2 mandates, but the agent is not verified with AstraSync. Register the agent to pair your mandates with a verified identity.",mpp:"This request presented MPP payment credentials, but the agent is not verified with AstraSync. Register the agent to transact on this property.",x402:"This request used x402 payment headers, but the agent is not verified with AstraSync. Register the agent to transact on this property.","platform-ua":"This traffic was identified as a platform AI agent without AstraSync credentials. Register the agent to access this property with a verified identity.","x-astra":"The presented AstraSync credentials failed verification or were denied by this property\u2019s access policy."};function ot(e,t){let r=t.replace(/\/api\/?$/,""),n=(e.protocol&&at[e.protocol])??"This request could not be verified by AstraSync.";return{success:!1,error:{code:e.tier==="verified"?"INSUFFICIENT_ACCESS":"UNAUTHORIZED",message:n,...e.protocol&&{protocol:e.protocol},failures:e.reasons,...e.correlationId&&{correlationId:e.correlationId},guidance:{message:e.guidance?.message||"Register this agent with AstraSync to receive verifiable credentials.",registrationUrl:e.guidance?.registrationUrl||`${r}/register`,documentationUrl:e.guidance?.documentationUrl||`${r}/docs/edge-adapter`}}}}function it(e){return{success:!1,status:"manual_review",message:"This action requires human approval. Poll the step-up approval endpoint and retry once approved.",...e.stepUp&&{stepUpApproval:e.stepUp},...e.correlationId&&{correlationId:e.correlationId}}}var st="X-Astra-Verified-Hop";function ct(e){return`${e.astraId};${e.sessionId??""};${e.checkedAt}`}var A={trustScore:"X-AstraSync-Trust-Score",agentId:"X-AstraSync-Agent-Id",developerId:"X-AstraSync-Developer-Id",permissions:"X-AstraSync-Permissions",observedDecision:"X-AstraSync-Observed-Decision"},N=["x-astrasync-","x-astra-verified-hop"];function T(){return{action:"pass",injectHeaders:{},stripHeaderPrefixes:[]}}function dt(e,t,r){let n={};if(t.misconfig&&(n[A.observedDecision]="misconfig"),r.mode!=="enforce")return t.decision!=="allow"&&(n[A.observedDecision]=t.decision),Object.assign(n,j(t),L(t)),{action:"pass",injectHeaders:n,stripHeaderPrefixes:N};switch(t.decision){case"deny":return{action:"respond",status:403,headers:{},bodyJson:ot(t,e.apiBaseUrl)};case"manual_review":return{action:"respond",status:202,headers:{"Retry-After":"30"},bodyJson:it(t)};default:return Object.assign(n,j(t),L(t)),{action:"pass",injectHeaders:n,stripHeaderPrefixes:N}}}function j(e){if(!e.asat)return{};let{trustScore:t,agentId:r,developerId:n,accessLevel:a}=e.asat;return{...t!==void 0&&{[A.trustScore]:String(t)},...r&&{[A.agentId]:r},...n&&{[A.developerId]:n},...a&&{[A.permissions]:a}}}function L(e){let t=e.asat?.agentId;return t?{[st]:ct({astraId:t,...e.sessionId&&{sessionId:e.sessionId},checkedAt:Date.now()})}:{}}var ut={GET:"data.read",HEAD:"data.read",OPTIONS:"data.read",POST:"data.write",PUT:"data.write",PATCH:"data.write",DELETE:"data.delete"},lt="data.write",pt="data";function ft(e){return ut[e.toUpperCase()]??lt}function gt(e){let t=e.indexOf(":");if(t>=0)return{purpose:e.slice(0,t)};let r=e.indexOf(".");return r>0&&r<e.length-1?{purpose:e.slice(0,r),actionCandidate:e}:{purpose:e}}function yt(e){let t=e.astraPurpose?gt(e.astraPurpose):void 0,r,n;e.routeAction?(r=e.routeAction,n="route_config"):e.hasCustomActionExtractor&&e.customAction?(r=e.customAction,n="custom_extractor"):!e.hasCustomActionExtractor&&e.astraAction?(r=e.astraAction,n="header"):!e.hasCustomActionExtractor&&t?.actionCandidate?(r=t.actionCandidate,n="purpose_header_derived"):(r=ft(e.method),n="method_table");let a,i;if(e.routePurpose?(a=e.routePurpose,i="route_config"):e.hasCustomPurposeExtractor?e.customPurpose&&(a=e.customPurpose,i="custom_extractor"):t?(a=t.purpose,i="header"):e.legacyPurpose?(a=e.legacyPurpose,i="legacy_header"):e.queryPurpose&&(a=e.queryPurpose,i="query"),!a){let o=r.indexOf(".");o>0?(a=r.slice(0,o),i="action_derived"):(a=pt,i="transport_default")}return{purpose:a,action:r,purposeSource:i,actionSource:n}}async function C(e,t){let r;try{return await Promise.race([e,new Promise(n=>{r=setTimeout(()=>n(void 0),t)})])}catch{return}finally{r&&clearTimeout(r)}}async function $(e,t,r,n){let a=C(Ge({apiBaseUrl:e.apiBaseUrl,counterpartyId:e.counterpartyId},{counterpartyUrl:e.counterpartyUrl,counterpartyType:"website",sourceIp:t.clientIp,userAgent:l(t,"user-agent"),requestPath:t.uri,requestMethod:t.method,protocol:r.protocol,evidence:r.evidence,observedMetadata:_(t)}),n);if(e.waitUntil){e.waitUntil(a);return}await a}function mt(e,t=Math.random){return e<=0?!1:e>=1?!0:t()<e}function v(e){return{tier:e.tier,decision:"allow",reasons:[],protocol:e.protocol}}function B(e,t,r,n){let a=yt({method:t.method,astraPurpose:l(t,"x-astra-purpose"),astraAction:l(t,"x-astra-action")}),i=l(t,"x-astra-attempt-id");return{...i&&/^att_[0-9a-f]{32}$/.test(i)&&{attemptId:i},credentials:Y(t.headers,{}),purpose:a.purpose,action:a.action,resource:t.uri,createSession:!0,counterpartyType:"website",counterpartyUrl:e.counterpartyUrl,callerMetadata:{sourceIp:t.clientIp,userAgent:l(t,"user-agent"),referer:l(t,"referer"),host:l(t,"host"),observedMetadata:_(t)},...n&&r.artifacts&&{commerceArtifacts:r.artifacts}}}function V(e,t){let r=v(e);return r.sessionId=t.sessionId,r.correlationId=t.correlationId,r.guidance=t.guidance,t.misconfigured?(r.reasons=["verification key misconfigured \u2014 fail-open (merchant_misconfig)"],r.misconfig=!0,r):t.requiresStepUp||t.stepUpApproval?(r.decision="manual_review",r.reasons=["step-up approval required"],r.stepUp={pollUrl:t.stepUpApproval?.pollUrl,expiresAt:t.stepUpApproval?.expiresAt},r):!t.identityVerified||!t.policyAllowed?(r.decision="deny",r.reasons=t.denialReasons?.length?t.denialReasons:[t.identityVerified?"denied by access policy":"identity not verified"],r):(r.asat={trustScore:t.agent?.trustScore,agentId:t.agent?.astraId,developerId:t.developer?.astradId,accessLevel:t.accessLevel},r)}async function ht(e,t,r,n,a,i){let o=e.budgets??{},s=o.beaconMs??300,c=n.mode==="enforce"?o.verifyEnforceMs??3e3:o.verifyObserveMs??1500,f={apiBaseUrl:e.apiBaseUrl,...a&&{apiKey:a},counterpartyId:e.counterpartyId,disableInitChecks:!0};try{switch(r.tier){case"verified":{if(n.depth==="classify")return v(r);let p=await C(D(f,B(e,t,r,n.depth==="authorize")),c);if(!p){let u=v(r);return u.reasons=["verify-access unreachable within budget \u2014 fail-open"],u}return V(r,p)}case"identified":{if(n.depth==="authorize"&&r.artifacts){let u=await C(D(f,B(e,t,r,!0)),c);if(!u){let g=v(r);return g.reasons=["verify-access unreachable within budget \u2014 fail-open"],g}return V(r,u)}await $(e,t,r,s);let p=v(r);return n.mode==="enforce"&&n.depth!=="classify"&&(p.decision="deny",p.reasons=["agent is not registered with AstraSync"]),p}case"anonymous-bot":return mt(i.anonymousBeaconRate,e.random)&&await $(e,t,{...r,protocol:r.protocol??"ua-heuristic"},s),v(r);default:return v(r)}}catch(p){let u=v(r);return u.reasons=[`edge pipeline error \u2014 fail-open: ${p instanceof Error?p.message:String(p)}`],u}}async function J(e,t,r,n){let a=rt(r,t.uri);try{if(a.skip)return{policy:a,application:T()};let i=l(t,"x-forwarded-host")??l(t,"host")??new URL(e.counterpartyUrl).host,o=nt(t,i,a.depth);if(o.tier==="human")return{policy:a,classification:o,application:T()};let s=await ht(e,t,o,a,n,r.sampling);return s.decision!=="allow"&&e.log?.("info",a.mode==="enforce"?`${s.decision.toUpperCase()}: ${t.method} ${t.uri}`:`[OBSERVE] would have been ${s.decision.toUpperCase()}: ${t.method} ${t.uri}`,{tier:s.tier,protocol:s.protocol,reasons:s.reasons}),{policy:a,classification:o,outcome:s,application:dt(e,s,a)}}catch(i){return e.log?.("error","edge evaluation error \u2014 passing request through",{error:i instanceof Error?i.message:String(i)}),{policy:a,application:T()}}}function W(e){return e.Records[0].cf.request}function w(e,t){return e.headers[t.toLowerCase()]?.[0]?.value}function vt(e){let t={};for(let[r,n]of Object.entries(e))n?.length&&(t[r]=n.map(a=>a.value).join(", "));return t}function At(e){let t=e.body;return t?.data?{text:t.encoding==="base64"?Buffer.from(t.data,"base64").toString("utf8"):t.data,truncated:t.inputTruncated===!0}:{truncated:!1}}function wt(e){let t={},r=(i,o,s)=>{let c=w(e,o);c&&(t[i]=s?s(c):c)},n=w(e,"cloudfront-viewer-address");n&&(t.ip=n.replace(/:\d+$/,"")),r("country","cloudfront-viewer-country"),r("countryName","cloudfront-viewer-country-name"),r("region","cloudfront-viewer-country-region"),r("countryRegionName","cloudfront-viewer-country-region-name"),r("city","cloudfront-viewer-city"),r("asn","cloudfront-viewer-asn"),r("tlsVersion","cloudfront-viewer-tls"),r("httpVersion","cloudfront-viewer-http-version"),r("timeZone","cloudfront-viewer-time-zone"),r("metroCode","cloudfront-viewer-metro-code"),r("tlsFingerprint","cloudfront-viewer-ja3-fingerprint"),r("ja4","cloudfront-viewer-ja4-fingerprint"),r("headerOrder","cloudfront-viewer-header-order",i=>i.slice(0,256)),r("headerCount","cloudfront-viewer-header-count");let a=[];for(let[i,o]of[["desktop","cloudfront-is-desktop-viewer"],["mobile","cloudfront-is-mobile-viewer"],["tablet","cloudfront-is-tablet-viewer"],["smarttv","cloudfront-is-smarttv-viewer"]])w(e,o)==="true"&&a.push(i);return a.length&&(t.device=a.join(",")),t}function Z(e){let t=At(e),r=wt(e),n=w(e,"cloudfront-viewer-address")?.split(":")[0];return{method:e.method,uri:e.uri,querystring:e.querystring??"",headers:vt(e.headers),...t.text!==void 0&&{bodyText:t.text},...t.truncated&&{bodyTruncated:!0},...n&&{clientIp:n},...Object.keys(r).length>0&&{connection:r}}}function Q(e,t){if(t.action==="respond")return bt(t.status,t.bodyJson,t.headers);for(let r of t.stripHeaderPrefixes)Rt(e,r);for(let[r,n]of Object.entries(t.injectHeaders))U(e,r,n);return e}function q(e){let t=e.origin?.custom?.domainName??e.origin?.s3?.domainName??void 0;if(!t)return;let r=w(e,"host");!r||r===t||(w(e,"x-forwarded-host")||U(e,"X-Forwarded-Host",r),U(e,"Host",t))}function U(e,t,r){e.headers[t.toLowerCase()]=[{key:t,value:r}]}function Rt(e,t){let r=t.toLowerCase();for(let n of Object.keys(e.headers))n.startsWith(r)&&delete e.headers[n]}function bt(e,t,r){let n={"content-type":[{key:"Content-Type",value:"application/json"}]};for(let[a,i]of Object.entries(r??{}))n[a.toLowerCase()]=[{key:a,value:i}];return{status:String(e),statusDescription:e===403?"Forbidden":e===202?"Accepted":"OK",headers:n,body:JSON.stringify(t)}}var x=Object.freeze({version:1,mode:"observe",depth:"classify",pathRules:[],sampling:Object.freeze({anonymousBeaconRate:.1}),failurePosture:"open"}),ee=["observe","enforce"],te=["classify","authenticate","authorize"];function St(e){if(typeof e!="object"||e===null)return!1;let t=e;if(t.version!==1||!ee.includes(t.mode)||!te.includes(t.depth)||!Array.isArray(t.pathRules))return!1;for(let n of t.pathRules){if(typeof n!="object"||n===null)return!1;let a=n;if(typeof a.pattern!="string"||a.pattern.length===0||a.mode!==void 0&&!ee.includes(a.mode)||a.depth!==void 0&&!te.includes(a.depth))return!1}let r=t.sampling;return typeof r!="object"||r===null||typeof r.anonymousBeaconRate!="number"||r.anonymousBeaconRate<0||r.anonymousBeaconRate>1?!1:t.failurePosture==="open"}async function It(e,t,r){if(!t)return null;let n={Accept:"application/json"};e.apiKey&&(n.Authorization=`Bearer ${e.apiKey}`,n["X-API-Key"]=e.apiKey),r?.etag&&(n["If-None-Match"]=r.etag);try{let a=await fetch(`${e.apiBaseUrl}/endpoints/${encodeURIComponent(t)}/edge-config`,{method:"GET",headers:n,signal:AbortSignal.timeout(r?.timeoutMs??5e3)});if(a.status===304)return{notModified:!0};if(!a.ok)return null;let o=(await a.json()).data?.edgeConfig;return St(o)?{edgeConfig:o,etag:a.headers.get("etag")??void 0}:null}catch{return null}}var Tt=60*1e3,Et=1440*60*1e3,b=new Map,P=new Map;function ne(e,t){return`${e}|${t}`}function Ct(e){return e.mode!=="enforce"&&!e.pathRules.some(t=>t.mode==="enforce")?e:{...e,mode:"observe",pathRules:e.pathRules.map(t=>t.mode==="enforce"?{...t,mode:"observe"}:t)}}async function re(e,t){let r=ne(e.apiBaseUrl,t),n=P.get(r);if(n)return n;let a=It(e,t,{etag:b.get(r)?.etag}).then(i=>{if(i===null)return;let o=Date.now();if("notModified"in i){let s=b.get(r);s&&(s.lastSuccessAt=o);return}b.set(r,{config:i.edgeConfig,etag:i.etag,lastSuccessAt:o})}).finally(()=>{P.delete(r)});return P.set(r,a),a}async function ae(e,t,r){let n=r?.ttlMs??Tt,a=r?.maxStaleMs??Et,i=ne(e.apiBaseUrl,t),o=b.get(i);if(o){let s=Date.now()-o.lastSuccessAt;return s>n&&re(e,t).catch(()=>{}),s>a?Ct(o.config):o.config}return await re(e,t).catch(()=>{}),b.get(i)?.config??x}var _t=900*1e3,oe=new Map;async function Ut(e){let{SecretsManagerClient:t,GetSecretValueCommand:r}=await import("@aws-sdk/client-secrets-manager");return(await new t({region:"us-east-1"}).send(new r({SecretId:e}))).SecretString??void 0}async function ie(e){if(e.apiKey)return e.apiKey;let t=e.apiKeySecretName;if(!t)return;let r=oe.get(t);if(r&&Date.now()-r.fetchedAt<_t)return r.value;try{let a=await(e.fetchSecret??Ut)(t);return oe.set(t,{value:a,fetchedAt:Date.now()}),a}catch(n){return e.log?.("warn","Secrets Manager fetch failed \u2014 continuing keyless",{secretName:t,error:n instanceof Error?n.message:String(n)}),r?.value}}async function se(e,t){try{return await ae({apiBaseUrl:e.apiBaseUrl,apiKey:t},e.counterpartyId,{ttlMs:e.configTtlMs})}catch{return e.fallbackConfig??x}}function ce(e){let t=e.log??((r,n,a)=>{console[r==="info"?"log":r](`[astrasync-edge] ${n}`,a??"")});return async function(n){let a=W(n);try{q(a);let i=await ie({...e,log:t}),o=await se(e,i),{application:s}=await J({...e,log:t},Z(a),o,i);return Q(a,s)}catch(i){return t("error","edge handler error \u2014 passing request through",{error:i instanceof Error?i.message:String(i)}),a}}}var Pt="https://astrasync.ai/api",de="",ue="",xt=void 0;(!de||!ue)&&console.error("[astrasync-edge] ASTRASYNC_COUNTERPARTY_ID / ASTRASYNC_COUNTERPARTY_URL were not baked into the bundle \u2014 rebuild via infra/deploy.sh with the ASTRASYNC_* env vars set.");var kt=ce({apiBaseUrl:Pt,counterpartyId:de,counterpartyUrl:ue,apiKeySecretName:xt});0&&(module.exports={handler});
package/infra/RUNBOOK.md CHANGED
@@ -84,6 +84,21 @@ Production rollover = the same commands with prod values
84
84
  (`astrasync.ai`, `https://astrasync.ai/api`, its own secret + cert + stack
85
85
  name) after the staging stability gate above.
86
86
 
87
+ ## Upgrading an existing stack for fingerprint capture (adapter-lambda ≥ 2.2.0)
88
+
89
+ Stacks deployed before 2.2.0 whitelist no JA3/JA4/header-structure headers in
90
+ the `EdgeOriginRequestPolicy`, so those signals never reach the function.
91
+ Upgrading is a normal CloudFormation stack update — re-run `deploy.sh` with
92
+ the same exports after updating the package. Origin-request-policy changes
93
+ propagate without replacing the distribution (minutes, no DNS impact, no
94
+ downtime).
95
+
96
+ Verify after update: make one HTTPS request through the distribution and
97
+ confirm a `cloudfront-viewer-ja3-fingerprint` value appears in the function's
98
+ CloudWatch log (or in the verify-access event's `observedMetadata.connection`
99
+ as `tlsFingerprint`). JA3/JA4 are present only for HTTPS viewers — an HTTP
100
+ probe redirects first, so probe the `https://` URL directly.
101
+
87
102
  ## DNS cutover
88
103
 
89
104
  Point the site domain at the `DistributionDomainName` stack output:
@@ -72,12 +72,15 @@ Resources:
72
72
  Role: !GetAtt EdgeFunctionRole.Arn
73
73
 
74
74
  # Maximal metadata capture — capture the CloudFront-Viewer-* connection signals
75
- # (IP / ASN / country / TLS version / HTTP version / device class). The
76
- # managed AllViewerExceptHostHeader policy does NOT add these; only a policy
75
+ # (IP / ASN / geo / TLS version / HTTP version / device class / JA3+JA4
76
+ # client fingerprints / header-structure fingerprint). The managed
77
+ # AllViewerExceptHostHeader policy does NOT add these; only a policy
77
78
  # with `allViewerAndWhitelistCloudFront` does — and that also forwards the
78
79
  # viewer Host. The edge Lambda restores origin-domain Host + X-Forwarded-Host
79
80
  # itself (see cf.ts:normalizeOriginHost), so vhost / PaaS origin routing is
80
- # unaffected.
81
+ # unaffected. JA3/JA4 are populated only for HTTPS viewer connections (moot
82
+ # here: ViewerProtocolPolicy is redirect-to-https) and are deliverable ONLY
83
+ # via an origin request policy, never a cache policy.
81
84
  EdgeOriginRequestPolicy:
82
85
  Type: AWS::CloudFront::OriginRequestPolicy
83
86
  Properties:
@@ -92,9 +95,16 @@ Resources:
92
95
  - CloudFront-Viewer-Country
93
96
  - CloudFront-Viewer-Country-Name
94
97
  - CloudFront-Viewer-Country-Region
98
+ - CloudFront-Viewer-Country-Region-Name
95
99
  - CloudFront-Viewer-City
100
+ - CloudFront-Viewer-Time-Zone
101
+ - CloudFront-Viewer-Metro-Code
96
102
  - CloudFront-Viewer-TLS
97
103
  - CloudFront-Viewer-HTTP-Version
104
+ - CloudFront-Viewer-JA3-Fingerprint
105
+ - CloudFront-Viewer-JA4-Fingerprint
106
+ - CloudFront-Viewer-Header-Order
107
+ - CloudFront-Viewer-Header-Count
98
108
  - CloudFront-Is-Desktop-Viewer
99
109
  - CloudFront-Is-Mobile-Viewer
100
110
  - CloudFront-Is-Tablet-Viewer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrasyncai/adapter-lambda",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "AstraSync Trusted Agent Gateway — Lambda@Edge/CloudFront adapter",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",