@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
|
@@ -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-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 {
|
|
63
|
+
export { CheckpointConfig, VERSION, buildReporter as _buildReporter, buildTrustedRootsResolver as _buildTrustedRootsResolver, buildVerifyOpts as _buildVerifyOpts, extractReporterContext as _extractReporterContext, withCheckpoint };
|
package/dist/middleware-node.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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;
|
package/dist/middleware-node.mjs
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
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 };
|
package/dist/middleware.d.mts
CHANGED
|
@@ -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 };
|
package/dist/middleware.d.ts
CHANGED
|
@@ -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 };
|
package/dist/middleware.js
CHANGED
|
@@ -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;
|
package/dist/middleware.mjs
CHANGED
|
@@ -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 };
|