@kya-os/checkpoint-nextjs 1.0.0 → 1.1.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/CHANGELOG.md +165 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapt.js +0 -2
- package/dist/adapt.mjs +0 -2
- package/dist/api-client.js +38 -24
- package/dist/api-client.mjs +38 -24
- package/dist/api-middleware.js +48 -28
- package/dist/api-middleware.mjs +48 -28
- package/dist/create-middleware.d.mts +1 -1
- package/dist/create-middleware.d.ts +1 -1
- package/dist/create-middleware.js +0 -2
- package/dist/create-middleware.mjs +0 -2
- package/dist/edge/index.d.mts +1 -1
- package/dist/edge/index.d.ts +1 -1
- package/dist/edge/index.js +4 -6
- package/dist/edge/index.mjs +4 -6
- package/dist/edge-runtime-loader.js +7 -2
- package/dist/edge-runtime-loader.mjs +7 -2
- package/dist/edge-wasm-middleware.js +0 -2
- package/dist/edge-wasm-middleware.mjs +0 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +77 -42
- package/dist/index.mjs +77 -42
- package/dist/middleware-edge.js +17 -10
- package/dist/middleware-edge.mjs +17 -10
- package/dist/middleware-node.d.mts +36 -0
- package/dist/middleware-node.d.ts +36 -0
- package/dist/middleware-node.js +17 -10
- package/dist/middleware-node.mjs +17 -10
- package/dist/middleware.d.mts +1 -1
- package/dist/middleware.d.ts +1 -1
- package/dist/middleware.js +0 -2
- package/dist/middleware.mjs +0 -2
- package/dist/nodejs-wasm-loader.js +0 -2
- package/dist/nodejs-wasm-loader.mjs +0 -2
- package/dist/policy.js +10 -6
- package/dist/policy.mjs +10 -6
- package/dist/session-tracker.js +1 -3
- package/dist/session-tracker.mjs +1 -3
- package/dist/signature-verifier.js +0 -2
- package/dist/signature-verifier.mjs +0 -2
- package/dist/translate.d.mts +36 -9
- package/dist/translate.d.ts +36 -9
- package/dist/translate.js +13 -8
- package/dist/translate.mjs +13 -8
- package/dist/{types-C-xCUNTr.d.mts → types-D9RQvPNy.d.mts} +1 -1
- package/dist/{types-C-xCUNTr.d.ts → types-D9RQvPNy.d.ts} +1 -1
- package/dist/wasm-middleware.d.mts +29 -10
- package/dist/wasm-middleware.d.ts +29 -10
- package/dist/wasm-middleware.js +0 -2
- package/dist/wasm-middleware.mjs +0 -2
- package/dist/wasm-setup.js +0 -2
- package/dist/wasm-setup.mjs +0 -2
- package/package.json +3 -3
- package/dist/adapt.js.map +0 -1
- package/dist/adapt.mjs.map +0 -1
- package/dist/api-client.js.map +0 -1
- package/dist/api-client.mjs.map +0 -1
- package/dist/api-middleware.js.map +0 -1
- package/dist/api-middleware.mjs.map +0 -1
- package/dist/create-middleware.js.map +0 -1
- package/dist/create-middleware.mjs.map +0 -1
- package/dist/edge/index.js.map +0 -1
- package/dist/edge/index.mjs.map +0 -1
- package/dist/edge-runtime-loader.js.map +0 -1
- package/dist/edge-runtime-loader.mjs.map +0 -1
- package/dist/edge-wasm-middleware.js.map +0 -1
- package/dist/edge-wasm-middleware.mjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/middleware-edge.js.map +0 -1
- package/dist/middleware-edge.mjs.map +0 -1
- package/dist/middleware-node.js.map +0 -1
- package/dist/middleware-node.mjs.map +0 -1
- package/dist/middleware.js.map +0 -1
- package/dist/middleware.mjs.map +0 -1
- package/dist/nodejs-wasm-loader.js.map +0 -1
- package/dist/nodejs-wasm-loader.mjs.map +0 -1
- package/dist/policy.js.map +0 -1
- package/dist/policy.mjs.map +0 -1
- package/dist/session-tracker.js.map +0 -1
- package/dist/session-tracker.mjs.map +0 -1
- package/dist/signature-verifier.js.map +0 -1
- package/dist/signature-verifier.mjs.map +0 -1
- package/dist/translate.js.map +0 -1
- package/dist/translate.mjs.map +0 -1
- package/dist/wasm-middleware.js.map +0 -1
- package/dist/wasm-middleware.mjs.map +0 -1
- package/dist/wasm-setup.js.map +0 -1
- package/dist/wasm-setup.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapt.ts","../src/translate.ts","../src/middleware-node.ts","../src/middleware-edge.ts"],"names":["acceptsHtml","encodeVerdictCookie","classifyResponseShape","NextResponse","BLOCKED_PATH","VERDICT_COOKIE_NAME","makeDidResolver","makeStatusListCache","makeReputationOracle","makePolicyEvaluator","makeSystemClock","initEngineEdge","verifyRequestEdge","renderDecisionAsResponse"],"mappings":"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","file":"middleware-edge.js","sourcesContent":["/**\n * D.3 — `RenderedResponse` → `NextResponse` adapter.\n *\n * The host wrapper's *only* job on the outbound path: take the\n * transport-agnostic `RenderedResponse` Phase C's\n * `renderDecisionAsResponse` produces and translate it to a\n * `NextResponse`. Zero verdict decisions, zero engine I/O.\n *\n * Shared between the Node-runtime and Edge-runtime entries. The\n * branching here is identical in both — Next.js `NextResponse` has the\n * same API surface across runtimes; only the underlying response\n * primitive differs (Node http.ServerResponse vs Edge `Response`).\n *\n * Architectural pins per architect § 4.3 / § 4.4:\n *\n * 1. **Verdict-cookie format is contract.** Sites-1's Sonner toast\n * depends on `__checkpoint_verdict=%7B%22verdict%22%3A%22<v>%22...\n * %7D` (single URL-encoded JSON). Byte-format pinned by adapt.test.\n *\n * 2. **HTML-accepting clients → `/blocked` rewrite at status 200**\n * (so the page renders with the verdict cookie set; Sonner picks\n * up the cookie and shows the toast). Non-HTML clients → JSON 4xx.\n *\n * 3. **`X-Checkpoint-Engine` carries `result.engineInfo.name`** —\n * `checkpoint-engine-wasm` after Phase D ships. Brian's Sites-2\n * deviation note confirmed the `X-Checkpoint-*` prefix is canon.\n */\n\nimport { type NextRequest, NextResponse } from 'next/server';\n\nimport type { RenderedResponse } from '@kya-os/checkpoint-wasm-runtime/orchestrator';\nimport {\n VERDICT_COOKIE_NAME,\n BLOCKED_PATH,\n encodeVerdictCookie,\n acceptsHtml,\n classifyResponseShape,\n} from '@kya-os/checkpoint-shared';\n\n/**\n * Convert the engine's transport-agnostic `RenderedResponse` into a\n * `NextResponse`. Sites-1's Playwright suite is the regression gate;\n * any drift here is caught downstream.\n */\nexport function adaptToNextResponse(rendered: RenderedResponse, req: NextRequest): NextResponse {\n const clientAcceptsHtml = acceptsHtml(req.headers);\n const verdictCookie = encodeVerdictCookie(rendered);\n const shape = classifyResponseShape(rendered, clientAcceptsHtml);\n\n switch (shape) {\n case 'pass-through': {\n // Permit OR Observe-mode any-verdict.\n const res = NextResponse.next();\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n\n case 'redirect': {\n // Decision::Redirect → 302 + Location.\n const target = new URL(rendered.headers.Location!);\n const res = NextResponse.redirect(target);\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n\n case 'html-block': {\n // Sites-1 contract: HTML clients (browsers) need a renderable page\n // to show the rejection UI. The verdict cookie carries the reason;\n // the /blocked route reads it and renders the toast.\n const blockedUrl = new URL(BLOCKED_PATH, req.url);\n const res = NextResponse.rewrite(blockedUrl, { status: 200 });\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n\n case 'json-block': {\n // The orchestrator's RenderedResponse already supplies the correct\n // status (401/403/422/...); we just need to materialise the body.\n // application/problem+json (Instruct) uses the Content-Type from\n // rendered.headers; defaults to application/json for everything else.\n const body = rendered.body ?? {};\n const res = NextResponse.json(body, { status: rendered.status as number });\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n }\n}\n\n// -----------------------------------------------------------------------------\n// Helpers — Next.js-specific glue. The framework-agnostic primitives\n// (encodeVerdictCookie, acceptsHtml, classifyResponseShape,\n// VERDICT_COOKIE_NAME, BLOCKED_PATH) live in `@kya-os/checkpoint-shared`\n// so checkpoint-express + future host wrappers produce byte-identical\n// cookies and route HTML/JSON branching the same way.\n// -----------------------------------------------------------------------------\n\nfunction setVerdictCookie(res: NextResponse, value: string): void {\n // Path / SameSite / HttpOnly chosen for the Sonner-bridge use case:\n // path=/ so any route can read it, SameSite=Lax so first-party\n // navigations carry it, HttpOnly=false so the client-side toast JS\n // can read it (it's verdict UX, not a session token).\n res.cookies.set({\n name: VERDICT_COOKIE_NAME,\n value,\n path: '/',\n sameSite: 'lax',\n httpOnly: false,\n });\n}\n\nfunction applyHeaders(res: NextResponse, headers: Record<string, string>): void {\n // NextResponse.next() / rewrite() / json() return responses with\n // some default headers; orchestrator headers (X-Checkpoint-*, Location)\n // override. We don't strip pre-existing headers — only set new ones.\n for (const [key, value] of Object.entries(headers)) {\n res.headers.set(key, value);\n }\n}\n","/**\n * D.2 — `NextRequest` → `IncomingHttpLike` translator.\n *\n * The host wrapper's *only* job on the inbound path: take Next.js's\n * native request shape and produce the transport-agnostic\n * `IncomingHttpLike` Phase C's orchestrator consumes. Zero verification\n * logic, zero adapter calls, zero engine I/O.\n *\n * Shared between the Node-runtime and Edge-runtime entries. Next.js\n * `NextRequest` is the same shape in both runtimes — `req.headers` is\n * a `Headers` instance, `req.body` is a `ReadableStream`, `req.ip` is\n * a getter (only present in some deployment surfaces; fall back to\n * `x-forwarded-for` first IP).\n */\n\nimport type { NextRequest } from 'next/server';\n\nimport type { IncomingHttpLike } from '@kya-os/checkpoint-wasm-runtime/orchestrator';\n\n/**\n * Translate a Next.js `NextRequest` into the orchestrator's\n * `IncomingHttpLike` shape.\n *\n * The body is passed through as-is — the orchestrator's\n * `buildAgentRequest` decides whether to parse JSON (looking for an\n * MCP-I `_meta.proof.jws` envelope) or treat the request as PlainHttp.\n * On Next.js middleware the body is typically not pre-parsed; consumers\n * who want to inspect the body for routing decisions should `await\n * req.json()` themselves and pass the parsed result via a second\n * `verifyRequest` call (not common).\n */\nexport function nextRequestToHttpLike(req: NextRequest): IncomingHttpLike {\n const url = new URL(req.url);\n return {\n method: req.method,\n // Path + query only — orchestrator's URL parsing expects no scheme/host.\n url: url.pathname + url.search,\n headers: headersToRecord(req.headers),\n // NextRequest.body is a ReadableStream; we don't drain it here.\n // The orchestrator routes to PlainHttp when body is falsy, which\n // is the right call for streaming middlewares that don't want to\n // buffer the request body just to detect agents.\n body: null,\n remoteAddress: extractRemoteAddress(req),\n };\n}\n\n/**\n * Convert a `Headers` instance into a lowercase-keyed plain object.\n * HTTP header names are case-insensitive (RFC 9110 § 5.1); the\n * orchestrator does case-sensitive lookups, so we normalise to\n * lowercase here. Multi-value headers (Set-Cookie, Accept) are\n * surfaced as their `Headers.get()` view — a single string with\n * comma-joined values, matching what other host adapters produce.\n */\nfunction headersToRecord(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n out[key.toLowerCase()] = value;\n });\n return out;\n}\n\n/**\n * Pull the originating client IP, preferring `x-forwarded-for`'s first\n * entry over `NextRequest.ip` (the latter is only populated on Vercel-\n * hosted deployments and is missing on self-hosted Next.js + nginx /\n * Fly.io / docker-compose surfaces). The `x-forwarded-for` first IP is\n * the closest the request has come to a load balancer's \"trust this is\n * the real client\" attestation — same convention as nginx, Caddy,\n * Cloudflare.\n */\nfunction extractRemoteAddress(req: NextRequest): string | undefined {\n const xff = req.headers.get('x-forwarded-for');\n if (xff) {\n const first = xff.split(',')[0]?.trim();\n if (first) return first;\n }\n // `req.ip` is typed but may be undefined off-Vercel.\n // Use `unknown` cast to avoid the type-narrowing optimism.\n const maybeIp = (req as unknown as { ip?: string }).ip;\n return maybeIp;\n}\n","/**\n * D.1 + D.3 — Node-runtime Next.js middleware entry.\n *\n * The host wrapper that composes Phase B adapters + Phase C\n * `verifyRequest` (sync engine) + Phase D translate/adapt into the\n * `withCheckpoint(config)` factory. Mounted under Vercel Node-runtime\n * serverless functions and long-lived Node servers.\n *\n * For Vercel Edge runtime (the Next.js middleware default), customers\n * import from `./edge` or `@kya-os/checkpoint-nextjs/edge` — that\n * variant uses `verifyRequestEdge` (async-init) and is otherwise\n * structurally identical. Both share `translate.ts` + `adapt.ts`.\n *\n * **Public API contract (architect § 4.1 — preserved):**\n *\n * - `withCheckpoint(config)` — factory returning the middleware.\n * - `CheckpointConfig` — the config shape; new fields are additive.\n *\n * Internal implementation gutted, external contract held. Sites-1's\n * Playwright suite is the regression gate.\n */\n\nimport { type NextRequest, type NextResponse } from 'next/server';\n\nimport {\n renderDecisionAsResponse,\n verifyRequest,\n} from '@kya-os/checkpoint-wasm-runtime/orchestrator';\nimport {\n makeDidResolver,\n makePolicyEvaluator,\n makeReputationOracle,\n makeStatusListCache,\n makeSystemClock,\n type DidResolverAdapter,\n type PolicyEvaluatorAdapter,\n type ReputationOracleAdapter,\n type StatusListCacheAdapter,\n} from '@kya-os/checkpoint-wasm-runtime/adapters';\nimport type { EnforcementMode, VerifyResult } from '@kya-os/checkpoint-wasm-runtime/engine';\n\nimport { adaptToNextResponse } from './adapt';\nimport { nextRequestToHttpLike } from './translate';\n\n/**\n * Configuration for `withCheckpoint`.\n *\n * The new minimal shape Phase D's middleware needs. Legacy\n * `AgentShieldMiddlewareConfig` (from `./api-middleware`) remains\n * exported during the deprecation window — see D.4 cutover.\n */\nexport interface CheckpointConfig {\n /**\n * Tenant identifier — typically the customer's dashboard hostname\n * (e.g. `acme.checkpoint.example`). The PolicyEvaluator uses this\n * to look up tenant policy from the dashboard.\n */\n tenantHost: string;\n\n /**\n * `'enforce'` (default) blocks; `'observe'` passes everything\n * through with `X-Checkpoint-Would-Have-Been` headers. Per Phase 0.2.\n */\n enforcementMode?: EnforcementMode;\n\n /**\n * Argus reputation oracle base URL. Omit to use the trust-by-default\n * baseline (reputation defaults to 1.0; orchestrator logs a one-shot\n * warning at first request).\n */\n argusUrl?: string;\n\n /**\n * Dashboard base URL for the PolicyEvaluator to fetch tenant policy\n * from. Omit to use the open-by-default tenant policy.\n */\n dashboardUrl?: string;\n\n /**\n * Returned to the PolicyEvaluator for anonymous requests (no agent\n * DID). Default 1.0 (trust-by-default).\n */\n reputationBaseline?: number;\n\n /**\n * Pre-built adapter instances. Production deployments use the\n * factory-built defaults from `@kya-os/checkpoint-wasm-runtime/adapters`;\n * tests use stubs. The factory composes any provided overrides over\n * defaults — partial overrides are supported.\n */\n adapters?: Partial<{\n didResolver: DidResolverAdapter;\n statusListCache: StatusListCacheAdapter;\n reputationOracle: ReputationOracleAdapter;\n policyEvaluator: PolicyEvaluatorAdapter;\n }>;\n\n /**\n * Optional callback for the post-verdict path — fires after every\n * verification, regardless of permit/block, with the full\n * `VerifyResult`. Use for logging, dashboards, telemetry. Errors\n * thrown here are swallowed so user code can't break the middleware\n * response.\n */\n onResult?: (result: VerifyResult, req: NextRequest) => void | Promise<void>;\n}\n\n/**\n * Build the Checkpoint middleware. Returns a function `(req) => NextResponse`\n * suitable for `export default withCheckpoint({...})` in `middleware.ts`.\n *\n * Every verification decision flows through the Rust `kya-os-engine`\n * via WASM. The TS layer translates request shape, calls\n * `verifyRequest`, and translates the verdict to `NextResponse`. No\n * verification logic lives in this file.\n */\nexport function withCheckpoint(\n config: CheckpointConfig\n): (req: NextRequest) => Promise<NextResponse> {\n const opts = buildVerifyOpts(config);\n return async function checkpointMiddleware(req: NextRequest): Promise<NextResponse> {\n const httpLike = nextRequestToHttpLike(req);\n const result = await verifyRequest(httpLike, opts);\n await dispatchOnResult(config, result, req);\n const rendered = renderDecisionAsResponse(result);\n return adaptToNextResponse(rendered, req);\n };\n}\n\n/**\n * Compose adapter defaults with caller-supplied overrides. Factored\n * out so the Edge entry (which uses the same composition) can reuse\n * the shape.\n */\nfunction buildVerifyOpts(config: CheckpointConfig) {\n const overrides = config.adapters ?? {};\n return {\n didResolver: overrides.didResolver ?? makeDidResolver(),\n statusListCache: overrides.statusListCache ?? makeStatusListCache(),\n reputationOracle:\n overrides.reputationOracle ?? makeReputationOracle({ argusUrl: config.argusUrl }),\n policyEvaluator:\n overrides.policyEvaluator ?? makePolicyEvaluator({ dashboardUrl: config.dashboardUrl }),\n clock: makeSystemClock(),\n tenantHost: config.tenantHost,\n enforcementMode: config.enforcementMode ?? 'enforce',\n reputationBaseline: config.reputationBaseline,\n argusUrl: config.argusUrl,\n };\n}\n\nasync function dispatchOnResult(\n config: CheckpointConfig,\n result: VerifyResult,\n req: NextRequest\n): Promise<void> {\n if (!config.onResult) return;\n try {\n await config.onResult(result, req);\n } catch {\n // Swallow — onResult is observability, not verdict-critical.\n // Verdict already computed; let the response proceed.\n }\n}\n\n// Re-export the shared opts builder for the Edge entry. Internal seam;\n// not part of the public surface.\nexport { buildVerifyOpts as _buildVerifyOpts };\n","/**\n * D.3 — Edge-runtime Next.js middleware entry.\n *\n * The async-init equivalent of `./middleware-node.ts`. Mounted under\n * Vercel Edge runtime (the Next.js middleware default) and Cloudflare\n * Workers when Next.js targets the Edge.\n *\n * Differs from the Node entry in exactly two places:\n *\n * 1. Imports `verifyRequestEdge` + `initEngineEdge` from the\n * orchestrator's `./edge` subpath (Edge-WASM-2 from D.1.5)\n * instead of `verifyRequest` from the Node orchestrator entry.\n * 2. Calls `initEngineEdge()` once at module load (eagerly, before\n * any request hits the middleware) so the first request's cold-\n * boot latency is amortised onto deploy time. Subsequent calls\n * to `initEngineEdge` are idempotent.\n *\n * Adapter composition (`buildVerifyOpts`), translate.ts, adapt.ts,\n * verdict-cookie format, X-Checkpoint-* headers — all shared with\n * the Node entry. Cross-runtime parity verified by Phase F's CI gate\n * (D.5 ships the Next.js-specific half).\n *\n * **Public API contract — preserved:** `withCheckpoint(config)`,\n * `CheckpointConfig`. Same exports as Node, same signatures.\n */\n\nimport { type NextRequest, type NextResponse } from 'next/server';\n\nimport {\n initEngineEdge,\n renderDecisionAsResponse,\n verifyRequestEdge,\n} from '@kya-os/checkpoint-wasm-runtime/orchestrator/edge';\nimport type { VerifyResult } from '@kya-os/checkpoint-wasm-runtime/engine';\n\nimport { adaptToNextResponse } from './adapt';\nimport { _buildVerifyOpts, type CheckpointConfig } from './middleware-node';\nimport { nextRequestToHttpLike } from './translate';\n\n// Re-export the config type so consumers can `import type` from the\n// edge entry without a second import line.\nexport type { CheckpointConfig } from './middleware-node';\n\n/**\n * Build the Checkpoint middleware for Edge runtime. Returns a function\n * `(req) => Promise<NextResponse>` suitable for\n * `export default withCheckpoint({...})` in `middleware.ts` under\n * `export const config = { runtime: 'edge' }`.\n *\n * Idempotent eager init: the first call to `withCheckpoint` kicks off\n * `initEngineEdge()` so the wasm artifact loads while the rest of the\n * factory closure is being built. The first request awaits the same\n * promise; subsequent requests resolve sync.\n */\nexport function withCheckpoint(\n config: CheckpointConfig\n): (req: NextRequest) => Promise<NextResponse> {\n // Eager init — fire-and-forget. The first request will await the\n // same promise via the orchestrator's lazy init path. Eager-init\n // hosts that want to await the init explicitly can call\n // `initEngineEdge()` themselves at startup.\n void initEngineEdge();\n\n const opts = _buildVerifyOpts(config);\n return async function checkpointMiddlewareEdge(req: NextRequest): Promise<NextResponse> {\n const httpLike = nextRequestToHttpLike(req);\n const result = await verifyRequestEdge(httpLike, opts);\n await dispatchOnResult(config, result, req);\n const rendered = renderDecisionAsResponse(result);\n return adaptToNextResponse(rendered, req);\n };\n}\n\nasync function dispatchOnResult(\n config: CheckpointConfig,\n result: VerifyResult,\n req: NextRequest\n): Promise<void> {\n if (!config.onResult) return;\n try {\n await config.onResult(result, req);\n } catch {\n // Swallow — onResult is observability, not verdict-critical.\n }\n}\n\n// Re-export `initEngineEdge` so eager-init hosts that want to warm the\n// wasm load at process startup can do so without a second import line.\nexport { initEngineEdge };\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapt.ts","../src/translate.ts","../src/middleware-node.ts","../src/middleware-edge.ts"],"names":["renderDecisionAsResponse"],"mappings":"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","file":"middleware-edge.mjs","sourcesContent":["/**\n * D.3 — `RenderedResponse` → `NextResponse` adapter.\n *\n * The host wrapper's *only* job on the outbound path: take the\n * transport-agnostic `RenderedResponse` Phase C's\n * `renderDecisionAsResponse` produces and translate it to a\n * `NextResponse`. Zero verdict decisions, zero engine I/O.\n *\n * Shared between the Node-runtime and Edge-runtime entries. The\n * branching here is identical in both — Next.js `NextResponse` has the\n * same API surface across runtimes; only the underlying response\n * primitive differs (Node http.ServerResponse vs Edge `Response`).\n *\n * Architectural pins per architect § 4.3 / § 4.4:\n *\n * 1. **Verdict-cookie format is contract.** Sites-1's Sonner toast\n * depends on `__checkpoint_verdict=%7B%22verdict%22%3A%22<v>%22...\n * %7D` (single URL-encoded JSON). Byte-format pinned by adapt.test.\n *\n * 2. **HTML-accepting clients → `/blocked` rewrite at status 200**\n * (so the page renders with the verdict cookie set; Sonner picks\n * up the cookie and shows the toast). Non-HTML clients → JSON 4xx.\n *\n * 3. **`X-Checkpoint-Engine` carries `result.engineInfo.name`** —\n * `checkpoint-engine-wasm` after Phase D ships. Brian's Sites-2\n * deviation note confirmed the `X-Checkpoint-*` prefix is canon.\n */\n\nimport { type NextRequest, NextResponse } from 'next/server';\n\nimport type { RenderedResponse } from '@kya-os/checkpoint-wasm-runtime/orchestrator';\nimport {\n VERDICT_COOKIE_NAME,\n BLOCKED_PATH,\n encodeVerdictCookie,\n acceptsHtml,\n classifyResponseShape,\n} from '@kya-os/checkpoint-shared';\n\n/**\n * Convert the engine's transport-agnostic `RenderedResponse` into a\n * `NextResponse`. Sites-1's Playwright suite is the regression gate;\n * any drift here is caught downstream.\n */\nexport function adaptToNextResponse(rendered: RenderedResponse, req: NextRequest): NextResponse {\n const clientAcceptsHtml = acceptsHtml(req.headers);\n const verdictCookie = encodeVerdictCookie(rendered);\n const shape = classifyResponseShape(rendered, clientAcceptsHtml);\n\n switch (shape) {\n case 'pass-through': {\n // Permit OR Observe-mode any-verdict.\n const res = NextResponse.next();\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n\n case 'redirect': {\n // Decision::Redirect → 302 + Location.\n const target = new URL(rendered.headers.Location!);\n const res = NextResponse.redirect(target);\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n\n case 'html-block': {\n // Sites-1 contract: HTML clients (browsers) need a renderable page\n // to show the rejection UI. The verdict cookie carries the reason;\n // the /blocked route reads it and renders the toast.\n const blockedUrl = new URL(BLOCKED_PATH, req.url);\n const res = NextResponse.rewrite(blockedUrl, { status: 200 });\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n\n case 'json-block': {\n // The orchestrator's RenderedResponse already supplies the correct\n // status (401/403/422/...); we just need to materialise the body.\n // application/problem+json (Instruct) uses the Content-Type from\n // rendered.headers; defaults to application/json for everything else.\n const body = rendered.body ?? {};\n const res = NextResponse.json(body, { status: rendered.status as number });\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n }\n}\n\n// -----------------------------------------------------------------------------\n// Helpers — Next.js-specific glue. The framework-agnostic primitives\n// (encodeVerdictCookie, acceptsHtml, classifyResponseShape,\n// VERDICT_COOKIE_NAME, BLOCKED_PATH) live in `@kya-os/checkpoint-shared`\n// so checkpoint-express + future host wrappers produce byte-identical\n// cookies and route HTML/JSON branching the same way.\n// -----------------------------------------------------------------------------\n\nfunction setVerdictCookie(res: NextResponse, value: string): void {\n // Path / SameSite / HttpOnly chosen for the Sonner-bridge use case:\n // path=/ so any route can read it, SameSite=Lax so first-party\n // navigations carry it, HttpOnly=false so the client-side toast JS\n // can read it (it's verdict UX, not a session token).\n res.cookies.set({\n name: VERDICT_COOKIE_NAME,\n value,\n path: '/',\n sameSite: 'lax',\n httpOnly: false,\n });\n}\n\nfunction applyHeaders(res: NextResponse, headers: Record<string, string>): void {\n // NextResponse.next() / rewrite() / json() return responses with\n // some default headers; orchestrator headers (X-Checkpoint-*, Location)\n // override. We don't strip pre-existing headers — only set new ones.\n for (const [key, value] of Object.entries(headers)) {\n res.headers.set(key, value);\n }\n}\n","/**\n * D.2 — `NextRequest` → `IncomingHttpLike` translator.\n *\n * The host wrapper's *only* job on the inbound path: take Next.js's\n * native request shape and produce the transport-agnostic\n * `IncomingHttpLike` Phase C's orchestrator consumes. Zero verification\n * logic, zero adapter calls, zero engine I/O.\n *\n * Shared between the Node-runtime and Edge-runtime entries. Next.js\n * `NextRequest` is the same shape in both runtimes — `req.headers` is\n * a `Headers` instance, `req.body` is a `ReadableStream`, `req.ip` is\n * a getter (only present in some deployment surfaces; fall back to\n * `x-forwarded-for` first IP).\n */\n\nimport type { NextRequest } from 'next/server';\n\nimport type { IncomingHttpLike } from '@kya-os/checkpoint-wasm-runtime/orchestrator';\n\n/**\n * Translate a Next.js `NextRequest` into the orchestrator's\n * `IncomingHttpLike` shape.\n *\n * The body is passed through as-is — the orchestrator's\n * `buildAgentRequest` decides whether to parse JSON (looking for an\n * MCP-I `_meta.proof.jws` envelope) or treat the request as PlainHttp.\n * On Next.js middleware the body is typically not pre-parsed; consumers\n * who want to inspect the body for routing decisions should `await\n * req.json()` themselves and pass the parsed result via a second\n * `verifyRequest` call (not common).\n */\nexport function nextRequestToHttpLike(req: NextRequest): IncomingHttpLike {\n const url = new URL(req.url);\n return {\n method: req.method,\n // Path + query only — orchestrator's URL parsing expects no scheme/host.\n url: url.pathname + url.search,\n headers: headersToRecord(req.headers),\n // NextRequest.body is a ReadableStream; we don't drain it here.\n // The orchestrator routes to PlainHttp when body is falsy, which\n // is the right call for streaming middlewares that don't want to\n // buffer the request body just to detect agents.\n body: null,\n remoteAddress: extractRemoteAddress(req),\n };\n}\n\n/**\n * Convert a `Headers` instance into a lowercase-keyed plain object.\n * HTTP header names are case-insensitive (RFC 9110 § 5.1); the\n * orchestrator does case-sensitive lookups, so we normalise to\n * lowercase here. Multi-value headers (Set-Cookie, Accept) are\n * surfaced as their `Headers.get()` view — a single string with\n * comma-joined values, matching what other host adapters produce.\n */\nfunction headersToRecord(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n out[key.toLowerCase()] = value;\n });\n return out;\n}\n\n/**\n * Pull the originating client IP, preferring `x-forwarded-for`'s first\n * entry over `NextRequest.ip` (the latter is only populated on Vercel-\n * hosted deployments and is missing on self-hosted Next.js + nginx /\n * Fly.io / docker-compose surfaces). The `x-forwarded-for` first IP is\n * the closest the request has come to a load balancer's \"trust this is\n * the real client\" attestation — same convention as nginx, Caddy,\n * Cloudflare.\n */\nfunction extractRemoteAddress(req: NextRequest): string | undefined {\n const xff = req.headers.get('x-forwarded-for');\n if (xff) {\n const first = xff.split(',')[0]?.trim();\n if (first) return first;\n }\n // `req.ip` is typed but may be undefined off-Vercel.\n // Use `unknown` cast to avoid the type-narrowing optimism.\n const maybeIp = (req as unknown as { ip?: string }).ip;\n return maybeIp;\n}\n","/**\n * D.1 + D.3 — Node-runtime Next.js middleware entry.\n *\n * The host wrapper that composes Phase B adapters + Phase C\n * `verifyRequest` (sync engine) + Phase D translate/adapt into the\n * `withCheckpoint(config)` factory. Mounted under Vercel Node-runtime\n * serverless functions and long-lived Node servers.\n *\n * For Vercel Edge runtime (the Next.js middleware default), customers\n * import from `./edge` or `@kya-os/checkpoint-nextjs/edge` — that\n * variant uses `verifyRequestEdge` (async-init) and is otherwise\n * structurally identical. Both share `translate.ts` + `adapt.ts`.\n *\n * **Public API contract (architect § 4.1 — preserved):**\n *\n * - `withCheckpoint(config)` — factory returning the middleware.\n * - `CheckpointConfig` — the config shape; new fields are additive.\n *\n * Internal implementation gutted, external contract held. Sites-1's\n * Playwright suite is the regression gate.\n */\n\nimport { type NextRequest, type NextResponse } from 'next/server';\n\nimport {\n renderDecisionAsResponse,\n verifyRequest,\n} from '@kya-os/checkpoint-wasm-runtime/orchestrator';\nimport {\n makeDidResolver,\n makePolicyEvaluator,\n makeReputationOracle,\n makeStatusListCache,\n makeSystemClock,\n type DidResolverAdapter,\n type PolicyEvaluatorAdapter,\n type ReputationOracleAdapter,\n type StatusListCacheAdapter,\n} from '@kya-os/checkpoint-wasm-runtime/adapters';\nimport type { EnforcementMode, VerifyResult } from '@kya-os/checkpoint-wasm-runtime/engine';\n\nimport { adaptToNextResponse } from './adapt';\nimport { nextRequestToHttpLike } from './translate';\n\n/**\n * Configuration for `withCheckpoint`.\n *\n * The new minimal shape Phase D's middleware needs. Legacy\n * `AgentShieldMiddlewareConfig` (from `./api-middleware`) remains\n * exported during the deprecation window — see D.4 cutover.\n */\nexport interface CheckpointConfig {\n /**\n * Tenant identifier — typically the customer's dashboard hostname\n * (e.g. `acme.checkpoint.example`). The PolicyEvaluator uses this\n * to look up tenant policy from the dashboard.\n */\n tenantHost: string;\n\n /**\n * `'enforce'` (default) blocks; `'observe'` passes everything\n * through with `X-Checkpoint-Would-Have-Been` headers. Per Phase 0.2.\n */\n enforcementMode?: EnforcementMode;\n\n /**\n * Argus reputation oracle base URL. Omit to use the trust-by-default\n * baseline (reputation defaults to 1.0; orchestrator logs a one-shot\n * warning at first request).\n */\n argusUrl?: string;\n\n /**\n * Dashboard base URL for the PolicyEvaluator to fetch tenant policy\n * from. Omit to use the open-by-default tenant policy.\n */\n dashboardUrl?: string;\n\n /**\n * Returned to the PolicyEvaluator for anonymous requests (no agent\n * DID). Default 1.0 (trust-by-default).\n */\n reputationBaseline?: number;\n\n /**\n * Pre-built adapter instances. Production deployments use the\n * factory-built defaults from `@kya-os/checkpoint-wasm-runtime/adapters`;\n * tests use stubs. The factory composes any provided overrides over\n * defaults — partial overrides are supported.\n */\n adapters?: Partial<{\n didResolver: DidResolverAdapter;\n statusListCache: StatusListCacheAdapter;\n reputationOracle: ReputationOracleAdapter;\n policyEvaluator: PolicyEvaluatorAdapter;\n }>;\n\n /**\n * Optional callback for the post-verdict path — fires after every\n * verification, regardless of permit/block, with the full\n * `VerifyResult`. Use for logging, dashboards, telemetry. Errors\n * thrown here are swallowed so user code can't break the middleware\n * response.\n */\n onResult?: (result: VerifyResult, req: NextRequest) => void | Promise<void>;\n}\n\n/**\n * Build the Checkpoint middleware. Returns a function `(req) => NextResponse`\n * suitable for `export default withCheckpoint({...})` in `middleware.ts`.\n *\n * Every verification decision flows through the Rust `kya-os-engine`\n * via WASM. The TS layer translates request shape, calls\n * `verifyRequest`, and translates the verdict to `NextResponse`. No\n * verification logic lives in this file.\n */\nexport function withCheckpoint(\n config: CheckpointConfig\n): (req: NextRequest) => Promise<NextResponse> {\n const opts = buildVerifyOpts(config);\n return async function checkpointMiddleware(req: NextRequest): Promise<NextResponse> {\n const httpLike = nextRequestToHttpLike(req);\n const result = await verifyRequest(httpLike, opts);\n await dispatchOnResult(config, result, req);\n const rendered = renderDecisionAsResponse(result);\n return adaptToNextResponse(rendered, req);\n };\n}\n\n/**\n * Compose adapter defaults with caller-supplied overrides. Factored\n * out so the Edge entry (which uses the same composition) can reuse\n * the shape.\n */\nfunction buildVerifyOpts(config: CheckpointConfig) {\n const overrides = config.adapters ?? {};\n return {\n didResolver: overrides.didResolver ?? makeDidResolver(),\n statusListCache: overrides.statusListCache ?? makeStatusListCache(),\n reputationOracle:\n overrides.reputationOracle ?? makeReputationOracle({ argusUrl: config.argusUrl }),\n policyEvaluator:\n overrides.policyEvaluator ?? makePolicyEvaluator({ dashboardUrl: config.dashboardUrl }),\n clock: makeSystemClock(),\n tenantHost: config.tenantHost,\n enforcementMode: config.enforcementMode ?? 'enforce',\n reputationBaseline: config.reputationBaseline,\n argusUrl: config.argusUrl,\n };\n}\n\nasync function dispatchOnResult(\n config: CheckpointConfig,\n result: VerifyResult,\n req: NextRequest\n): Promise<void> {\n if (!config.onResult) return;\n try {\n await config.onResult(result, req);\n } catch {\n // Swallow — onResult is observability, not verdict-critical.\n // Verdict already computed; let the response proceed.\n }\n}\n\n// Re-export the shared opts builder for the Edge entry. Internal seam;\n// not part of the public surface.\nexport { buildVerifyOpts as _buildVerifyOpts };\n","/**\n * D.3 — Edge-runtime Next.js middleware entry.\n *\n * The async-init equivalent of `./middleware-node.ts`. Mounted under\n * Vercel Edge runtime (the Next.js middleware default) and Cloudflare\n * Workers when Next.js targets the Edge.\n *\n * Differs from the Node entry in exactly two places:\n *\n * 1. Imports `verifyRequestEdge` + `initEngineEdge` from the\n * orchestrator's `./edge` subpath (Edge-WASM-2 from D.1.5)\n * instead of `verifyRequest` from the Node orchestrator entry.\n * 2. Calls `initEngineEdge()` once at module load (eagerly, before\n * any request hits the middleware) so the first request's cold-\n * boot latency is amortised onto deploy time. Subsequent calls\n * to `initEngineEdge` are idempotent.\n *\n * Adapter composition (`buildVerifyOpts`), translate.ts, adapt.ts,\n * verdict-cookie format, X-Checkpoint-* headers — all shared with\n * the Node entry. Cross-runtime parity verified by Phase F's CI gate\n * (D.5 ships the Next.js-specific half).\n *\n * **Public API contract — preserved:** `withCheckpoint(config)`,\n * `CheckpointConfig`. Same exports as Node, same signatures.\n */\n\nimport { type NextRequest, type NextResponse } from 'next/server';\n\nimport {\n initEngineEdge,\n renderDecisionAsResponse,\n verifyRequestEdge,\n} from '@kya-os/checkpoint-wasm-runtime/orchestrator/edge';\nimport type { VerifyResult } from '@kya-os/checkpoint-wasm-runtime/engine';\n\nimport { adaptToNextResponse } from './adapt';\nimport { _buildVerifyOpts, type CheckpointConfig } from './middleware-node';\nimport { nextRequestToHttpLike } from './translate';\n\n// Re-export the config type so consumers can `import type` from the\n// edge entry without a second import line.\nexport type { CheckpointConfig } from './middleware-node';\n\n/**\n * Build the Checkpoint middleware for Edge runtime. Returns a function\n * `(req) => Promise<NextResponse>` suitable for\n * `export default withCheckpoint({...})` in `middleware.ts` under\n * `export const config = { runtime: 'edge' }`.\n *\n * Idempotent eager init: the first call to `withCheckpoint` kicks off\n * `initEngineEdge()` so the wasm artifact loads while the rest of the\n * factory closure is being built. The first request awaits the same\n * promise; subsequent requests resolve sync.\n */\nexport function withCheckpoint(\n config: CheckpointConfig\n): (req: NextRequest) => Promise<NextResponse> {\n // Eager init — fire-and-forget. The first request will await the\n // same promise via the orchestrator's lazy init path. Eager-init\n // hosts that want to await the init explicitly can call\n // `initEngineEdge()` themselves at startup.\n void initEngineEdge();\n\n const opts = _buildVerifyOpts(config);\n return async function checkpointMiddlewareEdge(req: NextRequest): Promise<NextResponse> {\n const httpLike = nextRequestToHttpLike(req);\n const result = await verifyRequestEdge(httpLike, opts);\n await dispatchOnResult(config, result, req);\n const rendered = renderDecisionAsResponse(result);\n return adaptToNextResponse(rendered, req);\n };\n}\n\nasync function dispatchOnResult(\n config: CheckpointConfig,\n result: VerifyResult,\n req: NextRequest\n): Promise<void> {\n if (!config.onResult) return;\n try {\n await config.onResult(result, req);\n } catch {\n // Swallow — onResult is observability, not verdict-critical.\n }\n}\n\n// Re-export `initEngineEdge` so eager-init hosts that want to warm the\n// wasm load at process startup can do so without a second import line.\nexport { initEngineEdge };\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapt.ts","../src/translate.ts","../src/middleware-node.ts"],"names":["acceptsHtml","encodeVerdictCookie","classifyResponseShape","NextResponse","BLOCKED_PATH","VERDICT_COOKIE_NAME","verifyRequest","renderDecisionAsResponse","makeDidResolver","makeStatusListCache","makeReputationOracle","makePolicyEvaluator","makeSystemClock"],"mappings":"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","file":"middleware-node.js","sourcesContent":["/**\n * D.3 — `RenderedResponse` → `NextResponse` adapter.\n *\n * The host wrapper's *only* job on the outbound path: take the\n * transport-agnostic `RenderedResponse` Phase C's\n * `renderDecisionAsResponse` produces and translate it to a\n * `NextResponse`. Zero verdict decisions, zero engine I/O.\n *\n * Shared between the Node-runtime and Edge-runtime entries. The\n * branching here is identical in both — Next.js `NextResponse` has the\n * same API surface across runtimes; only the underlying response\n * primitive differs (Node http.ServerResponse vs Edge `Response`).\n *\n * Architectural pins per architect § 4.3 / § 4.4:\n *\n * 1. **Verdict-cookie format is contract.** Sites-1's Sonner toast\n * depends on `__checkpoint_verdict=%7B%22verdict%22%3A%22<v>%22...\n * %7D` (single URL-encoded JSON). Byte-format pinned by adapt.test.\n *\n * 2. **HTML-accepting clients → `/blocked` rewrite at status 200**\n * (so the page renders with the verdict cookie set; Sonner picks\n * up the cookie and shows the toast). Non-HTML clients → JSON 4xx.\n *\n * 3. **`X-Checkpoint-Engine` carries `result.engineInfo.name`** —\n * `checkpoint-engine-wasm` after Phase D ships. Brian's Sites-2\n * deviation note confirmed the `X-Checkpoint-*` prefix is canon.\n */\n\nimport { type NextRequest, NextResponse } from 'next/server';\n\nimport type { RenderedResponse } from '@kya-os/checkpoint-wasm-runtime/orchestrator';\nimport {\n VERDICT_COOKIE_NAME,\n BLOCKED_PATH,\n encodeVerdictCookie,\n acceptsHtml,\n classifyResponseShape,\n} from '@kya-os/checkpoint-shared';\n\n/**\n * Convert the engine's transport-agnostic `RenderedResponse` into a\n * `NextResponse`. Sites-1's Playwright suite is the regression gate;\n * any drift here is caught downstream.\n */\nexport function adaptToNextResponse(rendered: RenderedResponse, req: NextRequest): NextResponse {\n const clientAcceptsHtml = acceptsHtml(req.headers);\n const verdictCookie = encodeVerdictCookie(rendered);\n const shape = classifyResponseShape(rendered, clientAcceptsHtml);\n\n switch (shape) {\n case 'pass-through': {\n // Permit OR Observe-mode any-verdict.\n const res = NextResponse.next();\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n\n case 'redirect': {\n // Decision::Redirect → 302 + Location.\n const target = new URL(rendered.headers.Location!);\n const res = NextResponse.redirect(target);\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n\n case 'html-block': {\n // Sites-1 contract: HTML clients (browsers) need a renderable page\n // to show the rejection UI. The verdict cookie carries the reason;\n // the /blocked route reads it and renders the toast.\n const blockedUrl = new URL(BLOCKED_PATH, req.url);\n const res = NextResponse.rewrite(blockedUrl, { status: 200 });\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n\n case 'json-block': {\n // The orchestrator's RenderedResponse already supplies the correct\n // status (401/403/422/...); we just need to materialise the body.\n // application/problem+json (Instruct) uses the Content-Type from\n // rendered.headers; defaults to application/json for everything else.\n const body = rendered.body ?? {};\n const res = NextResponse.json(body, { status: rendered.status as number });\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n }\n}\n\n// -----------------------------------------------------------------------------\n// Helpers — Next.js-specific glue. The framework-agnostic primitives\n// (encodeVerdictCookie, acceptsHtml, classifyResponseShape,\n// VERDICT_COOKIE_NAME, BLOCKED_PATH) live in `@kya-os/checkpoint-shared`\n// so checkpoint-express + future host wrappers produce byte-identical\n// cookies and route HTML/JSON branching the same way.\n// -----------------------------------------------------------------------------\n\nfunction setVerdictCookie(res: NextResponse, value: string): void {\n // Path / SameSite / HttpOnly chosen for the Sonner-bridge use case:\n // path=/ so any route can read it, SameSite=Lax so first-party\n // navigations carry it, HttpOnly=false so the client-side toast JS\n // can read it (it's verdict UX, not a session token).\n res.cookies.set({\n name: VERDICT_COOKIE_NAME,\n value,\n path: '/',\n sameSite: 'lax',\n httpOnly: false,\n });\n}\n\nfunction applyHeaders(res: NextResponse, headers: Record<string, string>): void {\n // NextResponse.next() / rewrite() / json() return responses with\n // some default headers; orchestrator headers (X-Checkpoint-*, Location)\n // override. We don't strip pre-existing headers — only set new ones.\n for (const [key, value] of Object.entries(headers)) {\n res.headers.set(key, value);\n }\n}\n","/**\n * D.2 — `NextRequest` → `IncomingHttpLike` translator.\n *\n * The host wrapper's *only* job on the inbound path: take Next.js's\n * native request shape and produce the transport-agnostic\n * `IncomingHttpLike` Phase C's orchestrator consumes. Zero verification\n * logic, zero adapter calls, zero engine I/O.\n *\n * Shared between the Node-runtime and Edge-runtime entries. Next.js\n * `NextRequest` is the same shape in both runtimes — `req.headers` is\n * a `Headers` instance, `req.body` is a `ReadableStream`, `req.ip` is\n * a getter (only present in some deployment surfaces; fall back to\n * `x-forwarded-for` first IP).\n */\n\nimport type { NextRequest } from 'next/server';\n\nimport type { IncomingHttpLike } from '@kya-os/checkpoint-wasm-runtime/orchestrator';\n\n/**\n * Translate a Next.js `NextRequest` into the orchestrator's\n * `IncomingHttpLike` shape.\n *\n * The body is passed through as-is — the orchestrator's\n * `buildAgentRequest` decides whether to parse JSON (looking for an\n * MCP-I `_meta.proof.jws` envelope) or treat the request as PlainHttp.\n * On Next.js middleware the body is typically not pre-parsed; consumers\n * who want to inspect the body for routing decisions should `await\n * req.json()` themselves and pass the parsed result via a second\n * `verifyRequest` call (not common).\n */\nexport function nextRequestToHttpLike(req: NextRequest): IncomingHttpLike {\n const url = new URL(req.url);\n return {\n method: req.method,\n // Path + query only — orchestrator's URL parsing expects no scheme/host.\n url: url.pathname + url.search,\n headers: headersToRecord(req.headers),\n // NextRequest.body is a ReadableStream; we don't drain it here.\n // The orchestrator routes to PlainHttp when body is falsy, which\n // is the right call for streaming middlewares that don't want to\n // buffer the request body just to detect agents.\n body: null,\n remoteAddress: extractRemoteAddress(req),\n };\n}\n\n/**\n * Convert a `Headers` instance into a lowercase-keyed plain object.\n * HTTP header names are case-insensitive (RFC 9110 § 5.1); the\n * orchestrator does case-sensitive lookups, so we normalise to\n * lowercase here. Multi-value headers (Set-Cookie, Accept) are\n * surfaced as their `Headers.get()` view — a single string with\n * comma-joined values, matching what other host adapters produce.\n */\nfunction headersToRecord(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n out[key.toLowerCase()] = value;\n });\n return out;\n}\n\n/**\n * Pull the originating client IP, preferring `x-forwarded-for`'s first\n * entry over `NextRequest.ip` (the latter is only populated on Vercel-\n * hosted deployments and is missing on self-hosted Next.js + nginx /\n * Fly.io / docker-compose surfaces). The `x-forwarded-for` first IP is\n * the closest the request has come to a load balancer's \"trust this is\n * the real client\" attestation — same convention as nginx, Caddy,\n * Cloudflare.\n */\nfunction extractRemoteAddress(req: NextRequest): string | undefined {\n const xff = req.headers.get('x-forwarded-for');\n if (xff) {\n const first = xff.split(',')[0]?.trim();\n if (first) return first;\n }\n // `req.ip` is typed but may be undefined off-Vercel.\n // Use `unknown` cast to avoid the type-narrowing optimism.\n const maybeIp = (req as unknown as { ip?: string }).ip;\n return maybeIp;\n}\n","/**\n * D.1 + D.3 — Node-runtime Next.js middleware entry.\n *\n * The host wrapper that composes Phase B adapters + Phase C\n * `verifyRequest` (sync engine) + Phase D translate/adapt into the\n * `withCheckpoint(config)` factory. Mounted under Vercel Node-runtime\n * serverless functions and long-lived Node servers.\n *\n * For Vercel Edge runtime (the Next.js middleware default), customers\n * import from `./edge` or `@kya-os/checkpoint-nextjs/edge` — that\n * variant uses `verifyRequestEdge` (async-init) and is otherwise\n * structurally identical. Both share `translate.ts` + `adapt.ts`.\n *\n * **Public API contract (architect § 4.1 — preserved):**\n *\n * - `withCheckpoint(config)` — factory returning the middleware.\n * - `CheckpointConfig` — the config shape; new fields are additive.\n *\n * Internal implementation gutted, external contract held. Sites-1's\n * Playwright suite is the regression gate.\n */\n\nimport { type NextRequest, type NextResponse } from 'next/server';\n\nimport {\n renderDecisionAsResponse,\n verifyRequest,\n} from '@kya-os/checkpoint-wasm-runtime/orchestrator';\nimport {\n makeDidResolver,\n makePolicyEvaluator,\n makeReputationOracle,\n makeStatusListCache,\n makeSystemClock,\n type DidResolverAdapter,\n type PolicyEvaluatorAdapter,\n type ReputationOracleAdapter,\n type StatusListCacheAdapter,\n} from '@kya-os/checkpoint-wasm-runtime/adapters';\nimport type { EnforcementMode, VerifyResult } from '@kya-os/checkpoint-wasm-runtime/engine';\n\nimport { adaptToNextResponse } from './adapt';\nimport { nextRequestToHttpLike } from './translate';\n\n/**\n * Configuration for `withCheckpoint`.\n *\n * The new minimal shape Phase D's middleware needs. Legacy\n * `AgentShieldMiddlewareConfig` (from `./api-middleware`) remains\n * exported during the deprecation window — see D.4 cutover.\n */\nexport interface CheckpointConfig {\n /**\n * Tenant identifier — typically the customer's dashboard hostname\n * (e.g. `acme.checkpoint.example`). The PolicyEvaluator uses this\n * to look up tenant policy from the dashboard.\n */\n tenantHost: string;\n\n /**\n * `'enforce'` (default) blocks; `'observe'` passes everything\n * through with `X-Checkpoint-Would-Have-Been` headers. Per Phase 0.2.\n */\n enforcementMode?: EnforcementMode;\n\n /**\n * Argus reputation oracle base URL. Omit to use the trust-by-default\n * baseline (reputation defaults to 1.0; orchestrator logs a one-shot\n * warning at first request).\n */\n argusUrl?: string;\n\n /**\n * Dashboard base URL for the PolicyEvaluator to fetch tenant policy\n * from. Omit to use the open-by-default tenant policy.\n */\n dashboardUrl?: string;\n\n /**\n * Returned to the PolicyEvaluator for anonymous requests (no agent\n * DID). Default 1.0 (trust-by-default).\n */\n reputationBaseline?: number;\n\n /**\n * Pre-built adapter instances. Production deployments use the\n * factory-built defaults from `@kya-os/checkpoint-wasm-runtime/adapters`;\n * tests use stubs. The factory composes any provided overrides over\n * defaults — partial overrides are supported.\n */\n adapters?: Partial<{\n didResolver: DidResolverAdapter;\n statusListCache: StatusListCacheAdapter;\n reputationOracle: ReputationOracleAdapter;\n policyEvaluator: PolicyEvaluatorAdapter;\n }>;\n\n /**\n * Optional callback for the post-verdict path — fires after every\n * verification, regardless of permit/block, with the full\n * `VerifyResult`. Use for logging, dashboards, telemetry. Errors\n * thrown here are swallowed so user code can't break the middleware\n * response.\n */\n onResult?: (result: VerifyResult, req: NextRequest) => void | Promise<void>;\n}\n\n/**\n * Build the Checkpoint middleware. Returns a function `(req) => NextResponse`\n * suitable for `export default withCheckpoint({...})` in `middleware.ts`.\n *\n * Every verification decision flows through the Rust `kya-os-engine`\n * via WASM. The TS layer translates request shape, calls\n * `verifyRequest`, and translates the verdict to `NextResponse`. No\n * verification logic lives in this file.\n */\nexport function withCheckpoint(\n config: CheckpointConfig\n): (req: NextRequest) => Promise<NextResponse> {\n const opts = buildVerifyOpts(config);\n return async function checkpointMiddleware(req: NextRequest): Promise<NextResponse> {\n const httpLike = nextRequestToHttpLike(req);\n const result = await verifyRequest(httpLike, opts);\n await dispatchOnResult(config, result, req);\n const rendered = renderDecisionAsResponse(result);\n return adaptToNextResponse(rendered, req);\n };\n}\n\n/**\n * Compose adapter defaults with caller-supplied overrides. Factored\n * out so the Edge entry (which uses the same composition) can reuse\n * the shape.\n */\nfunction buildVerifyOpts(config: CheckpointConfig) {\n const overrides = config.adapters ?? {};\n return {\n didResolver: overrides.didResolver ?? makeDidResolver(),\n statusListCache: overrides.statusListCache ?? makeStatusListCache(),\n reputationOracle:\n overrides.reputationOracle ?? makeReputationOracle({ argusUrl: config.argusUrl }),\n policyEvaluator:\n overrides.policyEvaluator ?? makePolicyEvaluator({ dashboardUrl: config.dashboardUrl }),\n clock: makeSystemClock(),\n tenantHost: config.tenantHost,\n enforcementMode: config.enforcementMode ?? 'enforce',\n reputationBaseline: config.reputationBaseline,\n argusUrl: config.argusUrl,\n };\n}\n\nasync function dispatchOnResult(\n config: CheckpointConfig,\n result: VerifyResult,\n req: NextRequest\n): Promise<void> {\n if (!config.onResult) return;\n try {\n await config.onResult(result, req);\n } catch {\n // Swallow — onResult is observability, not verdict-critical.\n // Verdict already computed; let the response proceed.\n }\n}\n\n// Re-export the shared opts builder for the Edge entry. Internal seam;\n// not part of the public surface.\nexport { buildVerifyOpts as _buildVerifyOpts };\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapt.ts","../src/translate.ts","../src/middleware-node.ts"],"names":[],"mappings":"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","file":"middleware-node.mjs","sourcesContent":["/**\n * D.3 — `RenderedResponse` → `NextResponse` adapter.\n *\n * The host wrapper's *only* job on the outbound path: take the\n * transport-agnostic `RenderedResponse` Phase C's\n * `renderDecisionAsResponse` produces and translate it to a\n * `NextResponse`. Zero verdict decisions, zero engine I/O.\n *\n * Shared between the Node-runtime and Edge-runtime entries. The\n * branching here is identical in both — Next.js `NextResponse` has the\n * same API surface across runtimes; only the underlying response\n * primitive differs (Node http.ServerResponse vs Edge `Response`).\n *\n * Architectural pins per architect § 4.3 / § 4.4:\n *\n * 1. **Verdict-cookie format is contract.** Sites-1's Sonner toast\n * depends on `__checkpoint_verdict=%7B%22verdict%22%3A%22<v>%22...\n * %7D` (single URL-encoded JSON). Byte-format pinned by adapt.test.\n *\n * 2. **HTML-accepting clients → `/blocked` rewrite at status 200**\n * (so the page renders with the verdict cookie set; Sonner picks\n * up the cookie and shows the toast). Non-HTML clients → JSON 4xx.\n *\n * 3. **`X-Checkpoint-Engine` carries `result.engineInfo.name`** —\n * `checkpoint-engine-wasm` after Phase D ships. Brian's Sites-2\n * deviation note confirmed the `X-Checkpoint-*` prefix is canon.\n */\n\nimport { type NextRequest, NextResponse } from 'next/server';\n\nimport type { RenderedResponse } from '@kya-os/checkpoint-wasm-runtime/orchestrator';\nimport {\n VERDICT_COOKIE_NAME,\n BLOCKED_PATH,\n encodeVerdictCookie,\n acceptsHtml,\n classifyResponseShape,\n} from '@kya-os/checkpoint-shared';\n\n/**\n * Convert the engine's transport-agnostic `RenderedResponse` into a\n * `NextResponse`. Sites-1's Playwright suite is the regression gate;\n * any drift here is caught downstream.\n */\nexport function adaptToNextResponse(rendered: RenderedResponse, req: NextRequest): NextResponse {\n const clientAcceptsHtml = acceptsHtml(req.headers);\n const verdictCookie = encodeVerdictCookie(rendered);\n const shape = classifyResponseShape(rendered, clientAcceptsHtml);\n\n switch (shape) {\n case 'pass-through': {\n // Permit OR Observe-mode any-verdict.\n const res = NextResponse.next();\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n\n case 'redirect': {\n // Decision::Redirect → 302 + Location.\n const target = new URL(rendered.headers.Location!);\n const res = NextResponse.redirect(target);\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n\n case 'html-block': {\n // Sites-1 contract: HTML clients (browsers) need a renderable page\n // to show the rejection UI. The verdict cookie carries the reason;\n // the /blocked route reads it and renders the toast.\n const blockedUrl = new URL(BLOCKED_PATH, req.url);\n const res = NextResponse.rewrite(blockedUrl, { status: 200 });\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n\n case 'json-block': {\n // The orchestrator's RenderedResponse already supplies the correct\n // status (401/403/422/...); we just need to materialise the body.\n // application/problem+json (Instruct) uses the Content-Type from\n // rendered.headers; defaults to application/json for everything else.\n const body = rendered.body ?? {};\n const res = NextResponse.json(body, { status: rendered.status as number });\n applyHeaders(res, rendered.headers);\n setVerdictCookie(res, verdictCookie);\n return res;\n }\n }\n}\n\n// -----------------------------------------------------------------------------\n// Helpers — Next.js-specific glue. The framework-agnostic primitives\n// (encodeVerdictCookie, acceptsHtml, classifyResponseShape,\n// VERDICT_COOKIE_NAME, BLOCKED_PATH) live in `@kya-os/checkpoint-shared`\n// so checkpoint-express + future host wrappers produce byte-identical\n// cookies and route HTML/JSON branching the same way.\n// -----------------------------------------------------------------------------\n\nfunction setVerdictCookie(res: NextResponse, value: string): void {\n // Path / SameSite / HttpOnly chosen for the Sonner-bridge use case:\n // path=/ so any route can read it, SameSite=Lax so first-party\n // navigations carry it, HttpOnly=false so the client-side toast JS\n // can read it (it's verdict UX, not a session token).\n res.cookies.set({\n name: VERDICT_COOKIE_NAME,\n value,\n path: '/',\n sameSite: 'lax',\n httpOnly: false,\n });\n}\n\nfunction applyHeaders(res: NextResponse, headers: Record<string, string>): void {\n // NextResponse.next() / rewrite() / json() return responses with\n // some default headers; orchestrator headers (X-Checkpoint-*, Location)\n // override. We don't strip pre-existing headers — only set new ones.\n for (const [key, value] of Object.entries(headers)) {\n res.headers.set(key, value);\n }\n}\n","/**\n * D.2 — `NextRequest` → `IncomingHttpLike` translator.\n *\n * The host wrapper's *only* job on the inbound path: take Next.js's\n * native request shape and produce the transport-agnostic\n * `IncomingHttpLike` Phase C's orchestrator consumes. Zero verification\n * logic, zero adapter calls, zero engine I/O.\n *\n * Shared between the Node-runtime and Edge-runtime entries. Next.js\n * `NextRequest` is the same shape in both runtimes — `req.headers` is\n * a `Headers` instance, `req.body` is a `ReadableStream`, `req.ip` is\n * a getter (only present in some deployment surfaces; fall back to\n * `x-forwarded-for` first IP).\n */\n\nimport type { NextRequest } from 'next/server';\n\nimport type { IncomingHttpLike } from '@kya-os/checkpoint-wasm-runtime/orchestrator';\n\n/**\n * Translate a Next.js `NextRequest` into the orchestrator's\n * `IncomingHttpLike` shape.\n *\n * The body is passed through as-is — the orchestrator's\n * `buildAgentRequest` decides whether to parse JSON (looking for an\n * MCP-I `_meta.proof.jws` envelope) or treat the request as PlainHttp.\n * On Next.js middleware the body is typically not pre-parsed; consumers\n * who want to inspect the body for routing decisions should `await\n * req.json()` themselves and pass the parsed result via a second\n * `verifyRequest` call (not common).\n */\nexport function nextRequestToHttpLike(req: NextRequest): IncomingHttpLike {\n const url = new URL(req.url);\n return {\n method: req.method,\n // Path + query only — orchestrator's URL parsing expects no scheme/host.\n url: url.pathname + url.search,\n headers: headersToRecord(req.headers),\n // NextRequest.body is a ReadableStream; we don't drain it here.\n // The orchestrator routes to PlainHttp when body is falsy, which\n // is the right call for streaming middlewares that don't want to\n // buffer the request body just to detect agents.\n body: null,\n remoteAddress: extractRemoteAddress(req),\n };\n}\n\n/**\n * Convert a `Headers` instance into a lowercase-keyed plain object.\n * HTTP header names are case-insensitive (RFC 9110 § 5.1); the\n * orchestrator does case-sensitive lookups, so we normalise to\n * lowercase here. Multi-value headers (Set-Cookie, Accept) are\n * surfaced as their `Headers.get()` view — a single string with\n * comma-joined values, matching what other host adapters produce.\n */\nfunction headersToRecord(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n out[key.toLowerCase()] = value;\n });\n return out;\n}\n\n/**\n * Pull the originating client IP, preferring `x-forwarded-for`'s first\n * entry over `NextRequest.ip` (the latter is only populated on Vercel-\n * hosted deployments and is missing on self-hosted Next.js + nginx /\n * Fly.io / docker-compose surfaces). The `x-forwarded-for` first IP is\n * the closest the request has come to a load balancer's \"trust this is\n * the real client\" attestation — same convention as nginx, Caddy,\n * Cloudflare.\n */\nfunction extractRemoteAddress(req: NextRequest): string | undefined {\n const xff = req.headers.get('x-forwarded-for');\n if (xff) {\n const first = xff.split(',')[0]?.trim();\n if (first) return first;\n }\n // `req.ip` is typed but may be undefined off-Vercel.\n // Use `unknown` cast to avoid the type-narrowing optimism.\n const maybeIp = (req as unknown as { ip?: string }).ip;\n return maybeIp;\n}\n","/**\n * D.1 + D.3 — Node-runtime Next.js middleware entry.\n *\n * The host wrapper that composes Phase B adapters + Phase C\n * `verifyRequest` (sync engine) + Phase D translate/adapt into the\n * `withCheckpoint(config)` factory. Mounted under Vercel Node-runtime\n * serverless functions and long-lived Node servers.\n *\n * For Vercel Edge runtime (the Next.js middleware default), customers\n * import from `./edge` or `@kya-os/checkpoint-nextjs/edge` — that\n * variant uses `verifyRequestEdge` (async-init) and is otherwise\n * structurally identical. Both share `translate.ts` + `adapt.ts`.\n *\n * **Public API contract (architect § 4.1 — preserved):**\n *\n * - `withCheckpoint(config)` — factory returning the middleware.\n * - `CheckpointConfig` — the config shape; new fields are additive.\n *\n * Internal implementation gutted, external contract held. Sites-1's\n * Playwright suite is the regression gate.\n */\n\nimport { type NextRequest, type NextResponse } from 'next/server';\n\nimport {\n renderDecisionAsResponse,\n verifyRequest,\n} from '@kya-os/checkpoint-wasm-runtime/orchestrator';\nimport {\n makeDidResolver,\n makePolicyEvaluator,\n makeReputationOracle,\n makeStatusListCache,\n makeSystemClock,\n type DidResolverAdapter,\n type PolicyEvaluatorAdapter,\n type ReputationOracleAdapter,\n type StatusListCacheAdapter,\n} from '@kya-os/checkpoint-wasm-runtime/adapters';\nimport type { EnforcementMode, VerifyResult } from '@kya-os/checkpoint-wasm-runtime/engine';\n\nimport { adaptToNextResponse } from './adapt';\nimport { nextRequestToHttpLike } from './translate';\n\n/**\n * Configuration for `withCheckpoint`.\n *\n * The new minimal shape Phase D's middleware needs. Legacy\n * `AgentShieldMiddlewareConfig` (from `./api-middleware`) remains\n * exported during the deprecation window — see D.4 cutover.\n */\nexport interface CheckpointConfig {\n /**\n * Tenant identifier — typically the customer's dashboard hostname\n * (e.g. `acme.checkpoint.example`). The PolicyEvaluator uses this\n * to look up tenant policy from the dashboard.\n */\n tenantHost: string;\n\n /**\n * `'enforce'` (default) blocks; `'observe'` passes everything\n * through with `X-Checkpoint-Would-Have-Been` headers. Per Phase 0.2.\n */\n enforcementMode?: EnforcementMode;\n\n /**\n * Argus reputation oracle base URL. Omit to use the trust-by-default\n * baseline (reputation defaults to 1.0; orchestrator logs a one-shot\n * warning at first request).\n */\n argusUrl?: string;\n\n /**\n * Dashboard base URL for the PolicyEvaluator to fetch tenant policy\n * from. Omit to use the open-by-default tenant policy.\n */\n dashboardUrl?: string;\n\n /**\n * Returned to the PolicyEvaluator for anonymous requests (no agent\n * DID). Default 1.0 (trust-by-default).\n */\n reputationBaseline?: number;\n\n /**\n * Pre-built adapter instances. Production deployments use the\n * factory-built defaults from `@kya-os/checkpoint-wasm-runtime/adapters`;\n * tests use stubs. The factory composes any provided overrides over\n * defaults — partial overrides are supported.\n */\n adapters?: Partial<{\n didResolver: DidResolverAdapter;\n statusListCache: StatusListCacheAdapter;\n reputationOracle: ReputationOracleAdapter;\n policyEvaluator: PolicyEvaluatorAdapter;\n }>;\n\n /**\n * Optional callback for the post-verdict path — fires after every\n * verification, regardless of permit/block, with the full\n * `VerifyResult`. Use for logging, dashboards, telemetry. Errors\n * thrown here are swallowed so user code can't break the middleware\n * response.\n */\n onResult?: (result: VerifyResult, req: NextRequest) => void | Promise<void>;\n}\n\n/**\n * Build the Checkpoint middleware. Returns a function `(req) => NextResponse`\n * suitable for `export default withCheckpoint({...})` in `middleware.ts`.\n *\n * Every verification decision flows through the Rust `kya-os-engine`\n * via WASM. The TS layer translates request shape, calls\n * `verifyRequest`, and translates the verdict to `NextResponse`. No\n * verification logic lives in this file.\n */\nexport function withCheckpoint(\n config: CheckpointConfig\n): (req: NextRequest) => Promise<NextResponse> {\n const opts = buildVerifyOpts(config);\n return async function checkpointMiddleware(req: NextRequest): Promise<NextResponse> {\n const httpLike = nextRequestToHttpLike(req);\n const result = await verifyRequest(httpLike, opts);\n await dispatchOnResult(config, result, req);\n const rendered = renderDecisionAsResponse(result);\n return adaptToNextResponse(rendered, req);\n };\n}\n\n/**\n * Compose adapter defaults with caller-supplied overrides. Factored\n * out so the Edge entry (which uses the same composition) can reuse\n * the shape.\n */\nfunction buildVerifyOpts(config: CheckpointConfig) {\n const overrides = config.adapters ?? {};\n return {\n didResolver: overrides.didResolver ?? makeDidResolver(),\n statusListCache: overrides.statusListCache ?? makeStatusListCache(),\n reputationOracle:\n overrides.reputationOracle ?? makeReputationOracle({ argusUrl: config.argusUrl }),\n policyEvaluator:\n overrides.policyEvaluator ?? makePolicyEvaluator({ dashboardUrl: config.dashboardUrl }),\n clock: makeSystemClock(),\n tenantHost: config.tenantHost,\n enforcementMode: config.enforcementMode ?? 'enforce',\n reputationBaseline: config.reputationBaseline,\n argusUrl: config.argusUrl,\n };\n}\n\nasync function dispatchOnResult(\n config: CheckpointConfig,\n result: VerifyResult,\n req: NextRequest\n): Promise<void> {\n if (!config.onResult) return;\n try {\n await config.onResult(result, req);\n } catch {\n // Swallow — onResult is observability, not verdict-critical.\n // Verdict already computed; let the response proceed.\n }\n}\n\n// Re-export the shared opts builder for the Edge entry. Internal seam;\n// not part of the public surface.\nexport { buildVerifyOpts as _buildVerifyOpts };\n"]}
|
package/dist/middleware.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware.ts"],"names":[],"mappings":";;;AAsBA,IAAM,eAAA,GACJ,yXAAA;AAYK,SAAS,2BAAA,CACd,OAAA,GAA2C,EAAC,EACK;AACjD,EAAA,MAAM,IAAI,MAAM,eAAe,CAAA;AACjC;AAMO,SAAS,WAAA,CACd,MAAA,GAA0C,EAAC,EACM;AACjD,EAAA,OAAO,4BAA4B,MAAM,CAAA;AAC3C","file":"middleware.js","sourcesContent":["/**\n * @deprecated Phase D — legacy TS-pattern-matching middleware path is\n * gone. This file now exists only to preserve the historical export\n * surface (`createAgentShieldMiddleware`, `agentShield`) at compile\n * time. Calling either function throws a clear migration error.\n *\n * Migrate to `withCheckpoint` from `@kya-os/checkpoint-nextjs`\n * (Node runtime) or `@kya-os/checkpoint-nextjs/edge` (Edge runtime).\n * The new factory accepts a `CheckpointConfig` and routes every\n * verification through the Rust `kya-os-engine` via WASM. See the\n * package CHANGELOG for the recipe.\n *\n * Architect Q10 deletion ratification — the 600-line TS pattern\n * matcher (`edge-detector-wrapper.ts`) was removed in Phase D's\n * cutover. The new path is structurally simpler and decisive: one\n * engine, one verdict, every runtime.\n */\n\nimport type { NextRequest, NextResponse } from 'next/server';\n\nimport type { NextJSMiddlewareConfig } from './types';\n\nconst MIGRATION_ERROR =\n \"@kya-os/checkpoint-nextjs's `createAgentShieldMiddleware` / `agentShield` \" +\n 'were deleted in Phase D (engine consolidation). The 600-line TS pattern ' +\n 'matcher that backed them is gone. Migrate to `withCheckpoint` from ' +\n '`@kya-os/checkpoint-nextjs` (Node runtime) or ' +\n '`@kya-os/checkpoint-nextjs/edge` (Edge runtime). ' +\n 'See packages/checkpoint-nextjs/CHANGELOG.md (1.0.0) for the recipe.';\n\n/**\n * @deprecated Migrate to `withCheckpoint` from `@kya-os/checkpoint-nextjs`.\n * Throws on invocation; surface exists only so static analysis sees\n * the historical export.\n */\nexport function createAgentShieldMiddleware(\n _config: Partial<NextJSMiddlewareConfig> = {}\n): (request: NextRequest) => Promise<NextResponse> {\n throw new Error(MIGRATION_ERROR);\n}\n\n/**\n * @deprecated Alias of the deprecated `createAgentShieldMiddleware`.\n * Migrate to `withCheckpoint`.\n */\nexport function agentShield(\n config: Partial<NextJSMiddlewareConfig> = {}\n): (request: NextRequest) => Promise<NextResponse> {\n return createAgentShieldMiddleware(config);\n}\n"]}
|
package/dist/middleware.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware.ts"],"names":[],"mappings":";AAsBA,IAAM,eAAA,GACJ,yXAAA;AAYK,SAAS,2BAAA,CACd,OAAA,GAA2C,EAAC,EACK;AACjD,EAAA,MAAM,IAAI,MAAM,eAAe,CAAA;AACjC;AAMO,SAAS,WAAA,CACd,MAAA,GAA0C,EAAC,EACM;AACjD,EAAA,OAAO,4BAA4B,MAAM,CAAA;AAC3C","file":"middleware.mjs","sourcesContent":["/**\n * @deprecated Phase D — legacy TS-pattern-matching middleware path is\n * gone. This file now exists only to preserve the historical export\n * surface (`createAgentShieldMiddleware`, `agentShield`) at compile\n * time. Calling either function throws a clear migration error.\n *\n * Migrate to `withCheckpoint` from `@kya-os/checkpoint-nextjs`\n * (Node runtime) or `@kya-os/checkpoint-nextjs/edge` (Edge runtime).\n * The new factory accepts a `CheckpointConfig` and routes every\n * verification through the Rust `kya-os-engine` via WASM. See the\n * package CHANGELOG for the recipe.\n *\n * Architect Q10 deletion ratification — the 600-line TS pattern\n * matcher (`edge-detector-wrapper.ts`) was removed in Phase D's\n * cutover. The new path is structurally simpler and decisive: one\n * engine, one verdict, every runtime.\n */\n\nimport type { NextRequest, NextResponse } from 'next/server';\n\nimport type { NextJSMiddlewareConfig } from './types';\n\nconst MIGRATION_ERROR =\n \"@kya-os/checkpoint-nextjs's `createAgentShieldMiddleware` / `agentShield` \" +\n 'were deleted in Phase D (engine consolidation). The 600-line TS pattern ' +\n 'matcher that backed them is gone. Migrate to `withCheckpoint` from ' +\n '`@kya-os/checkpoint-nextjs` (Node runtime) or ' +\n '`@kya-os/checkpoint-nextjs/edge` (Edge runtime). ' +\n 'See packages/checkpoint-nextjs/CHANGELOG.md (1.0.0) for the recipe.';\n\n/**\n * @deprecated Migrate to `withCheckpoint` from `@kya-os/checkpoint-nextjs`.\n * Throws on invocation; surface exists only so static analysis sees\n * the historical export.\n */\nexport function createAgentShieldMiddleware(\n _config: Partial<NextJSMiddlewareConfig> = {}\n): (request: NextRequest) => Promise<NextResponse> {\n throw new Error(MIGRATION_ERROR);\n}\n\n/**\n * @deprecated Alias of the deprecated `createAgentShieldMiddleware`.\n * Migrate to `withCheckpoint`.\n */\nexport function agentShield(\n config: Partial<NextJSMiddlewareConfig> = {}\n): (request: NextRequest) => Promise<NextResponse> {\n return createAgentShieldMiddleware(config);\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/nodejs-wasm-loader.ts"],"names":["path","fs","setWasmModule"],"mappings":";;;;;;;;;;;;;;;;;AAWA,IAAI,eAAA,GAAkB,KAAA;AACtB,IAAI,UAAA,GAAwC,IAAA;AAM5C,eAAsB,cAAA,GAAmC;AACvD,EAAA,IAAI,eAAA,EAAiB;AACnB,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI;AAEF,IAAA,MAAM,aAAA,GAAgB;AAAA;AAAA,MAEpBA,qBAAA,CAAK,IAAA;AAAA,QACH,QAAQ,GAAA,EAAI;AAAA,QACZ,cAAA;AAAA,QACA,SAAA;AAAA,QACA,aAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA,OACF;AAAA;AAAA,MAEAA,qBAAA,CAAK,IAAA,CAAK,OAAA,CAAQ,GAAA,IAAO,0BAA0B,CAAA;AAAA;AAAA,MAEnDA,qBAAA,CAAK,IAAA;AAAA,QACH,SAAA;AAAA,QACA,IAAA;AAAA,QACA,IAAA;AAAA,QACA,IAAA;AAAA,QACA,aAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA;AACF,KACF;AAEA,IAAA,IAAI,UAAA,GAA4B,IAAA;AAChC,IAAA,IAAI,UAAA,GAA4B,IAAA;AAEhC,IAAA,KAAA,MAAW,YAAY,aAAA,EAAe;AACpC,MAAA,IAAI;AACF,QAAA,IAAIC,mBAAA,CAAG,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC3B,UAAA,UAAA,GAAaA,mBAAA,CAAG,aAAa,QAAQ,CAAA;AACrC,UAAA,UAAA,GAAa,QAAA;AACb,UAAA;AAAA,QACF;AAAA,MACF,SAAS,CAAA,EAAG;AAEV,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAI,CAAC,UAAA,EAAY;AACf,MAAA,OAAA,CAAQ,KAAK,2DAA2D,CAAA;AACxE,MAAA,OAAO,KAAA;AAAA,IACT;AAIA,IAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,UAAU,CAAA;AACvC,IAAA,UAAA,GAAa,MAAM,WAAA,CAAY,OAAA,CAAQ,KAAK,CAAA;AAG5C,IAAAC,wBAAA,CAAc,UAAU,CAAA;AAExB,IAAA,eAAA,GAAkB,IAAA;AAClB,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,kDAAA,EAAgD,UAAU,CAAA,kBAAA,CAAoB,CAAA;AAC1F,IAAA,OAAA,CAAQ,IAAI,mEAA4D,CAAA;AAExE,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,IAAA,CAAK,qEAA2D,KAAK,CAAA;AAC7E,IAAA,OAAA,CAAQ,IAAI,8DAAuD,CAAA;AACnE,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAKO,SAAS,eAAA,GAA2B;AACzC,EAAA,OACE,OAAO,OAAA,KAAY,WAAA,IACnB,OAAO,OAAA,CAAQ,QAAA,KAAa,WAAA,IAC5B,OAAO,OAAA,CAAQ,QAAA,CAAS,IAAA,KAAS,WAAA,IACjC,OAAO,SAAA,KAAY,WAAA;AAEvB;AAKO,SAAS,aAAA,GAA2C;AACzD,EAAA,OAAO,UAAA;AACT;AAKO,SAAS,iBAAA,GAA6B;AAC3C,EAAA,OAAO,eAAA;AACT","file":"nodejs-wasm-loader.js","sourcesContent":["/**\n * Node.js Runtime WASM Loader for AgentShield\n *\n * This loader uses fs.readFileSync to load WASM in Node.js runtime.\n * It provides full cryptographic verification capabilities.\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { setWasmModule } from '@kya-os/checkpoint';\n\nlet wasmInitialized = false;\nlet wasmModule: WebAssembly.Module | null = null;\n\n/**\n * Load WASM module using Node.js fs module\n * This only works in Node.js runtime, not Edge Runtime\n */\nexport async function loadWasmNodejs(): Promise<boolean> {\n if (wasmInitialized) {\n return true;\n }\n\n try {\n // Try multiple possible WASM locations\n const possiblePaths = [\n // In node_modules (most likely)\n path.join(\n process.cwd(),\n 'node_modules',\n '@kya-os',\n 'agentshield',\n 'dist',\n 'wasm',\n 'agentshield_wasm_bg.wasm'\n ),\n // In project root (if user copied it)\n path.join(process.cwd(), 'agentshield_wasm_bg.wasm'),\n // Relative to current file\n path.join(\n __dirname,\n '..',\n '..',\n '..',\n 'agentshield',\n 'dist',\n 'wasm',\n 'agentshield_wasm_bg.wasm'\n ),\n ];\n\n let wasmBuffer: Buffer | null = null;\n let loadedPath: string | null = null;\n\n for (const wasmPath of possiblePaths) {\n try {\n if (fs.existsSync(wasmPath)) {\n wasmBuffer = fs.readFileSync(wasmPath);\n loadedPath = wasmPath;\n break;\n }\n } catch (e) {\n // Try next path\n continue;\n }\n }\n\n if (!wasmBuffer) {\n console.warn('AgentShield: WASM file not found in any expected location');\n return false;\n }\n\n // Convert Buffer to Uint8Array for WebAssembly\n // This is the proper way to handle Buffer -> ArrayBuffer conversion\n const bytes = new Uint8Array(wasmBuffer);\n wasmModule = await WebAssembly.compile(bytes);\n\n // Set the module in AgentShield\n setWasmModule(wasmModule);\n\n wasmInitialized = true;\n console.log(`✅ AgentShield: WASM loaded successfully from ${loadedPath} (Node.js runtime)`);\n console.log('🔐 Cryptographic verification enabled (95-100% confidence)');\n\n return true;\n } catch (error) {\n console.warn('⚠️ AgentShield: Failed to load WASM in Node.js runtime:', error);\n console.log('📊 Falling back to pattern detection (85% confidence)');\n return false;\n }\n}\n\n/**\n * Check if we're in Node.js runtime\n */\nexport function isNodejsRuntime(): boolean {\n return (\n typeof process !== 'undefined' &&\n typeof process.versions !== 'undefined' &&\n typeof process.versions.node !== 'undefined' &&\n typeof require !== 'undefined'\n );\n}\n\n/**\n * Get the loaded WASM module\n */\nexport function getWasmModule(): WebAssembly.Module | null {\n return wasmModule;\n}\n\n/**\n * Check if WASM is initialized\n */\nexport function isWasmInitialized(): boolean {\n return wasmInitialized;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/nodejs-wasm-loader.ts"],"names":[],"mappings":";;;;;;;;;;AAWA,IAAI,eAAA,GAAkB,KAAA;AACtB,IAAI,UAAA,GAAwC,IAAA;AAM5C,eAAsB,cAAA,GAAmC;AACvD,EAAA,IAAI,eAAA,EAAiB;AACnB,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI;AAEF,IAAA,MAAM,aAAA,GAAgB;AAAA;AAAA,MAEpB,IAAA,CAAK,IAAA;AAAA,QACH,QAAQ,GAAA,EAAI;AAAA,QACZ,cAAA;AAAA,QACA,SAAA;AAAA,QACA,aAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA,OACF;AAAA;AAAA,MAEA,IAAA,CAAK,IAAA,CAAK,OAAA,CAAQ,GAAA,IAAO,0BAA0B,CAAA;AAAA;AAAA,MAEnD,IAAA,CAAK,IAAA;AAAA,QACH,SAAA;AAAA,QACA,IAAA;AAAA,QACA,IAAA;AAAA,QACA,IAAA;AAAA,QACA,aAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA;AACF,KACF;AAEA,IAAA,IAAI,UAAA,GAA4B,IAAA;AAChC,IAAA,IAAI,UAAA,GAA4B,IAAA;AAEhC,IAAA,KAAA,MAAW,YAAY,aAAA,EAAe;AACpC,MAAA,IAAI;AACF,QAAA,IAAI,EAAA,CAAG,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC3B,UAAA,UAAA,GAAa,EAAA,CAAG,aAAa,QAAQ,CAAA;AACrC,UAAA,UAAA,GAAa,QAAA;AACb,UAAA;AAAA,QACF;AAAA,MACF,SAAS,CAAA,EAAG;AAEV,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAI,CAAC,UAAA,EAAY;AACf,MAAA,OAAA,CAAQ,KAAK,2DAA2D,CAAA;AACxE,MAAA,OAAO,KAAA;AAAA,IACT;AAIA,IAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,UAAU,CAAA;AACvC,IAAA,UAAA,GAAa,MAAM,WAAA,CAAY,OAAA,CAAQ,KAAK,CAAA;AAG5C,IAAA,aAAA,CAAc,UAAU,CAAA;AAExB,IAAA,eAAA,GAAkB,IAAA;AAClB,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,kDAAA,EAAgD,UAAU,CAAA,kBAAA,CAAoB,CAAA;AAC1F,IAAA,OAAA,CAAQ,IAAI,mEAA4D,CAAA;AAExE,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,IAAA,CAAK,qEAA2D,KAAK,CAAA;AAC7E,IAAA,OAAA,CAAQ,IAAI,8DAAuD,CAAA;AACnE,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAKO,SAAS,eAAA,GAA2B;AACzC,EAAA,OACE,OAAO,OAAA,KAAY,WAAA,IACnB,OAAO,OAAA,CAAQ,QAAA,KAAa,WAAA,IAC5B,OAAO,OAAA,CAAQ,QAAA,CAAS,IAAA,KAAS,WAAA,IACjC,OAAO,SAAA,KAAY,WAAA;AAEvB;AAKO,SAAS,aAAA,GAA2C;AACzD,EAAA,OAAO,UAAA;AACT;AAKO,SAAS,iBAAA,GAA6B;AAC3C,EAAA,OAAO,eAAA;AACT","file":"nodejs-wasm-loader.mjs","sourcesContent":["/**\n * Node.js Runtime WASM Loader for AgentShield\n *\n * This loader uses fs.readFileSync to load WASM in Node.js runtime.\n * It provides full cryptographic verification capabilities.\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { setWasmModule } from '@kya-os/checkpoint';\n\nlet wasmInitialized = false;\nlet wasmModule: WebAssembly.Module | null = null;\n\n/**\n * Load WASM module using Node.js fs module\n * This only works in Node.js runtime, not Edge Runtime\n */\nexport async function loadWasmNodejs(): Promise<boolean> {\n if (wasmInitialized) {\n return true;\n }\n\n try {\n // Try multiple possible WASM locations\n const possiblePaths = [\n // In node_modules (most likely)\n path.join(\n process.cwd(),\n 'node_modules',\n '@kya-os',\n 'agentshield',\n 'dist',\n 'wasm',\n 'agentshield_wasm_bg.wasm'\n ),\n // In project root (if user copied it)\n path.join(process.cwd(), 'agentshield_wasm_bg.wasm'),\n // Relative to current file\n path.join(\n __dirname,\n '..',\n '..',\n '..',\n 'agentshield',\n 'dist',\n 'wasm',\n 'agentshield_wasm_bg.wasm'\n ),\n ];\n\n let wasmBuffer: Buffer | null = null;\n let loadedPath: string | null = null;\n\n for (const wasmPath of possiblePaths) {\n try {\n if (fs.existsSync(wasmPath)) {\n wasmBuffer = fs.readFileSync(wasmPath);\n loadedPath = wasmPath;\n break;\n }\n } catch (e) {\n // Try next path\n continue;\n }\n }\n\n if (!wasmBuffer) {\n console.warn('AgentShield: WASM file not found in any expected location');\n return false;\n }\n\n // Convert Buffer to Uint8Array for WebAssembly\n // This is the proper way to handle Buffer -> ArrayBuffer conversion\n const bytes = new Uint8Array(wasmBuffer);\n wasmModule = await WebAssembly.compile(bytes);\n\n // Set the module in AgentShield\n setWasmModule(wasmModule);\n\n wasmInitialized = true;\n console.log(`✅ AgentShield: WASM loaded successfully from ${loadedPath} (Node.js runtime)`);\n console.log('🔐 Cryptographic verification enabled (95-100% confidence)');\n\n return true;\n } catch (error) {\n console.warn('⚠️ AgentShield: Failed to load WASM in Node.js runtime:', error);\n console.log('📊 Falling back to pattern detection (85% confidence)');\n return false;\n }\n}\n\n/**\n * Check if we're in Node.js runtime\n */\nexport function isNodejsRuntime(): boolean {\n return (\n typeof process !== 'undefined' &&\n typeof process.versions !== 'undefined' &&\n typeof process.versions.node !== 'undefined' &&\n typeof require !== 'undefined'\n );\n}\n\n/**\n * Get the loaded WASM module\n */\nexport function getWasmModule(): WebAssembly.Module | null {\n return wasmModule;\n}\n\n/**\n * Check if WASM is initialized\n */\nexport function isWasmInitialized(): boolean {\n return wasmInitialized;\n}\n"]}
|
package/dist/policy.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/policy.ts"],"names":["createEvaluationContext","evaluatePolicy","NextResponse","ENFORCEMENT_ACTIONS","createPolicyFetcher","PolicyConfigSchema","DEFAULT_POLICY","matchPath"],"mappings":"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","file":"policy.js","sourcesContent":["/**\n * Policy Integration for agentshield-nextjs\n *\n * This module provides policy evaluation support for the Next.js middleware.\n * It can use:\n * - Local policy configuration (static)\n * - Fetched policy from AgentShield API (dynamic with caching)\n * - Fallback/default policies\n *\n * @example\n * ```typescript\n * import { createPolicyMiddleware } from '@kya-os/checkpoint-nextjs/policy';\n *\n * export default createPolicyMiddleware({\n * policy: {\n * enabled: true,\n * defaultAction: 'allow',\n * thresholds: { confidenceThreshold: 80, confidenceAction: 'block' },\n * allowList: [{ clientName: 'ChatGPT' }],\n * },\n * });\n * ```\n */\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport {\n evaluatePolicy,\n createEvaluationContext,\n createPolicyFetcher,\n matchPath,\n PolicyFetcher,\n PolicyConfigSchema,\n ENFORCEMENT_ACTIONS,\n DEFAULT_POLICY,\n type PolicyConfig,\n type PolicyEvaluationContext,\n type PolicyEvaluationResult,\n type PolicyFetcherConfig,\n type DetectionResult,\n} from '@kya-os/checkpoint-shared';\n\n// Re-export shared policy types for convenience\nexport {\n evaluatePolicy,\n createEvaluationContext,\n type PolicyConfig,\n type PolicyEvaluationContext,\n type PolicyEvaluationResult,\n ENFORCEMENT_ACTIONS,\n DEFAULT_POLICY,\n} from '@kya-os/checkpoint-shared';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Policy middleware configuration\n */\nexport interface PolicyMiddlewareConfig {\n /**\n * Local policy configuration (static)\n * If provided, this policy is used instead of fetching from API\n */\n policy?: Partial<PolicyConfig>;\n\n /**\n * Fetch policy from AgentShield API\n * Requires projectId and optionally an apiKey\n */\n fetchPolicy?: {\n /** Project ID to fetch policy for */\n projectId: string;\n /** API base URL (defaults to production) */\n apiUrl?: string;\n /** API key for authentication */\n apiKey?: string;\n /** Cache TTL in seconds (default: 300) */\n cacheTtlSeconds?: number;\n };\n\n /**\n * Fallback policy to use when fetch fails\n * Defaults to DEFAULT_POLICY (allow all)\n */\n fallbackPolicy?: Partial<PolicyConfig>;\n\n /**\n * Custom blocked response\n */\n blockedResponse?: {\n status?: number;\n message?: string;\n headers?: Record<string, string>;\n };\n\n /**\n * Default redirect URL for redirect actions\n */\n redirectUrl?: string;\n\n /**\n * Callback when policy decision is made\n */\n onPolicyDecision?: (\n request: NextRequest,\n decision: PolicyEvaluationResult,\n context: PolicyEvaluationContext\n ) => void | Promise<void>;\n\n /**\n * Custom response builder for blocked requests\n */\n customBlockedResponse?: (\n request: NextRequest,\n decision: PolicyEvaluationResult\n ) => NextResponse | Promise<NextResponse>;\n\n /**\n * Whether to fail open (allow) on policy evaluation errors\n * Default: true (recommended for production)\n */\n failOpen?: boolean;\n\n /**\n * Enable debug logging\n */\n debug?: boolean;\n}\n\n/**\n * Combined middleware configuration with policy support\n */\nexport interface NextJSPolicyMiddlewareConfig extends PolicyMiddlewareConfig {\n /**\n * Paths to skip (in addition to policy excludedPaths)\n */\n skipPaths?: string[];\n\n /**\n * Only enforce on these paths (overrides policy includedPaths)\n */\n includePaths?: string[];\n}\n\n// ============================================================================\n// Policy Evaluation Helper\n// ============================================================================\n\n/**\n * Create policy evaluation context from detection result and request\n */\nexport function createContextFromDetection(\n detection: DetectionResult,\n request: NextRequest\n): PolicyEvaluationContext {\n return createEvaluationContext({\n agentType: detection.detectedAgent?.type,\n agentName: detection.detectedAgent?.name,\n agentVendor: detection.detectedAgent?.vendor,\n confidence: detection.confidence,\n riskLevel: detection.riskLevel,\n path: request.nextUrl.pathname,\n method: request.method,\n signatureVerified: detection.verificationMethod === 'signature',\n isAuthenticated: false, // TODO: integrate with auth\n userAgent: request.headers.get('user-agent') || undefined,\n });\n}\n\n/**\n * Evaluate policy for a detection result\n */\nexport function evaluatePolicyForDetection(\n detection: DetectionResult,\n request: NextRequest,\n policy: PolicyConfig\n): PolicyEvaluationResult {\n const context = createContextFromDetection(detection, request);\n return evaluatePolicy(policy, context);\n}\n\n// ============================================================================\n// Response Builders\n// ============================================================================\n\n/**\n * Build blocked response based on policy decision\n */\nexport function buildBlockedResponse(\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig\n): NextResponse {\n const status = config.blockedResponse?.status ?? 403;\n const message = config.blockedResponse?.message ?? decision.message ?? 'Access denied';\n\n const response = NextResponse.json(\n {\n error: message,\n code: 'POLICY_BLOCKED',\n reason: decision.reason,\n ruleId: decision.ruleId,\n matchType: decision.matchType,\n },\n { status }\n );\n\n // Add custom headers\n if (config.blockedResponse?.headers) {\n for (const [key, value] of Object.entries(config.blockedResponse.headers)) {\n response.headers.set(key, value);\n }\n }\n\n // Add AgentShield headers\n response.headers.set('KYA-Action', decision.action);\n response.headers.set('KYA-Reason', decision.reason);\n response.headers.set('KYA-Match-Type', decision.matchType);\n\n return response;\n}\n\n/**\n * Build redirect response based on policy decision\n */\nexport function buildRedirectResponse(\n request: NextRequest,\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig,\n detection?: { detectedAgent?: { name?: string } }\n): NextResponse {\n const redirectUrl = decision.redirectUrl || config.redirectUrl || '/blocked';\n const url = new URL(redirectUrl, request.url);\n\n // Add query params with policy info\n url.searchParams.set('reason', decision.reason);\n if (decision.ruleId) {\n url.searchParams.set('ruleId', decision.ruleId);\n }\n const agentName = detection?.detectedAgent?.name;\n if (agentName && !url.searchParams.has('agent')) {\n url.searchParams.set('agent', agentName.toLowerCase());\n }\n\n return NextResponse.redirect(url);\n}\n\n/**\n * Build challenge response (placeholder - future implementation)\n */\nexport function buildChallengeResponse(\n request: NextRequest,\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig,\n detection?: { detectedAgent?: { name?: string } }\n): NextResponse {\n // For now, treat challenge as redirect\n // Future: implement CAPTCHA, proof-of-work, etc.\n return buildRedirectResponse(request, decision, config, detection);\n}\n\n// ============================================================================\n// Policy Handler\n// ============================================================================\n\n/**\n * Handle policy decision and return appropriate response\n */\nexport async function handlePolicyDecision(\n request: NextRequest,\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig,\n detection?: { detectedAgent?: { name?: string } }\n): Promise<NextResponse | null> {\n switch (decision.action) {\n case ENFORCEMENT_ACTIONS.BLOCK:\n if (config.customBlockedResponse) {\n return await config.customBlockedResponse(request, decision);\n }\n return buildBlockedResponse(decision, config);\n\n case ENFORCEMENT_ACTIONS.REDIRECT:\n return buildRedirectResponse(request, decision, config, detection);\n\n case ENFORCEMENT_ACTIONS.CHALLENGE:\n return buildChallengeResponse(request, decision, config, detection);\n\n case ENFORCEMENT_ACTIONS.LOG:\n // LOG action always logs - that's its purpose\n // (debug flag controls verbose debugging output, not LOG action behavior)\n console.log('[AgentShield] Policy decision (log):', {\n path: request.nextUrl.pathname,\n action: decision.action,\n reason: decision.reason,\n matchType: decision.matchType,\n ruleId: decision.ruleId,\n });\n return null; // Continue to allow\n\n case ENFORCEMENT_ACTIONS.ALLOW:\n default:\n return null; // Continue\n }\n}\n\n// ============================================================================\n// Policy Fetcher Integration\n// ============================================================================\n\n// Cache fetchers by config to avoid recreating them, but also support\n// different configurations (different apiUrl, apiKey, etc.)\nconst fetcherCache = new Map<string, PolicyFetcher>();\n\n/**\n * Generate a cache key for fetcher config.\n * Uses ?? to distinguish between explicit 0 and undefined values.\n */\nfunction getFetcherCacheKey(config: NonNullable<PolicyMiddlewareConfig['fetchPolicy']>): string {\n return `${config.apiUrl ?? 'default'}:${config.apiKey ?? ''}:${config.cacheTtlSeconds ?? 'default'}`;\n}\n\n/**\n * Get or create policy fetcher for the given config\n */\nfunction getPolicyFetcher(config: PolicyMiddlewareConfig['fetchPolicy']): PolicyFetcher {\n if (!config) {\n throw new Error('fetchPolicy config required');\n }\n\n const cacheKey = getFetcherCacheKey(config);\n let fetcher = fetcherCache.get(cacheKey);\n\n if (!fetcher) {\n const fetcherConfig: PolicyFetcherConfig = {\n apiBaseUrl: config.apiUrl || 'https://kya.vouched.id',\n apiKey: config.apiKey,\n cacheTtlSeconds: config.cacheTtlSeconds,\n };\n fetcher = createPolicyFetcher(fetcherConfig);\n fetcherCache.set(cacheKey, fetcher);\n }\n\n return fetcher;\n}\n\n/**\n * Get policy (local, fetched, or fallback)\n */\nexport async function getPolicy(config: PolicyMiddlewareConfig): Promise<PolicyConfig> {\n // Use local policy if provided\n if (config.policy) {\n return PolicyConfigSchema.parse({ ...DEFAULT_POLICY, ...config.policy });\n }\n\n // Fetch from API if configured\n if (config.fetchPolicy) {\n try {\n const fetcher = getPolicyFetcher(config.fetchPolicy);\n return await fetcher.getPolicy(config.fetchPolicy.projectId);\n } catch (error) {\n if (config.debug) {\n console.warn('[AgentShield] Policy fetch failed, using fallback:', error);\n }\n // Return fallback policy\n return PolicyConfigSchema.parse({\n ...DEFAULT_POLICY,\n ...(config.fallbackPolicy || {}),\n });\n }\n }\n\n // No policy configured - return default (allow all)\n return PolicyConfigSchema.parse(DEFAULT_POLICY);\n}\n\n// ============================================================================\n// Standalone Policy Middleware\n// ============================================================================\n\n/**\n * Apply policy to a detection result\n *\n * This function can be used standalone to evaluate policy after detection.\n * Supports extended config with skipPaths and includePaths for path-based filtering.\n *\n * @example\n * ```typescript\n * const result = await detector.analyze(context);\n * const response = await applyPolicy(request, result, {\n * policy: { thresholds: { confidenceThreshold: 80 } },\n * skipPaths: ['/health', '/api/public/*'],\n * includePaths: ['/api/*'],\n * });\n *\n * if (response) {\n * return response; // Policy blocked the request\n * }\n * ```\n */\nexport async function applyPolicy(\n request: NextRequest,\n detection: DetectionResult,\n config: NextJSPolicyMiddlewareConfig\n): Promise<NextResponse | null> {\n try {\n const path = request.nextUrl.pathname;\n\n // Check skipPaths - if path matches any skip pattern, allow through\n if (config.skipPaths?.some((pattern) => matchPath(path, pattern))) {\n return null; // Skip policy enforcement for this path\n }\n\n // Check includePaths - if defined, path must match at least one pattern\n if (config.includePaths && config.includePaths.length > 0) {\n if (!config.includePaths.some((pattern) => matchPath(path, pattern))) {\n return null; // Path not in included paths, skip policy enforcement\n }\n }\n\n // Get policy\n const policy = await getPolicy(config);\n\n // Create context and evaluate\n const context = createContextFromDetection(detection, request);\n const decision = evaluatePolicy(policy, context);\n\n // Call decision callback if provided\n if (config.onPolicyDecision) {\n await config.onPolicyDecision(request, decision, context);\n }\n\n // Handle decision — pass detection through so redirect can append ?agent=\n return await handlePolicyDecision(request, decision, config, detection);\n } catch (error) {\n if (config.debug) {\n console.error('[AgentShield] Policy evaluation error:', error);\n }\n\n if (config.failOpen !== false) {\n return null; // Allow on error\n }\n\n // Fail closed\n return NextResponse.json(\n { error: 'Security check failed', code: 'POLICY_ERROR' },\n { status: 503 }\n );\n }\n}\n"]}
|
package/dist/policy.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/policy.ts"],"names":[],"mappings":"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","file":"policy.mjs","sourcesContent":["/**\n * Policy Integration for agentshield-nextjs\n *\n * This module provides policy evaluation support for the Next.js middleware.\n * It can use:\n * - Local policy configuration (static)\n * - Fetched policy from AgentShield API (dynamic with caching)\n * - Fallback/default policies\n *\n * @example\n * ```typescript\n * import { createPolicyMiddleware } from '@kya-os/checkpoint-nextjs/policy';\n *\n * export default createPolicyMiddleware({\n * policy: {\n * enabled: true,\n * defaultAction: 'allow',\n * thresholds: { confidenceThreshold: 80, confidenceAction: 'block' },\n * allowList: [{ clientName: 'ChatGPT' }],\n * },\n * });\n * ```\n */\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport {\n evaluatePolicy,\n createEvaluationContext,\n createPolicyFetcher,\n matchPath,\n PolicyFetcher,\n PolicyConfigSchema,\n ENFORCEMENT_ACTIONS,\n DEFAULT_POLICY,\n type PolicyConfig,\n type PolicyEvaluationContext,\n type PolicyEvaluationResult,\n type PolicyFetcherConfig,\n type DetectionResult,\n} from '@kya-os/checkpoint-shared';\n\n// Re-export shared policy types for convenience\nexport {\n evaluatePolicy,\n createEvaluationContext,\n type PolicyConfig,\n type PolicyEvaluationContext,\n type PolicyEvaluationResult,\n ENFORCEMENT_ACTIONS,\n DEFAULT_POLICY,\n} from '@kya-os/checkpoint-shared';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Policy middleware configuration\n */\nexport interface PolicyMiddlewareConfig {\n /**\n * Local policy configuration (static)\n * If provided, this policy is used instead of fetching from API\n */\n policy?: Partial<PolicyConfig>;\n\n /**\n * Fetch policy from AgentShield API\n * Requires projectId and optionally an apiKey\n */\n fetchPolicy?: {\n /** Project ID to fetch policy for */\n projectId: string;\n /** API base URL (defaults to production) */\n apiUrl?: string;\n /** API key for authentication */\n apiKey?: string;\n /** Cache TTL in seconds (default: 300) */\n cacheTtlSeconds?: number;\n };\n\n /**\n * Fallback policy to use when fetch fails\n * Defaults to DEFAULT_POLICY (allow all)\n */\n fallbackPolicy?: Partial<PolicyConfig>;\n\n /**\n * Custom blocked response\n */\n blockedResponse?: {\n status?: number;\n message?: string;\n headers?: Record<string, string>;\n };\n\n /**\n * Default redirect URL for redirect actions\n */\n redirectUrl?: string;\n\n /**\n * Callback when policy decision is made\n */\n onPolicyDecision?: (\n request: NextRequest,\n decision: PolicyEvaluationResult,\n context: PolicyEvaluationContext\n ) => void | Promise<void>;\n\n /**\n * Custom response builder for blocked requests\n */\n customBlockedResponse?: (\n request: NextRequest,\n decision: PolicyEvaluationResult\n ) => NextResponse | Promise<NextResponse>;\n\n /**\n * Whether to fail open (allow) on policy evaluation errors\n * Default: true (recommended for production)\n */\n failOpen?: boolean;\n\n /**\n * Enable debug logging\n */\n debug?: boolean;\n}\n\n/**\n * Combined middleware configuration with policy support\n */\nexport interface NextJSPolicyMiddlewareConfig extends PolicyMiddlewareConfig {\n /**\n * Paths to skip (in addition to policy excludedPaths)\n */\n skipPaths?: string[];\n\n /**\n * Only enforce on these paths (overrides policy includedPaths)\n */\n includePaths?: string[];\n}\n\n// ============================================================================\n// Policy Evaluation Helper\n// ============================================================================\n\n/**\n * Create policy evaluation context from detection result and request\n */\nexport function createContextFromDetection(\n detection: DetectionResult,\n request: NextRequest\n): PolicyEvaluationContext {\n return createEvaluationContext({\n agentType: detection.detectedAgent?.type,\n agentName: detection.detectedAgent?.name,\n agentVendor: detection.detectedAgent?.vendor,\n confidence: detection.confidence,\n riskLevel: detection.riskLevel,\n path: request.nextUrl.pathname,\n method: request.method,\n signatureVerified: detection.verificationMethod === 'signature',\n isAuthenticated: false, // TODO: integrate with auth\n userAgent: request.headers.get('user-agent') || undefined,\n });\n}\n\n/**\n * Evaluate policy for a detection result\n */\nexport function evaluatePolicyForDetection(\n detection: DetectionResult,\n request: NextRequest,\n policy: PolicyConfig\n): PolicyEvaluationResult {\n const context = createContextFromDetection(detection, request);\n return evaluatePolicy(policy, context);\n}\n\n// ============================================================================\n// Response Builders\n// ============================================================================\n\n/**\n * Build blocked response based on policy decision\n */\nexport function buildBlockedResponse(\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig\n): NextResponse {\n const status = config.blockedResponse?.status ?? 403;\n const message = config.blockedResponse?.message ?? decision.message ?? 'Access denied';\n\n const response = NextResponse.json(\n {\n error: message,\n code: 'POLICY_BLOCKED',\n reason: decision.reason,\n ruleId: decision.ruleId,\n matchType: decision.matchType,\n },\n { status }\n );\n\n // Add custom headers\n if (config.blockedResponse?.headers) {\n for (const [key, value] of Object.entries(config.blockedResponse.headers)) {\n response.headers.set(key, value);\n }\n }\n\n // Add AgentShield headers\n response.headers.set('KYA-Action', decision.action);\n response.headers.set('KYA-Reason', decision.reason);\n response.headers.set('KYA-Match-Type', decision.matchType);\n\n return response;\n}\n\n/**\n * Build redirect response based on policy decision\n */\nexport function buildRedirectResponse(\n request: NextRequest,\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig,\n detection?: { detectedAgent?: { name?: string } }\n): NextResponse {\n const redirectUrl = decision.redirectUrl || config.redirectUrl || '/blocked';\n const url = new URL(redirectUrl, request.url);\n\n // Add query params with policy info\n url.searchParams.set('reason', decision.reason);\n if (decision.ruleId) {\n url.searchParams.set('ruleId', decision.ruleId);\n }\n const agentName = detection?.detectedAgent?.name;\n if (agentName && !url.searchParams.has('agent')) {\n url.searchParams.set('agent', agentName.toLowerCase());\n }\n\n return NextResponse.redirect(url);\n}\n\n/**\n * Build challenge response (placeholder - future implementation)\n */\nexport function buildChallengeResponse(\n request: NextRequest,\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig,\n detection?: { detectedAgent?: { name?: string } }\n): NextResponse {\n // For now, treat challenge as redirect\n // Future: implement CAPTCHA, proof-of-work, etc.\n return buildRedirectResponse(request, decision, config, detection);\n}\n\n// ============================================================================\n// Policy Handler\n// ============================================================================\n\n/**\n * Handle policy decision and return appropriate response\n */\nexport async function handlePolicyDecision(\n request: NextRequest,\n decision: PolicyEvaluationResult,\n config: PolicyMiddlewareConfig,\n detection?: { detectedAgent?: { name?: string } }\n): Promise<NextResponse | null> {\n switch (decision.action) {\n case ENFORCEMENT_ACTIONS.BLOCK:\n if (config.customBlockedResponse) {\n return await config.customBlockedResponse(request, decision);\n }\n return buildBlockedResponse(decision, config);\n\n case ENFORCEMENT_ACTIONS.REDIRECT:\n return buildRedirectResponse(request, decision, config, detection);\n\n case ENFORCEMENT_ACTIONS.CHALLENGE:\n return buildChallengeResponse(request, decision, config, detection);\n\n case ENFORCEMENT_ACTIONS.LOG:\n // LOG action always logs - that's its purpose\n // (debug flag controls verbose debugging output, not LOG action behavior)\n console.log('[AgentShield] Policy decision (log):', {\n path: request.nextUrl.pathname,\n action: decision.action,\n reason: decision.reason,\n matchType: decision.matchType,\n ruleId: decision.ruleId,\n });\n return null; // Continue to allow\n\n case ENFORCEMENT_ACTIONS.ALLOW:\n default:\n return null; // Continue\n }\n}\n\n// ============================================================================\n// Policy Fetcher Integration\n// ============================================================================\n\n// Cache fetchers by config to avoid recreating them, but also support\n// different configurations (different apiUrl, apiKey, etc.)\nconst fetcherCache = new Map<string, PolicyFetcher>();\n\n/**\n * Generate a cache key for fetcher config.\n * Uses ?? to distinguish between explicit 0 and undefined values.\n */\nfunction getFetcherCacheKey(config: NonNullable<PolicyMiddlewareConfig['fetchPolicy']>): string {\n return `${config.apiUrl ?? 'default'}:${config.apiKey ?? ''}:${config.cacheTtlSeconds ?? 'default'}`;\n}\n\n/**\n * Get or create policy fetcher for the given config\n */\nfunction getPolicyFetcher(config: PolicyMiddlewareConfig['fetchPolicy']): PolicyFetcher {\n if (!config) {\n throw new Error('fetchPolicy config required');\n }\n\n const cacheKey = getFetcherCacheKey(config);\n let fetcher = fetcherCache.get(cacheKey);\n\n if (!fetcher) {\n const fetcherConfig: PolicyFetcherConfig = {\n apiBaseUrl: config.apiUrl || 'https://kya.vouched.id',\n apiKey: config.apiKey,\n cacheTtlSeconds: config.cacheTtlSeconds,\n };\n fetcher = createPolicyFetcher(fetcherConfig);\n fetcherCache.set(cacheKey, fetcher);\n }\n\n return fetcher;\n}\n\n/**\n * Get policy (local, fetched, or fallback)\n */\nexport async function getPolicy(config: PolicyMiddlewareConfig): Promise<PolicyConfig> {\n // Use local policy if provided\n if (config.policy) {\n return PolicyConfigSchema.parse({ ...DEFAULT_POLICY, ...config.policy });\n }\n\n // Fetch from API if configured\n if (config.fetchPolicy) {\n try {\n const fetcher = getPolicyFetcher(config.fetchPolicy);\n return await fetcher.getPolicy(config.fetchPolicy.projectId);\n } catch (error) {\n if (config.debug) {\n console.warn('[AgentShield] Policy fetch failed, using fallback:', error);\n }\n // Return fallback policy\n return PolicyConfigSchema.parse({\n ...DEFAULT_POLICY,\n ...(config.fallbackPolicy || {}),\n });\n }\n }\n\n // No policy configured - return default (allow all)\n return PolicyConfigSchema.parse(DEFAULT_POLICY);\n}\n\n// ============================================================================\n// Standalone Policy Middleware\n// ============================================================================\n\n/**\n * Apply policy to a detection result\n *\n * This function can be used standalone to evaluate policy after detection.\n * Supports extended config with skipPaths and includePaths for path-based filtering.\n *\n * @example\n * ```typescript\n * const result = await detector.analyze(context);\n * const response = await applyPolicy(request, result, {\n * policy: { thresholds: { confidenceThreshold: 80 } },\n * skipPaths: ['/health', '/api/public/*'],\n * includePaths: ['/api/*'],\n * });\n *\n * if (response) {\n * return response; // Policy blocked the request\n * }\n * ```\n */\nexport async function applyPolicy(\n request: NextRequest,\n detection: DetectionResult,\n config: NextJSPolicyMiddlewareConfig\n): Promise<NextResponse | null> {\n try {\n const path = request.nextUrl.pathname;\n\n // Check skipPaths - if path matches any skip pattern, allow through\n if (config.skipPaths?.some((pattern) => matchPath(path, pattern))) {\n return null; // Skip policy enforcement for this path\n }\n\n // Check includePaths - if defined, path must match at least one pattern\n if (config.includePaths && config.includePaths.length > 0) {\n if (!config.includePaths.some((pattern) => matchPath(path, pattern))) {\n return null; // Path not in included paths, skip policy enforcement\n }\n }\n\n // Get policy\n const policy = await getPolicy(config);\n\n // Create context and evaluate\n const context = createContextFromDetection(detection, request);\n const decision = evaluatePolicy(policy, context);\n\n // Call decision callback if provided\n if (config.onPolicyDecision) {\n await config.onPolicyDecision(request, decision, context);\n }\n\n // Handle decision — pass detection through so redirect can append ?agent=\n return await handlePolicyDecision(request, decision, config, detection);\n } catch (error) {\n if (config.debug) {\n console.error('[AgentShield] Policy evaluation error:', error);\n }\n\n if (config.failOpen !== false) {\n return null; // Allow on error\n }\n\n // Fail closed\n return NextResponse.json(\n { error: 'Security check failed', code: 'POLICY_ERROR' },\n { status: 503 }\n );\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/session-tracker.ts"],"names":["shouldEnforce"],"mappings":"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","file":"session-tracker.js","sourcesContent":["/**\n * Edge-compatible session tracking for AI agents\n * Uses cookie-based storage to work in Edge Runtime\n */\n\nimport type { NextRequest, NextResponse } from 'next/server';\nimport type { DetectionResult } from '@kya-os/checkpoint-shared';\nimport { shouldEnforce } from '@kya-os/checkpoint-shared';\n\nexport interface SessionData {\n id: string;\n agent: string;\n confidence: number;\n detectedAt: number;\n expires: number;\n}\n\nexport interface SessionTrackingConfig {\n enabled: boolean;\n cookieName?: string;\n cookieMaxAge?: number; // in seconds\n encryptionKey?: string;\n}\n\nexport class EdgeSessionTracker {\n private readonly config: Required<SessionTrackingConfig>;\n\n constructor(config: SessionTrackingConfig) {\n this.config = {\n enabled: config.enabled,\n cookieName: config.cookieName || '__agentshield_session',\n cookieMaxAge: config.cookieMaxAge || 3600, // 1 hour default\n encryptionKey:\n config.encryptionKey || process.env.AGENTSHIELD_SECRET || 'agentshield-default-key',\n };\n }\n\n /**\n * Track a new AI agent session\n */\n async track(\n _request: NextRequest,\n response: NextResponse,\n result: DetectionResult\n ): Promise<NextResponse> {\n try {\n if (!this.config.enabled || !shouldEnforce(result)) {\n return response;\n }\n\n const sessionData: SessionData = {\n id: crypto.randomUUID(),\n agent: result.detectedAgent?.name || 'unknown',\n confidence: result.confidence,\n detectedAt: Date.now(),\n expires: Date.now() + this.config.cookieMaxAge * 1000,\n };\n\n // Encrypt session data for security\n const encrypted = await this.encrypt(JSON.stringify(sessionData));\n\n // Set secure httpOnly cookie\n response.cookies.set(this.config.cookieName, encrypted, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: this.config.cookieMaxAge,\n path: '/',\n });\n\n return response;\n } catch (error) {\n // Fail gracefully - log error but don't break request\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to track session:', error);\n }\n return response;\n }\n }\n\n /**\n * Check for existing AI agent session\n */\n async check(request: NextRequest): Promise<SessionData | null> {\n try {\n if (!this.config.enabled) {\n return null;\n }\n\n const cookie = request.cookies.get(this.config.cookieName);\n if (!cookie?.value) {\n return null;\n }\n\n // Decrypt and parse session data\n const decrypted = await this.decrypt(cookie.value);\n const session: SessionData = JSON.parse(decrypted);\n\n // Check if session is expired\n if (session.expires < Date.now()) {\n return null;\n }\n\n return session;\n } catch (error) {\n // Fail gracefully - invalid or corrupted session\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to check session:', error);\n }\n return null;\n }\n }\n\n /**\n * Clear an existing session\n */\n clear(response: NextResponse): NextResponse {\n try {\n response.cookies.delete(this.config.cookieName);\n } catch (error) {\n // Fail gracefully\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to clear session:', error);\n }\n }\n return response;\n }\n\n /**\n * Simple encryption using Web Crypto API (Edge-compatible)\n */\n private async encrypt(data: string): Promise<string> {\n try {\n // For Edge Runtime, use simple base64 encoding with obfuscation\n // In production, consider using Web Crypto API subtle.encrypt()\n const key = this.config.encryptionKey;\n const encoded = new TextEncoder().encode(data);\n\n // Simple XOR obfuscation\n const obfuscated = new Uint8Array(encoded.length);\n for (let i = 0; i < encoded.length; i++) {\n obfuscated[i] = (encoded[i] || 0) ^ key.charCodeAt(i % key.length);\n }\n\n // Convert to base64\n return btoa(Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join(''));\n } catch (error) {\n // Fallback to simple base64 if encryption fails\n return btoa(data);\n }\n }\n\n /**\n * Simple decryption (Edge-compatible)\n */\n private async decrypt(data: string): Promise<string> {\n try {\n const key = this.config.encryptionKey;\n const decoded = Uint8Array.from(atob(data), (c) => c.charCodeAt(0));\n\n // Reverse XOR obfuscation\n const deobfuscated = new Uint8Array(decoded.length);\n for (let i = 0; i < decoded.length; i++) {\n deobfuscated[i] = (decoded[i] || 0) ^ key.charCodeAt(i % key.length);\n }\n\n return new TextDecoder().decode(deobfuscated);\n } catch (error) {\n // Fallback to simple base64 if decryption fails\n return atob(data);\n }\n }\n}\n\n/**\n * Stateless session checker for non-Next.js environments (Express, etc.)\n * Uses a combination of headers to identify continued sessions\n */\nexport class StatelessSessionChecker {\n static check(headers: Record<string, string>): SessionData | null {\n try {\n // Check for session headers (set by previous response)\n const agent = headers['kya-session-agent'];\n const confidence = headers['kya-session-confidence'];\n const sessionId = headers['kya-session-id'];\n\n if (agent && confidence && sessionId) {\n return {\n id: sessionId,\n agent,\n confidence: parseFloat(confidence),\n detectedAt: Date.now(),\n expires: Date.now() + 3600000, // 1 hour\n };\n }\n\n // Check for cookie-based session (if cookies are parsed)\n const cookieHeader = headers['cookie'];\n if (cookieHeader && cookieHeader.includes('__agentshield_session=')) {\n // Simple cookie parsing\n const match = cookieHeader.match(/__agentshield_session=([^;]+)/);\n if (match && match[1]) {\n try {\n const decoded = atob(match[1]);\n return JSON.parse(decoded);\n } catch {\n // Invalid session data\n }\n }\n }\n\n return null;\n } catch {\n return null;\n }\n }\n\n static setHeaders(response: any, session: SessionData): void {\n try {\n // Set session headers for stateless tracking\n if (response.setHeader) {\n response.setHeader('KYA-Session-Agent', session.agent);\n response.setHeader('KYA-Session-Confidence', session.confidence.toString());\n response.setHeader('KYA-Session-Id', session.id);\n } else if (response.headers && response.headers.set) {\n response.headers.set('kya-session-agent', session.agent);\n response.headers.set('kya-session-confidence', session.confidence.toString());\n response.headers.set('kya-session-id', session.id);\n }\n } catch {\n // Fail gracefully\n }\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/session-tracker.ts"],"names":[],"mappings":"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","file":"session-tracker.mjs","sourcesContent":["/**\n * Edge-compatible session tracking for AI agents\n * Uses cookie-based storage to work in Edge Runtime\n */\n\nimport type { NextRequest, NextResponse } from 'next/server';\nimport type { DetectionResult } from '@kya-os/checkpoint-shared';\nimport { shouldEnforce } from '@kya-os/checkpoint-shared';\n\nexport interface SessionData {\n id: string;\n agent: string;\n confidence: number;\n detectedAt: number;\n expires: number;\n}\n\nexport interface SessionTrackingConfig {\n enabled: boolean;\n cookieName?: string;\n cookieMaxAge?: number; // in seconds\n encryptionKey?: string;\n}\n\nexport class EdgeSessionTracker {\n private readonly config: Required<SessionTrackingConfig>;\n\n constructor(config: SessionTrackingConfig) {\n this.config = {\n enabled: config.enabled,\n cookieName: config.cookieName || '__agentshield_session',\n cookieMaxAge: config.cookieMaxAge || 3600, // 1 hour default\n encryptionKey:\n config.encryptionKey || process.env.AGENTSHIELD_SECRET || 'agentshield-default-key',\n };\n }\n\n /**\n * Track a new AI agent session\n */\n async track(\n _request: NextRequest,\n response: NextResponse,\n result: DetectionResult\n ): Promise<NextResponse> {\n try {\n if (!this.config.enabled || !shouldEnforce(result)) {\n return response;\n }\n\n const sessionData: SessionData = {\n id: crypto.randomUUID(),\n agent: result.detectedAgent?.name || 'unknown',\n confidence: result.confidence,\n detectedAt: Date.now(),\n expires: Date.now() + this.config.cookieMaxAge * 1000,\n };\n\n // Encrypt session data for security\n const encrypted = await this.encrypt(JSON.stringify(sessionData));\n\n // Set secure httpOnly cookie\n response.cookies.set(this.config.cookieName, encrypted, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: this.config.cookieMaxAge,\n path: '/',\n });\n\n return response;\n } catch (error) {\n // Fail gracefully - log error but don't break request\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to track session:', error);\n }\n return response;\n }\n }\n\n /**\n * Check for existing AI agent session\n */\n async check(request: NextRequest): Promise<SessionData | null> {\n try {\n if (!this.config.enabled) {\n return null;\n }\n\n const cookie = request.cookies.get(this.config.cookieName);\n if (!cookie?.value) {\n return null;\n }\n\n // Decrypt and parse session data\n const decrypted = await this.decrypt(cookie.value);\n const session: SessionData = JSON.parse(decrypted);\n\n // Check if session is expired\n if (session.expires < Date.now()) {\n return null;\n }\n\n return session;\n } catch (error) {\n // Fail gracefully - invalid or corrupted session\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to check session:', error);\n }\n return null;\n }\n }\n\n /**\n * Clear an existing session\n */\n clear(response: NextResponse): NextResponse {\n try {\n response.cookies.delete(this.config.cookieName);\n } catch (error) {\n // Fail gracefully\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to clear session:', error);\n }\n }\n return response;\n }\n\n /**\n * Simple encryption using Web Crypto API (Edge-compatible)\n */\n private async encrypt(data: string): Promise<string> {\n try {\n // For Edge Runtime, use simple base64 encoding with obfuscation\n // In production, consider using Web Crypto API subtle.encrypt()\n const key = this.config.encryptionKey;\n const encoded = new TextEncoder().encode(data);\n\n // Simple XOR obfuscation\n const obfuscated = new Uint8Array(encoded.length);\n for (let i = 0; i < encoded.length; i++) {\n obfuscated[i] = (encoded[i] || 0) ^ key.charCodeAt(i % key.length);\n }\n\n // Convert to base64\n return btoa(Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join(''));\n } catch (error) {\n // Fallback to simple base64 if encryption fails\n return btoa(data);\n }\n }\n\n /**\n * Simple decryption (Edge-compatible)\n */\n private async decrypt(data: string): Promise<string> {\n try {\n const key = this.config.encryptionKey;\n const decoded = Uint8Array.from(atob(data), (c) => c.charCodeAt(0));\n\n // Reverse XOR obfuscation\n const deobfuscated = new Uint8Array(decoded.length);\n for (let i = 0; i < decoded.length; i++) {\n deobfuscated[i] = (decoded[i] || 0) ^ key.charCodeAt(i % key.length);\n }\n\n return new TextDecoder().decode(deobfuscated);\n } catch (error) {\n // Fallback to simple base64 if decryption fails\n return atob(data);\n }\n }\n}\n\n/**\n * Stateless session checker for non-Next.js environments (Express, etc.)\n * Uses a combination of headers to identify continued sessions\n */\nexport class StatelessSessionChecker {\n static check(headers: Record<string, string>): SessionData | null {\n try {\n // Check for session headers (set by previous response)\n const agent = headers['kya-session-agent'];\n const confidence = headers['kya-session-confidence'];\n const sessionId = headers['kya-session-id'];\n\n if (agent && confidence && sessionId) {\n return {\n id: sessionId,\n agent,\n confidence: parseFloat(confidence),\n detectedAt: Date.now(),\n expires: Date.now() + 3600000, // 1 hour\n };\n }\n\n // Check for cookie-based session (if cookies are parsed)\n const cookieHeader = headers['cookie'];\n if (cookieHeader && cookieHeader.includes('__agentshield_session=')) {\n // Simple cookie parsing\n const match = cookieHeader.match(/__agentshield_session=([^;]+)/);\n if (match && match[1]) {\n try {\n const decoded = atob(match[1]);\n return JSON.parse(decoded);\n } catch {\n // Invalid session data\n }\n }\n }\n\n return null;\n } catch {\n return null;\n }\n }\n\n static setHeaders(response: any, session: SessionData): void {\n try {\n // Set session headers for stateless tracking\n if (response.setHeader) {\n response.setHeader('KYA-Session-Agent', session.agent);\n response.setHeader('KYA-Session-Confidence', session.confidence.toString());\n response.setHeader('KYA-Session-Id', session.id);\n } else if (response.headers && response.headers.set) {\n response.headers.set('kya-session-agent', session.agent);\n response.headers.set('kya-session-confidence', session.confidence.toString());\n response.headers.set('kya-session-id', session.id);\n }\n } catch {\n // Fail gracefully\n }\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/signature-verifier.ts"],"names":["ed25519","sha512","now"],"mappings":"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","file":"signature-verifier.js","sourcesContent":["/**\n * Ed25519 Signature Verification for HTTP Message Signatures\n * Implements proper cryptographic verification for ChatGPT and other agents\n *\n * Based on RFC 9421 (HTTP Message Signatures) and ChatGPT's implementation\n * Reference: https://help.openai.com/en/articles/9785974-chatgpt-user-allowlisting\n */\n\nimport * as ed25519 from '@noble/ed25519';\nimport { sha512 } from '@noble/hashes/sha2.js';\n\n// Configure @noble/ed25519 to use sync SHA-512 from @noble/hashes\n// This works in all environments including Edge Runtime\ned25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));\n\n/**\n * Known public keys for AI agents (fallback)\n *\n * IMPORTANT: These keys are used as fallback when the API is unavailable.\n * The primary source of keys should be the /api/internal/signature-keys endpoint\n * which fetches from https://chatgpt.com/.well-known/http-message-signatures-directory\n *\n * TODO: Implement automated key rotation by:\n * 1. Setting up a cron job to fetch from OpenAI's well-known endpoint\n * 2. Storing keys in database/KV store with proper expiration handling\n * 3. Removing hardcoded fallback keys entirely\n */\nconst KNOWN_KEYS = {\n chatgpt: [\n {\n kid: 'otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg',\n // ChatGPT's current Ed25519 public key (base64)\n // Source: https://chatgpt.com/.well-known/http-message-signatures-directory\n publicKey: '7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g',\n validFrom: 1735689600, // Jan 1, 2025 (nbf from OpenAI)\n validUntil: 1769029093, // Jan 21, 2026 (exp from OpenAI)\n },\n ],\n};\n\n/**\n * In-memory cache for API-fetched keys\n */\ninterface CachedKeys {\n keys: Array<{\n kid: string;\n publicKey: string;\n validFrom: number;\n validUntil: number;\n }>;\n cachedAt: number;\n}\n\nconst keyCache = new Map<string, CachedKeys>();\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\nconst CACHE_MAX_SIZE = 100; // Maximum cache entries before cleanup\n\n/**\n * Get API base URL for fetching keys\n * Returns absolute URL for server-side, relative for browser\n */\nfunction getApiBaseUrl(): string {\n if (typeof window !== 'undefined') {\n // Browser: use relative path\n return '/api/internal';\n }\n\n // Server-side: must use absolute URL\n // Try environment variables first\n const baseUrl =\n process.env.NEXT_PUBLIC_APP_URL ||\n process.env.NEXT_PUBLIC_API_URL ||\n process.env.API_URL ||\n (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : null);\n\n if (baseUrl) {\n return baseUrl.replace(/\\/$/, '') + '/api/internal';\n }\n\n // Fallback: try to construct from request context if available\n // For middleware/edge runtime, we may need to pass the request URL\n // For now, return relative path and log warning\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[Signature] No base URL configured for server-side fetch. Using localhost fallback.'\n );\n return 'http://localhost:3000/api/internal';\n }\n\n // Production fallback - should not reach here if properly configured\n console.error(\n '[Signature] CRITICAL: No base URL configured for server-side fetch in production!'\n );\n return '/api/internal'; // Will fail, but prevents silent success\n}\n\n/**\n * Clean up expired cache entries and enforce size limit\n * Called periodically to prevent unbounded memory growth\n * Uses LRU-style eviction: removes expired entries first, then oldest entries if still over limit\n */\nfunction cleanupExpiredCache(): void {\n const now = Date.now();\n const entriesToDelete: string[] = [];\n\n // First pass: remove expired entries\n for (const [agent, cached] of keyCache.entries()) {\n if (now - cached.cachedAt > CACHE_TTL_MS) {\n entriesToDelete.push(agent);\n }\n }\n\n for (const agent of entriesToDelete) {\n keyCache.delete(agent);\n }\n\n // Second pass: if still over limit, remove oldest entries (LRU eviction)\n if (keyCache.size > CACHE_MAX_SIZE) {\n // Convert entries to array with cachedAt timestamp for sorting\n const entries = Array.from(keyCache.entries()).map(([agent, cached]) => ({\n agent,\n cachedAt: cached.cachedAt,\n }));\n\n // Sort by cachedAt (oldest first)\n entries.sort((a, b) => a.cachedAt - b.cachedAt);\n\n // Remove oldest entries until we're under the limit\n const toRemove = entries.slice(0, keyCache.size - CACHE_MAX_SIZE);\n for (const entry of toRemove) {\n keyCache.delete(entry.agent);\n }\n }\n}\n\n/**\n * Fetch keys from API with caching\n */\nasync function fetchKeysFromApi(agent: string): Promise<Array<{\n kid: string;\n publicKey: string;\n validFrom: number;\n validUntil: number;\n}> | null> {\n // Periodic cleanup to prevent memory leaks\n if (keyCache.size > CACHE_MAX_SIZE) {\n cleanupExpiredCache();\n }\n\n // Check cache first\n const cached = keyCache.get(agent);\n if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {\n return cached.keys;\n }\n\n // Check if fetch is available (Edge Runtime compatibility)\n if (typeof fetch === 'undefined') {\n console.warn('[Signature] fetch() not available in this environment');\n return null;\n }\n\n try {\n const apiBaseUrl = getApiBaseUrl();\n const url = `${apiBaseUrl}/signature-keys?agent=${encodeURIComponent(agent)}`;\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n // 5 second timeout\n signal: AbortSignal.timeout(5000),\n });\n\n if (!response.ok) {\n console.warn(`[Signature] Failed to fetch keys from API: ${response.status}`);\n return null;\n }\n\n const data = await response.json();\n\n if (!data.keys || !Array.isArray(data.keys) || data.keys.length === 0) {\n console.warn(`[Signature] No keys returned from API for agent: ${agent}`);\n return null;\n }\n\n // Cache the result\n keyCache.set(agent, {\n keys: data.keys,\n cachedAt: Date.now(),\n });\n\n return data.keys;\n } catch (error) {\n console.warn('[Signature] Error fetching keys from API, using fallback', {\n error: error instanceof Error ? error.message : 'Unknown error',\n agent,\n });\n return null;\n }\n}\n\n/**\n * Type guard to check if agent is a valid key in KNOWN_KEYS\n */\nfunction isValidAgent(agent: string): agent is keyof typeof KNOWN_KEYS {\n return agent in KNOWN_KEYS;\n}\n\n/**\n * Get keys for an agent (API first, then fallback)\n */\nasync function getKeysForAgent(agent: string): Promise<\n Array<{\n kid: string;\n publicKey: string;\n validFrom: number;\n validUntil: number;\n }>\n> {\n // Try API first\n const apiKeys = await fetchKeysFromApi(agent);\n if (apiKeys && apiKeys.length > 0) {\n return apiKeys;\n }\n\n // Fallback to hardcoded keys with type guard\n if (isValidAgent(agent)) {\n return KNOWN_KEYS[agent];\n }\n\n return [];\n}\n\n/**\n * Parse the Signature-Input header according to RFC 9421\n */\nfunction parseSignatureInput(signatureInput: string): {\n keyid: string;\n created?: number | undefined;\n expires?: number | undefined;\n signedHeaders: string[];\n} | null {\n try {\n // Example: sig1=(\"@method\" \"@path\" \"@authority\" \"date\");keyid=\"...\";created=1234567890\n const match = signatureInput.match(/sig1=\\((.*?)\\);(.+)/);\n if (!match) return null;\n\n const [, headersList, params] = match;\n\n // Parse signed headers\n const signedHeaders = headersList\n ? headersList\n .split(' ')\n .map((h) => h.replace(/\"/g, '').trim())\n .filter((h) => h.length > 0)\n : [];\n\n // Parse parameters\n const keyidMatch = params ? params.match(/keyid=\"([^\"]+)\"/) : null;\n const createdMatch = params ? params.match(/created=(\\d+)/) : null;\n const expiresMatch = params ? params.match(/expires=(\\d+)/) : null;\n\n if (!keyidMatch || !keyidMatch[1]) return null;\n\n return {\n keyid: keyidMatch[1],\n created: createdMatch && createdMatch[1] ? parseInt(createdMatch[1]) : undefined,\n expires: expiresMatch && expiresMatch[1] ? parseInt(expiresMatch[1]) : undefined,\n signedHeaders,\n };\n } catch (error) {\n console.error('[Signature] Failed to parse Signature-Input:', error);\n return null;\n }\n}\n\n/**\n * Build the signature base string according to RFC 9421\n * This is what gets signed\n */\nfunction buildSignatureBase(\n method: string,\n path: string,\n headers: Record<string, string>,\n signedHeaders: string[]\n): string {\n const components: string[] = [];\n\n for (const headerName of signedHeaders) {\n let value: string;\n\n switch (headerName) {\n case '@method':\n value = method.toUpperCase();\n break;\n case '@path':\n value = path;\n break;\n case '@authority':\n // Get from Host header or URL\n value = headers['host'] || headers['Host'] || '';\n break;\n default: {\n // Regular headers (case-insensitive lookup)\n const key = Object.keys(headers).find((k) => k.toLowerCase() === headerName.toLowerCase());\n value = key ? headers[key] || '' : '';\n break;\n }\n }\n\n // Format according to RFC 9421\n components.push(`\"${headerName}\": ${value}`);\n }\n\n return components.join('\\n');\n}\n\n/**\n * Decode base64 (handles both standard and URL-safe variants)\n * URL-safe base64 uses - instead of + and _ instead of /\n */\nfunction base64ToBytes(base64: string): Uint8Array {\n // Convert URL-safe base64 to standard base64\n let standardBase64 = base64.replace(/-/g, '+').replace(/_/g, '/');\n\n // Add padding if needed\n const padding = standardBase64.length % 4;\n if (padding) {\n standardBase64 += '='.repeat(4 - padding);\n }\n\n const binaryString = atob(standardBase64);\n return Uint8Array.from(binaryString, (c) => c.charCodeAt(0));\n}\n\n/**\n * Verify Ed25519 signature using @noble/ed25519 (works in all environments including Edge Runtime)\n * Falls back to Web Crypto API if available\n */\nasync function verifyEd25519Signature(\n publicKeyBase64: string,\n signatureBase64: string,\n message: string\n): Promise<boolean> {\n try {\n // Decode base64 to Uint8Array (handles URL-safe base64)\n const publicKeyBytes = base64ToBytes(publicKeyBase64);\n const signatureBytes = base64ToBytes(signatureBase64);\n const messageBytes = new TextEncoder().encode(message);\n\n // Check key and signature lengths\n if (publicKeyBytes.length !== 32) {\n console.error('[Signature] Invalid public key length:', publicKeyBytes.length);\n return false;\n }\n if (signatureBytes.length !== 64) {\n console.error('[Signature] Invalid signature length:', signatureBytes.length);\n return false;\n }\n\n // Use @noble/ed25519 with sync SHA-512 - works in all environments including Edge Runtime\n return ed25519.verify(signatureBytes, messageBytes, publicKeyBytes);\n } catch (nobleError) {\n console.warn('[Signature] @noble/ed25519 failed, trying Web Crypto fallback:', nobleError);\n\n // Fallback to Web Crypto API (may not work in Edge Runtime)\n try {\n const publicKeyBytes = base64ToBytes(publicKeyBase64);\n const signatureBytes = base64ToBytes(signatureBase64);\n const messageBytes = new TextEncoder().encode(message);\n\n const publicKey = await crypto.subtle.importKey(\n 'raw',\n publicKeyBytes.buffer as ArrayBuffer,\n {\n name: 'Ed25519',\n namedCurve: 'Ed25519',\n },\n false,\n ['verify']\n );\n\n return await crypto.subtle.verify(\n 'Ed25519',\n publicKey,\n signatureBytes.buffer as ArrayBuffer,\n messageBytes\n );\n } catch (cryptoError) {\n console.error('[Signature] Both @noble/ed25519 and Web Crypto failed:', {\n nobleError: nobleError instanceof Error ? nobleError.message : 'Unknown',\n cryptoError: cryptoError instanceof Error ? cryptoError.message : 'Unknown',\n });\n return false;\n }\n }\n}\n\n/**\n * Signature verification result\n */\nexport interface SignatureVerificationResult {\n isValid: boolean;\n agent?: string;\n keyid?: string;\n confidence: number;\n reason?: string;\n verificationMethod: 'signature' | 'none';\n}\n\n/**\n * Verify HTTP Message Signature for AI agents\n */\nexport async function verifyAgentSignature(\n method: string,\n path: string,\n headers: Record<string, string>\n): Promise<SignatureVerificationResult> {\n // Check for signature headers\n const signature = headers['signature'] || headers['Signature'];\n const signatureInput = headers['signature-input'] || headers['Signature-Input'];\n const signatureAgent = headers['signature-agent'] || headers['Signature-Agent'];\n\n // No signature present\n if (!signature || !signatureInput) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'No signature headers present',\n verificationMethod: 'none',\n };\n }\n\n // Parse Signature-Input header\n const parsed = parseSignatureInput(signatureInput);\n if (!parsed) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Invalid Signature-Input header',\n verificationMethod: 'none',\n };\n }\n\n // Check timestamp if present\n if (parsed.created) {\n const now = Math.floor(Date.now() / 1000);\n const age = now - parsed.created;\n\n // Reject signatures older than 5 minutes\n if (age > 300) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Signature expired (older than 5 minutes)',\n verificationMethod: 'none',\n };\n }\n\n // Reject signatures from the future (clock skew tolerance: 30 seconds)\n if (age < -30) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Signature timestamp is in the future',\n verificationMethod: 'none',\n };\n }\n }\n\n // Determine which agent based on signature-agent header\n let agent: string | undefined;\n let agentKey: string | undefined;\n\n // Validate URL hostname to prevent bypass attacks (e.g., evil.com/chatgpt.com)\n const isChatGPT =\n signatureAgent === '\"https://chatgpt.com\"' ||\n (() => {\n try {\n const url = new URL(signatureAgent?.replace(/^\"|\"$/g, '') || '');\n return url.hostname === 'chatgpt.com' || url.hostname.endsWith('.chatgpt.com');\n } catch {\n return false;\n }\n })();\n\n if (isChatGPT) {\n agent = 'ChatGPT';\n agentKey = 'chatgpt';\n }\n // Add other agents here as needed\n\n if (!agent || !agentKey) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Unknown signature agent',\n verificationMethod: 'none',\n };\n }\n\n // Get keys (API first, then fallback)\n const knownKeys = await getKeysForAgent(agentKey);\n\n if (knownKeys.length === 0) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'No keys available for agent',\n verificationMethod: 'none',\n };\n }\n\n // Find the key by ID\n const key = knownKeys.find((k) => k.kid === parsed.keyid);\n if (!key) {\n return {\n isValid: false,\n confidence: 0,\n reason: `Unknown key ID: ${parsed.keyid}`,\n verificationMethod: 'none',\n };\n }\n\n // Check key validity period\n const now = Math.floor(Date.now() / 1000);\n if (now < key.validFrom || now > key.validUntil) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Key is not valid at current time',\n verificationMethod: 'none',\n };\n }\n\n // Build the signature base string\n const signatureBase = buildSignatureBase(method, path, headers, parsed.signedHeaders);\n\n // Extract the actual signature value (remove \"sig1=:\" prefix and \"::\" suffix if present)\n let signatureValue = signature;\n if (signatureValue.startsWith('sig1=:')) {\n signatureValue = signatureValue.substring(6);\n }\n if (signatureValue.endsWith(':')) {\n signatureValue = signatureValue.slice(0, -1);\n }\n\n // Verify the signature\n const isValid = await verifyEd25519Signature(key.publicKey, signatureValue, signatureBase);\n\n if (isValid) {\n return {\n isValid: true,\n agent,\n keyid: parsed.keyid,\n confidence: 1.0, // 100% confidence for valid signature\n verificationMethod: 'signature',\n };\n } else {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Signature verification failed',\n verificationMethod: 'none',\n };\n }\n}\n\n/**\n * Quick check if signature headers are present (for performance)\n */\nexport function hasSignatureHeaders(headers: Record<string, string>): boolean {\n return !!(\n (headers['signature'] || headers['Signature']) &&\n (headers['signature-input'] || headers['Signature-Input'])\n );\n}\n\n/**\n * Check if this is a ChatGPT signature based on headers\n * Uses secure URL parsing to prevent spoofing attacks\n */\nexport function isChatGPTSignature(headers: Record<string, string>): boolean {\n const signatureAgent = headers['signature-agent'] || headers['Signature-Agent'];\n\n if (!signatureAgent) {\n return false;\n }\n\n // Strip leading/trailing quotes if present\n const agentUrlStr = signatureAgent.replace(/^\"+|\"+$/g, '');\n\n // Exact match for the standard ChatGPT signature agent\n if (agentUrlStr === 'https://chatgpt.com') {\n return true;\n }\n\n // Parse URL and validate host to prevent spoofing\n try {\n const agentUrl = new URL(agentUrlStr);\n const allowedHosts = ['chatgpt.com', 'www.chatgpt.com'];\n return allowedHosts.includes(agentUrl.host);\n } catch {\n // Not a valid URL, return false for security\n return false;\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/signature-verifier.ts"],"names":["now"],"mappings":"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","file":"signature-verifier.mjs","sourcesContent":["/**\n * Ed25519 Signature Verification for HTTP Message Signatures\n * Implements proper cryptographic verification for ChatGPT and other agents\n *\n * Based on RFC 9421 (HTTP Message Signatures) and ChatGPT's implementation\n * Reference: https://help.openai.com/en/articles/9785974-chatgpt-user-allowlisting\n */\n\nimport * as ed25519 from '@noble/ed25519';\nimport { sha512 } from '@noble/hashes/sha2.js';\n\n// Configure @noble/ed25519 to use sync SHA-512 from @noble/hashes\n// This works in all environments including Edge Runtime\ned25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));\n\n/**\n * Known public keys for AI agents (fallback)\n *\n * IMPORTANT: These keys are used as fallback when the API is unavailable.\n * The primary source of keys should be the /api/internal/signature-keys endpoint\n * which fetches from https://chatgpt.com/.well-known/http-message-signatures-directory\n *\n * TODO: Implement automated key rotation by:\n * 1. Setting up a cron job to fetch from OpenAI's well-known endpoint\n * 2. Storing keys in database/KV store with proper expiration handling\n * 3. Removing hardcoded fallback keys entirely\n */\nconst KNOWN_KEYS = {\n chatgpt: [\n {\n kid: 'otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg',\n // ChatGPT's current Ed25519 public key (base64)\n // Source: https://chatgpt.com/.well-known/http-message-signatures-directory\n publicKey: '7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g',\n validFrom: 1735689600, // Jan 1, 2025 (nbf from OpenAI)\n validUntil: 1769029093, // Jan 21, 2026 (exp from OpenAI)\n },\n ],\n};\n\n/**\n * In-memory cache for API-fetched keys\n */\ninterface CachedKeys {\n keys: Array<{\n kid: string;\n publicKey: string;\n validFrom: number;\n validUntil: number;\n }>;\n cachedAt: number;\n}\n\nconst keyCache = new Map<string, CachedKeys>();\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\nconst CACHE_MAX_SIZE = 100; // Maximum cache entries before cleanup\n\n/**\n * Get API base URL for fetching keys\n * Returns absolute URL for server-side, relative for browser\n */\nfunction getApiBaseUrl(): string {\n if (typeof window !== 'undefined') {\n // Browser: use relative path\n return '/api/internal';\n }\n\n // Server-side: must use absolute URL\n // Try environment variables first\n const baseUrl =\n process.env.NEXT_PUBLIC_APP_URL ||\n process.env.NEXT_PUBLIC_API_URL ||\n process.env.API_URL ||\n (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : null);\n\n if (baseUrl) {\n return baseUrl.replace(/\\/$/, '') + '/api/internal';\n }\n\n // Fallback: try to construct from request context if available\n // For middleware/edge runtime, we may need to pass the request URL\n // For now, return relative path and log warning\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[Signature] No base URL configured for server-side fetch. Using localhost fallback.'\n );\n return 'http://localhost:3000/api/internal';\n }\n\n // Production fallback - should not reach here if properly configured\n console.error(\n '[Signature] CRITICAL: No base URL configured for server-side fetch in production!'\n );\n return '/api/internal'; // Will fail, but prevents silent success\n}\n\n/**\n * Clean up expired cache entries and enforce size limit\n * Called periodically to prevent unbounded memory growth\n * Uses LRU-style eviction: removes expired entries first, then oldest entries if still over limit\n */\nfunction cleanupExpiredCache(): void {\n const now = Date.now();\n const entriesToDelete: string[] = [];\n\n // First pass: remove expired entries\n for (const [agent, cached] of keyCache.entries()) {\n if (now - cached.cachedAt > CACHE_TTL_MS) {\n entriesToDelete.push(agent);\n }\n }\n\n for (const agent of entriesToDelete) {\n keyCache.delete(agent);\n }\n\n // Second pass: if still over limit, remove oldest entries (LRU eviction)\n if (keyCache.size > CACHE_MAX_SIZE) {\n // Convert entries to array with cachedAt timestamp for sorting\n const entries = Array.from(keyCache.entries()).map(([agent, cached]) => ({\n agent,\n cachedAt: cached.cachedAt,\n }));\n\n // Sort by cachedAt (oldest first)\n entries.sort((a, b) => a.cachedAt - b.cachedAt);\n\n // Remove oldest entries until we're under the limit\n const toRemove = entries.slice(0, keyCache.size - CACHE_MAX_SIZE);\n for (const entry of toRemove) {\n keyCache.delete(entry.agent);\n }\n }\n}\n\n/**\n * Fetch keys from API with caching\n */\nasync function fetchKeysFromApi(agent: string): Promise<Array<{\n kid: string;\n publicKey: string;\n validFrom: number;\n validUntil: number;\n}> | null> {\n // Periodic cleanup to prevent memory leaks\n if (keyCache.size > CACHE_MAX_SIZE) {\n cleanupExpiredCache();\n }\n\n // Check cache first\n const cached = keyCache.get(agent);\n if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {\n return cached.keys;\n }\n\n // Check if fetch is available (Edge Runtime compatibility)\n if (typeof fetch === 'undefined') {\n console.warn('[Signature] fetch() not available in this environment');\n return null;\n }\n\n try {\n const apiBaseUrl = getApiBaseUrl();\n const url = `${apiBaseUrl}/signature-keys?agent=${encodeURIComponent(agent)}`;\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n // 5 second timeout\n signal: AbortSignal.timeout(5000),\n });\n\n if (!response.ok) {\n console.warn(`[Signature] Failed to fetch keys from API: ${response.status}`);\n return null;\n }\n\n const data = await response.json();\n\n if (!data.keys || !Array.isArray(data.keys) || data.keys.length === 0) {\n console.warn(`[Signature] No keys returned from API for agent: ${agent}`);\n return null;\n }\n\n // Cache the result\n keyCache.set(agent, {\n keys: data.keys,\n cachedAt: Date.now(),\n });\n\n return data.keys;\n } catch (error) {\n console.warn('[Signature] Error fetching keys from API, using fallback', {\n error: error instanceof Error ? error.message : 'Unknown error',\n agent,\n });\n return null;\n }\n}\n\n/**\n * Type guard to check if agent is a valid key in KNOWN_KEYS\n */\nfunction isValidAgent(agent: string): agent is keyof typeof KNOWN_KEYS {\n return agent in KNOWN_KEYS;\n}\n\n/**\n * Get keys for an agent (API first, then fallback)\n */\nasync function getKeysForAgent(agent: string): Promise<\n Array<{\n kid: string;\n publicKey: string;\n validFrom: number;\n validUntil: number;\n }>\n> {\n // Try API first\n const apiKeys = await fetchKeysFromApi(agent);\n if (apiKeys && apiKeys.length > 0) {\n return apiKeys;\n }\n\n // Fallback to hardcoded keys with type guard\n if (isValidAgent(agent)) {\n return KNOWN_KEYS[agent];\n }\n\n return [];\n}\n\n/**\n * Parse the Signature-Input header according to RFC 9421\n */\nfunction parseSignatureInput(signatureInput: string): {\n keyid: string;\n created?: number | undefined;\n expires?: number | undefined;\n signedHeaders: string[];\n} | null {\n try {\n // Example: sig1=(\"@method\" \"@path\" \"@authority\" \"date\");keyid=\"...\";created=1234567890\n const match = signatureInput.match(/sig1=\\((.*?)\\);(.+)/);\n if (!match) return null;\n\n const [, headersList, params] = match;\n\n // Parse signed headers\n const signedHeaders = headersList\n ? headersList\n .split(' ')\n .map((h) => h.replace(/\"/g, '').trim())\n .filter((h) => h.length > 0)\n : [];\n\n // Parse parameters\n const keyidMatch = params ? params.match(/keyid=\"([^\"]+)\"/) : null;\n const createdMatch = params ? params.match(/created=(\\d+)/) : null;\n const expiresMatch = params ? params.match(/expires=(\\d+)/) : null;\n\n if (!keyidMatch || !keyidMatch[1]) return null;\n\n return {\n keyid: keyidMatch[1],\n created: createdMatch && createdMatch[1] ? parseInt(createdMatch[1]) : undefined,\n expires: expiresMatch && expiresMatch[1] ? parseInt(expiresMatch[1]) : undefined,\n signedHeaders,\n };\n } catch (error) {\n console.error('[Signature] Failed to parse Signature-Input:', error);\n return null;\n }\n}\n\n/**\n * Build the signature base string according to RFC 9421\n * This is what gets signed\n */\nfunction buildSignatureBase(\n method: string,\n path: string,\n headers: Record<string, string>,\n signedHeaders: string[]\n): string {\n const components: string[] = [];\n\n for (const headerName of signedHeaders) {\n let value: string;\n\n switch (headerName) {\n case '@method':\n value = method.toUpperCase();\n break;\n case '@path':\n value = path;\n break;\n case '@authority':\n // Get from Host header or URL\n value = headers['host'] || headers['Host'] || '';\n break;\n default: {\n // Regular headers (case-insensitive lookup)\n const key = Object.keys(headers).find((k) => k.toLowerCase() === headerName.toLowerCase());\n value = key ? headers[key] || '' : '';\n break;\n }\n }\n\n // Format according to RFC 9421\n components.push(`\"${headerName}\": ${value}`);\n }\n\n return components.join('\\n');\n}\n\n/**\n * Decode base64 (handles both standard and URL-safe variants)\n * URL-safe base64 uses - instead of + and _ instead of /\n */\nfunction base64ToBytes(base64: string): Uint8Array {\n // Convert URL-safe base64 to standard base64\n let standardBase64 = base64.replace(/-/g, '+').replace(/_/g, '/');\n\n // Add padding if needed\n const padding = standardBase64.length % 4;\n if (padding) {\n standardBase64 += '='.repeat(4 - padding);\n }\n\n const binaryString = atob(standardBase64);\n return Uint8Array.from(binaryString, (c) => c.charCodeAt(0));\n}\n\n/**\n * Verify Ed25519 signature using @noble/ed25519 (works in all environments including Edge Runtime)\n * Falls back to Web Crypto API if available\n */\nasync function verifyEd25519Signature(\n publicKeyBase64: string,\n signatureBase64: string,\n message: string\n): Promise<boolean> {\n try {\n // Decode base64 to Uint8Array (handles URL-safe base64)\n const publicKeyBytes = base64ToBytes(publicKeyBase64);\n const signatureBytes = base64ToBytes(signatureBase64);\n const messageBytes = new TextEncoder().encode(message);\n\n // Check key and signature lengths\n if (publicKeyBytes.length !== 32) {\n console.error('[Signature] Invalid public key length:', publicKeyBytes.length);\n return false;\n }\n if (signatureBytes.length !== 64) {\n console.error('[Signature] Invalid signature length:', signatureBytes.length);\n return false;\n }\n\n // Use @noble/ed25519 with sync SHA-512 - works in all environments including Edge Runtime\n return ed25519.verify(signatureBytes, messageBytes, publicKeyBytes);\n } catch (nobleError) {\n console.warn('[Signature] @noble/ed25519 failed, trying Web Crypto fallback:', nobleError);\n\n // Fallback to Web Crypto API (may not work in Edge Runtime)\n try {\n const publicKeyBytes = base64ToBytes(publicKeyBase64);\n const signatureBytes = base64ToBytes(signatureBase64);\n const messageBytes = new TextEncoder().encode(message);\n\n const publicKey = await crypto.subtle.importKey(\n 'raw',\n publicKeyBytes.buffer as ArrayBuffer,\n {\n name: 'Ed25519',\n namedCurve: 'Ed25519',\n },\n false,\n ['verify']\n );\n\n return await crypto.subtle.verify(\n 'Ed25519',\n publicKey,\n signatureBytes.buffer as ArrayBuffer,\n messageBytes\n );\n } catch (cryptoError) {\n console.error('[Signature] Both @noble/ed25519 and Web Crypto failed:', {\n nobleError: nobleError instanceof Error ? nobleError.message : 'Unknown',\n cryptoError: cryptoError instanceof Error ? cryptoError.message : 'Unknown',\n });\n return false;\n }\n }\n}\n\n/**\n * Signature verification result\n */\nexport interface SignatureVerificationResult {\n isValid: boolean;\n agent?: string;\n keyid?: string;\n confidence: number;\n reason?: string;\n verificationMethod: 'signature' | 'none';\n}\n\n/**\n * Verify HTTP Message Signature for AI agents\n */\nexport async function verifyAgentSignature(\n method: string,\n path: string,\n headers: Record<string, string>\n): Promise<SignatureVerificationResult> {\n // Check for signature headers\n const signature = headers['signature'] || headers['Signature'];\n const signatureInput = headers['signature-input'] || headers['Signature-Input'];\n const signatureAgent = headers['signature-agent'] || headers['Signature-Agent'];\n\n // No signature present\n if (!signature || !signatureInput) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'No signature headers present',\n verificationMethod: 'none',\n };\n }\n\n // Parse Signature-Input header\n const parsed = parseSignatureInput(signatureInput);\n if (!parsed) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Invalid Signature-Input header',\n verificationMethod: 'none',\n };\n }\n\n // Check timestamp if present\n if (parsed.created) {\n const now = Math.floor(Date.now() / 1000);\n const age = now - parsed.created;\n\n // Reject signatures older than 5 minutes\n if (age > 300) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Signature expired (older than 5 minutes)',\n verificationMethod: 'none',\n };\n }\n\n // Reject signatures from the future (clock skew tolerance: 30 seconds)\n if (age < -30) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Signature timestamp is in the future',\n verificationMethod: 'none',\n };\n }\n }\n\n // Determine which agent based on signature-agent header\n let agent: string | undefined;\n let agentKey: string | undefined;\n\n // Validate URL hostname to prevent bypass attacks (e.g., evil.com/chatgpt.com)\n const isChatGPT =\n signatureAgent === '\"https://chatgpt.com\"' ||\n (() => {\n try {\n const url = new URL(signatureAgent?.replace(/^\"|\"$/g, '') || '');\n return url.hostname === 'chatgpt.com' || url.hostname.endsWith('.chatgpt.com');\n } catch {\n return false;\n }\n })();\n\n if (isChatGPT) {\n agent = 'ChatGPT';\n agentKey = 'chatgpt';\n }\n // Add other agents here as needed\n\n if (!agent || !agentKey) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Unknown signature agent',\n verificationMethod: 'none',\n };\n }\n\n // Get keys (API first, then fallback)\n const knownKeys = await getKeysForAgent(agentKey);\n\n if (knownKeys.length === 0) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'No keys available for agent',\n verificationMethod: 'none',\n };\n }\n\n // Find the key by ID\n const key = knownKeys.find((k) => k.kid === parsed.keyid);\n if (!key) {\n return {\n isValid: false,\n confidence: 0,\n reason: `Unknown key ID: ${parsed.keyid}`,\n verificationMethod: 'none',\n };\n }\n\n // Check key validity period\n const now = Math.floor(Date.now() / 1000);\n if (now < key.validFrom || now > key.validUntil) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Key is not valid at current time',\n verificationMethod: 'none',\n };\n }\n\n // Build the signature base string\n const signatureBase = buildSignatureBase(method, path, headers, parsed.signedHeaders);\n\n // Extract the actual signature value (remove \"sig1=:\" prefix and \"::\" suffix if present)\n let signatureValue = signature;\n if (signatureValue.startsWith('sig1=:')) {\n signatureValue = signatureValue.substring(6);\n }\n if (signatureValue.endsWith(':')) {\n signatureValue = signatureValue.slice(0, -1);\n }\n\n // Verify the signature\n const isValid = await verifyEd25519Signature(key.publicKey, signatureValue, signatureBase);\n\n if (isValid) {\n return {\n isValid: true,\n agent,\n keyid: parsed.keyid,\n confidence: 1.0, // 100% confidence for valid signature\n verificationMethod: 'signature',\n };\n } else {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Signature verification failed',\n verificationMethod: 'none',\n };\n }\n}\n\n/**\n * Quick check if signature headers are present (for performance)\n */\nexport function hasSignatureHeaders(headers: Record<string, string>): boolean {\n return !!(\n (headers['signature'] || headers['Signature']) &&\n (headers['signature-input'] || headers['Signature-Input'])\n );\n}\n\n/**\n * Check if this is a ChatGPT signature based on headers\n * Uses secure URL parsing to prevent spoofing attacks\n */\nexport function isChatGPTSignature(headers: Record<string, string>): boolean {\n const signatureAgent = headers['signature-agent'] || headers['Signature-Agent'];\n\n if (!signatureAgent) {\n return false;\n }\n\n // Strip leading/trailing quotes if present\n const agentUrlStr = signatureAgent.replace(/^\"+|\"+$/g, '');\n\n // Exact match for the standard ChatGPT signature agent\n if (agentUrlStr === 'https://chatgpt.com') {\n return true;\n }\n\n // Parse URL and validate host to prevent spoofing\n try {\n const agentUrl = new URL(agentUrlStr);\n const allowedHosts = ['chatgpt.com', 'www.chatgpt.com'];\n return allowedHosts.includes(agentUrl.host);\n } catch {\n // Not a valid URL, return false for security\n return false;\n }\n}\n"]}
|