@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,164 @@
|
|
|
1
1
|
# @kya-os/checkpoint-nextjs
|
|
2
2
|
|
|
3
|
+
## 1.6.0 — Self-report SDK identity + runtime (version-gating)
|
|
4
|
+
|
|
5
|
+
**Minor release — additive + a fix.** `withCheckpoint`'s detection reporter now
|
|
6
|
+
self-reports `sdk: { name: '@kya-os/checkpoint-nextjs', version, runtime }` —
|
|
7
|
+
with `runtime: 'node' | 'edge'` distinguishing the two entries — so the dashboard
|
|
8
|
+
can version-gate "composed policy enforces here" on the actual installed SDK,
|
|
9
|
+
and correctly show the **edge** runtime as opt-in (composed enforcement there
|
|
10
|
+
needs `cedarWasmModule`, which detections can't observe) rather than falsely
|
|
11
|
+
"Enforcing" (the @Policy version-gating work, #3076).
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **`VERSION` was stale (`0.1.0`)** — long drifted from `package.json`. It is now
|
|
16
|
+
single-sourced (re-exported from `middleware-node`) and pinned to
|
|
17
|
+
`package.json` by a unit test, so it can't drift again. This matters because
|
|
18
|
+
`VERSION` is now the SDK version the reporter self-reports for gating; the old
|
|
19
|
+
`0.1.0` would have failed the gate for every install.
|
|
20
|
+
|
|
21
|
+
## 1.5.0 — Composed-policy (kya-os-engine) enforcement
|
|
22
|
+
|
|
23
|
+
**Minor release — additive.** Makes a customer's `/policy-compose` output enforce
|
|
24
|
+
IN-PROCESS via the shared composed-policy core
|
|
25
|
+
(`@kya-os/checkpoint-wasm-runtime/composed-policy`), byte-for-byte the same as
|
|
26
|
+
the DNS Gateway and `@kya-os/checkpoint-express@1.5.0` (engine = the detection +
|
|
27
|
+
enforcement SSOT). Without the new `projectId` config the middleware behaves
|
|
28
|
+
exactly as 1.4.0 (detection + structured policy only).
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **`projectId`** on `CheckpointConfig` — opt into composed-policy enforcement.
|
|
33
|
+
The project's policy is fetched from
|
|
34
|
+
`<dashboardUrl ?? baseUrl ?? default>/api/internal/policies/${projectId}`; when
|
|
35
|
+
it carries a deployed Cedar bundle with `engineEnforcementEnabled` on, the
|
|
36
|
+
engine decision is enforced in-process.
|
|
37
|
+
- **Shadow-first**: deployed-but-not-enforcing computes + logs divergence
|
|
38
|
+
(gated on `debug`) without acting.
|
|
39
|
+
- **Fail-open**: any compile/evaluation/fetch fault keeps the structured
|
|
40
|
+
decision — a policy fault never 5xxs.
|
|
41
|
+
- **Empty-Cedar guard**: blank source never reaches the evaluator.
|
|
42
|
+
- **`composedPolicyEnforcer`** — advanced/testing seam to inject a pre-built
|
|
43
|
+
composed-policy context.
|
|
44
|
+
- **`cedarWasmModule`** (Edge only) — the statically-imported cedar-web
|
|
45
|
+
`WebAssembly.Module` that enables composed enforcement under the Edge runtime.
|
|
46
|
+
|
|
47
|
+
### Node vs Edge
|
|
48
|
+
|
|
49
|
+
- **Node runtime (`./node`)** — composed enforcement is **default-on** (sync
|
|
50
|
+
`createPolicyEvaluator`; cedar loads lazily, never bundled to edge).
|
|
51
|
+
- **Edge runtime (`./edge`)** — **consumer-opt-in**: the seam stays inert unless
|
|
52
|
+
the host wires `cedarWasmModule`:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import cedarWasmModule from '@kya-os/checkpoint-wasm-runtime/wasm/kya-os-engine-cedar-web/kya_os_engine_bg.wasm?module';
|
|
56
|
+
export default withCheckpoint({ projectId, cedarWasmModule });
|
|
57
|
+
// next.config: experiments.asyncWebAssembly + a .wasm asset rule
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The ~2 MB cedar binary is deliberately NOT forced into every consumer's Edge
|
|
61
|
+
Middleware bundle (Vercel's plan-dependent size cap). Edge composed
|
|
62
|
+
enforcement is unproven on Vercel/webpack edge in-repo; flipping it to
|
|
63
|
+
default-on is gated on a real edge deploy measurement (follow-up). Until then,
|
|
64
|
+
edge without `cedarWasmModule` behaves exactly as 1.4.0.
|
|
65
|
+
|
|
66
|
+
### Cross-runtime parity
|
|
67
|
+
|
|
68
|
+
Built on the shared core, so the `VerifyResult → AuthorizeInput` projection
|
|
69
|
+
(category map, verified-delegation allowlist, the `mcp_i_handshake`
|
|
70
|
+
optimistic-detail override), the empty-Cedar guard, the compile cache, and the
|
|
71
|
+
act-gate are identical across the DNS Gateway, Express, and Next.js. The composed
|
|
72
|
+
seam runs BEFORE the detection reporter so the dashboard records the enforced
|
|
73
|
+
decision (matching Express). Path comes from `req.nextUrl.pathname` (query-free,
|
|
74
|
+
what verification saw); fetch base honors `dashboardUrl ?? baseUrl`.
|
|
75
|
+
|
|
76
|
+
## 1.4.0 — 2026-05-26 — Detection reporter on `withCheckpoint` (closes latent bug)
|
|
77
|
+
|
|
78
|
+
**Minor release.** Closes the same architectural gap as
|
|
79
|
+
`@kya-os/checkpoint-express@1.4.0` — `withCheckpoint` (both Node and
|
|
80
|
+
Edge variants) verified locally via WASM but had no path to ship
|
|
81
|
+
detections back to the dashboard. The setup flow currently generates
|
|
82
|
+
`withAgentShield` snippets (legacy `api-middleware.ts`, which has its
|
|
83
|
+
own reporter), so this gap was latent in nextjs. Closing it now
|
|
84
|
+
prevents the same silent-onboarding failure when customers migrate to
|
|
85
|
+
the documented canonical API.
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
|
|
89
|
+
- **`apiKey`** on `CheckpointConfig` — wires the shared reporter from
|
|
90
|
+
`@kya-os/checkpoint-wasm-runtime/reporter`. Mirrors the express
|
|
91
|
+
config exactly. Resolve from `process.env.CHECKPOINT_API_KEY`.
|
|
92
|
+
- **`baseUrl`** — override the dashboard host. Default
|
|
93
|
+
`https://kya.vouched.id`.
|
|
94
|
+
- **`debug`** — surface reporter failures via `console.warn`. Default
|
|
95
|
+
`false`.
|
|
96
|
+
|
|
97
|
+
Both `middleware-node.ts` and `middleware-edge.ts` wire the reporter
|
|
98
|
+
from the same `_buildReporter` + `_extractReporterContext` seams, so
|
|
99
|
+
Node and Edge runtimes ship byte-equivalent payloads to
|
|
100
|
+
`/api/v1/log-detection`.
|
|
101
|
+
|
|
102
|
+
## 1.3.0 — 2026-05-18
|
|
103
|
+
|
|
104
|
+
**Minor release with BREAKING removals.** Closes the deprecation cycle
|
|
105
|
+
opened in 1.1.2 via PR #2610 / AgentDetector-Deletion-1. Per project
|
|
106
|
+
semver policy this is shipped as a minor bump (not a major) because:
|
|
107
|
+
(a) the deprecation landed yesterday and was effectively one calendar
|
|
108
|
+
day, (b) the package is pre-Adobe-trial with no external pins to
|
|
109
|
+
`^1.x.0`, and (c) the workspace-wide deletion-gate test pins the absence
|
|
110
|
+
structurally so re-introduction is mechanically blocked.
|
|
111
|
+
|
|
112
|
+
### BREAKING — removals
|
|
113
|
+
|
|
114
|
+
- **`useAgentDetection` hook removed** (`src/hooks.ts`). Wrapped the
|
|
115
|
+
legacy `AgentDetector` class that has now been removed from
|
|
116
|
+
`@kya-os/checkpoint@1.1.0`. **Migration path:** use `withCheckpoint`
|
|
117
|
+
from `@kya-os/checkpoint-nextjs` for server-side middleware
|
|
118
|
+
(engine-backed, returns canonical envelopes) or import
|
|
119
|
+
`KNOWN_AGENT_PATTERNS` directly from `@kya-os/checkpoint-shared` for
|
|
120
|
+
rare client-side pattern matching.
|
|
121
|
+
- **`createWasmAgentShieldMiddleware` factory removed** along with the
|
|
122
|
+
whole `src/wasm-middleware.ts` file. It internally constructed a
|
|
123
|
+
legacy `AgentDetector` and never actually used the WASM instance for
|
|
124
|
+
detection (the `wasmInstance` arg only bumped confidence by 15%).
|
|
125
|
+
**Migration path:** `withCheckpoint` from `@kya-os/checkpoint-nextjs`
|
|
126
|
+
— engine-backed, runs the full orchestrator including envelope
|
|
127
|
+
verification.
|
|
128
|
+
- **`./wasm-middleware` subpath export removed** from `package.json`
|
|
129
|
+
`exports`. Imports of
|
|
130
|
+
`@kya-os/checkpoint-nextjs/wasm-middleware` will fail to resolve.
|
|
131
|
+
- **`instantiateWasm` helper removed** (was co-located in
|
|
132
|
+
`wasm-middleware.ts`). It was a one-line `WebAssembly.instantiate`
|
|
133
|
+
wrapper; customers loading WASM manually can call the Web API
|
|
134
|
+
directly. With `withCheckpoint`, manual WASM loading is no longer
|
|
135
|
+
needed at all — `@kya-os/checkpoint-wasm-runtime`'s loaders handle
|
|
136
|
+
resolution under Node and Edge runtimes.
|
|
137
|
+
- **`useDetectionMonitor` continues to be exported** — it accepts any
|
|
138
|
+
`DetectionResult` and never depended on `AgentDetector`. Reusable
|
|
139
|
+
with engine-backed detection results.
|
|
140
|
+
- **`middleware-wasm-100.ts` template removed** — the canonical starter
|
|
141
|
+
template is now `withCheckpoint` (see `README.md` and the templates
|
|
142
|
+
under `templates/` that already cover Node + Edge runtimes).
|
|
143
|
+
|
|
144
|
+
### Removed tests
|
|
145
|
+
|
|
146
|
+
- `src/__tests__/hooks.test.ts` — covered `useAgentDetection`
|
|
147
|
+
including the deprecation-warning regression test added in #2610.
|
|
148
|
+
- `src/__tests__/wasm-middleware.test.ts` — covered
|
|
149
|
+
`createWasmAgentShieldMiddleware` including the deprecation-warning
|
|
150
|
+
regression test added in #2610.
|
|
151
|
+
|
|
152
|
+
### Build config
|
|
153
|
+
|
|
154
|
+
- `tsup.config.ts` entry list no longer references `src/wasm-middleware.ts`.
|
|
155
|
+
|
|
156
|
+
### Coordination
|
|
157
|
+
|
|
158
|
+
Ships alongside `@kya-os/checkpoint@1.1.0` (defining package — removes
|
|
159
|
+
the `AgentDetector` class). No engine-surface change; cross-runtime
|
|
160
|
+
parity gate is unaffected.
|
|
161
|
+
|
|
3
162
|
## 1.2.0 — 2026-05-18
|
|
4
163
|
|
|
5
164
|
**Minor release** — peer to `@kya-os/checkpoint-wasm-runtime@1.4.0`.
|
|
@@ -236,7 +236,7 @@ export function createAgentShieldMiddleware(options: {
|
|
|
236
236
|
{ pattern: /openai/i, name: 'OpenAI' },
|
|
237
237
|
{ pattern: /gpt-/i, name: 'GPT' },
|
|
238
238
|
{ pattern: /copilot/i, name: 'GitHub Copilot' },
|
|
239
|
-
{ pattern: /bard/i, name: 'Google
|
|
239
|
+
{ pattern: /bard/i, name: 'Google Gemini' },
|
|
240
240
|
{ pattern: /gemini/i, name: 'Google Gemini' },
|
|
241
241
|
];
|
|
242
242
|
|
package/bin/setup-edge-wasm.js
CHANGED
|
@@ -310,7 +310,7 @@ function patternDetection(
|
|
|
310
310
|
{ pattern: /openai/i, name: 'OpenAI', confidence: 0.90 },
|
|
311
311
|
{ pattern: /gpt-crawler/i, name: 'GPT Crawler', confidence: 0.95 },
|
|
312
312
|
{ pattern: /copilot/i, name: 'GitHub Copilot', confidence: 0.85 },
|
|
313
|
-
{ pattern: /bard/i, name: 'Google
|
|
313
|
+
{ pattern: /bard/i, name: 'Google Gemini', confidence: 0.85 }, // legacy Bard UA → Gemini (brand retired 2024)
|
|
314
314
|
{ pattern: /gemini/i, name: 'Google Gemini', confidence: 0.85 },
|
|
315
315
|
{ pattern: /perplexity/i, name: 'Perplexity', confidence: 0.85 },
|
|
316
316
|
];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { ChallengeEnvelopeMode } from '@kya-os/checkpoint-shared';
|
|
2
3
|
import { EnforcementDecision } from './api-client.mjs';
|
|
3
|
-
import '@kya-os/checkpoint-shared';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* API-based AgentShield Middleware for Next.js
|
|
@@ -76,6 +76,14 @@ interface CheckpointApiMiddlewareConfig {
|
|
|
76
76
|
* @default 'instruct'
|
|
77
77
|
*/
|
|
78
78
|
redirectMode?: 'instruct' | 'http';
|
|
79
|
+
/**
|
|
80
|
+
* Challenge-emission envelope mode (draft-kya-http-02 §8.1 fetcher-200).
|
|
81
|
+
* `negotiated` serves a body-readable HTTP 200 instruct response to
|
|
82
|
+
* cooperative agents (so fetchers that drop 4xx bodies can read it),
|
|
83
|
+
* keeping the spec 401 for everyone else. Defaults to `spec-401`.
|
|
84
|
+
* A cooperative-UX bridge, NOT an access control.
|
|
85
|
+
*/
|
|
86
|
+
delegationChallengeMode?: ChallengeEnvelopeMode;
|
|
79
87
|
/**
|
|
80
88
|
* Custom blocked response
|
|
81
89
|
*/
|
package/dist/api-middleware.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { ChallengeEnvelopeMode } from '@kya-os/checkpoint-shared';
|
|
2
3
|
import { EnforcementDecision } from './api-client.js';
|
|
3
|
-
import '@kya-os/checkpoint-shared';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* API-based AgentShield Middleware for Next.js
|
|
@@ -76,6 +76,14 @@ interface CheckpointApiMiddlewareConfig {
|
|
|
76
76
|
* @default 'instruct'
|
|
77
77
|
*/
|
|
78
78
|
redirectMode?: 'instruct' | 'http';
|
|
79
|
+
/**
|
|
80
|
+
* Challenge-emission envelope mode (draft-kya-http-02 §8.1 fetcher-200).
|
|
81
|
+
* `negotiated` serves a body-readable HTTP 200 instruct response to
|
|
82
|
+
* cooperative agents (so fetchers that drop 4xx bodies can read it),
|
|
83
|
+
* keeping the spec 401 for everyone else. Defaults to `spec-401`.
|
|
84
|
+
* A cooperative-UX bridge, NOT an access control.
|
|
85
|
+
*/
|
|
86
|
+
delegationChallengeMode?: ChallengeEnvelopeMode;
|
|
79
87
|
/**
|
|
80
88
|
* Custom blocked response
|
|
81
89
|
*/
|
package/dist/api-middleware.js
CHANGED
|
@@ -237,7 +237,7 @@ function safeHostname(url) {
|
|
|
237
237
|
// src/responses/agent-instruction.ts
|
|
238
238
|
var MCP_I_DOCS_URL = "https://docs.knowthat.ai/mcp-i/getting-started";
|
|
239
239
|
var DEFAULT_CONNECT_PATH = "/connect";
|
|
240
|
-
function buildAgentInstructionResponse(request, decision, redirectUrl) {
|
|
240
|
+
function buildAgentInstructionResponse(request, decision, redirectUrl, options) {
|
|
241
241
|
const resolved = resolveUrl(redirectUrl ?? DEFAULT_CONNECT_PATH, request.url);
|
|
242
242
|
const agentName = decision.agentName || decision.agentType || "unknown";
|
|
243
243
|
if (!resolved.searchParams.has("agent")) {
|
|
@@ -288,8 +288,11 @@ It only takes a moment and you won't need to do it again. Once you're done, ask
|
|
|
288
288
|
confidence: decision.confidence
|
|
289
289
|
}
|
|
290
290
|
};
|
|
291
|
-
const
|
|
292
|
-
response.
|
|
291
|
+
const bodyReadable200 = options?.bodyReadable200 ?? false;
|
|
292
|
+
const response = server.NextResponse.json(body, { status: bodyReadable200 ? 200 : 401 });
|
|
293
|
+
if (!bodyReadable200) {
|
|
294
|
+
response.headers.set("WWW-Authenticate", `KYA realm="api", authorization_uri="${authUrl}"`);
|
|
295
|
+
}
|
|
293
296
|
response.headers.set(
|
|
294
297
|
"Link",
|
|
295
298
|
`<${authUrl}>; rel="kya-authorize", <${MCP_I_DOCS_URL}>; rel="help"`
|
|
@@ -482,7 +485,14 @@ function withCheckpointApi(config = {}) {
|
|
|
482
485
|
return buildRedirectResponse(request, decision, config);
|
|
483
486
|
}
|
|
484
487
|
const targetUrl = config.redirectUrl || decision.redirectUrl;
|
|
485
|
-
|
|
488
|
+
const bodyReadable200 = checkpointShared.selectBodyReadable200(
|
|
489
|
+
config.delegationChallengeMode ?? "spec-401",
|
|
490
|
+
request.headers,
|
|
491
|
+
result.data.detection?.detectionClass
|
|
492
|
+
);
|
|
493
|
+
return buildAgentInstructionResponse(request, decision, targetUrl, {
|
|
494
|
+
bodyReadable200
|
|
495
|
+
});
|
|
486
496
|
}
|
|
487
497
|
case "challenge": {
|
|
488
498
|
return buildRedirectResponse(request, decision, config);
|
package/dist/api-middleware.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
|
-
import { matchPath } from '@kya-os/checkpoint-shared';
|
|
2
|
+
import { selectBodyReadable200, matchPath } from '@kya-os/checkpoint-shared';
|
|
3
3
|
|
|
4
4
|
// src/api-middleware.ts
|
|
5
5
|
|
|
@@ -235,7 +235,7 @@ function safeHostname(url) {
|
|
|
235
235
|
// src/responses/agent-instruction.ts
|
|
236
236
|
var MCP_I_DOCS_URL = "https://docs.knowthat.ai/mcp-i/getting-started";
|
|
237
237
|
var DEFAULT_CONNECT_PATH = "/connect";
|
|
238
|
-
function buildAgentInstructionResponse(request, decision, redirectUrl) {
|
|
238
|
+
function buildAgentInstructionResponse(request, decision, redirectUrl, options) {
|
|
239
239
|
const resolved = resolveUrl(redirectUrl ?? DEFAULT_CONNECT_PATH, request.url);
|
|
240
240
|
const agentName = decision.agentName || decision.agentType || "unknown";
|
|
241
241
|
if (!resolved.searchParams.has("agent")) {
|
|
@@ -286,8 +286,11 @@ It only takes a moment and you won't need to do it again. Once you're done, ask
|
|
|
286
286
|
confidence: decision.confidence
|
|
287
287
|
}
|
|
288
288
|
};
|
|
289
|
-
const
|
|
290
|
-
response.
|
|
289
|
+
const bodyReadable200 = options?.bodyReadable200 ?? false;
|
|
290
|
+
const response = NextResponse.json(body, { status: bodyReadable200 ? 200 : 401 });
|
|
291
|
+
if (!bodyReadable200) {
|
|
292
|
+
response.headers.set("WWW-Authenticate", `KYA realm="api", authorization_uri="${authUrl}"`);
|
|
293
|
+
}
|
|
291
294
|
response.headers.set(
|
|
292
295
|
"Link",
|
|
293
296
|
`<${authUrl}>; rel="kya-authorize", <${MCP_I_DOCS_URL}>; rel="help"`
|
|
@@ -480,7 +483,14 @@ function withCheckpointApi(config = {}) {
|
|
|
480
483
|
return buildRedirectResponse(request, decision, config);
|
|
481
484
|
}
|
|
482
485
|
const targetUrl = config.redirectUrl || decision.redirectUrl;
|
|
483
|
-
|
|
486
|
+
const bodyReadable200 = selectBodyReadable200(
|
|
487
|
+
config.delegationChallengeMode ?? "spec-401",
|
|
488
|
+
request.headers,
|
|
489
|
+
result.data.detection?.detectionClass
|
|
490
|
+
);
|
|
491
|
+
return buildAgentInstructionResponse(request, decision, targetUrl, {
|
|
492
|
+
bodyReadable200
|
|
493
|
+
});
|
|
484
494
|
}
|
|
485
495
|
case "challenge": {
|
|
486
496
|
return buildRedirectResponse(request, decision, config);
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { ComposedPolicyCompile } from '@kya-os/checkpoint-wasm-runtime/composed-policy';
|
|
2
|
+
import { Decision, VerifyResult } from '@kya-os/checkpoint-wasm-runtime/engine';
|
|
3
|
+
import { PolicyFetcher } from '@kya-os/checkpoint-shared';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Composed-policy (kya-os-engine) wiring for the Next.js middleware.
|
|
7
|
+
*
|
|
8
|
+
* A THIN layer on top of the shared core
|
|
9
|
+
* (`@kya-os/checkpoint-wasm-runtime/composed-policy`): the core owns all the
|
|
10
|
+
* logic (empty-Cedar deny-all guard, compile-once LRU cache, act-gate,
|
|
11
|
+
* divergence, and the `verifyResultToAuthorizeInput` parity projection), so this
|
|
12
|
+
* module only fetches the project's `PolicyConfig`, drives the core once per
|
|
13
|
+
* request, maps the tagged outcome to a decision-swap signal, and logs
|
|
14
|
+
* (debug-gated). Runtime-neutral — the caller injects the compiler:
|
|
15
|
+
* - Node: `(language, source) => createPolicyEvaluator(source)` (`./policy`)
|
|
16
|
+
* - Edge: `(language, source) => evaluatePolicy(language, source)` (`./policy-edge`)
|
|
17
|
+
*
|
|
18
|
+
* This is the SDK sibling of `@kya-os/checkpoint-express`'s composed-policy
|
|
19
|
+
* wiring; both consume the same core so the DNS Gateway, Express, and Next.js
|
|
20
|
+
* produce the same decision for the same composed Cedar bundle.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Dashboard the composed-policy fetch defaults to when no base URL is set. */
|
|
24
|
+
declare const DEFAULT_DASHBOARD_URL = "https://kya.vouched.id";
|
|
25
|
+
/**
|
|
26
|
+
* Telemetry sink. The shadow-divergence signal is the go/no-go instrument for
|
|
27
|
+
* flipping a project to ENFORCE. Defaults to a no-op so the SDK never logs
|
|
28
|
+
* unprompted — `withCheckpoint({ debug: true })` wires the console logger.
|
|
29
|
+
*/
|
|
30
|
+
interface ComposedPolicyLogger {
|
|
31
|
+
shadowDivergence(info: ShadowDivergenceInfo): void;
|
|
32
|
+
evaluationError(projectId: string, error: unknown): void;
|
|
33
|
+
}
|
|
34
|
+
interface ShadowDivergenceInfo {
|
|
35
|
+
projectId: string;
|
|
36
|
+
path?: string;
|
|
37
|
+
engineDecision: Decision['kind'];
|
|
38
|
+
structuredDecision: Decision['kind'];
|
|
39
|
+
detectionClass: string;
|
|
40
|
+
verificationMethod?: string;
|
|
41
|
+
confidence: number;
|
|
42
|
+
agentName?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Result of one composed-policy evaluation: the decision to enforce + whether it replaced the baseline. */
|
|
45
|
+
interface ComposedPolicyApplyResult {
|
|
46
|
+
decision: Decision;
|
|
47
|
+
acted: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* SSOT-served revision pin (sha256 of the evaluated bundle, byte-identical
|
|
50
|
+
* to `policy_revisions.composed_blob_hash`) — set ONLY when `acted`, echoed
|
|
51
|
+
* from `PolicyConfig.policySourceHash` (#3246 PR5). Absent on structured
|
|
52
|
+
* decisions and when the dashboard predates the field.
|
|
53
|
+
*/
|
|
54
|
+
composedBlobHash?: string;
|
|
55
|
+
}
|
|
56
|
+
interface ComposedPolicyContext {
|
|
57
|
+
/**
|
|
58
|
+
* Evaluate the project's composed policy for one request. Reads
|
|
59
|
+
* `result.decision` as the structured baseline for the divergence comparison;
|
|
60
|
+
* the caller applies `acted ? decision` afterwards. Never throws.
|
|
61
|
+
*/
|
|
62
|
+
apply(result: VerifyResult, path: string | undefined): Promise<ComposedPolicyApplyResult>;
|
|
63
|
+
/**
|
|
64
|
+
* Resolve the org's trusted Layer-2 delegation root DIDs from the project's
|
|
65
|
+
* policy payload (P2 / DR-1). The middleware forwards these into the engine's
|
|
66
|
+
* `ContextSpec.trustedDelegationRoots` on delegation requests. Optional so an
|
|
67
|
+
* injected `composedPolicyEnforcer` (advanced/testing) need not implement it —
|
|
68
|
+
* absence ⇒ no roots ⇒ the engine's open default. Never throws (a fetch fault
|
|
69
|
+
* resolves to `[]`).
|
|
70
|
+
*/
|
|
71
|
+
trustedDelegationRoots?(): Promise<string[]>;
|
|
72
|
+
}
|
|
73
|
+
interface MakeComposedPolicyContextOptions {
|
|
74
|
+
projectId: string;
|
|
75
|
+
/** Fetches the project's `PolicyConfig` (carries the composed-policy fields). */
|
|
76
|
+
fetcher: PolicyFetcher;
|
|
77
|
+
/** Target compiler — Node `createPolicyEvaluator`-backed or edge `evaluatePolicy`. */
|
|
78
|
+
compile: ComposedPolicyCompile;
|
|
79
|
+
logger?: ComposedPolicyLogger;
|
|
80
|
+
cacheMax?: number;
|
|
81
|
+
}
|
|
82
|
+
declare function makeComposedPolicyContext(opts: MakeComposedPolicyContextOptions): ComposedPolicyContext;
|
|
83
|
+
/**
|
|
84
|
+
* Resolves the org's trusted delegation roots (empty when none). Built per
|
|
85
|
+
* middleware factory from whatever source can supply them — the composed
|
|
86
|
+
* context's accessor, or (when no context exists, e.g. Edge without
|
|
87
|
+
* `cedarWasmModule`) a `projectId`-only policy fetch.
|
|
88
|
+
*/
|
|
89
|
+
type TrustedDelegationRootsResolver = () => Promise<string[]>;
|
|
90
|
+
/**
|
|
91
|
+
* Resolve the org trusted-delegation roots to inject into the engine for THIS
|
|
92
|
+
* request (P2 / DR-1). Returns `undefined` — leaving `VerifyRequestOpts`
|
|
93
|
+
* byte-identical to pre-P2 — unless (a) a resolver is configured and (b) the
|
|
94
|
+
* request actually carries a delegation-proof header (so the human /
|
|
95
|
+
* no-delegation hot path never pays the early policy fetch). Shared by the Node
|
|
96
|
+
* and Edge middleware entries.
|
|
97
|
+
*/
|
|
98
|
+
declare function resolveTrustedDelegationRootsForRequest(resolver: TrustedDelegationRootsResolver | null, headers: Headers): Promise<string[] | undefined>;
|
|
99
|
+
/**
|
|
100
|
+
* The per-request seam shared by both middleware entries. Evaluates the composed
|
|
101
|
+
* policy and swaps `result.decision` IN PLACE when the engine acts — so the
|
|
102
|
+
* swap happens BEFORE the detection reporter fires and before
|
|
103
|
+
* `renderDecisionAsResponse`, exactly like the Express runtime (the reporter
|
|
104
|
+
* records the enforced decision). Fail-OPEN: any fault keeps the structured
|
|
105
|
+
* decision; a policy fault never breaks the response.
|
|
106
|
+
*/
|
|
107
|
+
declare function applyComposedPolicy(context: ComposedPolicyContext | null, result: VerifyResult, path: string | undefined): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Debug logger mirroring the worker's `console.warn`/`console.error` shadow +
|
|
110
|
+
* fail-open telemetry. Wired by `withCheckpoint` only when `config.debug` is
|
|
111
|
+
* set, so the default SDK posture stays silent.
|
|
112
|
+
*/
|
|
113
|
+
declare const consoleComposedPolicyLogger: ComposedPolicyLogger;
|
|
114
|
+
|
|
115
|
+
export { type ComposedPolicyApplyResult, type ComposedPolicyContext, type ComposedPolicyLogger, DEFAULT_DASHBOARD_URL, type MakeComposedPolicyContextOptions, type ShadowDivergenceInfo, type TrustedDelegationRootsResolver, applyComposedPolicy, consoleComposedPolicyLogger, makeComposedPolicyContext, resolveTrustedDelegationRootsForRequest };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { ComposedPolicyCompile } from '@kya-os/checkpoint-wasm-runtime/composed-policy';
|
|
2
|
+
import { Decision, VerifyResult } from '@kya-os/checkpoint-wasm-runtime/engine';
|
|
3
|
+
import { PolicyFetcher } from '@kya-os/checkpoint-shared';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Composed-policy (kya-os-engine) wiring for the Next.js middleware.
|
|
7
|
+
*
|
|
8
|
+
* A THIN layer on top of the shared core
|
|
9
|
+
* (`@kya-os/checkpoint-wasm-runtime/composed-policy`): the core owns all the
|
|
10
|
+
* logic (empty-Cedar deny-all guard, compile-once LRU cache, act-gate,
|
|
11
|
+
* divergence, and the `verifyResultToAuthorizeInput` parity projection), so this
|
|
12
|
+
* module only fetches the project's `PolicyConfig`, drives the core once per
|
|
13
|
+
* request, maps the tagged outcome to a decision-swap signal, and logs
|
|
14
|
+
* (debug-gated). Runtime-neutral — the caller injects the compiler:
|
|
15
|
+
* - Node: `(language, source) => createPolicyEvaluator(source)` (`./policy`)
|
|
16
|
+
* - Edge: `(language, source) => evaluatePolicy(language, source)` (`./policy-edge`)
|
|
17
|
+
*
|
|
18
|
+
* This is the SDK sibling of `@kya-os/checkpoint-express`'s composed-policy
|
|
19
|
+
* wiring; both consume the same core so the DNS Gateway, Express, and Next.js
|
|
20
|
+
* produce the same decision for the same composed Cedar bundle.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Dashboard the composed-policy fetch defaults to when no base URL is set. */
|
|
24
|
+
declare const DEFAULT_DASHBOARD_URL = "https://kya.vouched.id";
|
|
25
|
+
/**
|
|
26
|
+
* Telemetry sink. The shadow-divergence signal is the go/no-go instrument for
|
|
27
|
+
* flipping a project to ENFORCE. Defaults to a no-op so the SDK never logs
|
|
28
|
+
* unprompted — `withCheckpoint({ debug: true })` wires the console logger.
|
|
29
|
+
*/
|
|
30
|
+
interface ComposedPolicyLogger {
|
|
31
|
+
shadowDivergence(info: ShadowDivergenceInfo): void;
|
|
32
|
+
evaluationError(projectId: string, error: unknown): void;
|
|
33
|
+
}
|
|
34
|
+
interface ShadowDivergenceInfo {
|
|
35
|
+
projectId: string;
|
|
36
|
+
path?: string;
|
|
37
|
+
engineDecision: Decision['kind'];
|
|
38
|
+
structuredDecision: Decision['kind'];
|
|
39
|
+
detectionClass: string;
|
|
40
|
+
verificationMethod?: string;
|
|
41
|
+
confidence: number;
|
|
42
|
+
agentName?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Result of one composed-policy evaluation: the decision to enforce + whether it replaced the baseline. */
|
|
45
|
+
interface ComposedPolicyApplyResult {
|
|
46
|
+
decision: Decision;
|
|
47
|
+
acted: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* SSOT-served revision pin (sha256 of the evaluated bundle, byte-identical
|
|
50
|
+
* to `policy_revisions.composed_blob_hash`) — set ONLY when `acted`, echoed
|
|
51
|
+
* from `PolicyConfig.policySourceHash` (#3246 PR5). Absent on structured
|
|
52
|
+
* decisions and when the dashboard predates the field.
|
|
53
|
+
*/
|
|
54
|
+
composedBlobHash?: string;
|
|
55
|
+
}
|
|
56
|
+
interface ComposedPolicyContext {
|
|
57
|
+
/**
|
|
58
|
+
* Evaluate the project's composed policy for one request. Reads
|
|
59
|
+
* `result.decision` as the structured baseline for the divergence comparison;
|
|
60
|
+
* the caller applies `acted ? decision` afterwards. Never throws.
|
|
61
|
+
*/
|
|
62
|
+
apply(result: VerifyResult, path: string | undefined): Promise<ComposedPolicyApplyResult>;
|
|
63
|
+
/**
|
|
64
|
+
* Resolve the org's trusted Layer-2 delegation root DIDs from the project's
|
|
65
|
+
* policy payload (P2 / DR-1). The middleware forwards these into the engine's
|
|
66
|
+
* `ContextSpec.trustedDelegationRoots` on delegation requests. Optional so an
|
|
67
|
+
* injected `composedPolicyEnforcer` (advanced/testing) need not implement it —
|
|
68
|
+
* absence ⇒ no roots ⇒ the engine's open default. Never throws (a fetch fault
|
|
69
|
+
* resolves to `[]`).
|
|
70
|
+
*/
|
|
71
|
+
trustedDelegationRoots?(): Promise<string[]>;
|
|
72
|
+
}
|
|
73
|
+
interface MakeComposedPolicyContextOptions {
|
|
74
|
+
projectId: string;
|
|
75
|
+
/** Fetches the project's `PolicyConfig` (carries the composed-policy fields). */
|
|
76
|
+
fetcher: PolicyFetcher;
|
|
77
|
+
/** Target compiler — Node `createPolicyEvaluator`-backed or edge `evaluatePolicy`. */
|
|
78
|
+
compile: ComposedPolicyCompile;
|
|
79
|
+
logger?: ComposedPolicyLogger;
|
|
80
|
+
cacheMax?: number;
|
|
81
|
+
}
|
|
82
|
+
declare function makeComposedPolicyContext(opts: MakeComposedPolicyContextOptions): ComposedPolicyContext;
|
|
83
|
+
/**
|
|
84
|
+
* Resolves the org's trusted delegation roots (empty when none). Built per
|
|
85
|
+
* middleware factory from whatever source can supply them — the composed
|
|
86
|
+
* context's accessor, or (when no context exists, e.g. Edge without
|
|
87
|
+
* `cedarWasmModule`) a `projectId`-only policy fetch.
|
|
88
|
+
*/
|
|
89
|
+
type TrustedDelegationRootsResolver = () => Promise<string[]>;
|
|
90
|
+
/**
|
|
91
|
+
* Resolve the org trusted-delegation roots to inject into the engine for THIS
|
|
92
|
+
* request (P2 / DR-1). Returns `undefined` — leaving `VerifyRequestOpts`
|
|
93
|
+
* byte-identical to pre-P2 — unless (a) a resolver is configured and (b) the
|
|
94
|
+
* request actually carries a delegation-proof header (so the human /
|
|
95
|
+
* no-delegation hot path never pays the early policy fetch). Shared by the Node
|
|
96
|
+
* and Edge middleware entries.
|
|
97
|
+
*/
|
|
98
|
+
declare function resolveTrustedDelegationRootsForRequest(resolver: TrustedDelegationRootsResolver | null, headers: Headers): Promise<string[] | undefined>;
|
|
99
|
+
/**
|
|
100
|
+
* The per-request seam shared by both middleware entries. Evaluates the composed
|
|
101
|
+
* policy and swaps `result.decision` IN PLACE when the engine acts — so the
|
|
102
|
+
* swap happens BEFORE the detection reporter fires and before
|
|
103
|
+
* `renderDecisionAsResponse`, exactly like the Express runtime (the reporter
|
|
104
|
+
* records the enforced decision). Fail-OPEN: any fault keeps the structured
|
|
105
|
+
* decision; a policy fault never breaks the response.
|
|
106
|
+
*/
|
|
107
|
+
declare function applyComposedPolicy(context: ComposedPolicyContext | null, result: VerifyResult, path: string | undefined): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Debug logger mirroring the worker's `console.warn`/`console.error` shadow +
|
|
110
|
+
* fail-open telemetry. Wired by `withCheckpoint` only when `config.debug` is
|
|
111
|
+
* set, so the default SDK posture stays silent.
|
|
112
|
+
*/
|
|
113
|
+
declare const consoleComposedPolicyLogger: ComposedPolicyLogger;
|
|
114
|
+
|
|
115
|
+
export { type ComposedPolicyApplyResult, type ComposedPolicyContext, type ComposedPolicyLogger, DEFAULT_DASHBOARD_URL, type MakeComposedPolicyContextOptions, type ShadowDivergenceInfo, type TrustedDelegationRootsResolver, applyComposedPolicy, consoleComposedPolicyLogger, makeComposedPolicyContext, resolveTrustedDelegationRootsForRequest };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var composedPolicy = require('@kya-os/checkpoint-wasm-runtime/composed-policy');
|
|
4
|
+
var checkpointShared = require('@kya-os/checkpoint-shared');
|
|
5
|
+
|
|
6
|
+
// src/composed-policy.ts
|
|
7
|
+
var DEFAULT_DASHBOARD_URL = "https://kya.vouched.id";
|
|
8
|
+
var NOOP_LOGGER = {
|
|
9
|
+
shadowDivergence: () => {
|
|
10
|
+
},
|
|
11
|
+
evaluationError: () => {
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
function makeComposedPolicyContext(opts) {
|
|
15
|
+
const { projectId, fetcher } = opts;
|
|
16
|
+
const cache = composedPolicy.makeComposedPolicyCache({ compile: opts.compile, cacheMax: opts.cacheMax });
|
|
17
|
+
const logger = opts.logger ?? NOOP_LOGGER;
|
|
18
|
+
return {
|
|
19
|
+
async apply(result, path) {
|
|
20
|
+
const structured = { decision: result.decision, acted: false };
|
|
21
|
+
const policy = await fetcher.getPolicy(projectId);
|
|
22
|
+
const outcome = await composedPolicy.evaluateComposedPolicy({
|
|
23
|
+
cache,
|
|
24
|
+
projectId,
|
|
25
|
+
flags: {
|
|
26
|
+
policyLanguage: policy.policyLanguage,
|
|
27
|
+
policySourceText: policy.policySourceText,
|
|
28
|
+
engineEnforcementEnabled: policy.engineEnforcementEnabled,
|
|
29
|
+
enabled: policy.enabled
|
|
30
|
+
},
|
|
31
|
+
authorizeInput: composedPolicy.verifyResultToAuthorizeInput(result, { tenantId: projectId, path }),
|
|
32
|
+
baselineDecisionKind: result.decision.kind
|
|
33
|
+
});
|
|
34
|
+
if ((outcome.status === "acting" || outcome.status === "shadow") && outcome.diverged) {
|
|
35
|
+
logger.shadowDivergence({
|
|
36
|
+
projectId,
|
|
37
|
+
path,
|
|
38
|
+
engineDecision: outcome.engineDecision.kind,
|
|
39
|
+
structuredDecision: result.decision.kind,
|
|
40
|
+
detectionClass: result.detectionDetail.detectionClass.type,
|
|
41
|
+
verificationMethod: result.detectionDetail.verificationMethod,
|
|
42
|
+
confidence: result.detectionDetail.confidence,
|
|
43
|
+
agentName: result.detectionDetail.detectedAgent?.name
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (outcome.status === "error") {
|
|
47
|
+
logger.evaluationError(projectId, outcome.error);
|
|
48
|
+
return structured;
|
|
49
|
+
}
|
|
50
|
+
if (outcome.status === "acting") {
|
|
51
|
+
return {
|
|
52
|
+
decision: outcome.engineDecision,
|
|
53
|
+
acted: true,
|
|
54
|
+
// Echo the SSOT revision pin with the decision it produced (#3246
|
|
55
|
+
// PR5). Conditional spread: absent upstream stays absent here.
|
|
56
|
+
...policy.policySourceHash ? { composedBlobHash: policy.policySourceHash } : {}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return structured;
|
|
60
|
+
},
|
|
61
|
+
async trustedDelegationRoots() {
|
|
62
|
+
const policy = await fetcher.getPolicy(projectId);
|
|
63
|
+
return policy.trustedDelegationRoots ?? [];
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async function resolveTrustedDelegationRootsForRequest(resolver, headers) {
|
|
68
|
+
if (!resolver) return void 0;
|
|
69
|
+
if (!checkpointShared.requestCarriesDelegationProof(headers)) return void 0;
|
|
70
|
+
const roots = await resolver();
|
|
71
|
+
return roots.length > 0 ? roots : void 0;
|
|
72
|
+
}
|
|
73
|
+
async function applyComposedPolicy(context, result, path) {
|
|
74
|
+
if (!context) return;
|
|
75
|
+
try {
|
|
76
|
+
const outcome = await context.apply(result, path);
|
|
77
|
+
if (outcome.acted) {
|
|
78
|
+
result.decision = outcome.decision;
|
|
79
|
+
if (outcome.composedBlobHash) {
|
|
80
|
+
result.composedBlobHash = outcome.composedBlobHash;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
var consoleComposedPolicyLogger = {
|
|
87
|
+
shadowDivergence(info) {
|
|
88
|
+
console.warn("[checkpoint/composed-policy] shadow-divergence", info);
|
|
89
|
+
},
|
|
90
|
+
evaluationError(projectId, error) {
|
|
91
|
+
console.error(
|
|
92
|
+
`[checkpoint/composed-policy] evaluation failed for ${projectId}; using structured decision:`,
|
|
93
|
+
error
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
exports.DEFAULT_DASHBOARD_URL = DEFAULT_DASHBOARD_URL;
|
|
99
|
+
exports.applyComposedPolicy = applyComposedPolicy;
|
|
100
|
+
exports.consoleComposedPolicyLogger = consoleComposedPolicyLogger;
|
|
101
|
+
exports.makeComposedPolicyContext = makeComposedPolicyContext;
|
|
102
|
+
exports.resolveTrustedDelegationRootsForRequest = resolveTrustedDelegationRootsForRequest;
|