@kairosinternational/watchman-nextjs 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +32 -9
- package/dist/middleware.js.map +1 -1
- package/dist/route-handler.d.ts.map +1 -1
- package/dist/route-handler.js +11 -0
- package/dist/route-handler.js.map +1 -1
- package/dist/telemetry-session.d.ts +37 -0
- package/dist/telemetry-session.d.ts.map +1 -0
- package/dist/telemetry-session.js +80 -0
- package/dist/telemetry-session.js.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/kairosinternational-watchman-nextjs-0.1.1.tgz +0 -0
- package/kairosinternational-watchman-nextjs-0.1.2.tgz +0 -0
- package/package.json +2 -2
- package/src/config.ts +4 -1
- package/src/index.ts +1 -0
- package/src/middleware.ts +38 -7
- package/src/route-handler.ts +16 -0
- package/src/telemetry-session.ts +133 -0
- package/src/types.ts +18 -0
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { WatchmanMiddlewareConfig } from './types.js';
|
|
2
|
-
export declare const DEFAULT_MIDDLEWARE_CONFIG: Required<Omit<WatchmanMiddlewareConfig, 'projectRoot' | 'onFinding' | 'onColdStartComplete'>>;
|
|
2
|
+
export declare const DEFAULT_MIDDLEWARE_CONFIG: Required<Omit<WatchmanMiddlewareConfig, 'projectRoot' | 'onFinding' | 'onColdStartComplete' | 'telemetry'>>;
|
|
3
3
|
export declare function resolveConfig(user?: WatchmanMiddlewareConfig): WatchmanMiddlewareConfig & typeof DEFAULT_MIDDLEWARE_CONFIG;
|
|
4
4
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE3D,eAAO,MAAM,yBAAyB,EAAE,QAAQ,CAC9C,IAAI,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE3D,eAAO,MAAM,yBAAyB,EAAE,QAAQ,CAC9C,IAAI,CACF,wBAAwB,EACxB,aAAa,GAAG,WAAW,GAAG,qBAAqB,GAAG,WAAW,CAClE,CAUF,CAAC;AAEF,wBAAgB,aAAa,CAC3B,IAAI,GAAE,wBAA6B,GAClC,wBAAwB,GAAG,OAAO,yBAAyB,CAM7D"}
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";;;AAkBA,sCAQC;AAxBY,QAAA,yBAAyB,GAKlC;IACF,OAAO,EAAE,UAAU;IACnB,MAAM,EAAE,MAAM;IACd,QAAQ,EAAE,IAAI;IACd,WAAW,EAAE,IAAI;IACjB,OAAO,EAAE,IAAI;IACb,aAAa,EAAE,IAAI;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,SAAS,CAAC;IAChD,KAAK,EAAE,KAAK;CACb,CAAC;AAEF,SAAgB,aAAa,CAC3B,OAAiC,EAAE;IAEnC,OAAO;QACL,GAAG,iCAAyB;QAC5B,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE;QAC9C,GAAG,IAAI;KACR,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { withWatchman, scanRequest, scanString } from './middleware.js';
|
|
2
2
|
export { withWatchmanRoute } from './route-handler.js';
|
|
3
3
|
export { DEFAULT_MIDDLEWARE_CONFIG, resolveConfig } from './config.js';
|
|
4
|
-
export type { WatchmanMiddlewareConfig, RequestFinding, ScanOutcome, ScanTarget, Severity, Finding, WatchmanReport, } from './types.js';
|
|
4
|
+
export type { WatchmanMiddlewareConfig, NextjsTelemetryConfig, RequestFinding, ScanOutcome, ScanTarget, Severity, Finding, WatchmanReport, } from './types.js';
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACvE,YAAY,EACV,wBAAwB,EACxB,cAAc,EACd,WAAW,EACX,UAAU,EACV,QAAQ,EACR,OAAO,EACP,cAAc,GACf,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACvE,YAAY,EACV,wBAAwB,EACxB,qBAAqB,EACrB,cAAc,EACd,WAAW,EACX,UAAU,EACV,QAAQ,EACR,OAAO,EACP,cAAc,GACf,MAAM,YAAY,CAAC"}
|
package/dist/middleware.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAOlF,OAAO,EAAiB,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAEvE,OAAO,KAAK,EACV,wBAAwB,EACxB,cAAc,EACd,WAAW,EACX,UAAU,EACX,MAAM,YAAY,CAAC;AAkCpB;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,cAAc,EAAE,CAiC9E;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,wBAAwB,GAAG,OAAO,yBAAyB,GAClE,OAAO,CAAC,WAAW,CAAC,CAsCtB;AA8DD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,UAAU,GAAE,wBAA6B,GACxC,cAAc,CA+EhB"}
|
package/dist/middleware.js
CHANGED
|
@@ -3,10 +3,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.scanString = scanString;
|
|
4
4
|
exports.scanRequest = scanRequest;
|
|
5
5
|
exports.withWatchman = withWatchman;
|
|
6
|
-
const node_crypto_1 = require("node:crypto");
|
|
7
6
|
const server_1 = require("next/server");
|
|
8
|
-
const
|
|
7
|
+
const patterns_1 = require("@kairosinternational/watchman/patterns");
|
|
9
8
|
const config_js_1 = require("./config.js");
|
|
9
|
+
const telemetry_session_js_1 = require("./telemetry-session.js");
|
|
10
|
+
function randomId() {
|
|
11
|
+
// Use Web Crypto API (Edge-compatible) with Node.js fallback
|
|
12
|
+
if (typeof globalThis.crypto?.randomUUID === 'function') {
|
|
13
|
+
return globalThis.crypto.randomUUID();
|
|
14
|
+
}
|
|
15
|
+
// Fallback for older runtimes
|
|
16
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
17
|
+
const r = (Math.random() * 16) | 0;
|
|
18
|
+
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
10
21
|
const SEVERITY_ORDER = {
|
|
11
22
|
critical: 4,
|
|
12
23
|
high: 3,
|
|
@@ -30,10 +41,10 @@ function scanString(input, target) {
|
|
|
30
41
|
const findings = [];
|
|
31
42
|
if (!input)
|
|
32
43
|
return findings;
|
|
33
|
-
for (const sig of
|
|
44
|
+
for (const sig of patterns_1.INJECTION_SIGNATURES) {
|
|
34
45
|
if (sig.pattern.test(input)) {
|
|
35
46
|
findings.push({
|
|
36
|
-
id: (
|
|
47
|
+
id: randomId(),
|
|
37
48
|
target,
|
|
38
49
|
rule: 'prompt-injection',
|
|
39
50
|
severity: sig.severity,
|
|
@@ -43,10 +54,10 @@ function scanString(input, target) {
|
|
|
43
54
|
});
|
|
44
55
|
}
|
|
45
56
|
}
|
|
46
|
-
for (const sig of
|
|
57
|
+
for (const sig of patterns_1.SECRET_SIGNATURES) {
|
|
47
58
|
if (sig.pattern.test(input)) {
|
|
48
59
|
findings.push({
|
|
49
|
-
id: (
|
|
60
|
+
id: randomId(),
|
|
50
61
|
target,
|
|
51
62
|
rule: 'known-patterns',
|
|
52
63
|
severity: 'critical',
|
|
@@ -106,7 +117,9 @@ function runColdStartScan(config) {
|
|
|
106
117
|
if (!config.coldStartScan)
|
|
107
118
|
return null;
|
|
108
119
|
try {
|
|
109
|
-
|
|
120
|
+
// Dynamic import to avoid bundling Node.js-only modules in Edge Runtime
|
|
121
|
+
const { WatchmanEngine, dependencyIntegrityScanner, runtimeMonitorScanner, } = await import('@kairosinternational/watchman');
|
|
122
|
+
const engine = new WatchmanEngine({
|
|
110
123
|
projectRoot: config.projectRoot ?? process.cwd(),
|
|
111
124
|
scanners: {
|
|
112
125
|
'dependency-integrity': { enabled: true },
|
|
@@ -115,8 +128,8 @@ function runColdStartScan(config) {
|
|
|
115
128
|
exclude: ['node_modules', '.git', 'dist', 'build', '.next'],
|
|
116
129
|
failOn: 'high',
|
|
117
130
|
});
|
|
118
|
-
engine.register(
|
|
119
|
-
engine.register(
|
|
131
|
+
engine.register(dependencyIntegrityScanner);
|
|
132
|
+
engine.register(runtimeMonitorScanner);
|
|
120
133
|
const report = await engine.scan();
|
|
121
134
|
if (config.onColdStartComplete) {
|
|
122
135
|
await config.onColdStartComplete(report);
|
|
@@ -165,6 +178,16 @@ function withWatchman(userConfig = {}) {
|
|
|
165
178
|
await config.onFinding(finding);
|
|
166
179
|
}
|
|
167
180
|
}
|
|
181
|
+
for (const finding of outcome.findings) {
|
|
182
|
+
(0, telemetry_session_js_1.reportFindingIfConfigured)({
|
|
183
|
+
rule: finding.rule,
|
|
184
|
+
severity: finding.severity,
|
|
185
|
+
target: finding.target,
|
|
186
|
+
label: finding.label,
|
|
187
|
+
message: finding.message,
|
|
188
|
+
evidence: finding.evidence,
|
|
189
|
+
}, config.telemetry, config.quiet);
|
|
190
|
+
}
|
|
168
191
|
if (outcome.shouldBlock) {
|
|
169
192
|
if (!config.quiet) {
|
|
170
193
|
console.warn(`[watchman] BLOCKED ${request.method} ${pathname} -- ${outcome.maxSeverity}: ${outcome.findings.length} findings`);
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-handler.d.ts","sourceRoot":"","sources":["../src/route-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"route-handler.d.ts","sourceRoot":"","sources":["../src/route-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK/C,OAAO,KAAK,EACV,wBAAwB,EAIzB,MAAM,YAAY,CAAC;AAEpB,KAAK,YAAY,GAAG,CAClB,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAA;CAAE,KACpD,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;AA+DlC;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,YAAY,EACtD,OAAO,EAAE,CAAC,EACV,UAAU,GAAE,wBAA6B,GACxC,CAAC,CAkEH"}
|
package/dist/route-handler.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.withWatchmanRoute = withWatchmanRoute;
|
|
|
4
4
|
const server_1 = require("next/server");
|
|
5
5
|
const config_js_1 = require("./config.js");
|
|
6
6
|
const middleware_js_1 = require("./middleware.js");
|
|
7
|
+
const telemetry_session_js_1 = require("./telemetry-session.js");
|
|
7
8
|
const SEVERITY_ORDER = {
|
|
8
9
|
critical: 4,
|
|
9
10
|
high: 3,
|
|
@@ -72,6 +73,16 @@ function withWatchmanRoute(handler, userConfig = {}) {
|
|
|
72
73
|
await config.onFinding(finding);
|
|
73
74
|
}
|
|
74
75
|
}
|
|
76
|
+
for (const finding of outcome.findings) {
|
|
77
|
+
(0, telemetry_session_js_1.reportFindingIfConfigured)({
|
|
78
|
+
rule: finding.rule,
|
|
79
|
+
severity: finding.severity,
|
|
80
|
+
target: finding.target,
|
|
81
|
+
label: finding.label,
|
|
82
|
+
message: finding.message,
|
|
83
|
+
evidence: finding.evidence,
|
|
84
|
+
}, config.telemetry, config.quiet);
|
|
85
|
+
}
|
|
75
86
|
if (outcome.shouldBlock) {
|
|
76
87
|
if (!config.quiet) {
|
|
77
88
|
console.warn(`[watchman] BLOCKED route handler ${request.method} ${new URL(request.url).pathname} -- ${outcome.maxSeverity}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-handler.js","sourceRoot":"","sources":["../src/route-handler.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"route-handler.js","sourceRoot":"","sources":["../src/route-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type TelemetryReporter } from '@kairosinternational/watchman/telemetry';
|
|
2
|
+
import type { NextjsTelemetryConfig } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Memoized per-process telemetry session. A Next.js process (or Vercel
|
|
5
|
+
* Function instance) calls startSession once on first use, then reuses
|
|
6
|
+
* the same sessionId for every request scanned by this instance.
|
|
7
|
+
*
|
|
8
|
+
* Sessions are never explicitly ended from Next.js — Fluid Compute
|
|
9
|
+
* recycles instances without a clean shutdown. A server-side reaper
|
|
10
|
+
* marks stale sessions as completed.
|
|
11
|
+
*/
|
|
12
|
+
interface RuntimeState {
|
|
13
|
+
reporter: TelemetryReporter;
|
|
14
|
+
sessionIdPromise: Promise<string | null>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get or initialize the session. Safe to call on every request —
|
|
18
|
+
* the startSession RPC runs at most once per process.
|
|
19
|
+
*/
|
|
20
|
+
export declare function ensureSession(config?: NextjsTelemetryConfig, quiet?: boolean): RuntimeState | null;
|
|
21
|
+
/**
|
|
22
|
+
* Fire-and-forget finding report. Resolves the memoized session ID
|
|
23
|
+
* first; if session-open failed, findings are silently dropped.
|
|
24
|
+
*/
|
|
25
|
+
export declare function reportFindingIfConfigured(finding: {
|
|
26
|
+
scanner?: string;
|
|
27
|
+
rule: string;
|
|
28
|
+
severity: import('@kairosinternational/watchman/telemetry').TelemetryFinding['severity'];
|
|
29
|
+
target?: string;
|
|
30
|
+
label?: string;
|
|
31
|
+
message: string;
|
|
32
|
+
evidence?: string;
|
|
33
|
+
}, config?: NextjsTelemetryConfig, quiet?: boolean): void;
|
|
34
|
+
/** Test-only helper — resets module state between tests. */
|
|
35
|
+
export declare function __resetSessionState(): void;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=telemetry-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry-session.d.ts","sourceRoot":"","sources":["../src/telemetry-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD;;;;;;;;GAQG;AAEH,UAAU,YAAY;IACpB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,gBAAgB,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC1C;AAuCD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,CAAC,EAAE,qBAAqB,EAC9B,KAAK,UAAQ,GACZ,YAAY,GAAG,IAAI,CAwBrB;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,yCAAyC,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC;IACzF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,EACD,MAAM,CAAC,EAAE,qBAAqB,EAC9B,KAAK,UAAQ,GACZ,IAAI,CAmBN;AAED,4DAA4D;AAC5D,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureSession = ensureSession;
|
|
4
|
+
exports.reportFindingIfConfigured = reportFindingIfConfigured;
|
|
5
|
+
exports.__resetSessionState = __resetSessionState;
|
|
6
|
+
const telemetry_1 = require("@kairosinternational/watchman/telemetry");
|
|
7
|
+
let state = null;
|
|
8
|
+
let skippedLogged = false;
|
|
9
|
+
function resolveTelemetryConfig(config) {
|
|
10
|
+
const apiKey = config?.apiKey ?? process.env['WATCHMAN_API_KEY'];
|
|
11
|
+
const endpoint = config?.endpoint ?? process.env['WATCHMAN_ENDPOINT'];
|
|
12
|
+
if (!apiKey || !endpoint)
|
|
13
|
+
return null;
|
|
14
|
+
const projectSlug = config?.projectSlug ??
|
|
15
|
+
process.env['WATCHMAN_PROJECT_SLUG'] ??
|
|
16
|
+
process.env['VERCEL_GIT_REPO_SLUG'] ??
|
|
17
|
+
'nextjs-app';
|
|
18
|
+
return { apiKey, endpoint, projectSlug };
|
|
19
|
+
}
|
|
20
|
+
function detectEnv(config) {
|
|
21
|
+
return {
|
|
22
|
+
commitSha: config?.commitSha ?? process.env['VERCEL_GIT_COMMIT_SHA'] ?? undefined,
|
|
23
|
+
branch: config?.branch ?? process.env['VERCEL_GIT_COMMIT_REF'] ?? undefined,
|
|
24
|
+
environment: config?.environment ??
|
|
25
|
+
process.env['WATCHMAN_ENVIRONMENT'] ??
|
|
26
|
+
process.env['VERCEL_ENV'] ??
|
|
27
|
+
'nextjs-middleware',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get or initialize the session. Safe to call on every request —
|
|
32
|
+
* the startSession RPC runs at most once per process.
|
|
33
|
+
*/
|
|
34
|
+
function ensureSession(config, quiet = false) {
|
|
35
|
+
if (state)
|
|
36
|
+
return state;
|
|
37
|
+
const resolved = resolveTelemetryConfig(config);
|
|
38
|
+
if (!resolved) {
|
|
39
|
+
if (!quiet && !skippedLogged) {
|
|
40
|
+
console.info('[watchman] telemetry disabled (no apiKey/endpoint)');
|
|
41
|
+
skippedLogged = true;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const reporter = (0, telemetry_1.createTelemetryReporter)({
|
|
46
|
+
apiKey: resolved.apiKey,
|
|
47
|
+
endpoint: resolved.endpoint,
|
|
48
|
+
});
|
|
49
|
+
const sessionIdPromise = reporter.startSession(resolved.projectSlug, detectEnv(config));
|
|
50
|
+
state = { reporter, sessionIdPromise };
|
|
51
|
+
return state;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Fire-and-forget finding report. Resolves the memoized session ID
|
|
55
|
+
* first; if session-open failed, findings are silently dropped.
|
|
56
|
+
*/
|
|
57
|
+
function reportFindingIfConfigured(finding, config, quiet = false) {
|
|
58
|
+
const runtime = ensureSession(config, quiet);
|
|
59
|
+
if (!runtime)
|
|
60
|
+
return;
|
|
61
|
+
void runtime.sessionIdPromise.then((sessionId) => {
|
|
62
|
+
if (!sessionId)
|
|
63
|
+
return;
|
|
64
|
+
runtime.reporter.reportFinding({
|
|
65
|
+
scanner: finding.scanner ?? 'nextjs-middleware',
|
|
66
|
+
rule: finding.rule,
|
|
67
|
+
severity: finding.severity,
|
|
68
|
+
target: finding.target,
|
|
69
|
+
label: finding.label,
|
|
70
|
+
message: finding.message,
|
|
71
|
+
evidence: finding.evidence,
|
|
72
|
+
}, sessionId);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/** Test-only helper — resets module state between tests. */
|
|
76
|
+
function __resetSessionState() {
|
|
77
|
+
state = null;
|
|
78
|
+
skippedLogged = false;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=telemetry-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry-session.js","sourceRoot":"","sources":["../src/telemetry-session.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -9,9 +9,25 @@ export interface RequestFinding {
|
|
|
9
9
|
message: string;
|
|
10
10
|
evidence: string;
|
|
11
11
|
}
|
|
12
|
+
export interface NextjsTelemetryConfig {
|
|
13
|
+
/** Watchman API key (raw). Falls back to WATCHMAN_API_KEY env. */
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
/** Watchman endpoint, e.g. "https://watchman.guide". Falls back to WATCHMAN_ENDPOINT env. */
|
|
16
|
+
endpoint?: string;
|
|
17
|
+
/** Project slug. Falls back to WATCHMAN_PROJECT_SLUG, then VERCEL_GIT_REPO_SLUG. */
|
|
18
|
+
projectSlug?: string;
|
|
19
|
+
/** Commit SHA. Falls back to VERCEL_GIT_COMMIT_SHA. */
|
|
20
|
+
commitSha?: string;
|
|
21
|
+
/** Branch. Falls back to VERCEL_GIT_COMMIT_REF. */
|
|
22
|
+
branch?: string;
|
|
23
|
+
/** Environment label. Falls back to WATCHMAN_ENVIRONMENT, then VERCEL_ENV. */
|
|
24
|
+
environment?: string;
|
|
25
|
+
}
|
|
12
26
|
export interface WatchmanMiddlewareConfig {
|
|
13
27
|
/** Project root for cold-start file scanning. Defaults to process.cwd() */
|
|
14
28
|
projectRoot?: string;
|
|
29
|
+
/** Optional telemetry — stream findings to a Watchman server. */
|
|
30
|
+
telemetry?: NextjsTelemetryConfig;
|
|
15
31
|
/** Severity at or above which to block the request. Default: 'critical' */
|
|
16
32
|
blockAt?: Severity;
|
|
17
33
|
/** Severity at or above which to attach a warning header. Default: 'high' */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAEvF,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,UAAU,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,QAAQ,CAAC;IAEnB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,QAAQ,CAAC;IAElB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,0FAA0F;IAC1F,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAErB,oDAAoD;IACpD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9D,iDAAiD;IACjD,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvE,6DAA6D;IAC7D,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,WAAW,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAEvF,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,UAAU,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,iEAAiE;IACjE,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAElC,2EAA2E;IAC3E,OAAO,CAAC,EAAE,QAAQ,CAAC;IAEnB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,QAAQ,CAAC;IAElB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,0FAA0F;IAC1F,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAErB,oDAAoD;IACpD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9D,iDAAiD;IACjD,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvE,6DAA6D;IAC7D,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,WAAW,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC"}
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kairosinternational/watchman-nextjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Next.js 14 App Router adapter for @kairosinternational/watchman",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"node": ">=18"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@kairosinternational/watchman": "0.
|
|
28
|
+
"@kairosinternational/watchman": "0.2.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"next": "^14.0.0"
|
package/src/config.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { WatchmanMiddlewareConfig } from './types.js';
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_MIDDLEWARE_CONFIG: Required<
|
|
4
|
-
Omit<
|
|
4
|
+
Omit<
|
|
5
|
+
WatchmanMiddlewareConfig,
|
|
6
|
+
'projectRoot' | 'onFinding' | 'onColdStartComplete' | 'telemetry'
|
|
7
|
+
>
|
|
5
8
|
> = {
|
|
6
9
|
blockAt: 'critical',
|
|
7
10
|
warnAt: 'high',
|
package/src/index.ts
CHANGED
package/src/middleware.ts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
1
|
import { NextResponse, type NextRequest, type NextMiddleware } from 'next/server';
|
|
3
2
|
import {
|
|
4
|
-
WatchmanEngine,
|
|
5
3
|
INJECTION_SIGNATURES,
|
|
6
4
|
SECRET_SIGNATURES,
|
|
7
|
-
dependencyIntegrityScanner,
|
|
8
|
-
runtimeMonitorScanner,
|
|
9
5
|
type Severity,
|
|
10
6
|
type WatchmanReport,
|
|
11
|
-
} from '@kairosinternational/watchman';
|
|
7
|
+
} from '@kairosinternational/watchman/patterns';
|
|
12
8
|
import { resolveConfig, DEFAULT_MIDDLEWARE_CONFIG } from './config.js';
|
|
9
|
+
import { reportFindingIfConfigured } from './telemetry-session.js';
|
|
13
10
|
import type {
|
|
14
11
|
WatchmanMiddlewareConfig,
|
|
15
12
|
RequestFinding,
|
|
@@ -17,6 +14,18 @@ import type {
|
|
|
17
14
|
ScanTarget,
|
|
18
15
|
} from './types.js';
|
|
19
16
|
|
|
17
|
+
function randomId(): string {
|
|
18
|
+
// Use Web Crypto API (Edge-compatible) with Node.js fallback
|
|
19
|
+
if (typeof globalThis.crypto?.randomUUID === 'function') {
|
|
20
|
+
return globalThis.crypto.randomUUID();
|
|
21
|
+
}
|
|
22
|
+
// Fallback for older runtimes
|
|
23
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
24
|
+
const r = (Math.random() * 16) | 0;
|
|
25
|
+
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
const SEVERITY_ORDER: Record<Severity, number> = {
|
|
21
30
|
critical: 4,
|
|
22
31
|
high: 3,
|
|
@@ -48,7 +57,7 @@ export function scanString(input: string, target: ScanTarget): RequestFinding[]
|
|
|
48
57
|
for (const sig of INJECTION_SIGNATURES) {
|
|
49
58
|
if (sig.pattern.test(input)) {
|
|
50
59
|
findings.push({
|
|
51
|
-
id:
|
|
60
|
+
id: randomId(),
|
|
52
61
|
target,
|
|
53
62
|
rule: 'prompt-injection',
|
|
54
63
|
severity: sig.severity,
|
|
@@ -62,7 +71,7 @@ export function scanString(input: string, target: ScanTarget): RequestFinding[]
|
|
|
62
71
|
for (const sig of SECRET_SIGNATURES) {
|
|
63
72
|
if (sig.pattern.test(input)) {
|
|
64
73
|
findings.push({
|
|
65
|
-
id:
|
|
74
|
+
id: randomId(),
|
|
66
75
|
target,
|
|
67
76
|
rule: 'known-patterns',
|
|
68
77
|
severity: 'critical',
|
|
@@ -134,6 +143,13 @@ function runColdStartScan(
|
|
|
134
143
|
if (!config.coldStartScan) return null;
|
|
135
144
|
|
|
136
145
|
try {
|
|
146
|
+
// Dynamic import to avoid bundling Node.js-only modules in Edge Runtime
|
|
147
|
+
const {
|
|
148
|
+
WatchmanEngine,
|
|
149
|
+
dependencyIntegrityScanner,
|
|
150
|
+
runtimeMonitorScanner,
|
|
151
|
+
} = await import('@kairosinternational/watchman');
|
|
152
|
+
|
|
137
153
|
const engine = new WatchmanEngine({
|
|
138
154
|
projectRoot: config.projectRoot ?? process.cwd(),
|
|
139
155
|
scanners: {
|
|
@@ -209,6 +225,21 @@ export function withWatchman(
|
|
|
209
225
|
}
|
|
210
226
|
}
|
|
211
227
|
|
|
228
|
+
for (const finding of outcome.findings) {
|
|
229
|
+
reportFindingIfConfigured(
|
|
230
|
+
{
|
|
231
|
+
rule: finding.rule,
|
|
232
|
+
severity: finding.severity,
|
|
233
|
+
target: finding.target,
|
|
234
|
+
label: finding.label,
|
|
235
|
+
message: finding.message,
|
|
236
|
+
evidence: finding.evidence,
|
|
237
|
+
},
|
|
238
|
+
config.telemetry,
|
|
239
|
+
config.quiet,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
212
243
|
if (outcome.shouldBlock) {
|
|
213
244
|
if (!config.quiet) {
|
|
214
245
|
console.warn(
|
package/src/route-handler.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { NextRequest } from 'next/server';
|
|
|
2
2
|
import { NextResponse } from 'next/server';
|
|
3
3
|
import { resolveConfig } from './config.js';
|
|
4
4
|
import { scanString } from './middleware.js';
|
|
5
|
+
import { reportFindingIfConfigured } from './telemetry-session.js';
|
|
5
6
|
import type {
|
|
6
7
|
WatchmanMiddlewareConfig,
|
|
7
8
|
RequestFinding,
|
|
@@ -99,6 +100,21 @@ export function withWatchmanRoute<H extends RouteHandler>(
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
|
|
103
|
+
for (const finding of outcome.findings) {
|
|
104
|
+
reportFindingIfConfigured(
|
|
105
|
+
{
|
|
106
|
+
rule: finding.rule,
|
|
107
|
+
severity: finding.severity,
|
|
108
|
+
target: finding.target,
|
|
109
|
+
label: finding.label,
|
|
110
|
+
message: finding.message,
|
|
111
|
+
evidence: finding.evidence,
|
|
112
|
+
},
|
|
113
|
+
config.telemetry,
|
|
114
|
+
config.quiet,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
102
118
|
if (outcome.shouldBlock) {
|
|
103
119
|
if (!config.quiet) {
|
|
104
120
|
console.warn(
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTelemetryReporter,
|
|
3
|
+
type TelemetryReporter,
|
|
4
|
+
} from '@kairosinternational/watchman/telemetry';
|
|
5
|
+
import type { NextjsTelemetryConfig } from './types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Memoized per-process telemetry session. A Next.js process (or Vercel
|
|
9
|
+
* Function instance) calls startSession once on first use, then reuses
|
|
10
|
+
* the same sessionId for every request scanned by this instance.
|
|
11
|
+
*
|
|
12
|
+
* Sessions are never explicitly ended from Next.js — Fluid Compute
|
|
13
|
+
* recycles instances without a clean shutdown. A server-side reaper
|
|
14
|
+
* marks stale sessions as completed.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface RuntimeState {
|
|
18
|
+
reporter: TelemetryReporter;
|
|
19
|
+
sessionIdPromise: Promise<string | null>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let state: RuntimeState | null = null;
|
|
23
|
+
let skippedLogged = false;
|
|
24
|
+
|
|
25
|
+
function resolveTelemetryConfig(
|
|
26
|
+
config?: NextjsTelemetryConfig,
|
|
27
|
+
): { apiKey: string; endpoint: string; projectSlug: string } | null {
|
|
28
|
+
const apiKey = config?.apiKey ?? process.env['WATCHMAN_API_KEY'];
|
|
29
|
+
const endpoint = config?.endpoint ?? process.env['WATCHMAN_ENDPOINT'];
|
|
30
|
+
if (!apiKey || !endpoint) return null;
|
|
31
|
+
|
|
32
|
+
const projectSlug =
|
|
33
|
+
config?.projectSlug ??
|
|
34
|
+
process.env['WATCHMAN_PROJECT_SLUG'] ??
|
|
35
|
+
process.env['VERCEL_GIT_REPO_SLUG'] ??
|
|
36
|
+
'nextjs-app';
|
|
37
|
+
|
|
38
|
+
return { apiKey, endpoint, projectSlug };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function detectEnv(config?: NextjsTelemetryConfig): {
|
|
42
|
+
commitSha?: string;
|
|
43
|
+
branch?: string;
|
|
44
|
+
environment?: string;
|
|
45
|
+
} {
|
|
46
|
+
return {
|
|
47
|
+
commitSha:
|
|
48
|
+
config?.commitSha ?? process.env['VERCEL_GIT_COMMIT_SHA'] ?? undefined,
|
|
49
|
+
branch:
|
|
50
|
+
config?.branch ?? process.env['VERCEL_GIT_COMMIT_REF'] ?? undefined,
|
|
51
|
+
environment:
|
|
52
|
+
config?.environment ??
|
|
53
|
+
process.env['WATCHMAN_ENVIRONMENT'] ??
|
|
54
|
+
process.env['VERCEL_ENV'] ??
|
|
55
|
+
'nextjs-middleware',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get or initialize the session. Safe to call on every request —
|
|
61
|
+
* the startSession RPC runs at most once per process.
|
|
62
|
+
*/
|
|
63
|
+
export function ensureSession(
|
|
64
|
+
config?: NextjsTelemetryConfig,
|
|
65
|
+
quiet = false,
|
|
66
|
+
): RuntimeState | null {
|
|
67
|
+
if (state) return state;
|
|
68
|
+
|
|
69
|
+
const resolved = resolveTelemetryConfig(config);
|
|
70
|
+
if (!resolved) {
|
|
71
|
+
if (!quiet && !skippedLogged) {
|
|
72
|
+
console.info('[watchman] telemetry disabled (no apiKey/endpoint)');
|
|
73
|
+
skippedLogged = true;
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const reporter = createTelemetryReporter({
|
|
79
|
+
apiKey: resolved.apiKey,
|
|
80
|
+
endpoint: resolved.endpoint,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const sessionIdPromise = reporter.startSession(
|
|
84
|
+
resolved.projectSlug,
|
|
85
|
+
detectEnv(config),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
state = { reporter, sessionIdPromise };
|
|
89
|
+
return state;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Fire-and-forget finding report. Resolves the memoized session ID
|
|
94
|
+
* first; if session-open failed, findings are silently dropped.
|
|
95
|
+
*/
|
|
96
|
+
export function reportFindingIfConfigured(
|
|
97
|
+
finding: {
|
|
98
|
+
scanner?: string;
|
|
99
|
+
rule: string;
|
|
100
|
+
severity: import('@kairosinternational/watchman/telemetry').TelemetryFinding['severity'];
|
|
101
|
+
target?: string;
|
|
102
|
+
label?: string;
|
|
103
|
+
message: string;
|
|
104
|
+
evidence?: string;
|
|
105
|
+
},
|
|
106
|
+
config?: NextjsTelemetryConfig,
|
|
107
|
+
quiet = false,
|
|
108
|
+
): void {
|
|
109
|
+
const runtime = ensureSession(config, quiet);
|
|
110
|
+
if (!runtime) return;
|
|
111
|
+
|
|
112
|
+
void runtime.sessionIdPromise.then((sessionId) => {
|
|
113
|
+
if (!sessionId) return;
|
|
114
|
+
runtime.reporter.reportFinding(
|
|
115
|
+
{
|
|
116
|
+
scanner: finding.scanner ?? 'nextjs-middleware',
|
|
117
|
+
rule: finding.rule,
|
|
118
|
+
severity: finding.severity,
|
|
119
|
+
target: finding.target,
|
|
120
|
+
label: finding.label,
|
|
121
|
+
message: finding.message,
|
|
122
|
+
evidence: finding.evidence,
|
|
123
|
+
},
|
|
124
|
+
sessionId,
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Test-only helper — resets module state between tests. */
|
|
130
|
+
export function __resetSessionState(): void {
|
|
131
|
+
state = null;
|
|
132
|
+
skippedLogged = false;
|
|
133
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -12,10 +12,28 @@ export interface RequestFinding {
|
|
|
12
12
|
evidence: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export interface NextjsTelemetryConfig {
|
|
16
|
+
/** Watchman API key (raw). Falls back to WATCHMAN_API_KEY env. */
|
|
17
|
+
apiKey?: string;
|
|
18
|
+
/** Watchman endpoint, e.g. "https://watchman.guide". Falls back to WATCHMAN_ENDPOINT env. */
|
|
19
|
+
endpoint?: string;
|
|
20
|
+
/** Project slug. Falls back to WATCHMAN_PROJECT_SLUG, then VERCEL_GIT_REPO_SLUG. */
|
|
21
|
+
projectSlug?: string;
|
|
22
|
+
/** Commit SHA. Falls back to VERCEL_GIT_COMMIT_SHA. */
|
|
23
|
+
commitSha?: string;
|
|
24
|
+
/** Branch. Falls back to VERCEL_GIT_COMMIT_REF. */
|
|
25
|
+
branch?: string;
|
|
26
|
+
/** Environment label. Falls back to WATCHMAN_ENVIRONMENT, then VERCEL_ENV. */
|
|
27
|
+
environment?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
15
30
|
export interface WatchmanMiddlewareConfig {
|
|
16
31
|
/** Project root for cold-start file scanning. Defaults to process.cwd() */
|
|
17
32
|
projectRoot?: string;
|
|
18
33
|
|
|
34
|
+
/** Optional telemetry — stream findings to a Watchman server. */
|
|
35
|
+
telemetry?: NextjsTelemetryConfig;
|
|
36
|
+
|
|
19
37
|
/** Severity at or above which to block the request. Default: 'critical' */
|
|
20
38
|
blockAt?: Severity;
|
|
21
39
|
|