@zarex/next 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kodjogbe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CLOUDFLARE_RANGES: () => import_core2.CLOUDFLARE_RANGES,
24
+ PRIVATE_NETWORKS: () => import_core2.PRIVATE_NETWORKS,
25
+ createZarex: () => import_core2.createZarex,
26
+ detectBot: () => import_core2.detectBot,
27
+ emailValidation: () => import_core2.emailValidation,
28
+ phoneValidation: () => import_core2.phoneValidation,
29
+ promptInjection: () => import_core2.promptInjection,
30
+ rateLimit: () => import_core2.rateLimit,
31
+ requestFilter: () => import_core2.requestFilter,
32
+ secureHeaders: () => import_core2.secureHeaders,
33
+ sensitiveInfo: () => import_core2.sensitiveInfo,
34
+ shield: () => import_core2.shield,
35
+ tokenBucket: () => import_core2.tokenBucket,
36
+ withSecureHeaders: () => import_core2.withSecureHeaders,
37
+ withZarex: () => withZarex,
38
+ zarexNext: () => zarexNext
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+ var import_core = require("@zarex/core");
42
+ var import_core2 = require("@zarex/core");
43
+ function build(options, extra) {
44
+ const isInstance = typeof options.protect === "function";
45
+ return isInstance ? { instance: options, opts: extra } : { instance: (0, import_core.createZarex)(options), opts: options };
46
+ }
47
+ function callOf(opts, request) {
48
+ try {
49
+ return opts.call?.(request);
50
+ } catch {
51
+ return void 0;
52
+ }
53
+ }
54
+ function buildGuard(options, extra, warnAboutMiddlewareFinalize) {
55
+ const { instance, opts } = build(options, extra);
56
+ const finalizer = (0, import_core.createWebFinalizer)({ secureHeaders: opts.secureHeaders, responseGuard: opts.responseGuard });
57
+ if (finalizer && warnAboutMiddlewareFinalize) {
58
+ console.warn(
59
+ "[zarex] next: `secureHeaders`/`responseGuard` is configured. In middleware.ts, call `guard.finalize(NextResponse.next(), request)` on the ALLOWED path too (not just on a denial) \u2014 otherwise it is never applied to allowed traffic: return (await guard(request)) ?? guard.finalize(NextResponse.next(), request)."
60
+ );
61
+ }
62
+ const finalize = async (response, request) => {
63
+ try {
64
+ return finalizer ? await finalizer(response, request) : response;
65
+ } catch {
66
+ return response;
67
+ }
68
+ };
69
+ async function guard(request) {
70
+ try {
71
+ const call = callOf(opts, request);
72
+ const { deny } = await (0, import_core.guardWebRequest)(instance, request, {
73
+ ...opts.inspectBody ? { inspectBody: true } : {},
74
+ ...opts.onDeny ? { onDeny: opts.onDeny } : {},
75
+ ...opts.onDegraded ? { onDegraded: opts.onDegraded } : {},
76
+ ...call ? { call } : {}
77
+ });
78
+ return deny ? await finalize(deny, request) : null;
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
83
+ return Object.assign(guard, { finalize });
84
+ }
85
+ function zarexNext(options, extra = {}) {
86
+ return buildGuard(options, extra, true);
87
+ }
88
+ function withZarex(options, handler, extra = {}) {
89
+ const guard = buildGuard(options, extra, false);
90
+ return async (request, ...args) => {
91
+ const denied = await guard(request);
92
+ if (denied) return denied;
93
+ const out = await handler(request, ...args);
94
+ return out instanceof Response ? await guard.finalize(out, request) : out;
95
+ };
96
+ }
97
+ // Annotate the CommonJS export names for ESM import in node:
98
+ 0 && (module.exports = {
99
+ CLOUDFLARE_RANGES,
100
+ PRIVATE_NETWORKS,
101
+ createZarex,
102
+ detectBot,
103
+ emailValidation,
104
+ phoneValidation,
105
+ promptInjection,
106
+ rateLimit,
107
+ requestFilter,
108
+ secureHeaders,
109
+ sensitiveInfo,
110
+ shield,
111
+ tokenBucket,
112
+ withSecureHeaders,
113
+ withZarex,
114
+ zarexNext
115
+ });
116
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { createWebFinalizer, createZarex, guardWebRequest, type ResponseGuardOptions, type SecureHeadersOptions, type Decision, type ProtectCall, type WebGuardOptions, type Zarex, type ZarexOptions } from \"@zarex/core\";\r\n\r\n/**\r\n * Next.js — fonctionne dans le middleware (runtime edge) ET dans les route handlers.\r\n * Aucune importation de `next` : on utilise la `Request` standard, donc aucun risque de\r\n * version incompatible et rien de propre à Node dans le middleware.\r\n *\r\n * // middleware.ts\r\n * import { NextResponse } from \"next/server\";\r\n * import { zarexNext, shield, rateLimit } from \"@zarex/next\";\r\n * const guard = zarexNext({ key: process.env.ZAREX_KEY!, baseUrl: process.env.ZAREX_URL!, ipSource: \"vercel\",\r\n * rules: [shield(), rateLimit({ max: 100, window: \"1m\" })] });\r\n * export async function middleware(request: Request) {\r\n * // guard.finalize(...) sur la réponse autorisée AUSSI, pas seulement sur un refus : sans cet appel,\r\n * // `secureHeaders`/`responseGuard` ne s'appliqueraient JAMAIS au trafic autorisé (trouvé lors de\r\n * // l'audit du 04-05/11/2026 — `guard()` ne peut pas le faire elle-même : importer next/server ici\r\n * // romprait la garantie « aucune dépendance à next » de ce fichier, voir l'en-tête ci-dessus).\r\n * return (await guard(request)) ?? guard.finalize(NextResponse.next(), request);\r\n * }\r\n *\r\n * LIMITE IMPORTANTE : le middleware Next.js ne voit pas l'adresse de la connexion TCP. Seules les sources\r\n * qui lisent un en-tête posé par la plateforme fonctionnent donc : \"vercel\", \"fly\", \"auto\". Hébergé ailleurs\r\n * (nginx, Docker…), utilisez un route handler avec `withZarex` n'y change rien non plus : voir la documentation.\r\n */\r\nexport interface NextZarexOptions extends ZarexOptions {\r\n inspectBody?: boolean;\r\n /** En-têtes de sécurité (true = défauts prudents). Voir `guard.finalize` pour le middleware. */\r\n secureHeaders?: SecureHeadersOptions | boolean;\r\n /** Surveille les réponses des route handlers (withZarex) : masque les cartes bancaires, clés d'API, etc. */\r\n responseGuard?: ResponseGuardOptions | boolean;\r\n onDeny?: WebGuardOptions[\"onDeny\"];\r\n onDegraded?: WebGuardOptions[\"onDegraded\"];\r\n /** Identifiants / jetons propres à chaque requête (ex. userId). Une erreur dans cette fonction est ignorée. */\r\n call?: (request: Request) => ProtectCall | undefined;\r\n}\r\ntype Extra = Pick<NextZarexOptions, \"inspectBody\" | \"onDeny\" | \"onDegraded\" | \"call\" | \"secureHeaders\" | \"responseGuard\">;\r\n\r\nexport type NextGuard = ((request: Request) => Promise<Response | null>) & {\r\n /** Ajoute les en-têtes de sécurité (et surveille la réponse) : `return guard.finalize(NextResponse.next(), request)`. */\r\n finalize(response: Response, request?: Request): Promise<Response>;\r\n};\r\n\r\nfunction build(options: NextZarexOptions | Zarex, extra: Extra): { instance: Zarex; opts: Extra } {\r\n const isInstance = typeof (options as Zarex).protect === \"function\";\r\n return isInstance\r\n ? { instance: options as Zarex, opts: extra }\r\n : { instance: createZarex(options as NextZarexOptions), opts: options as NextZarexOptions };\r\n}\r\n\r\nfunction callOf(opts: Extra, request: Request): ProtectCall | undefined {\r\n try {\r\n return opts.call?.(request);\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n// warnAboutMiddlewareFinalize : seul `zarexNext()` appelé directement (usage middleware) a besoin du\r\n// rappel ci-dessous — `withZarex` (route handlers) applique déjà `finalize()` lui-même sur le chemin\r\n// autorisé (voir plus bas), le même avertissement y serait un faux positif systématique.\r\nfunction buildGuard(options: NextZarexOptions | Zarex, extra: Extra, warnAboutMiddlewareFinalize: boolean): NextGuard {\r\n const { instance, opts } = build(options, extra);\r\n const finalizer = createWebFinalizer({ secureHeaders: opts.secureHeaders, responseGuard: opts.responseGuard });\r\n // Rappel au démarrage (pas par requête) : `guard()` renvoie `null` quand la requête est autorisée et ne\r\n // peut pas construire elle-même la réponse de continuation (NextResponse.next(), propre à next/server —\r\n // voir l'en-tête du fichier). Sans `guard.finalize(...)` explicite sur cette réponse-là dans le\r\n // middleware, `secureHeaders`/`responseGuard` ne s'appliqueraient JAMAIS au trafic autorisé. Trouvé lors\r\n // de l'audit du 04-05/11/2026 : ce piège n'était signalé nulle part à l'exécution.\r\n if (finalizer && warnAboutMiddlewareFinalize) {\r\n console.warn(\r\n '[zarex] next: `secureHeaders`/`responseGuard` is configured. In middleware.ts, call `guard.finalize(NextResponse.next(), request)` ' +\r\n \"on the ALLOWED path too (not just on a denial) — otherwise it is never applied to allowed traffic: \" +\r\n 'return (await guard(request)) ?? guard.finalize(NextResponse.next(), request).'\r\n );\r\n }\r\n const finalize = async (response: Response, request?: Request): Promise<Response> => {\r\n try {\r\n return finalizer ? await finalizer(response, request) : response;\r\n } catch {\r\n return response;\r\n }\r\n };\r\n async function guard(request: Request): Promise<Response | null> {\r\n try {\r\n const call = callOf(opts, request);\r\n const { deny } = await guardWebRequest(instance, request, {\r\n ...(opts.inspectBody ? { inspectBody: true } : {}),\r\n ...(opts.onDeny ? { onDeny: opts.onDeny } : {}),\r\n ...(opts.onDegraded ? { onDegraded: opts.onDegraded } : {}),\r\n ...(call ? { call } : {}),\r\n });\r\n return deny ? await finalize(deny, request) : null;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n return Object.assign(guard, { finalize });\r\n}\r\n\r\n/**\r\n * Renvoie une Response de refus, ou null si la requête peut continuer. Ne lève jamais.\r\n * `guard.finalize(response, request)` ajoute les en-têtes de sécurité demandés à n'importe quelle réponse —\r\n * y compris la réponse AUTORISÉE en usage middleware, sinon jamais appliqués à ce trafic (voir l'exemple\r\n * en tête de fichier).\r\n */\r\nexport function zarexNext(options: NextZarexOptions | Zarex, extra: Extra = {}): NextGuard {\r\n return buildGuard(options, extra, true);\r\n}\r\n\r\n/** Enveloppe un route handler : `export const POST = withZarex(options, async (req) => …)`. */\r\nexport function withZarex<A extends unknown[], R>(\r\n options: NextZarexOptions | Zarex,\r\n handler: (request: Request, ...args: A) => R | Promise<R>,\r\n extra: Extra = {}\r\n): (request: Request, ...args: A) => Promise<R | Response> {\r\n const guard = buildGuard(options, extra, false); // applique déjà finalize() lui-même ci-dessous, pas de rappel nécessaire\r\n return async (request, ...args) => {\r\n const denied = await guard(request);\r\n if (denied) return denied;\r\n const out = await handler(request, ...args);\r\n return out instanceof Response ? await guard.finalize(out, request) : out;\r\n };\r\n}\r\n\r\nexport {\r\n shield,\r\n rateLimit,\r\n tokenBucket,\r\n detectBot,\r\n requestFilter,\r\n sensitiveInfo,\r\n emailValidation,\r\n phoneValidation,\r\n promptInjection,\r\n secureHeaders,\r\n withSecureHeaders,\r\n createZarex,\r\n PRIVATE_NETWORKS,\r\n CLOUDFLARE_RANGES,\r\n} from \"@zarex/core\";\r\nexport type { SecureHeadersOptions, ResponseGuardOptions, ResponseGuardEvent } from \"@zarex/core\";\r\nexport type { Decision, Zarex, ZarexOptions, IpSource, Rule, ProtectCall } from \"@zarex/core\";"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAA6M;AA4H7M,IAAAA,eAeO;AAjGP,SAAS,MAAM,SAAmC,OAAgD;AAChG,QAAM,aAAa,OAAQ,QAAkB,YAAY;AACzD,SAAO,aACH,EAAE,UAAU,SAAkB,MAAM,MAAM,IAC1C,EAAE,cAAU,yBAAY,OAA2B,GAAG,MAAM,QAA4B;AAC9F;AAEA,SAAS,OAAO,MAAa,SAA2C;AACtE,MAAI;AACF,WAAO,KAAK,OAAO,OAAO;AAAA,EAC5B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,WAAW,SAAmC,OAAc,6BAAiD;AACpH,QAAM,EAAE,UAAU,KAAK,IAAI,MAAM,SAAS,KAAK;AAC/C,QAAM,gBAAY,gCAAmB,EAAE,eAAe,KAAK,eAAe,eAAe,KAAK,cAAc,CAAC;AAM7G,MAAI,aAAa,6BAA6B;AAC5C,YAAQ;AAAA,MACN;AAAA,IAGF;AAAA,EACF;AACA,QAAM,WAAW,OAAO,UAAoB,YAAyC;AACnF,QAAI;AACF,aAAO,YAAY,MAAM,UAAU,UAAU,OAAO,IAAI;AAAA,IAC1D,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,iBAAe,MAAM,SAA4C;AAC/D,QAAI;AACF,YAAM,OAAO,OAAO,MAAM,OAAO;AACjC,YAAM,EAAE,KAAK,IAAI,UAAM,6BAAgB,UAAU,SAAS;AAAA,QACxD,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,IAAI,CAAC;AAAA,QAChD,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,QAC7C,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,QACzD,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MACzB,CAAC;AACD,aAAO,OAAO,MAAM,SAAS,MAAM,OAAO,IAAI;AAAA,IAChD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO,OAAO,OAAO,OAAO,EAAE,SAAS,CAAC;AAC1C;AAQO,SAAS,UAAU,SAAmC,QAAe,CAAC,GAAc;AACzF,SAAO,WAAW,SAAS,OAAO,IAAI;AACxC;AAGO,SAAS,UACd,SACA,SACA,QAAe,CAAC,GACyC;AACzD,QAAM,QAAQ,WAAW,SAAS,OAAO,KAAK;AAC9C,SAAO,OAAO,YAAY,SAAS;AACjC,UAAM,SAAS,MAAM,MAAM,OAAO;AAClC,QAAI,OAAQ,QAAO;AACnB,UAAM,MAAM,MAAM,QAAQ,SAAS,GAAG,IAAI;AAC1C,WAAO,eAAe,WAAW,MAAM,MAAM,SAAS,KAAK,OAAO,IAAI;AAAA,EACxE;AACF;","names":["import_core"]}
@@ -0,0 +1,52 @@
1
+ import { ZarexOptions, SecureHeadersOptions, ResponseGuardOptions, WebGuardOptions, ProtectCall, Zarex } from '@zarex/core';
2
+ export { CLOUDFLARE_RANGES, Decision, IpSource, PRIVATE_NETWORKS, ProtectCall, ResponseGuardEvent, ResponseGuardOptions, Rule, SecureHeadersOptions, Zarex, ZarexOptions, createZarex, detectBot, emailValidation, phoneValidation, promptInjection, rateLimit, requestFilter, secureHeaders, sensitiveInfo, shield, tokenBucket, withSecureHeaders } from '@zarex/core';
3
+
4
+ /**
5
+ * Next.js — fonctionne dans le middleware (runtime edge) ET dans les route handlers.
6
+ * Aucune importation de `next` : on utilise la `Request` standard, donc aucun risque de
7
+ * version incompatible et rien de propre à Node dans le middleware.
8
+ *
9
+ * // middleware.ts
10
+ * import { NextResponse } from "next/server";
11
+ * import { zarexNext, shield, rateLimit } from "@zarex/next";
12
+ * const guard = zarexNext({ key: process.env.ZAREX_KEY!, baseUrl: process.env.ZAREX_URL!, ipSource: "vercel",
13
+ * rules: [shield(), rateLimit({ max: 100, window: "1m" })] });
14
+ * export async function middleware(request: Request) {
15
+ * // guard.finalize(...) sur la réponse autorisée AUSSI, pas seulement sur un refus : sans cet appel,
16
+ * // `secureHeaders`/`responseGuard` ne s'appliqueraient JAMAIS au trafic autorisé (trouvé lors de
17
+ * // l'audit du 04-05/11/2026 — `guard()` ne peut pas le faire elle-même : importer next/server ici
18
+ * // romprait la garantie « aucune dépendance à next » de ce fichier, voir l'en-tête ci-dessus).
19
+ * return (await guard(request)) ?? guard.finalize(NextResponse.next(), request);
20
+ * }
21
+ *
22
+ * LIMITE IMPORTANTE : le middleware Next.js ne voit pas l'adresse de la connexion TCP. Seules les sources
23
+ * qui lisent un en-tête posé par la plateforme fonctionnent donc : "vercel", "fly", "auto". Hébergé ailleurs
24
+ * (nginx, Docker…), utilisez un route handler avec `withZarex` n'y change rien non plus : voir la documentation.
25
+ */
26
+ interface NextZarexOptions extends ZarexOptions {
27
+ inspectBody?: boolean;
28
+ /** En-têtes de sécurité (true = défauts prudents). Voir `guard.finalize` pour le middleware. */
29
+ secureHeaders?: SecureHeadersOptions | boolean;
30
+ /** Surveille les réponses des route handlers (withZarex) : masque les cartes bancaires, clés d'API, etc. */
31
+ responseGuard?: ResponseGuardOptions | boolean;
32
+ onDeny?: WebGuardOptions["onDeny"];
33
+ onDegraded?: WebGuardOptions["onDegraded"];
34
+ /** Identifiants / jetons propres à chaque requête (ex. userId). Une erreur dans cette fonction est ignorée. */
35
+ call?: (request: Request) => ProtectCall | undefined;
36
+ }
37
+ type Extra = Pick<NextZarexOptions, "inspectBody" | "onDeny" | "onDegraded" | "call" | "secureHeaders" | "responseGuard">;
38
+ type NextGuard = ((request: Request) => Promise<Response | null>) & {
39
+ /** Ajoute les en-têtes de sécurité (et surveille la réponse) : `return guard.finalize(NextResponse.next(), request)`. */
40
+ finalize(response: Response, request?: Request): Promise<Response>;
41
+ };
42
+ /**
43
+ * Renvoie une Response de refus, ou null si la requête peut continuer. Ne lève jamais.
44
+ * `guard.finalize(response, request)` ajoute les en-têtes de sécurité demandés à n'importe quelle réponse —
45
+ * y compris la réponse AUTORISÉE en usage middleware, sinon jamais appliqués à ce trafic (voir l'exemple
46
+ * en tête de fichier).
47
+ */
48
+ declare function zarexNext(options: NextZarexOptions | Zarex, extra?: Extra): NextGuard;
49
+ /** Enveloppe un route handler : `export const POST = withZarex(options, async (req) => …)`. */
50
+ declare function withZarex<A extends unknown[], R>(options: NextZarexOptions | Zarex, handler: (request: Request, ...args: A) => R | Promise<R>, extra?: Extra): (request: Request, ...args: A) => Promise<R | Response>;
51
+
52
+ export { type NextGuard, type NextZarexOptions, withZarex, zarexNext };
@@ -0,0 +1,52 @@
1
+ import { ZarexOptions, SecureHeadersOptions, ResponseGuardOptions, WebGuardOptions, ProtectCall, Zarex } from '@zarex/core';
2
+ export { CLOUDFLARE_RANGES, Decision, IpSource, PRIVATE_NETWORKS, ProtectCall, ResponseGuardEvent, ResponseGuardOptions, Rule, SecureHeadersOptions, Zarex, ZarexOptions, createZarex, detectBot, emailValidation, phoneValidation, promptInjection, rateLimit, requestFilter, secureHeaders, sensitiveInfo, shield, tokenBucket, withSecureHeaders } from '@zarex/core';
3
+
4
+ /**
5
+ * Next.js — fonctionne dans le middleware (runtime edge) ET dans les route handlers.
6
+ * Aucune importation de `next` : on utilise la `Request` standard, donc aucun risque de
7
+ * version incompatible et rien de propre à Node dans le middleware.
8
+ *
9
+ * // middleware.ts
10
+ * import { NextResponse } from "next/server";
11
+ * import { zarexNext, shield, rateLimit } from "@zarex/next";
12
+ * const guard = zarexNext({ key: process.env.ZAREX_KEY!, baseUrl: process.env.ZAREX_URL!, ipSource: "vercel",
13
+ * rules: [shield(), rateLimit({ max: 100, window: "1m" })] });
14
+ * export async function middleware(request: Request) {
15
+ * // guard.finalize(...) sur la réponse autorisée AUSSI, pas seulement sur un refus : sans cet appel,
16
+ * // `secureHeaders`/`responseGuard` ne s'appliqueraient JAMAIS au trafic autorisé (trouvé lors de
17
+ * // l'audit du 04-05/11/2026 — `guard()` ne peut pas le faire elle-même : importer next/server ici
18
+ * // romprait la garantie « aucune dépendance à next » de ce fichier, voir l'en-tête ci-dessus).
19
+ * return (await guard(request)) ?? guard.finalize(NextResponse.next(), request);
20
+ * }
21
+ *
22
+ * LIMITE IMPORTANTE : le middleware Next.js ne voit pas l'adresse de la connexion TCP. Seules les sources
23
+ * qui lisent un en-tête posé par la plateforme fonctionnent donc : "vercel", "fly", "auto". Hébergé ailleurs
24
+ * (nginx, Docker…), utilisez un route handler avec `withZarex` n'y change rien non plus : voir la documentation.
25
+ */
26
+ interface NextZarexOptions extends ZarexOptions {
27
+ inspectBody?: boolean;
28
+ /** En-têtes de sécurité (true = défauts prudents). Voir `guard.finalize` pour le middleware. */
29
+ secureHeaders?: SecureHeadersOptions | boolean;
30
+ /** Surveille les réponses des route handlers (withZarex) : masque les cartes bancaires, clés d'API, etc. */
31
+ responseGuard?: ResponseGuardOptions | boolean;
32
+ onDeny?: WebGuardOptions["onDeny"];
33
+ onDegraded?: WebGuardOptions["onDegraded"];
34
+ /** Identifiants / jetons propres à chaque requête (ex. userId). Une erreur dans cette fonction est ignorée. */
35
+ call?: (request: Request) => ProtectCall | undefined;
36
+ }
37
+ type Extra = Pick<NextZarexOptions, "inspectBody" | "onDeny" | "onDegraded" | "call" | "secureHeaders" | "responseGuard">;
38
+ type NextGuard = ((request: Request) => Promise<Response | null>) & {
39
+ /** Ajoute les en-têtes de sécurité (et surveille la réponse) : `return guard.finalize(NextResponse.next(), request)`. */
40
+ finalize(response: Response, request?: Request): Promise<Response>;
41
+ };
42
+ /**
43
+ * Renvoie une Response de refus, ou null si la requête peut continuer. Ne lève jamais.
44
+ * `guard.finalize(response, request)` ajoute les en-têtes de sécurité demandés à n'importe quelle réponse —
45
+ * y compris la réponse AUTORISÉE en usage middleware, sinon jamais appliqués à ce trafic (voir l'exemple
46
+ * en tête de fichier).
47
+ */
48
+ declare function zarexNext(options: NextZarexOptions | Zarex, extra?: Extra): NextGuard;
49
+ /** Enveloppe un route handler : `export const POST = withZarex(options, async (req) => …)`. */
50
+ declare function withZarex<A extends unknown[], R>(options: NextZarexOptions | Zarex, handler: (request: Request, ...args: A) => R | Promise<R>, extra?: Extra): (request: Request, ...args: A) => Promise<R | Response>;
51
+
52
+ export { type NextGuard, type NextZarexOptions, withZarex, zarexNext };
package/dist/index.js ADDED
@@ -0,0 +1,91 @@
1
+ // src/index.ts
2
+ import { createWebFinalizer, createZarex, guardWebRequest } from "@zarex/core";
3
+ import {
4
+ shield,
5
+ rateLimit,
6
+ tokenBucket,
7
+ detectBot,
8
+ requestFilter,
9
+ sensitiveInfo,
10
+ emailValidation,
11
+ phoneValidation,
12
+ promptInjection,
13
+ secureHeaders,
14
+ withSecureHeaders,
15
+ createZarex as createZarex2,
16
+ PRIVATE_NETWORKS,
17
+ CLOUDFLARE_RANGES
18
+ } from "@zarex/core";
19
+ function build(options, extra) {
20
+ const isInstance = typeof options.protect === "function";
21
+ return isInstance ? { instance: options, opts: extra } : { instance: createZarex(options), opts: options };
22
+ }
23
+ function callOf(opts, request) {
24
+ try {
25
+ return opts.call?.(request);
26
+ } catch {
27
+ return void 0;
28
+ }
29
+ }
30
+ function buildGuard(options, extra, warnAboutMiddlewareFinalize) {
31
+ const { instance, opts } = build(options, extra);
32
+ const finalizer = createWebFinalizer({ secureHeaders: opts.secureHeaders, responseGuard: opts.responseGuard });
33
+ if (finalizer && warnAboutMiddlewareFinalize) {
34
+ console.warn(
35
+ "[zarex] next: `secureHeaders`/`responseGuard` is configured. In middleware.ts, call `guard.finalize(NextResponse.next(), request)` on the ALLOWED path too (not just on a denial) \u2014 otherwise it is never applied to allowed traffic: return (await guard(request)) ?? guard.finalize(NextResponse.next(), request)."
36
+ );
37
+ }
38
+ const finalize = async (response, request) => {
39
+ try {
40
+ return finalizer ? await finalizer(response, request) : response;
41
+ } catch {
42
+ return response;
43
+ }
44
+ };
45
+ async function guard(request) {
46
+ try {
47
+ const call = callOf(opts, request);
48
+ const { deny } = await guardWebRequest(instance, request, {
49
+ ...opts.inspectBody ? { inspectBody: true } : {},
50
+ ...opts.onDeny ? { onDeny: opts.onDeny } : {},
51
+ ...opts.onDegraded ? { onDegraded: opts.onDegraded } : {},
52
+ ...call ? { call } : {}
53
+ });
54
+ return deny ? await finalize(deny, request) : null;
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+ return Object.assign(guard, { finalize });
60
+ }
61
+ function zarexNext(options, extra = {}) {
62
+ return buildGuard(options, extra, true);
63
+ }
64
+ function withZarex(options, handler, extra = {}) {
65
+ const guard = buildGuard(options, extra, false);
66
+ return async (request, ...args) => {
67
+ const denied = await guard(request);
68
+ if (denied) return denied;
69
+ const out = await handler(request, ...args);
70
+ return out instanceof Response ? await guard.finalize(out, request) : out;
71
+ };
72
+ }
73
+ export {
74
+ CLOUDFLARE_RANGES,
75
+ PRIVATE_NETWORKS,
76
+ createZarex2 as createZarex,
77
+ detectBot,
78
+ emailValidation,
79
+ phoneValidation,
80
+ promptInjection,
81
+ rateLimit,
82
+ requestFilter,
83
+ secureHeaders,
84
+ sensitiveInfo,
85
+ shield,
86
+ tokenBucket,
87
+ withSecureHeaders,
88
+ withZarex,
89
+ zarexNext
90
+ };
91
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { createWebFinalizer, createZarex, guardWebRequest, type ResponseGuardOptions, type SecureHeadersOptions, type Decision, type ProtectCall, type WebGuardOptions, type Zarex, type ZarexOptions } from \"@zarex/core\";\r\n\r\n/**\r\n * Next.js — fonctionne dans le middleware (runtime edge) ET dans les route handlers.\r\n * Aucune importation de `next` : on utilise la `Request` standard, donc aucun risque de\r\n * version incompatible et rien de propre à Node dans le middleware.\r\n *\r\n * // middleware.ts\r\n * import { NextResponse } from \"next/server\";\r\n * import { zarexNext, shield, rateLimit } from \"@zarex/next\";\r\n * const guard = zarexNext({ key: process.env.ZAREX_KEY!, baseUrl: process.env.ZAREX_URL!, ipSource: \"vercel\",\r\n * rules: [shield(), rateLimit({ max: 100, window: \"1m\" })] });\r\n * export async function middleware(request: Request) {\r\n * // guard.finalize(...) sur la réponse autorisée AUSSI, pas seulement sur un refus : sans cet appel,\r\n * // `secureHeaders`/`responseGuard` ne s'appliqueraient JAMAIS au trafic autorisé (trouvé lors de\r\n * // l'audit du 04-05/11/2026 — `guard()` ne peut pas le faire elle-même : importer next/server ici\r\n * // romprait la garantie « aucune dépendance à next » de ce fichier, voir l'en-tête ci-dessus).\r\n * return (await guard(request)) ?? guard.finalize(NextResponse.next(), request);\r\n * }\r\n *\r\n * LIMITE IMPORTANTE : le middleware Next.js ne voit pas l'adresse de la connexion TCP. Seules les sources\r\n * qui lisent un en-tête posé par la plateforme fonctionnent donc : \"vercel\", \"fly\", \"auto\". Hébergé ailleurs\r\n * (nginx, Docker…), utilisez un route handler avec `withZarex` n'y change rien non plus : voir la documentation.\r\n */\r\nexport interface NextZarexOptions extends ZarexOptions {\r\n inspectBody?: boolean;\r\n /** En-têtes de sécurité (true = défauts prudents). Voir `guard.finalize` pour le middleware. */\r\n secureHeaders?: SecureHeadersOptions | boolean;\r\n /** Surveille les réponses des route handlers (withZarex) : masque les cartes bancaires, clés d'API, etc. */\r\n responseGuard?: ResponseGuardOptions | boolean;\r\n onDeny?: WebGuardOptions[\"onDeny\"];\r\n onDegraded?: WebGuardOptions[\"onDegraded\"];\r\n /** Identifiants / jetons propres à chaque requête (ex. userId). Une erreur dans cette fonction est ignorée. */\r\n call?: (request: Request) => ProtectCall | undefined;\r\n}\r\ntype Extra = Pick<NextZarexOptions, \"inspectBody\" | \"onDeny\" | \"onDegraded\" | \"call\" | \"secureHeaders\" | \"responseGuard\">;\r\n\r\nexport type NextGuard = ((request: Request) => Promise<Response | null>) & {\r\n /** Ajoute les en-têtes de sécurité (et surveille la réponse) : `return guard.finalize(NextResponse.next(), request)`. */\r\n finalize(response: Response, request?: Request): Promise<Response>;\r\n};\r\n\r\nfunction build(options: NextZarexOptions | Zarex, extra: Extra): { instance: Zarex; opts: Extra } {\r\n const isInstance = typeof (options as Zarex).protect === \"function\";\r\n return isInstance\r\n ? { instance: options as Zarex, opts: extra }\r\n : { instance: createZarex(options as NextZarexOptions), opts: options as NextZarexOptions };\r\n}\r\n\r\nfunction callOf(opts: Extra, request: Request): ProtectCall | undefined {\r\n try {\r\n return opts.call?.(request);\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n// warnAboutMiddlewareFinalize : seul `zarexNext()` appelé directement (usage middleware) a besoin du\r\n// rappel ci-dessous — `withZarex` (route handlers) applique déjà `finalize()` lui-même sur le chemin\r\n// autorisé (voir plus bas), le même avertissement y serait un faux positif systématique.\r\nfunction buildGuard(options: NextZarexOptions | Zarex, extra: Extra, warnAboutMiddlewareFinalize: boolean): NextGuard {\r\n const { instance, opts } = build(options, extra);\r\n const finalizer = createWebFinalizer({ secureHeaders: opts.secureHeaders, responseGuard: opts.responseGuard });\r\n // Rappel au démarrage (pas par requête) : `guard()` renvoie `null` quand la requête est autorisée et ne\r\n // peut pas construire elle-même la réponse de continuation (NextResponse.next(), propre à next/server —\r\n // voir l'en-tête du fichier). Sans `guard.finalize(...)` explicite sur cette réponse-là dans le\r\n // middleware, `secureHeaders`/`responseGuard` ne s'appliqueraient JAMAIS au trafic autorisé. Trouvé lors\r\n // de l'audit du 04-05/11/2026 : ce piège n'était signalé nulle part à l'exécution.\r\n if (finalizer && warnAboutMiddlewareFinalize) {\r\n console.warn(\r\n '[zarex] next: `secureHeaders`/`responseGuard` is configured. In middleware.ts, call `guard.finalize(NextResponse.next(), request)` ' +\r\n \"on the ALLOWED path too (not just on a denial) — otherwise it is never applied to allowed traffic: \" +\r\n 'return (await guard(request)) ?? guard.finalize(NextResponse.next(), request).'\r\n );\r\n }\r\n const finalize = async (response: Response, request?: Request): Promise<Response> => {\r\n try {\r\n return finalizer ? await finalizer(response, request) : response;\r\n } catch {\r\n return response;\r\n }\r\n };\r\n async function guard(request: Request): Promise<Response | null> {\r\n try {\r\n const call = callOf(opts, request);\r\n const { deny } = await guardWebRequest(instance, request, {\r\n ...(opts.inspectBody ? { inspectBody: true } : {}),\r\n ...(opts.onDeny ? { onDeny: opts.onDeny } : {}),\r\n ...(opts.onDegraded ? { onDegraded: opts.onDegraded } : {}),\r\n ...(call ? { call } : {}),\r\n });\r\n return deny ? await finalize(deny, request) : null;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n return Object.assign(guard, { finalize });\r\n}\r\n\r\n/**\r\n * Renvoie une Response de refus, ou null si la requête peut continuer. Ne lève jamais.\r\n * `guard.finalize(response, request)` ajoute les en-têtes de sécurité demandés à n'importe quelle réponse —\r\n * y compris la réponse AUTORISÉE en usage middleware, sinon jamais appliqués à ce trafic (voir l'exemple\r\n * en tête de fichier).\r\n */\r\nexport function zarexNext(options: NextZarexOptions | Zarex, extra: Extra = {}): NextGuard {\r\n return buildGuard(options, extra, true);\r\n}\r\n\r\n/** Enveloppe un route handler : `export const POST = withZarex(options, async (req) => …)`. */\r\nexport function withZarex<A extends unknown[], R>(\r\n options: NextZarexOptions | Zarex,\r\n handler: (request: Request, ...args: A) => R | Promise<R>,\r\n extra: Extra = {}\r\n): (request: Request, ...args: A) => Promise<R | Response> {\r\n const guard = buildGuard(options, extra, false); // applique déjà finalize() lui-même ci-dessous, pas de rappel nécessaire\r\n return async (request, ...args) => {\r\n const denied = await guard(request);\r\n if (denied) return denied;\r\n const out = await handler(request, ...args);\r\n return out instanceof Response ? await guard.finalize(out, request) : out;\r\n };\r\n}\r\n\r\nexport {\r\n shield,\r\n rateLimit,\r\n tokenBucket,\r\n detectBot,\r\n requestFilter,\r\n sensitiveInfo,\r\n emailValidation,\r\n phoneValidation,\r\n promptInjection,\r\n secureHeaders,\r\n withSecureHeaders,\r\n createZarex,\r\n PRIVATE_NETWORKS,\r\n CLOUDFLARE_RANGES,\r\n} from \"@zarex/core\";\r\nexport type { SecureHeadersOptions, ResponseGuardOptions, ResponseGuardEvent } from \"@zarex/core\";\r\nexport type { Decision, Zarex, ZarexOptions, IpSource, Rule, ProtectCall } from \"@zarex/core\";"],"mappings":";AAAA,SAAS,oBAAoB,aAAa,uBAAmK;AA4H7M;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAAA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAjGP,SAAS,MAAM,SAAmC,OAAgD;AAChG,QAAM,aAAa,OAAQ,QAAkB,YAAY;AACzD,SAAO,aACH,EAAE,UAAU,SAAkB,MAAM,MAAM,IAC1C,EAAE,UAAU,YAAY,OAA2B,GAAG,MAAM,QAA4B;AAC9F;AAEA,SAAS,OAAO,MAAa,SAA2C;AACtE,MAAI;AACF,WAAO,KAAK,OAAO,OAAO;AAAA,EAC5B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,WAAW,SAAmC,OAAc,6BAAiD;AACpH,QAAM,EAAE,UAAU,KAAK,IAAI,MAAM,SAAS,KAAK;AAC/C,QAAM,YAAY,mBAAmB,EAAE,eAAe,KAAK,eAAe,eAAe,KAAK,cAAc,CAAC;AAM7G,MAAI,aAAa,6BAA6B;AAC5C,YAAQ;AAAA,MACN;AAAA,IAGF;AAAA,EACF;AACA,QAAM,WAAW,OAAO,UAAoB,YAAyC;AACnF,QAAI;AACF,aAAO,YAAY,MAAM,UAAU,UAAU,OAAO,IAAI;AAAA,IAC1D,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,iBAAe,MAAM,SAA4C;AAC/D,QAAI;AACF,YAAM,OAAO,OAAO,MAAM,OAAO;AACjC,YAAM,EAAE,KAAK,IAAI,MAAM,gBAAgB,UAAU,SAAS;AAAA,QACxD,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,IAAI,CAAC;AAAA,QAChD,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,QAC7C,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,QACzD,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MACzB,CAAC;AACD,aAAO,OAAO,MAAM,SAAS,MAAM,OAAO,IAAI;AAAA,IAChD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO,OAAO,OAAO,OAAO,EAAE,SAAS,CAAC;AAC1C;AAQO,SAAS,UAAU,SAAmC,QAAe,CAAC,GAAc;AACzF,SAAO,WAAW,SAAS,OAAO,IAAI;AACxC;AAGO,SAAS,UACd,SACA,SACA,QAAe,CAAC,GACyC;AACzD,QAAM,QAAQ,WAAW,SAAS,OAAO,KAAK;AAC9C,SAAO,OAAO,YAAY,SAAS;AACjC,UAAM,SAAS,MAAM,MAAM,OAAO;AAClC,QAAI,OAAQ,QAAO;AACnB,UAAM,MAAM,MAAM,QAAQ,SAAS,GAAG,IAAI;AAC1C,WAAO,eAAe,WAAW,MAAM,MAAM,SAAS,KAAK,OAAO,IAAI;AAAA,EACxE;AACF;","names":["createZarex"]}
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "@zarex/next",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Zarex — Next.js (middleware edge et route handlers)",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "type": "module",
10
+ "main": "./dist/index.cjs",
11
+ "module": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "import": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "require": {
20
+ "types": "./dist/index.d.cts",
21
+ "default": "./dist/index.cjs"
22
+ }
23
+ }
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "sideEffects": false,
29
+ "dependencies": {
30
+ "@zarex/core": "^0.1.0"
31
+ },
32
+ "peerDependencies": {
33
+ "next": ">=13"
34
+ },
35
+ "peerDependenciesMeta": {
36
+ "next": {
37
+ "optional": true
38
+ }
39
+ },
40
+ "scripts": {
41
+ "build": "tsup"
42
+ }
6
43
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.