@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.
- package/CHANGELOG.md +159 -0
- package/EDGE_RUNTIME_WASM_SETUP.md +1 -1
- package/bin/setup-edge-wasm.js +1 -1
- package/dist/api-middleware.d.mts +9 -1
- package/dist/api-middleware.d.ts +9 -1
- package/dist/api-middleware.js +14 -4
- package/dist/api-middleware.mjs +15 -5
- package/dist/composed-policy.d.mts +115 -0
- package/dist/composed-policy.d.ts +115 -0
- package/dist/composed-policy.js +102 -0
- package/dist/composed-policy.mjs +96 -0
- package/dist/config-DAwIA4DB.d.mts +214 -0
- package/dist/config-DyU4l5er.d.ts +214 -0
- package/dist/create-middleware.js +0 -2
- package/dist/create-middleware.mjs +0 -2
- package/dist/edge-runtime-loader.js +3 -1
- package/dist/edge-runtime-loader.mjs +3 -1
- package/dist/edge-wasm-middleware.d.mts +6 -6
- package/dist/edge-wasm-middleware.d.ts +6 -6
- package/dist/index.d.mts +6 -14
- package/dist/index.d.ts +6 -14
- package/dist/index.js +191 -13
- package/dist/index.mjs +192 -14
- package/dist/middleware-edge.d.mts +7 -3
- package/dist/middleware-edge.d.ts +7 -3
- package/dist/middleware-edge.js +174 -4
- package/dist/middleware-edge.mjs +171 -4
- package/dist/middleware-node.d.mts +39 -116
- package/dist/middleware-node.d.ts +39 -116
- package/dist/middleware-node.js +181 -4
- package/dist/middleware-node.mjs +178 -5
- package/dist/middleware.d.mts +10 -1
- package/dist/middleware.d.ts +10 -1
- package/dist/middleware.js +6 -0
- package/dist/middleware.mjs +6 -1
- package/dist/nodejs-wasm-loader.d.mts +3 -4
- package/dist/nodejs-wasm-loader.d.ts +3 -4
- package/dist/nodejs-wasm-loader.js +1 -1
- package/dist/nodejs-wasm-loader.mjs +1 -1
- package/dist/wasm-setup.js +1 -1
- package/dist/wasm-setup.mjs +1 -1
- package/package.json +4 -9
- package/dist/.tsbuildinfo +0 -1
- package/dist/signature-verifier.d.mts +0 -33
- package/dist/signature-verifier.d.ts +0 -33
- package/dist/signature-verifier.js +0 -384
- package/dist/signature-verifier.mjs +0 -360
- package/dist/wasm-middleware.d.mts +0 -98
- package/dist/wasm-middleware.d.ts +0 -98
- package/dist/wasm-middleware.js +0 -125
- package/dist/wasm-middleware.mjs +0 -121
- package/templates/middleware-wasm-100.ts +0 -161
package/dist/middleware-edge.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var edge = require('@kya-os/checkpoint-wasm-runtime/orchestrator/edge');
|
|
4
|
-
var
|
|
4
|
+
var policyEdge = require('@kya-os/checkpoint-wasm-runtime/policy-edge');
|
|
5
5
|
var checkpointShared = require('@kya-os/checkpoint-shared');
|
|
6
|
+
var server = require('next/server');
|
|
7
|
+
var composedPolicy = require('@kya-os/checkpoint-wasm-runtime/composed-policy');
|
|
6
8
|
require('@kya-os/checkpoint-wasm-runtime/orchestrator');
|
|
7
9
|
var adapters = require('@kya-os/checkpoint-wasm-runtime/adapters');
|
|
10
|
+
var reporter = require('@kya-os/checkpoint-wasm-runtime/reporter');
|
|
8
11
|
|
|
9
12
|
// src/middleware-edge.ts
|
|
10
13
|
function adaptToNextResponse(rendered, req) {
|
|
@@ -55,6 +58,96 @@ function applyHeaders(res, headers) {
|
|
|
55
58
|
res.headers.set(key, value);
|
|
56
59
|
}
|
|
57
60
|
}
|
|
61
|
+
var DEFAULT_DASHBOARD_URL = "https://kya.vouched.id";
|
|
62
|
+
var NOOP_LOGGER = {
|
|
63
|
+
shadowDivergence: () => {
|
|
64
|
+
},
|
|
65
|
+
evaluationError: () => {
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
function makeComposedPolicyContext(opts) {
|
|
69
|
+
const { projectId, fetcher } = opts;
|
|
70
|
+
const cache = composedPolicy.makeComposedPolicyCache({ compile: opts.compile, cacheMax: opts.cacheMax });
|
|
71
|
+
const logger = opts.logger ?? NOOP_LOGGER;
|
|
72
|
+
return {
|
|
73
|
+
async apply(result, path) {
|
|
74
|
+
const structured = { decision: result.decision, acted: false };
|
|
75
|
+
const policy = await fetcher.getPolicy(projectId);
|
|
76
|
+
const outcome = await composedPolicy.evaluateComposedPolicy({
|
|
77
|
+
cache,
|
|
78
|
+
projectId,
|
|
79
|
+
flags: {
|
|
80
|
+
policyLanguage: policy.policyLanguage,
|
|
81
|
+
policySourceText: policy.policySourceText,
|
|
82
|
+
engineEnforcementEnabled: policy.engineEnforcementEnabled,
|
|
83
|
+
enabled: policy.enabled
|
|
84
|
+
},
|
|
85
|
+
authorizeInput: composedPolicy.verifyResultToAuthorizeInput(result, { tenantId: projectId, path }),
|
|
86
|
+
baselineDecisionKind: result.decision.kind
|
|
87
|
+
});
|
|
88
|
+
if ((outcome.status === "acting" || outcome.status === "shadow") && outcome.diverged) {
|
|
89
|
+
logger.shadowDivergence({
|
|
90
|
+
projectId,
|
|
91
|
+
path,
|
|
92
|
+
engineDecision: outcome.engineDecision.kind,
|
|
93
|
+
structuredDecision: result.decision.kind,
|
|
94
|
+
detectionClass: result.detectionDetail.detectionClass.type,
|
|
95
|
+
verificationMethod: result.detectionDetail.verificationMethod,
|
|
96
|
+
confidence: result.detectionDetail.confidence,
|
|
97
|
+
agentName: result.detectionDetail.detectedAgent?.name
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (outcome.status === "error") {
|
|
101
|
+
logger.evaluationError(projectId, outcome.error);
|
|
102
|
+
return structured;
|
|
103
|
+
}
|
|
104
|
+
if (outcome.status === "acting") {
|
|
105
|
+
return {
|
|
106
|
+
decision: outcome.engineDecision,
|
|
107
|
+
acted: true,
|
|
108
|
+
// Echo the SSOT revision pin with the decision it produced (#3246
|
|
109
|
+
// PR5). Conditional spread: absent upstream stays absent here.
|
|
110
|
+
...policy.policySourceHash ? { composedBlobHash: policy.policySourceHash } : {}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return structured;
|
|
114
|
+
},
|
|
115
|
+
async trustedDelegationRoots() {
|
|
116
|
+
const policy = await fetcher.getPolicy(projectId);
|
|
117
|
+
return policy.trustedDelegationRoots ?? [];
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function resolveTrustedDelegationRootsForRequest(resolver, headers) {
|
|
122
|
+
if (!resolver) return void 0;
|
|
123
|
+
if (!checkpointShared.requestCarriesDelegationProof(headers)) return void 0;
|
|
124
|
+
const roots = await resolver();
|
|
125
|
+
return roots.length > 0 ? roots : void 0;
|
|
126
|
+
}
|
|
127
|
+
async function applyComposedPolicy(context, result, path) {
|
|
128
|
+
if (!context) return;
|
|
129
|
+
try {
|
|
130
|
+
const outcome = await context.apply(result, path);
|
|
131
|
+
if (outcome.acted) {
|
|
132
|
+
result.decision = outcome.decision;
|
|
133
|
+
if (outcome.composedBlobHash) {
|
|
134
|
+
result.composedBlobHash = outcome.composedBlobHash;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
var consoleComposedPolicyLogger = {
|
|
141
|
+
shadowDivergence(info) {
|
|
142
|
+
console.warn("[checkpoint/composed-policy] shadow-divergence", info);
|
|
143
|
+
},
|
|
144
|
+
evaluationError(projectId, error) {
|
|
145
|
+
console.error(
|
|
146
|
+
`[checkpoint/composed-policy] evaluation failed for ${projectId}; using structured decision:`,
|
|
147
|
+
error
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
58
151
|
|
|
59
152
|
// src/translate.ts
|
|
60
153
|
async function nextRequestToHttpLike(req, opts = {}) {
|
|
@@ -97,6 +190,42 @@ function extractRemoteAddress(req) {
|
|
|
97
190
|
}
|
|
98
191
|
|
|
99
192
|
// src/middleware-node.ts
|
|
193
|
+
var SDK_NAME = "@kya-os/checkpoint-nextjs";
|
|
194
|
+
var VERSION = "1.7.1";
|
|
195
|
+
function buildReporter(config, runtime = "node") {
|
|
196
|
+
if (!config.apiKey) return null;
|
|
197
|
+
return reporter.makeDetectionReporter({
|
|
198
|
+
apiKey: config.apiKey,
|
|
199
|
+
baseUrl: config.baseUrl,
|
|
200
|
+
debug: config.debug,
|
|
201
|
+
// Self-identify (incl. node-vs-edge) so the dashboard can version-gate
|
|
202
|
+
// enforcement. Next.js EDGE composed enforcement is opt-in (needs
|
|
203
|
+
// `cedarWasmModule`), so the dashboard shows it as opt-in, never "Enforcing".
|
|
204
|
+
sdk: { name: SDK_NAME, version: VERSION, runtime }
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function buildTrustedRootsResolver(config, composed) {
|
|
208
|
+
if (composed?.trustedDelegationRoots) {
|
|
209
|
+
return () => composed.trustedDelegationRoots();
|
|
210
|
+
}
|
|
211
|
+
if (!config.projectId) return null;
|
|
212
|
+
const fetcher = new checkpointShared.PolicyFetcher({
|
|
213
|
+
apiBaseUrl: config.dashboardUrl ?? config.baseUrl ?? DEFAULT_DASHBOARD_URL,
|
|
214
|
+
apiKey: config.apiKey,
|
|
215
|
+
cacheTtlSeconds: config.policyCacheTtlSeconds
|
|
216
|
+
});
|
|
217
|
+
const projectId = config.projectId;
|
|
218
|
+
return async () => (await fetcher.getPolicy(projectId)).trustedDelegationRoots ?? [];
|
|
219
|
+
}
|
|
220
|
+
function extractReporterContext(req) {
|
|
221
|
+
return {
|
|
222
|
+
userAgent: req.headers.get("user-agent") ?? void 0,
|
|
223
|
+
ipAddress: req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? req.headers.get("x-real-ip") ?? void 0,
|
|
224
|
+
path: req.nextUrl.pathname,
|
|
225
|
+
url: req.nextUrl.href,
|
|
226
|
+
method: req.method
|
|
227
|
+
};
|
|
228
|
+
}
|
|
100
229
|
function buildVerifyOpts(config) {
|
|
101
230
|
const overrides = config.adapters ?? {};
|
|
102
231
|
return {
|
|
@@ -118,12 +247,34 @@ function buildVerifyOpts(config) {
|
|
|
118
247
|
function withCheckpoint(config) {
|
|
119
248
|
void edge.initEngineEdge();
|
|
120
249
|
const opts = buildVerifyOpts(config);
|
|
250
|
+
const reporter = buildReporter(config, "edge");
|
|
251
|
+
const composed = buildComposedContext(config);
|
|
252
|
+
const trustedRootsResolver = buildTrustedRootsResolver(config, composed);
|
|
121
253
|
const translateOpts = { drainJsonBody: config.drainJsonBody };
|
|
122
|
-
return async function checkpointMiddlewareEdge(req) {
|
|
254
|
+
return async function checkpointMiddlewareEdge(req, event) {
|
|
123
255
|
const httpLike = await nextRequestToHttpLike(req, translateOpts);
|
|
124
|
-
const
|
|
256
|
+
const trustedDelegationRoots = await resolveTrustedDelegationRootsForRequest(
|
|
257
|
+
trustedRootsResolver,
|
|
258
|
+
req.headers
|
|
259
|
+
);
|
|
260
|
+
const result = await edge.verifyRequestEdge(
|
|
261
|
+
httpLike,
|
|
262
|
+
trustedDelegationRoots ? { ...opts, trustedDelegationRoots } : opts
|
|
263
|
+
);
|
|
264
|
+
await applyComposedPolicy(composed, result, req.nextUrl.pathname);
|
|
265
|
+
if (reporter) {
|
|
266
|
+
const reportPromise = reporter(result, extractReporterContext(req));
|
|
267
|
+
if (event) {
|
|
268
|
+
event.waitUntil(reportPromise);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
125
271
|
await dispatchOnResult(config, result, req);
|
|
126
|
-
const
|
|
272
|
+
const bodyReadable200 = checkpointShared.selectBodyReadable200(
|
|
273
|
+
config.delegationChallengeMode ?? "spec-401",
|
|
274
|
+
req.headers,
|
|
275
|
+
result.detectionDetail.detectionClass
|
|
276
|
+
);
|
|
277
|
+
const rendered = edge.renderDecisionAsResponse(result, { bodyReadable200 });
|
|
127
278
|
return adaptToNextResponse(rendered, req);
|
|
128
279
|
};
|
|
129
280
|
}
|
|
@@ -134,9 +285,28 @@ async function dispatchOnResult(config, result, req) {
|
|
|
134
285
|
} catch {
|
|
135
286
|
}
|
|
136
287
|
}
|
|
288
|
+
function buildComposedContext(config) {
|
|
289
|
+
if (config.composedPolicyEnforcer) return config.composedPolicyEnforcer;
|
|
290
|
+
if (!config.projectId || !config.cedarWasmModule) return null;
|
|
291
|
+
policyEdge.initPolicyEvaluatorEdge(config.cedarWasmModule);
|
|
292
|
+
return makeComposedPolicyContext({
|
|
293
|
+
projectId: config.projectId,
|
|
294
|
+
fetcher: new checkpointShared.PolicyFetcher({
|
|
295
|
+
apiBaseUrl: config.dashboardUrl ?? config.baseUrl ?? DEFAULT_DASHBOARD_URL,
|
|
296
|
+
apiKey: config.apiKey,
|
|
297
|
+
cacheTtlSeconds: config.policyCacheTtlSeconds
|
|
298
|
+
}),
|
|
299
|
+
compile: (language, source) => policyEdge.evaluatePolicy(language, source),
|
|
300
|
+
logger: config.debug ? consoleComposedPolicyLogger : void 0
|
|
301
|
+
});
|
|
302
|
+
}
|
|
137
303
|
|
|
138
304
|
Object.defineProperty(exports, "initEngineEdge", {
|
|
139
305
|
enumerable: true,
|
|
140
306
|
get: function () { return edge.initEngineEdge; }
|
|
141
307
|
});
|
|
308
|
+
Object.defineProperty(exports, "initPolicyEvaluatorEdge", {
|
|
309
|
+
enumerable: true,
|
|
310
|
+
get: function () { return policyEdge.initPolicyEvaluatorEdge; }
|
|
311
|
+
});
|
|
142
312
|
exports.withCheckpoint = withCheckpoint;
|
package/dist/middleware-edge.mjs
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { initEngineEdge, verifyRequestEdge, renderDecisionAsResponse } from '@kya-os/checkpoint-wasm-runtime/orchestrator/edge';
|
|
2
2
|
export { initEngineEdge } from '@kya-os/checkpoint-wasm-runtime/orchestrator/edge';
|
|
3
|
+
import { initPolicyEvaluatorEdge, evaluatePolicy } from '@kya-os/checkpoint-wasm-runtime/policy-edge';
|
|
4
|
+
export { initPolicyEvaluatorEdge } from '@kya-os/checkpoint-wasm-runtime/policy-edge';
|
|
5
|
+
import { selectBodyReadable200, PolicyFetcher, requestCarriesDelegationProof, acceptsHtml, encodeVerdictCookie, classifyResponseShape, BLOCKED_PATH, VERDICT_COOKIE_NAME } from '@kya-os/checkpoint-shared';
|
|
3
6
|
import { NextResponse } from 'next/server';
|
|
4
|
-
import {
|
|
7
|
+
import { makeComposedPolicyCache, evaluateComposedPolicy, verifyResultToAuthorizeInput } from '@kya-os/checkpoint-wasm-runtime/composed-policy';
|
|
5
8
|
import '@kya-os/checkpoint-wasm-runtime/orchestrator';
|
|
6
9
|
import { makeSystemClock, makePolicyEvaluator, makeReputationOracle, makeStatusListCache, makeDidResolver } from '@kya-os/checkpoint-wasm-runtime/adapters';
|
|
10
|
+
import { makeDetectionReporter } from '@kya-os/checkpoint-wasm-runtime/reporter';
|
|
7
11
|
|
|
8
12
|
// src/middleware-edge.ts
|
|
9
13
|
function adaptToNextResponse(rendered, req) {
|
|
@@ -54,6 +58,96 @@ function applyHeaders(res, headers) {
|
|
|
54
58
|
res.headers.set(key, value);
|
|
55
59
|
}
|
|
56
60
|
}
|
|
61
|
+
var DEFAULT_DASHBOARD_URL = "https://kya.vouched.id";
|
|
62
|
+
var NOOP_LOGGER = {
|
|
63
|
+
shadowDivergence: () => {
|
|
64
|
+
},
|
|
65
|
+
evaluationError: () => {
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
function makeComposedPolicyContext(opts) {
|
|
69
|
+
const { projectId, fetcher } = opts;
|
|
70
|
+
const cache = makeComposedPolicyCache({ compile: opts.compile, cacheMax: opts.cacheMax });
|
|
71
|
+
const logger = opts.logger ?? NOOP_LOGGER;
|
|
72
|
+
return {
|
|
73
|
+
async apply(result, path) {
|
|
74
|
+
const structured = { decision: result.decision, acted: false };
|
|
75
|
+
const policy = await fetcher.getPolicy(projectId);
|
|
76
|
+
const outcome = await evaluateComposedPolicy({
|
|
77
|
+
cache,
|
|
78
|
+
projectId,
|
|
79
|
+
flags: {
|
|
80
|
+
policyLanguage: policy.policyLanguage,
|
|
81
|
+
policySourceText: policy.policySourceText,
|
|
82
|
+
engineEnforcementEnabled: policy.engineEnforcementEnabled,
|
|
83
|
+
enabled: policy.enabled
|
|
84
|
+
},
|
|
85
|
+
authorizeInput: verifyResultToAuthorizeInput(result, { tenantId: projectId, path }),
|
|
86
|
+
baselineDecisionKind: result.decision.kind
|
|
87
|
+
});
|
|
88
|
+
if ((outcome.status === "acting" || outcome.status === "shadow") && outcome.diverged) {
|
|
89
|
+
logger.shadowDivergence({
|
|
90
|
+
projectId,
|
|
91
|
+
path,
|
|
92
|
+
engineDecision: outcome.engineDecision.kind,
|
|
93
|
+
structuredDecision: result.decision.kind,
|
|
94
|
+
detectionClass: result.detectionDetail.detectionClass.type,
|
|
95
|
+
verificationMethod: result.detectionDetail.verificationMethod,
|
|
96
|
+
confidence: result.detectionDetail.confidence,
|
|
97
|
+
agentName: result.detectionDetail.detectedAgent?.name
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (outcome.status === "error") {
|
|
101
|
+
logger.evaluationError(projectId, outcome.error);
|
|
102
|
+
return structured;
|
|
103
|
+
}
|
|
104
|
+
if (outcome.status === "acting") {
|
|
105
|
+
return {
|
|
106
|
+
decision: outcome.engineDecision,
|
|
107
|
+
acted: true,
|
|
108
|
+
// Echo the SSOT revision pin with the decision it produced (#3246
|
|
109
|
+
// PR5). Conditional spread: absent upstream stays absent here.
|
|
110
|
+
...policy.policySourceHash ? { composedBlobHash: policy.policySourceHash } : {}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return structured;
|
|
114
|
+
},
|
|
115
|
+
async trustedDelegationRoots() {
|
|
116
|
+
const policy = await fetcher.getPolicy(projectId);
|
|
117
|
+
return policy.trustedDelegationRoots ?? [];
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function resolveTrustedDelegationRootsForRequest(resolver, headers) {
|
|
122
|
+
if (!resolver) return void 0;
|
|
123
|
+
if (!requestCarriesDelegationProof(headers)) return void 0;
|
|
124
|
+
const roots = await resolver();
|
|
125
|
+
return roots.length > 0 ? roots : void 0;
|
|
126
|
+
}
|
|
127
|
+
async function applyComposedPolicy(context, result, path) {
|
|
128
|
+
if (!context) return;
|
|
129
|
+
try {
|
|
130
|
+
const outcome = await context.apply(result, path);
|
|
131
|
+
if (outcome.acted) {
|
|
132
|
+
result.decision = outcome.decision;
|
|
133
|
+
if (outcome.composedBlobHash) {
|
|
134
|
+
result.composedBlobHash = outcome.composedBlobHash;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
var consoleComposedPolicyLogger = {
|
|
141
|
+
shadowDivergence(info) {
|
|
142
|
+
console.warn("[checkpoint/composed-policy] shadow-divergence", info);
|
|
143
|
+
},
|
|
144
|
+
evaluationError(projectId, error) {
|
|
145
|
+
console.error(
|
|
146
|
+
`[checkpoint/composed-policy] evaluation failed for ${projectId}; using structured decision:`,
|
|
147
|
+
error
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
57
151
|
|
|
58
152
|
// src/translate.ts
|
|
59
153
|
async function nextRequestToHttpLike(req, opts = {}) {
|
|
@@ -96,6 +190,42 @@ function extractRemoteAddress(req) {
|
|
|
96
190
|
}
|
|
97
191
|
|
|
98
192
|
// src/middleware-node.ts
|
|
193
|
+
var SDK_NAME = "@kya-os/checkpoint-nextjs";
|
|
194
|
+
var VERSION = "1.7.1";
|
|
195
|
+
function buildReporter(config, runtime = "node") {
|
|
196
|
+
if (!config.apiKey) return null;
|
|
197
|
+
return makeDetectionReporter({
|
|
198
|
+
apiKey: config.apiKey,
|
|
199
|
+
baseUrl: config.baseUrl,
|
|
200
|
+
debug: config.debug,
|
|
201
|
+
// Self-identify (incl. node-vs-edge) so the dashboard can version-gate
|
|
202
|
+
// enforcement. Next.js EDGE composed enforcement is opt-in (needs
|
|
203
|
+
// `cedarWasmModule`), so the dashboard shows it as opt-in, never "Enforcing".
|
|
204
|
+
sdk: { name: SDK_NAME, version: VERSION, runtime }
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function buildTrustedRootsResolver(config, composed) {
|
|
208
|
+
if (composed?.trustedDelegationRoots) {
|
|
209
|
+
return () => composed.trustedDelegationRoots();
|
|
210
|
+
}
|
|
211
|
+
if (!config.projectId) return null;
|
|
212
|
+
const fetcher = new PolicyFetcher({
|
|
213
|
+
apiBaseUrl: config.dashboardUrl ?? config.baseUrl ?? DEFAULT_DASHBOARD_URL,
|
|
214
|
+
apiKey: config.apiKey,
|
|
215
|
+
cacheTtlSeconds: config.policyCacheTtlSeconds
|
|
216
|
+
});
|
|
217
|
+
const projectId = config.projectId;
|
|
218
|
+
return async () => (await fetcher.getPolicy(projectId)).trustedDelegationRoots ?? [];
|
|
219
|
+
}
|
|
220
|
+
function extractReporterContext(req) {
|
|
221
|
+
return {
|
|
222
|
+
userAgent: req.headers.get("user-agent") ?? void 0,
|
|
223
|
+
ipAddress: req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? req.headers.get("x-real-ip") ?? void 0,
|
|
224
|
+
path: req.nextUrl.pathname,
|
|
225
|
+
url: req.nextUrl.href,
|
|
226
|
+
method: req.method
|
|
227
|
+
};
|
|
228
|
+
}
|
|
99
229
|
function buildVerifyOpts(config) {
|
|
100
230
|
const overrides = config.adapters ?? {};
|
|
101
231
|
return {
|
|
@@ -117,12 +247,34 @@ function buildVerifyOpts(config) {
|
|
|
117
247
|
function withCheckpoint(config) {
|
|
118
248
|
void initEngineEdge();
|
|
119
249
|
const opts = buildVerifyOpts(config);
|
|
250
|
+
const reporter = buildReporter(config, "edge");
|
|
251
|
+
const composed = buildComposedContext(config);
|
|
252
|
+
const trustedRootsResolver = buildTrustedRootsResolver(config, composed);
|
|
120
253
|
const translateOpts = { drainJsonBody: config.drainJsonBody };
|
|
121
|
-
return async function checkpointMiddlewareEdge(req) {
|
|
254
|
+
return async function checkpointMiddlewareEdge(req, event) {
|
|
122
255
|
const httpLike = await nextRequestToHttpLike(req, translateOpts);
|
|
123
|
-
const
|
|
256
|
+
const trustedDelegationRoots = await resolveTrustedDelegationRootsForRequest(
|
|
257
|
+
trustedRootsResolver,
|
|
258
|
+
req.headers
|
|
259
|
+
);
|
|
260
|
+
const result = await verifyRequestEdge(
|
|
261
|
+
httpLike,
|
|
262
|
+
trustedDelegationRoots ? { ...opts, trustedDelegationRoots } : opts
|
|
263
|
+
);
|
|
264
|
+
await applyComposedPolicy(composed, result, req.nextUrl.pathname);
|
|
265
|
+
if (reporter) {
|
|
266
|
+
const reportPromise = reporter(result, extractReporterContext(req));
|
|
267
|
+
if (event) {
|
|
268
|
+
event.waitUntil(reportPromise);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
124
271
|
await dispatchOnResult(config, result, req);
|
|
125
|
-
const
|
|
272
|
+
const bodyReadable200 = selectBodyReadable200(
|
|
273
|
+
config.delegationChallengeMode ?? "spec-401",
|
|
274
|
+
req.headers,
|
|
275
|
+
result.detectionDetail.detectionClass
|
|
276
|
+
);
|
|
277
|
+
const rendered = renderDecisionAsResponse(result, { bodyReadable200 });
|
|
126
278
|
return adaptToNextResponse(rendered, req);
|
|
127
279
|
};
|
|
128
280
|
}
|
|
@@ -133,5 +285,20 @@ async function dispatchOnResult(config, result, req) {
|
|
|
133
285
|
} catch {
|
|
134
286
|
}
|
|
135
287
|
}
|
|
288
|
+
function buildComposedContext(config) {
|
|
289
|
+
if (config.composedPolicyEnforcer) return config.composedPolicyEnforcer;
|
|
290
|
+
if (!config.projectId || !config.cedarWasmModule) return null;
|
|
291
|
+
initPolicyEvaluatorEdge(config.cedarWasmModule);
|
|
292
|
+
return makeComposedPolicyContext({
|
|
293
|
+
projectId: config.projectId,
|
|
294
|
+
fetcher: new PolicyFetcher({
|
|
295
|
+
apiBaseUrl: config.dashboardUrl ?? config.baseUrl ?? DEFAULT_DASHBOARD_URL,
|
|
296
|
+
apiKey: config.apiKey,
|
|
297
|
+
cacheTtlSeconds: config.policyCacheTtlSeconds
|
|
298
|
+
}),
|
|
299
|
+
compile: (language, source) => evaluatePolicy(language, source),
|
|
300
|
+
logger: config.debug ? consoleComposedPolicyLogger : void 0
|
|
301
|
+
});
|
|
302
|
+
}
|
|
136
303
|
|
|
137
304
|
export { withCheckpoint };
|
|
@@ -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 {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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-DAwIA4DB.mjs';
|
|
6
|
+
import { ComposedPolicyContext, TrustedDelegationRootsResolver } from './composed-policy.mjs';
|
|
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 {
|
|
63
|
+
export { CheckpointConfig, VERSION, buildReporter as _buildReporter, buildTrustedRootsResolver as _buildTrustedRootsResolver, buildVerifyOpts as _buildVerifyOpts, extractReporterContext as _extractReporterContext, withCheckpoint };
|