@kya-os/checkpoint-nextjs 1.2.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +159 -0
  2. package/EDGE_RUNTIME_WASM_SETUP.md +1 -1
  3. package/bin/setup-edge-wasm.js +1 -1
  4. package/dist/api-middleware.d.mts +9 -1
  5. package/dist/api-middleware.d.ts +9 -1
  6. package/dist/api-middleware.js +14 -4
  7. package/dist/api-middleware.mjs +15 -5
  8. package/dist/composed-policy.d.mts +115 -0
  9. package/dist/composed-policy.d.ts +115 -0
  10. package/dist/composed-policy.js +102 -0
  11. package/dist/composed-policy.mjs +96 -0
  12. package/dist/config-DAwIA4DB.d.mts +214 -0
  13. package/dist/config-DyU4l5er.d.ts +214 -0
  14. package/dist/create-middleware.js +0 -2
  15. package/dist/create-middleware.mjs +0 -2
  16. package/dist/edge-runtime-loader.js +3 -1
  17. package/dist/edge-runtime-loader.mjs +3 -1
  18. package/dist/edge-wasm-middleware.d.mts +6 -6
  19. package/dist/edge-wasm-middleware.d.ts +6 -6
  20. package/dist/index.d.mts +6 -14
  21. package/dist/index.d.ts +6 -14
  22. package/dist/index.js +191 -13
  23. package/dist/index.mjs +192 -14
  24. package/dist/middleware-edge.d.mts +7 -3
  25. package/dist/middleware-edge.d.ts +7 -3
  26. package/dist/middleware-edge.js +174 -4
  27. package/dist/middleware-edge.mjs +171 -4
  28. package/dist/middleware-node.d.mts +39 -116
  29. package/dist/middleware-node.d.ts +39 -116
  30. package/dist/middleware-node.js +181 -4
  31. package/dist/middleware-node.mjs +178 -5
  32. package/dist/middleware.d.mts +10 -1
  33. package/dist/middleware.d.ts +10 -1
  34. package/dist/middleware.js +6 -0
  35. package/dist/middleware.mjs +6 -1
  36. package/dist/nodejs-wasm-loader.d.mts +3 -4
  37. package/dist/nodejs-wasm-loader.d.ts +3 -4
  38. package/dist/nodejs-wasm-loader.js +1 -1
  39. package/dist/nodejs-wasm-loader.mjs +1 -1
  40. package/dist/wasm-setup.js +1 -1
  41. package/dist/wasm-setup.mjs +1 -1
  42. package/package.json +4 -9
  43. package/dist/.tsbuildinfo +0 -1
  44. package/dist/signature-verifier.d.mts +0 -33
  45. package/dist/signature-verifier.d.ts +0 -33
  46. package/dist/signature-verifier.js +0 -384
  47. package/dist/signature-verifier.mjs +0 -360
  48. package/dist/wasm-middleware.d.mts +0 -98
  49. package/dist/wasm-middleware.d.ts +0 -98
  50. package/dist/wasm-middleware.js +0 -125
  51. package/dist/wasm-middleware.mjs +0 -121
  52. package/templates/middleware-wasm-100.ts +0 -161
@@ -1,113 +1,12 @@
1
+ import * as _kya_os_checkpoint_wasm_runtime_engine from '@kya-os/checkpoint-wasm-runtime/engine';
1
2
  import * as _kya_os_checkpoint_wasm_runtime_adapters from '@kya-os/checkpoint-wasm-runtime/adapters';
2
- import { DidResolverAdapter, StatusListCacheAdapter, ReputationOracleAdapter, PolicyEvaluatorAdapter } from '@kya-os/checkpoint-wasm-runtime/adapters';
3
- import { NextRequest, NextResponse } from 'next/server';
4
- import { EnforcementMode, VerifyResult, EngineConfig } from '@kya-os/checkpoint-wasm-runtime/engine';
3
+ import { NextRequest, NextFetchEvent, NextResponse } from 'next/server';
4
+ import { DetectionReporter, ReporterContext } from '@kya-os/checkpoint-wasm-runtime/reporter';
5
+ import { C as CheckpointConfig } from './config-DyU4l5er.js';
6
+ import { ComposedPolicyContext, TrustedDelegationRootsResolver } from './composed-policy.js';
7
+ import '@kya-os/checkpoint-shared';
8
+ import '@kya-os/checkpoint-wasm-runtime/composed-policy';
5
9
 
6
- /**
7
- * Configuration for `withCheckpoint`.
8
- *
9
- * The new minimal shape Phase D's middleware needs. Legacy
10
- * `AgentShieldMiddlewareConfig` (from `./api-middleware`) remains
11
- * exported during the deprecation window — see D.4 cutover.
12
- */
13
- interface CheckpointConfig {
14
- /**
15
- * Tenant identifier — typically the customer's dashboard hostname
16
- * (e.g. `acme.checkpoint.example`). The PolicyEvaluator uses this
17
- * to look up tenant policy from the dashboard.
18
- */
19
- tenantHost: string;
20
- /**
21
- * `'enforce'` (default) blocks; `'observe'` passes everything
22
- * through with `X-Checkpoint-Would-Have-Been` headers. Per Phase 0.2.
23
- */
24
- enforcementMode?: EnforcementMode;
25
- /**
26
- * Argus reputation oracle base URL. Omit to use the trust-by-default
27
- * baseline (reputation defaults to 1.0; orchestrator logs a one-shot
28
- * warning at first request).
29
- */
30
- argusUrl?: string;
31
- /**
32
- * Dashboard base URL for the PolicyEvaluator to fetch tenant policy
33
- * from. Omit to use the open-by-default tenant policy.
34
- */
35
- dashboardUrl?: string;
36
- /**
37
- * Returned to the PolicyEvaluator for anonymous requests (no agent
38
- * DID). Default 1.0 (trust-by-default).
39
- */
40
- reputationBaseline?: number;
41
- /**
42
- * Pre-built adapter instances. Production deployments use the
43
- * factory-built defaults from `@kya-os/checkpoint-wasm-runtime/adapters`;
44
- * tests use stubs. The factory composes any provided overrides over
45
- * defaults — partial overrides are supported.
46
- */
47
- adapters?: Partial<{
48
- didResolver: DidResolverAdapter;
49
- statusListCache: StatusListCacheAdapter;
50
- reputationOracle: ReputationOracleAdapter;
51
- policyEvaluator: PolicyEvaluatorAdapter;
52
- }>;
53
- /**
54
- * Optional callback for the post-verdict path — fires after every
55
- * verification, regardless of permit/block, with the full
56
- * `VerifyResult`. Use for logging, dashboards, telemetry. Errors
57
- * thrown here are swallowed so user code can't break the middleware
58
- * response.
59
- */
60
- onResult?: (result: VerifyResult, req: NextRequest) => void | Promise<void>;
61
- /**
62
- * Accept legacy `KYA-Delegation`-header envelope form alongside the
63
- * canonical `_meta.proof.jws` body form. Default `false`.
64
- *
65
- * **When to enable** — customers whose agents pre-date Envelope-1
66
- * (#2537) and ship MCP-I proofs as `{protected,payload,signature}`
67
- * JSON in a `KYA-Delegation` HTTP header. Post-Envelope-1 agents
68
- * ship compact JWS in the request body's `_meta.proof.jws` field;
69
- * those don't need this flag.
70
- *
71
- * Forwarded to the orchestrator's `VerifyRequestOpts.legacyEnvelopeFallback`.
72
- * Both transports (header + body) are honored when this is `true`;
73
- * the orchestrator's detection order is body first, then header
74
- * (`packages/checkpoint-wasm-runtime/src/engine/orchestrator/build-agent-request.ts`).
75
- *
76
- * SDK-Envelope-Plumbing-1 (#2594). Added in `@kya-os/checkpoint-nextjs@1.1.0`.
77
- */
78
- legacyEnvelopeFallback?: boolean;
79
- /**
80
- * Read the request body when `content-type` is `application/json` so
81
- * the orchestrator can extract an MCP-I envelope from
82
- * `_meta.proof.jws`. Default `true`.
83
- *
84
- * **When to disable** — streaming middlewares that can't tolerate
85
- * the `req.clone()` memory overhead (one full-body copy is buffered
86
- * during the read). For those, set `false` and route MCP-I
87
- * envelopes through the `KYA-Delegation` header transport instead
88
- * (requires `legacyEnvelopeFallback: true`).
89
- *
90
- * The clone preserves `req.body` for downstream handlers — disabling
91
- * is a performance optimization, not a correctness fix.
92
- *
93
- * SDK-Envelope-Plumbing-1 (#2594). Added in `@kya-os/checkpoint-nextjs@1.1.0`.
94
- */
95
- drainJsonBody?: boolean;
96
- /**
97
- * Engine-default behaviour knobs forwarded to every composed
98
- * `ContextSpec`. Defaults to `{ tier3Action: 'monitor' }` —
99
- * customer-onboarding-safe (tenant policy decides; engine doesn't
100
- * short-circuit known-agent UAs with an engine-default Block).
101
- *
102
- * Opt into `{ tier3Action: 'block' }` when the host wants the
103
- * calibrated engine-default block for KnownAiAgent / AiCrawler /
104
- * HeadlessBrowser classifications BEFORE the tenant policy seam.
105
- *
106
- * Added in `@kya-os/checkpoint-nextjs@1.2.0` (Engine-Tier3-Monitor-
107
- * Default, #2653 + this PR's plumbing follow-up).
108
- */
109
- engineConfig?: EngineConfig;
110
- }
111
10
  /**
112
11
  * Build the Checkpoint middleware. Returns a function `(req) => NextResponse`
113
12
  * suitable for `export default withCheckpoint({...})` in `middleware.ts`.
@@ -117,24 +16,48 @@ interface CheckpointConfig {
117
16
  * `verifyRequest`, and translates the verdict to `NextResponse`. No
118
17
  * verification logic lives in this file.
119
18
  */
120
- declare function withCheckpoint(config: CheckpointConfig): (req: NextRequest) => Promise<NextResponse>;
19
+ declare function withCheckpoint(config: CheckpointConfig): (req: NextRequest, event?: NextFetchEvent) => Promise<NextResponse>;
20
+ /**
21
+ * Installed SDK version, self-reported to the detection reporter so the
22
+ * dashboard can version-gate "composed policy enforces here". MUST equal
23
+ * `package.json` version — pinned by a unit test (the old hardcoded `0.1.0` had
24
+ * drifted). Re-exported as `VERSION` from the package index.
25
+ */
26
+ declare const VERSION = "1.7.1";
27
+ declare function buildReporter(config: CheckpointConfig, runtime?: 'node' | 'edge'): DetectionReporter | null;
28
+ /**
29
+ * Build the per-factory trusted-delegation-roots resolver (P2 / DR-1). Root
30
+ * pinning is a security concern INDEPENDENT of composed Cedar enforcement, so it
31
+ * must resolve wherever a `projectId` exists — including Edge setups that never
32
+ * wire `cedarWasmModule` (where `buildComposedContext` returns null). Prefers the
33
+ * composed context's accessor (reuses its cached fetcher / honors an injected
34
+ * enforcer); otherwise falls back to a `projectId`-only policy fetch. Returns
35
+ * `null` when no source is configured (no roots → engine open default).
36
+ */
37
+ declare function buildTrustedRootsResolver(config: CheckpointConfig, composed: ComposedPolicyContext | null): TrustedDelegationRootsResolver | null;
38
+ /**
39
+ * Pull the request context the dashboard's `/api/v1/log-detection`
40
+ * endpoint expects out of a `NextRequest`. Works under both Node and
41
+ * Edge runtimes — no runtime-specific APIs.
42
+ */
43
+ declare function extractReporterContext(req: NextRequest): ReporterContext;
121
44
  /**
122
45
  * Compose adapter defaults with caller-supplied overrides. Factored
123
46
  * out so the Edge entry (which uses the same composition) can reuse
124
47
  * the shape.
125
48
  */
126
49
  declare function buildVerifyOpts(config: CheckpointConfig): {
127
- didResolver: DidResolverAdapter;
128
- statusListCache: StatusListCacheAdapter;
129
- reputationOracle: ReputationOracleAdapter;
130
- policyEvaluator: PolicyEvaluatorAdapter;
50
+ didResolver: _kya_os_checkpoint_wasm_runtime_adapters.DidResolverAdapter;
51
+ statusListCache: _kya_os_checkpoint_wasm_runtime_adapters.StatusListCacheAdapter;
52
+ reputationOracle: _kya_os_checkpoint_wasm_runtime_adapters.ReputationOracleAdapter;
53
+ policyEvaluator: _kya_os_checkpoint_wasm_runtime_adapters.PolicyEvaluatorAdapter;
131
54
  clock: _kya_os_checkpoint_wasm_runtime_adapters.ClockAdapter;
132
55
  tenantHost: string;
133
- enforcementMode: EnforcementMode;
56
+ enforcementMode: _kya_os_checkpoint_wasm_runtime_engine.EnforcementMode;
134
57
  reputationBaseline: number | undefined;
135
58
  argusUrl: string | undefined;
136
59
  legacyEnvelopeFallback: boolean;
137
- engineConfig: EngineConfig | undefined;
60
+ engineConfig: _kya_os_checkpoint_wasm_runtime_engine.EngineConfig | undefined;
138
61
  };
139
62
 
140
- export { type CheckpointConfig, buildVerifyOpts as _buildVerifyOpts, withCheckpoint };
63
+ export { CheckpointConfig, VERSION, buildReporter as _buildReporter, buildTrustedRootsResolver as _buildTrustedRootsResolver, buildVerifyOpts as _buildVerifyOpts, extractReporterContext as _extractReporterContext, withCheckpoint };
@@ -2,8 +2,10 @@
2
2
 
3
3
  var orchestrator = require('@kya-os/checkpoint-wasm-runtime/orchestrator');
4
4
  var adapters = require('@kya-os/checkpoint-wasm-runtime/adapters');
5
- var server = require('next/server');
5
+ var reporter = require('@kya-os/checkpoint-wasm-runtime/reporter');
6
6
  var checkpointShared = require('@kya-os/checkpoint-shared');
7
+ var server = require('next/server');
8
+ var composedPolicy = require('@kya-os/checkpoint-wasm-runtime/composed-policy');
7
9
 
8
10
  // src/middleware-node.ts
9
11
  function adaptToNextResponse(rendered, req) {
@@ -54,6 +56,96 @@ function applyHeaders(res, headers) {
54
56
  res.headers.set(key, value);
55
57
  }
56
58
  }
59
+ var DEFAULT_DASHBOARD_URL = "https://kya.vouched.id";
60
+ var NOOP_LOGGER = {
61
+ shadowDivergence: () => {
62
+ },
63
+ evaluationError: () => {
64
+ }
65
+ };
66
+ function makeComposedPolicyContext(opts) {
67
+ const { projectId, fetcher } = opts;
68
+ const cache = composedPolicy.makeComposedPolicyCache({ compile: opts.compile, cacheMax: opts.cacheMax });
69
+ const logger = opts.logger ?? NOOP_LOGGER;
70
+ return {
71
+ async apply(result, path) {
72
+ const structured = { decision: result.decision, acted: false };
73
+ const policy = await fetcher.getPolicy(projectId);
74
+ const outcome = await composedPolicy.evaluateComposedPolicy({
75
+ cache,
76
+ projectId,
77
+ flags: {
78
+ policyLanguage: policy.policyLanguage,
79
+ policySourceText: policy.policySourceText,
80
+ engineEnforcementEnabled: policy.engineEnforcementEnabled,
81
+ enabled: policy.enabled
82
+ },
83
+ authorizeInput: composedPolicy.verifyResultToAuthorizeInput(result, { tenantId: projectId, path }),
84
+ baselineDecisionKind: result.decision.kind
85
+ });
86
+ if ((outcome.status === "acting" || outcome.status === "shadow") && outcome.diverged) {
87
+ logger.shadowDivergence({
88
+ projectId,
89
+ path,
90
+ engineDecision: outcome.engineDecision.kind,
91
+ structuredDecision: result.decision.kind,
92
+ detectionClass: result.detectionDetail.detectionClass.type,
93
+ verificationMethod: result.detectionDetail.verificationMethod,
94
+ confidence: result.detectionDetail.confidence,
95
+ agentName: result.detectionDetail.detectedAgent?.name
96
+ });
97
+ }
98
+ if (outcome.status === "error") {
99
+ logger.evaluationError(projectId, outcome.error);
100
+ return structured;
101
+ }
102
+ if (outcome.status === "acting") {
103
+ return {
104
+ decision: outcome.engineDecision,
105
+ acted: true,
106
+ // Echo the SSOT revision pin with the decision it produced (#3246
107
+ // PR5). Conditional spread: absent upstream stays absent here.
108
+ ...policy.policySourceHash ? { composedBlobHash: policy.policySourceHash } : {}
109
+ };
110
+ }
111
+ return structured;
112
+ },
113
+ async trustedDelegationRoots() {
114
+ const policy = await fetcher.getPolicy(projectId);
115
+ return policy.trustedDelegationRoots ?? [];
116
+ }
117
+ };
118
+ }
119
+ async function resolveTrustedDelegationRootsForRequest(resolver, headers) {
120
+ if (!resolver) return void 0;
121
+ if (!checkpointShared.requestCarriesDelegationProof(headers)) return void 0;
122
+ const roots = await resolver();
123
+ return roots.length > 0 ? roots : void 0;
124
+ }
125
+ async function applyComposedPolicy(context, result, path) {
126
+ if (!context) return;
127
+ try {
128
+ const outcome = await context.apply(result, path);
129
+ if (outcome.acted) {
130
+ result.decision = outcome.decision;
131
+ if (outcome.composedBlobHash) {
132
+ result.composedBlobHash = outcome.composedBlobHash;
133
+ }
134
+ }
135
+ } catch {
136
+ }
137
+ }
138
+ var consoleComposedPolicyLogger = {
139
+ shadowDivergence(info) {
140
+ console.warn("[checkpoint/composed-policy] shadow-divergence", info);
141
+ },
142
+ evaluationError(projectId, error) {
143
+ console.error(
144
+ `[checkpoint/composed-policy] evaluation failed for ${projectId}; using structured decision:`,
145
+ error
146
+ );
147
+ }
148
+ };
57
149
 
58
150
  // src/translate.ts
59
151
  async function nextRequestToHttpLike(req, opts = {}) {
@@ -98,15 +190,96 @@ function extractRemoteAddress(req) {
98
190
  // src/middleware-node.ts
99
191
  function withCheckpoint(config) {
100
192
  const opts = buildVerifyOpts(config);
193
+ const reporter = buildReporter(config);
194
+ const composed = buildComposedContext(config);
195
+ const trustedRootsResolver = buildTrustedRootsResolver(config, composed);
101
196
  const translateOpts = { drainJsonBody: config.drainJsonBody };
102
- return async function checkpointMiddleware(req) {
197
+ return async function checkpointMiddleware(req, event) {
103
198
  const httpLike = await nextRequestToHttpLike(req, translateOpts);
104
- const result = await orchestrator.verifyRequest(httpLike, opts);
199
+ const trustedDelegationRoots = await resolveTrustedDelegationRootsForRequest(
200
+ trustedRootsResolver,
201
+ req.headers
202
+ );
203
+ const result = await orchestrator.verifyRequest(
204
+ httpLike,
205
+ trustedDelegationRoots ? { ...opts, trustedDelegationRoots } : opts
206
+ );
207
+ await applyComposedPolicy(composed, result, req.nextUrl.pathname);
208
+ if (reporter) {
209
+ const reportPromise = reporter(result, extractReporterContext(req));
210
+ if (event) {
211
+ event.waitUntil(reportPromise);
212
+ }
213
+ }
105
214
  await dispatchOnResult(config, result, req);
106
- const rendered = orchestrator.renderDecisionAsResponse(result);
215
+ const bodyReadable200 = checkpointShared.selectBodyReadable200(
216
+ config.delegationChallengeMode ?? "spec-401",
217
+ req.headers,
218
+ result.detectionDetail.detectionClass
219
+ );
220
+ const rendered = orchestrator.renderDecisionAsResponse(result, { bodyReadable200 });
107
221
  return adaptToNextResponse(rendered, req);
108
222
  };
109
223
  }
224
+ var SDK_NAME = "@kya-os/checkpoint-nextjs";
225
+ var VERSION = "1.7.1";
226
+ function buildReporter(config, runtime = "node") {
227
+ if (!config.apiKey) return null;
228
+ return reporter.makeDetectionReporter({
229
+ apiKey: config.apiKey,
230
+ baseUrl: config.baseUrl,
231
+ debug: config.debug,
232
+ // Self-identify (incl. node-vs-edge) so the dashboard can version-gate
233
+ // enforcement. Next.js EDGE composed enforcement is opt-in (needs
234
+ // `cedarWasmModule`), so the dashboard shows it as opt-in, never "Enforcing".
235
+ sdk: { name: SDK_NAME, version: VERSION, runtime }
236
+ });
237
+ }
238
+ function buildTrustedRootsResolver(config, composed) {
239
+ if (composed?.trustedDelegationRoots) {
240
+ return () => composed.trustedDelegationRoots();
241
+ }
242
+ if (!config.projectId) return null;
243
+ const fetcher = new checkpointShared.PolicyFetcher({
244
+ apiBaseUrl: config.dashboardUrl ?? config.baseUrl ?? DEFAULT_DASHBOARD_URL,
245
+ apiKey: config.apiKey,
246
+ cacheTtlSeconds: config.policyCacheTtlSeconds
247
+ });
248
+ const projectId = config.projectId;
249
+ return async () => (await fetcher.getPolicy(projectId)).trustedDelegationRoots ?? [];
250
+ }
251
+ function buildComposedContext(config) {
252
+ if (config.composedPolicyEnforcer) return config.composedPolicyEnforcer;
253
+ if (!config.projectId) return null;
254
+ return makeComposedPolicyContext({
255
+ projectId: config.projectId,
256
+ fetcher: new checkpointShared.PolicyFetcher({
257
+ apiBaseUrl: config.dashboardUrl ?? config.baseUrl ?? DEFAULT_DASHBOARD_URL,
258
+ apiKey: config.apiKey,
259
+ cacheTtlSeconds: config.policyCacheTtlSeconds
260
+ }),
261
+ // LAZY dynamic import — NOT a top-level `import` — so the node-only
262
+ // `./policy` glue (`createRequire`/`fs` at module load) is never pulled into
263
+ // the Edge bundle. `middleware-edge.ts` imports helpers from this file, so a
264
+ // top-level `./policy` import would surface as a side-effect import in the
265
+ // edge bundle and boot-fail on Vercel edge. The import is cached after first
266
+ // call; the core's single-flight cache wraps the (now async) compile.
267
+ compile: async (_language, source) => {
268
+ const { createPolicyEvaluator } = await import('@kya-os/checkpoint-wasm-runtime/policy');
269
+ return createPolicyEvaluator(source);
270
+ },
271
+ logger: config.debug ? consoleComposedPolicyLogger : void 0
272
+ });
273
+ }
274
+ function extractReporterContext(req) {
275
+ return {
276
+ userAgent: req.headers.get("user-agent") ?? void 0,
277
+ ipAddress: req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? req.headers.get("x-real-ip") ?? void 0,
278
+ path: req.nextUrl.pathname,
279
+ url: req.nextUrl.href,
280
+ method: req.method
281
+ };
282
+ }
110
283
  function buildVerifyOpts(config) {
111
284
  const overrides = config.adapters ?? {};
112
285
  return {
@@ -131,5 +304,9 @@ async function dispatchOnResult(config, result, req) {
131
304
  }
132
305
  }
133
306
 
307
+ exports.VERSION = VERSION;
308
+ exports._buildReporter = buildReporter;
309
+ exports._buildTrustedRootsResolver = buildTrustedRootsResolver;
134
310
  exports._buildVerifyOpts = buildVerifyOpts;
311
+ exports._extractReporterContext = extractReporterContext;
135
312
  exports.withCheckpoint = withCheckpoint;
@@ -1,7 +1,9 @@
1
1
  import { verifyRequest, renderDecisionAsResponse } from '@kya-os/checkpoint-wasm-runtime/orchestrator';
2
2
  import { makeSystemClock, makePolicyEvaluator, makeReputationOracle, makeStatusListCache, makeDidResolver } from '@kya-os/checkpoint-wasm-runtime/adapters';
3
+ import { makeDetectionReporter } from '@kya-os/checkpoint-wasm-runtime/reporter';
4
+ import { selectBodyReadable200, PolicyFetcher, requestCarriesDelegationProof, acceptsHtml, encodeVerdictCookie, classifyResponseShape, BLOCKED_PATH, VERDICT_COOKIE_NAME } from '@kya-os/checkpoint-shared';
3
5
  import { NextResponse } from 'next/server';
4
- import { acceptsHtml, encodeVerdictCookie, classifyResponseShape, BLOCKED_PATH, VERDICT_COOKIE_NAME } from '@kya-os/checkpoint-shared';
6
+ import { makeComposedPolicyCache, evaluateComposedPolicy, verifyResultToAuthorizeInput } from '@kya-os/checkpoint-wasm-runtime/composed-policy';
5
7
 
6
8
  // src/middleware-node.ts
7
9
  function adaptToNextResponse(rendered, req) {
@@ -52,6 +54,96 @@ function applyHeaders(res, headers) {
52
54
  res.headers.set(key, value);
53
55
  }
54
56
  }
57
+ var DEFAULT_DASHBOARD_URL = "https://kya.vouched.id";
58
+ var NOOP_LOGGER = {
59
+ shadowDivergence: () => {
60
+ },
61
+ evaluationError: () => {
62
+ }
63
+ };
64
+ function makeComposedPolicyContext(opts) {
65
+ const { projectId, fetcher } = opts;
66
+ const cache = makeComposedPolicyCache({ compile: opts.compile, cacheMax: opts.cacheMax });
67
+ const logger = opts.logger ?? NOOP_LOGGER;
68
+ return {
69
+ async apply(result, path) {
70
+ const structured = { decision: result.decision, acted: false };
71
+ const policy = await fetcher.getPolicy(projectId);
72
+ const outcome = await evaluateComposedPolicy({
73
+ cache,
74
+ projectId,
75
+ flags: {
76
+ policyLanguage: policy.policyLanguage,
77
+ policySourceText: policy.policySourceText,
78
+ engineEnforcementEnabled: policy.engineEnforcementEnabled,
79
+ enabled: policy.enabled
80
+ },
81
+ authorizeInput: verifyResultToAuthorizeInput(result, { tenantId: projectId, path }),
82
+ baselineDecisionKind: result.decision.kind
83
+ });
84
+ if ((outcome.status === "acting" || outcome.status === "shadow") && outcome.diverged) {
85
+ logger.shadowDivergence({
86
+ projectId,
87
+ path,
88
+ engineDecision: outcome.engineDecision.kind,
89
+ structuredDecision: result.decision.kind,
90
+ detectionClass: result.detectionDetail.detectionClass.type,
91
+ verificationMethod: result.detectionDetail.verificationMethod,
92
+ confidence: result.detectionDetail.confidence,
93
+ agentName: result.detectionDetail.detectedAgent?.name
94
+ });
95
+ }
96
+ if (outcome.status === "error") {
97
+ logger.evaluationError(projectId, outcome.error);
98
+ return structured;
99
+ }
100
+ if (outcome.status === "acting") {
101
+ return {
102
+ decision: outcome.engineDecision,
103
+ acted: true,
104
+ // Echo the SSOT revision pin with the decision it produced (#3246
105
+ // PR5). Conditional spread: absent upstream stays absent here.
106
+ ...policy.policySourceHash ? { composedBlobHash: policy.policySourceHash } : {}
107
+ };
108
+ }
109
+ return structured;
110
+ },
111
+ async trustedDelegationRoots() {
112
+ const policy = await fetcher.getPolicy(projectId);
113
+ return policy.trustedDelegationRoots ?? [];
114
+ }
115
+ };
116
+ }
117
+ async function resolveTrustedDelegationRootsForRequest(resolver, headers) {
118
+ if (!resolver) return void 0;
119
+ if (!requestCarriesDelegationProof(headers)) return void 0;
120
+ const roots = await resolver();
121
+ return roots.length > 0 ? roots : void 0;
122
+ }
123
+ async function applyComposedPolicy(context, result, path) {
124
+ if (!context) return;
125
+ try {
126
+ const outcome = await context.apply(result, path);
127
+ if (outcome.acted) {
128
+ result.decision = outcome.decision;
129
+ if (outcome.composedBlobHash) {
130
+ result.composedBlobHash = outcome.composedBlobHash;
131
+ }
132
+ }
133
+ } catch {
134
+ }
135
+ }
136
+ var consoleComposedPolicyLogger = {
137
+ shadowDivergence(info) {
138
+ console.warn("[checkpoint/composed-policy] shadow-divergence", info);
139
+ },
140
+ evaluationError(projectId, error) {
141
+ console.error(
142
+ `[checkpoint/composed-policy] evaluation failed for ${projectId}; using structured decision:`,
143
+ error
144
+ );
145
+ }
146
+ };
55
147
 
56
148
  // src/translate.ts
57
149
  async function nextRequestToHttpLike(req, opts = {}) {
@@ -96,15 +188,96 @@ function extractRemoteAddress(req) {
96
188
  // src/middleware-node.ts
97
189
  function withCheckpoint(config) {
98
190
  const opts = buildVerifyOpts(config);
191
+ const reporter = buildReporter(config);
192
+ const composed = buildComposedContext(config);
193
+ const trustedRootsResolver = buildTrustedRootsResolver(config, composed);
99
194
  const translateOpts = { drainJsonBody: config.drainJsonBody };
100
- return async function checkpointMiddleware(req) {
195
+ return async function checkpointMiddleware(req, event) {
101
196
  const httpLike = await nextRequestToHttpLike(req, translateOpts);
102
- const result = await verifyRequest(httpLike, opts);
197
+ const trustedDelegationRoots = await resolveTrustedDelegationRootsForRequest(
198
+ trustedRootsResolver,
199
+ req.headers
200
+ );
201
+ const result = await verifyRequest(
202
+ httpLike,
203
+ trustedDelegationRoots ? { ...opts, trustedDelegationRoots } : opts
204
+ );
205
+ await applyComposedPolicy(composed, result, req.nextUrl.pathname);
206
+ if (reporter) {
207
+ const reportPromise = reporter(result, extractReporterContext(req));
208
+ if (event) {
209
+ event.waitUntil(reportPromise);
210
+ }
211
+ }
103
212
  await dispatchOnResult(config, result, req);
104
- const rendered = renderDecisionAsResponse(result);
213
+ const bodyReadable200 = selectBodyReadable200(
214
+ config.delegationChallengeMode ?? "spec-401",
215
+ req.headers,
216
+ result.detectionDetail.detectionClass
217
+ );
218
+ const rendered = renderDecisionAsResponse(result, { bodyReadable200 });
105
219
  return adaptToNextResponse(rendered, req);
106
220
  };
107
221
  }
222
+ var SDK_NAME = "@kya-os/checkpoint-nextjs";
223
+ var VERSION = "1.7.1";
224
+ function buildReporter(config, runtime = "node") {
225
+ if (!config.apiKey) return null;
226
+ return makeDetectionReporter({
227
+ apiKey: config.apiKey,
228
+ baseUrl: config.baseUrl,
229
+ debug: config.debug,
230
+ // Self-identify (incl. node-vs-edge) so the dashboard can version-gate
231
+ // enforcement. Next.js EDGE composed enforcement is opt-in (needs
232
+ // `cedarWasmModule`), so the dashboard shows it as opt-in, never "Enforcing".
233
+ sdk: { name: SDK_NAME, version: VERSION, runtime }
234
+ });
235
+ }
236
+ function buildTrustedRootsResolver(config, composed) {
237
+ if (composed?.trustedDelegationRoots) {
238
+ return () => composed.trustedDelegationRoots();
239
+ }
240
+ if (!config.projectId) return null;
241
+ const fetcher = new PolicyFetcher({
242
+ apiBaseUrl: config.dashboardUrl ?? config.baseUrl ?? DEFAULT_DASHBOARD_URL,
243
+ apiKey: config.apiKey,
244
+ cacheTtlSeconds: config.policyCacheTtlSeconds
245
+ });
246
+ const projectId = config.projectId;
247
+ return async () => (await fetcher.getPolicy(projectId)).trustedDelegationRoots ?? [];
248
+ }
249
+ function buildComposedContext(config) {
250
+ if (config.composedPolicyEnforcer) return config.composedPolicyEnforcer;
251
+ if (!config.projectId) return null;
252
+ return makeComposedPolicyContext({
253
+ projectId: config.projectId,
254
+ fetcher: new PolicyFetcher({
255
+ apiBaseUrl: config.dashboardUrl ?? config.baseUrl ?? DEFAULT_DASHBOARD_URL,
256
+ apiKey: config.apiKey,
257
+ cacheTtlSeconds: config.policyCacheTtlSeconds
258
+ }),
259
+ // LAZY dynamic import — NOT a top-level `import` — so the node-only
260
+ // `./policy` glue (`createRequire`/`fs` at module load) is never pulled into
261
+ // the Edge bundle. `middleware-edge.ts` imports helpers from this file, so a
262
+ // top-level `./policy` import would surface as a side-effect import in the
263
+ // edge bundle and boot-fail on Vercel edge. The import is cached after first
264
+ // call; the core's single-flight cache wraps the (now async) compile.
265
+ compile: async (_language, source) => {
266
+ const { createPolicyEvaluator } = await import('@kya-os/checkpoint-wasm-runtime/policy');
267
+ return createPolicyEvaluator(source);
268
+ },
269
+ logger: config.debug ? consoleComposedPolicyLogger : void 0
270
+ });
271
+ }
272
+ function extractReporterContext(req) {
273
+ return {
274
+ userAgent: req.headers.get("user-agent") ?? void 0,
275
+ ipAddress: req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? req.headers.get("x-real-ip") ?? void 0,
276
+ path: req.nextUrl.pathname,
277
+ url: req.nextUrl.href,
278
+ method: req.method
279
+ };
280
+ }
108
281
  function buildVerifyOpts(config) {
109
282
  const overrides = config.adapters ?? {};
110
283
  return {
@@ -129,4 +302,4 @@ async function dispatchOnResult(config, result, req) {
129
302
  }
130
303
  }
131
304
 
132
- export { buildVerifyOpts as _buildVerifyOpts, withCheckpoint };
305
+ export { VERSION, buildReporter as _buildReporter, buildTrustedRootsResolver as _buildTrustedRootsResolver, buildVerifyOpts as _buildVerifyOpts, extractReporterContext as _extractReporterContext, withCheckpoint };
@@ -32,5 +32,14 @@ declare function createAgentShieldMiddleware(_config?: Partial<NextJSMiddlewareC
32
32
  * Migrate to `withCheckpoint`.
33
33
  */
34
34
  declare function agentShield(config?: Partial<NextJSMiddlewareConfig>): (request: NextRequest) => Promise<NextResponse>;
35
+ /**
36
+ * Pass-through export required by Next.js 16+ middleware file validation.
37
+ * Next.js requires any file named `middleware.ts` to export a function named
38
+ * `middleware` or a default function. This stub satisfies that constraint so
39
+ * consumers that still reference the `./middleware` subpath can build.
40
+ *
41
+ * @deprecated Migrate to `withCheckpoint` from `@kya-os/checkpoint-nextjs`.
42
+ */
43
+ declare function middleware(_request: NextRequest): NextResponse<unknown>;
35
44
 
36
- export { agentShield, createAgentShieldMiddleware };
45
+ export { agentShield, createAgentShieldMiddleware, middleware };
@@ -32,5 +32,14 @@ declare function createAgentShieldMiddleware(_config?: Partial<NextJSMiddlewareC
32
32
  * Migrate to `withCheckpoint`.
33
33
  */
34
34
  declare function agentShield(config?: Partial<NextJSMiddlewareConfig>): (request: NextRequest) => Promise<NextResponse>;
35
+ /**
36
+ * Pass-through export required by Next.js 16+ middleware file validation.
37
+ * Next.js requires any file named `middleware.ts` to export a function named
38
+ * `middleware` or a default function. This stub satisfies that constraint so
39
+ * consumers that still reference the `./middleware` subpath can build.
40
+ *
41
+ * @deprecated Migrate to `withCheckpoint` from `@kya-os/checkpoint-nextjs`.
42
+ */
43
+ declare function middleware(_request: NextRequest): NextResponse<unknown>;
35
44
 
36
- export { agentShield, createAgentShieldMiddleware };
45
+ export { agentShield, createAgentShieldMiddleware, middleware };
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ var server = require('next/server');
4
+
3
5
  // src/middleware.ts
4
6
  var MIGRATION_ERROR = "@kya-os/checkpoint-nextjs's `createAgentShieldMiddleware` / `agentShield` were deleted in Phase D (engine consolidation). The 600-line TS pattern matcher that backed them is gone. Migrate to `withCheckpoint` from `@kya-os/checkpoint-nextjs` (Node runtime) or `@kya-os/checkpoint-nextjs/edge` (Edge runtime). See packages/checkpoint-nextjs/CHANGELOG.md (1.0.0) for the recipe.";
5
7
  function createAgentShieldMiddleware(_config = {}) {
@@ -8,6 +10,10 @@ function createAgentShieldMiddleware(_config = {}) {
8
10
  function agentShield(config = {}) {
9
11
  return createAgentShieldMiddleware(config);
10
12
  }
13
+ function middleware(_request) {
14
+ return server.NextResponse.next();
15
+ }
11
16
 
12
17
  exports.agentShield = agentShield;
13
18
  exports.createAgentShieldMiddleware = createAgentShieldMiddleware;
19
+ exports.middleware = middleware;
@@ -1,3 +1,5 @@
1
+ import { NextResponse } from 'next/server';
2
+
1
3
  // src/middleware.ts
2
4
  var MIGRATION_ERROR = "@kya-os/checkpoint-nextjs's `createAgentShieldMiddleware` / `agentShield` were deleted in Phase D (engine consolidation). The 600-line TS pattern matcher that backed them is gone. Migrate to `withCheckpoint` from `@kya-os/checkpoint-nextjs` (Node runtime) or `@kya-os/checkpoint-nextjs/edge` (Edge runtime). See packages/checkpoint-nextjs/CHANGELOG.md (1.0.0) for the recipe.";
3
5
  function createAgentShieldMiddleware(_config = {}) {
@@ -6,5 +8,8 @@ function createAgentShieldMiddleware(_config = {}) {
6
8
  function agentShield(config = {}) {
7
9
  return createAgentShieldMiddleware(config);
8
10
  }
11
+ function middleware(_request) {
12
+ return NextResponse.next();
13
+ }
9
14
 
10
- export { agentShield, createAgentShieldMiddleware };
15
+ export { agentShield, createAgentShieldMiddleware, middleware };