guardian-risk-express 0.1.0 → 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/README.md CHANGED
@@ -6,34 +6,57 @@
6
6
  npm install guardian-risk guardian-risk-express
7
7
  ```
8
8
 
9
- > **Stub package** API may change before `1.0.0`.
9
+ Express integration for [guardian-risk](https://www.npmjs.com/package/guardian-risk). Validates client IP, reads HTTP metadata, and provides production middleware.
10
10
 
11
- Express integration for [guardian-risk](https://www.npmjs.com/package/guardian-risk). Reads HTTP request data and adds risk signals.
12
-
13
- ## Planned signals
11
+ ## Signals
14
12
 
15
13
  | Signal | Source |
16
14
  |--------|--------|
17
- | `clientIp` | `req.ip` / forwarded headers |
18
- | `userAgent` | `User-Agent` header |
15
+ | `clientIp` | Validated `req.ip` / `req.ips` (trust proxy aware) |
16
+ | `userAgent` | `User-Agent` header (truncated) |
19
17
  | `requestMethod` | `req.method` |
20
- | `requestsPerMinute` | Rate counter (with Redis plugin) |
18
+ | `requestPath` | `req.path` |
21
19
 
22
- ## Usage (stub)
20
+ ## Production usage
23
21
 
24
22
  ```typescript
23
+ import express from 'express';
25
24
  import { Guardian } from 'guardian-risk';
26
- import { expressPlugin, fromRequest } from 'guardian-risk-express';
25
+ import { expressPlugin, guardianMiddleware } from 'guardian-risk-express';
26
+ import { redisPlugin } from 'guardian-risk-redis';
27
+
28
+ const app = express();
29
+ app.set('trust proxy', 1); // required behind load balancers
30
+
31
+ const template = new Guardian()
32
+ .use(expressPlugin({ trustProxy: true }))
33
+ .use(redisPlugin({ url: process.env.REDIS_URL }))
34
+ .rule({ name: 'HighRate', when: (s) => (s.requestsPerMinute ?? 0) > 120, score: 40 });
35
+
36
+ app.get('/health', (_req, res) => res.json({ ok: true }));
37
+
38
+ app.use(
39
+ guardianMiddleware(template, {
40
+ blockAboveScore: 70,
41
+ onAnalyzeError: 'block', // fail closed when hooks fail
42
+ exposeBlockDetails: false, // never leak reasons to clients
43
+ }),
44
+ );
45
+ ```
27
46
 
28
- const guardian = new Guardian().use(expressPlugin({ trustProxy: true }));
47
+ ## Security notes
29
48
 
30
- // Future: per-request signal injection
31
- // app.use((req, res, next) => {
32
- // fromRequest(req, guardian);
33
- // next();
34
- // });
35
- ```
49
+ - Set **`app.set('trust proxy', N)`** when behind a reverse proxy — otherwise `clientIp` reflects the proxy, not the user.
50
+ - **`clientIp` is validated** malformed `X-Forwarded-For` values are rejected.
51
+ - Use **`onAnalyzeError: 'block'`** in production when `blockAboveScore` is set.
52
+ - Use **`template.fork()`** or `guardianMiddleware` — never share one `Guardian` across requests.
53
+ - Headers and user agents are **spoofable** — treat as hints, not proof.
54
+
55
+ ## API
36
56
 
37
- ## Status
57
+ - `expressPlugin(options)` — registers `beforeAnalyze` hook
58
+ - `fromRequest(req, guardian, options)` — manual per-request signal injection
59
+ - `guardianMiddleware(template, options)` — Express middleware with optional blocking
60
+ - `analyzeRequest(template, req, options)` — programmatic analyze helper
38
61
 
39
- Not yet published. Implementation in progress.
62
+ See [SECURITY.md](../../SECURITY.md) and [MIGRATION.md](../../MIGRATION.md).
package/dist/index.cjs CHANGED
@@ -1,19 +1,141 @@
1
1
  'use strict';
2
2
 
3
+ var guardianRisk = require('guardian-risk');
4
+
5
+ // src/request.ts
6
+ var MAX_HEADER_LENGTH = 512;
7
+ function fromRequest(req, guardian, options = {}) {
8
+ const { trustProxy = false } = options;
9
+ const clientIp = resolveClientIp(req, trustProxy);
10
+ const userAgent = truncate(readHeader(req, "user-agent") ?? "unknown", MAX_HEADER_LENGTH);
11
+ const contentLength = Number(readHeader(req, "content-length") ?? 0);
12
+ const acceptLanguage = truncate(
13
+ readHeader(req, "accept-language") ?? "unknown",
14
+ MAX_HEADER_LENGTH
15
+ );
16
+ return guardian.signal("clientIp", clientIp).signal("userAgent", userAgent).signal("requestMethod", req.method ?? "UNKNOWN").signal("requestPath", truncate(req.path ?? req.originalUrl ?? "/", MAX_HEADER_LENGTH)).signal("contentLength", Number.isFinite(contentLength) ? contentLength : 0).signal("acceptLanguage", acceptLanguage).signal("requestSource", "express");
17
+ }
18
+ function expressBeforeAnalyze(options = {}) {
19
+ return ({ data: req, guardian }) => {
20
+ fromRequest(req, guardian, options);
21
+ };
22
+ }
23
+ function resolveClientIp(req, trustProxy) {
24
+ if (trustProxy) {
25
+ if (req.ips && req.ips.length > 0) {
26
+ for (const candidate of req.ips) {
27
+ const parsed = guardianRisk.parseIpAddress(candidate);
28
+ if (parsed) {
29
+ return parsed;
30
+ }
31
+ }
32
+ }
33
+ const forwarded = readHeader(req, "x-forwarded-for");
34
+ if (forwarded) {
35
+ for (const part of forwarded.split(",")) {
36
+ const parsed = guardianRisk.parseIpAddress(part);
37
+ if (parsed) {
38
+ return parsed;
39
+ }
40
+ }
41
+ }
42
+ }
43
+ const fromReqIp = req.ip ? guardianRisk.parseIpAddress(req.ip) : null;
44
+ if (fromReqIp) {
45
+ return fromReqIp;
46
+ }
47
+ const fromSocket = req.socket?.remoteAddress ? guardianRisk.parseIpAddress(req.socket.remoteAddress) : null;
48
+ if (fromSocket) {
49
+ return fromSocket;
50
+ }
51
+ return "unknown";
52
+ }
53
+ function readHeader(req, name) {
54
+ const fromGetter = req.get?.(name);
55
+ if (fromGetter) {
56
+ return fromGetter;
57
+ }
58
+ const value = req.headers[name.toLowerCase()] ?? req.headers[name];
59
+ if (Array.isArray(value)) {
60
+ return value[0];
61
+ }
62
+ return value;
63
+ }
64
+ function truncate(value, max) {
65
+ return value.length > max ? value.slice(0, max) : value;
66
+ }
67
+
68
+ // src/middleware.ts
69
+ async function analyzeRequest(template, req, options = {}) {
70
+ const guardian = template.fork();
71
+ if (!template.getInstalledPlugins().includes("guardian-risk-express")) {
72
+ fromRequest(req, guardian, options);
73
+ }
74
+ const report = await guardian.analyzeAsync(req);
75
+ return { guardian, report };
76
+ }
77
+ function guardianMiddleware(template, options = {}) {
78
+ const {
79
+ attachToRequest = true,
80
+ blockAboveScore,
81
+ trustProxy = false,
82
+ exposeBlockDetails = false
83
+ } = options;
84
+ const onAnalyzeError = options.onAnalyzeError ?? (blockAboveScore !== void 0 ? "block" : "pass");
85
+ return (req, res, next) => {
86
+ void (async () => {
87
+ try {
88
+ const guardian = template.fork();
89
+ if (!template.getInstalledPlugins().includes("guardian-risk-express")) {
90
+ fromRequest(req, guardian, { trustProxy });
91
+ }
92
+ const report = await guardian.analyzeAsync(req);
93
+ if (attachToRequest) {
94
+ req.guardian = guardian;
95
+ req.riskReport = report;
96
+ }
97
+ if (blockAboveScore !== void 0 && report.score >= blockAboveScore) {
98
+ sendBlocked(res, report, exposeBlockDetails);
99
+ return;
100
+ }
101
+ next();
102
+ } catch (error) {
103
+ if (onAnalyzeError === "block") {
104
+ res.status(503).json({ error: "Risk analysis unavailable" });
105
+ return;
106
+ }
107
+ next(error);
108
+ }
109
+ })();
110
+ };
111
+ }
112
+ function sendBlocked(res, report, exposeDetails) {
113
+ if (exposeDetails) {
114
+ res.status(403).json({
115
+ error: "Request blocked",
116
+ score: report.score,
117
+ level: report.level,
118
+ reasons: report.reasons
119
+ });
120
+ return;
121
+ }
122
+ res.status(403).json({ error: "Request blocked" });
123
+ }
124
+
3
125
  // src/index.ts
4
126
  function expressPlugin(options = {}) {
5
- const { trustProxy = false } = options;
127
+ const hook = expressBeforeAnalyze(options);
6
128
  return {
7
129
  name: "guardian-risk-express",
8
- install(_guardian) {
130
+ install(guardian) {
131
+ guardian.beforeAnalyze(hook);
9
132
  }
10
133
  };
11
134
  }
12
- function fromRequest(_req, guardian) {
13
- return guardian.signal("requestSource", "express").signal("expressPlugin", "stub");
14
- }
15
135
 
136
+ exports.analyzeRequest = analyzeRequest;
137
+ exports.expressBeforeAnalyze = expressBeforeAnalyze;
16
138
  exports.expressPlugin = expressPlugin;
17
139
  exports.fromRequest = fromRequest;
18
- //# sourceMappingURL=index.cjs.map
19
- //# sourceMappingURL=index.cjs.map
140
+ exports.guardianMiddleware = guardianMiddleware;
141
+ exports.resolveClientIp = resolveClientIp;
package/dist/index.d.cts CHANGED
@@ -1,22 +1,85 @@
1
1
  import * as guardian_risk from 'guardian-risk';
2
- import { Plugin } from 'guardian-risk';
2
+ import { RiskReport, Guardian, Plugin } from 'guardian-risk';
3
3
 
4
- /** Options for the Express plugin (stub). */
4
+ /** Minimal Express request shape no express import required at runtime. */
5
+ interface ExpressRequestLike {
6
+ readonly ip?: string;
7
+ readonly ips?: readonly string[];
8
+ readonly method?: string;
9
+ readonly path?: string;
10
+ readonly originalUrl?: string;
11
+ readonly headers: Record<string, string | string[] | undefined>;
12
+ readonly socket?: {
13
+ readonly remoteAddress?: string;
14
+ };
15
+ get?(name: string): string | undefined;
16
+ }
17
+ /** Options for reading request signals. */
5
18
  interface ExpressPluginOptions {
6
- /** Trust X-Forwarded-* headers when reading client IP. */
19
+ /**
20
+ * Trust proxy-forwarded IPs (requires `app.set('trust proxy', ...)` in Express).
21
+ */
7
22
  readonly trustProxy?: boolean;
8
23
  }
9
24
  /**
10
- * Express plugin for guardian-risk.
11
- *
12
- * @stub This plugin is a stub. Future versions will read Express requests
13
- * and add signals such as `clientIp`, `userAgent`, `requestMethod`, and
14
- * `requestsPerMinute`.
25
+ * Extract HTTP request signals and attach them to a Guardian instance.
26
+ */
27
+ declare function fromRequest(req: ExpressRequestLike, guardian: guardian_risk.Guardian, options?: ExpressPluginOptions): guardian_risk.Guardian;
28
+ /**
29
+ * Returns a beforeAnalyze hook that enriches signals from an Express request.
15
30
  */
16
- declare function expressPlugin(options?: ExpressPluginOptions): Plugin;
31
+ declare function expressBeforeAnalyze(options?: ExpressPluginOptions): guardian_risk.BeforeAnalyzeHook<ExpressRequestLike>;
17
32
  /**
18
- * @stub Future middleware that enriches a Guardian instance from an Express request.
33
+ * Resolve client IP with validation. Spoofed or invalid values are rejected.
34
+ */
35
+ declare function resolveClientIp(req: ExpressRequestLike, trustProxy: boolean): string;
36
+
37
+ /** Express-compatible middleware types (optional peer). */
38
+ interface ExpressRequest extends ExpressRequestLike {
39
+ riskReport?: RiskReport;
40
+ guardian?: Guardian;
41
+ }
42
+ type ExpressNextFunction = (error?: unknown) => void;
43
+ interface ExpressResponse {
44
+ status(code: number): ExpressResponse;
45
+ json(body: unknown): void;
46
+ }
47
+ type ExpressRequestHandler = (req: ExpressRequest, res: ExpressResponse, next: ExpressNextFunction) => void;
48
+ type AnalyzeErrorPolicy = 'pass' | 'block';
49
+ interface GuardianMiddlewareOptions extends ExpressPluginOptions {
50
+ readonly attachToRequest?: boolean;
51
+ /** Block when score is greater than or equal to this threshold. */
52
+ readonly blockAboveScore?: number;
53
+ /**
54
+ * When analysis fails (hook timeout, VPN error), `block` returns 503.
55
+ * Defaults to `block` when `blockAboveScore` is set, otherwise `pass`.
56
+ */
57
+ readonly onAnalyzeError?: AnalyzeErrorPolicy;
58
+ /** Include score/reasons in block response (default: false). */
59
+ readonly exposeBlockDetails?: boolean;
60
+ }
61
+ declare function analyzeRequest(template: Guardian, req: ExpressRequestLike, options?: ExpressPluginOptions): Promise<{
62
+ guardian: Guardian;
63
+ report: RiskReport;
64
+ }>;
65
+ declare function guardianMiddleware(template: Guardian, options?: GuardianMiddlewareOptions): ExpressRequestHandler;
66
+
67
+ /** Options for the Express plugin. */
68
+ type ExpressPluginConfig = ExpressPluginOptions;
69
+ /**
70
+ * Express plugin — registers a beforeAnalyze hook for request signal collection.
71
+ *
72
+ * Usage with middleware (recommended):
73
+ * ```typescript
74
+ * app.use(guardianMiddleware(template, { trustProxy: true }));
75
+ * ```
76
+ *
77
+ * Or manual:
78
+ * ```typescript
79
+ * const g = template.fork().use(expressPlugin({ trustProxy: true }));
80
+ * const report = await g.analyzeAsync(req);
81
+ * ```
19
82
  */
20
- declare function fromRequest(_req: unknown, guardian: guardian_risk.Guardian): guardian_risk.Guardian;
83
+ declare function expressPlugin(options?: ExpressPluginConfig): Plugin;
21
84
 
22
- export { type ExpressPluginOptions, expressPlugin, fromRequest };
85
+ export { type AnalyzeErrorPolicy, type ExpressNextFunction, type ExpressPluginConfig, type ExpressPluginOptions, type ExpressRequest, type ExpressRequestHandler, type ExpressRequestLike, type ExpressResponse, type GuardianMiddlewareOptions, analyzeRequest, expressBeforeAnalyze, expressPlugin, fromRequest, guardianMiddleware, resolveClientIp };
package/dist/index.d.ts CHANGED
@@ -1,22 +1,85 @@
1
1
  import * as guardian_risk from 'guardian-risk';
2
- import { Plugin } from 'guardian-risk';
2
+ import { RiskReport, Guardian, Plugin } from 'guardian-risk';
3
3
 
4
- /** Options for the Express plugin (stub). */
4
+ /** Minimal Express request shape no express import required at runtime. */
5
+ interface ExpressRequestLike {
6
+ readonly ip?: string;
7
+ readonly ips?: readonly string[];
8
+ readonly method?: string;
9
+ readonly path?: string;
10
+ readonly originalUrl?: string;
11
+ readonly headers: Record<string, string | string[] | undefined>;
12
+ readonly socket?: {
13
+ readonly remoteAddress?: string;
14
+ };
15
+ get?(name: string): string | undefined;
16
+ }
17
+ /** Options for reading request signals. */
5
18
  interface ExpressPluginOptions {
6
- /** Trust X-Forwarded-* headers when reading client IP. */
19
+ /**
20
+ * Trust proxy-forwarded IPs (requires `app.set('trust proxy', ...)` in Express).
21
+ */
7
22
  readonly trustProxy?: boolean;
8
23
  }
9
24
  /**
10
- * Express plugin for guardian-risk.
11
- *
12
- * @stub This plugin is a stub. Future versions will read Express requests
13
- * and add signals such as `clientIp`, `userAgent`, `requestMethod`, and
14
- * `requestsPerMinute`.
25
+ * Extract HTTP request signals and attach them to a Guardian instance.
26
+ */
27
+ declare function fromRequest(req: ExpressRequestLike, guardian: guardian_risk.Guardian, options?: ExpressPluginOptions): guardian_risk.Guardian;
28
+ /**
29
+ * Returns a beforeAnalyze hook that enriches signals from an Express request.
15
30
  */
16
- declare function expressPlugin(options?: ExpressPluginOptions): Plugin;
31
+ declare function expressBeforeAnalyze(options?: ExpressPluginOptions): guardian_risk.BeforeAnalyzeHook<ExpressRequestLike>;
17
32
  /**
18
- * @stub Future middleware that enriches a Guardian instance from an Express request.
33
+ * Resolve client IP with validation. Spoofed or invalid values are rejected.
34
+ */
35
+ declare function resolveClientIp(req: ExpressRequestLike, trustProxy: boolean): string;
36
+
37
+ /** Express-compatible middleware types (optional peer). */
38
+ interface ExpressRequest extends ExpressRequestLike {
39
+ riskReport?: RiskReport;
40
+ guardian?: Guardian;
41
+ }
42
+ type ExpressNextFunction = (error?: unknown) => void;
43
+ interface ExpressResponse {
44
+ status(code: number): ExpressResponse;
45
+ json(body: unknown): void;
46
+ }
47
+ type ExpressRequestHandler = (req: ExpressRequest, res: ExpressResponse, next: ExpressNextFunction) => void;
48
+ type AnalyzeErrorPolicy = 'pass' | 'block';
49
+ interface GuardianMiddlewareOptions extends ExpressPluginOptions {
50
+ readonly attachToRequest?: boolean;
51
+ /** Block when score is greater than or equal to this threshold. */
52
+ readonly blockAboveScore?: number;
53
+ /**
54
+ * When analysis fails (hook timeout, VPN error), `block` returns 503.
55
+ * Defaults to `block` when `blockAboveScore` is set, otherwise `pass`.
56
+ */
57
+ readonly onAnalyzeError?: AnalyzeErrorPolicy;
58
+ /** Include score/reasons in block response (default: false). */
59
+ readonly exposeBlockDetails?: boolean;
60
+ }
61
+ declare function analyzeRequest(template: Guardian, req: ExpressRequestLike, options?: ExpressPluginOptions): Promise<{
62
+ guardian: Guardian;
63
+ report: RiskReport;
64
+ }>;
65
+ declare function guardianMiddleware(template: Guardian, options?: GuardianMiddlewareOptions): ExpressRequestHandler;
66
+
67
+ /** Options for the Express plugin. */
68
+ type ExpressPluginConfig = ExpressPluginOptions;
69
+ /**
70
+ * Express plugin — registers a beforeAnalyze hook for request signal collection.
71
+ *
72
+ * Usage with middleware (recommended):
73
+ * ```typescript
74
+ * app.use(guardianMiddleware(template, { trustProxy: true }));
75
+ * ```
76
+ *
77
+ * Or manual:
78
+ * ```typescript
79
+ * const g = template.fork().use(expressPlugin({ trustProxy: true }));
80
+ * const report = await g.analyzeAsync(req);
81
+ * ```
19
82
  */
20
- declare function fromRequest(_req: unknown, guardian: guardian_risk.Guardian): guardian_risk.Guardian;
83
+ declare function expressPlugin(options?: ExpressPluginConfig): Plugin;
21
84
 
22
- export { type ExpressPluginOptions, expressPlugin, fromRequest };
85
+ export { type AnalyzeErrorPolicy, type ExpressNextFunction, type ExpressPluginConfig, type ExpressPluginOptions, type ExpressRequest, type ExpressRequestHandler, type ExpressRequestLike, type ExpressResponse, type GuardianMiddlewareOptions, analyzeRequest, expressBeforeAnalyze, expressPlugin, fromRequest, guardianMiddleware, resolveClientIp };
package/dist/index.js CHANGED
@@ -1,16 +1,134 @@
1
+ import { parseIpAddress } from 'guardian-risk';
2
+
3
+ // src/request.ts
4
+ var MAX_HEADER_LENGTH = 512;
5
+ function fromRequest(req, guardian, options = {}) {
6
+ const { trustProxy = false } = options;
7
+ const clientIp = resolveClientIp(req, trustProxy);
8
+ const userAgent = truncate(readHeader(req, "user-agent") ?? "unknown", MAX_HEADER_LENGTH);
9
+ const contentLength = Number(readHeader(req, "content-length") ?? 0);
10
+ const acceptLanguage = truncate(
11
+ readHeader(req, "accept-language") ?? "unknown",
12
+ MAX_HEADER_LENGTH
13
+ );
14
+ return guardian.signal("clientIp", clientIp).signal("userAgent", userAgent).signal("requestMethod", req.method ?? "UNKNOWN").signal("requestPath", truncate(req.path ?? req.originalUrl ?? "/", MAX_HEADER_LENGTH)).signal("contentLength", Number.isFinite(contentLength) ? contentLength : 0).signal("acceptLanguage", acceptLanguage).signal("requestSource", "express");
15
+ }
16
+ function expressBeforeAnalyze(options = {}) {
17
+ return ({ data: req, guardian }) => {
18
+ fromRequest(req, guardian, options);
19
+ };
20
+ }
21
+ function resolveClientIp(req, trustProxy) {
22
+ if (trustProxy) {
23
+ if (req.ips && req.ips.length > 0) {
24
+ for (const candidate of req.ips) {
25
+ const parsed = parseIpAddress(candidate);
26
+ if (parsed) {
27
+ return parsed;
28
+ }
29
+ }
30
+ }
31
+ const forwarded = readHeader(req, "x-forwarded-for");
32
+ if (forwarded) {
33
+ for (const part of forwarded.split(",")) {
34
+ const parsed = parseIpAddress(part);
35
+ if (parsed) {
36
+ return parsed;
37
+ }
38
+ }
39
+ }
40
+ }
41
+ const fromReqIp = req.ip ? parseIpAddress(req.ip) : null;
42
+ if (fromReqIp) {
43
+ return fromReqIp;
44
+ }
45
+ const fromSocket = req.socket?.remoteAddress ? parseIpAddress(req.socket.remoteAddress) : null;
46
+ if (fromSocket) {
47
+ return fromSocket;
48
+ }
49
+ return "unknown";
50
+ }
51
+ function readHeader(req, name) {
52
+ const fromGetter = req.get?.(name);
53
+ if (fromGetter) {
54
+ return fromGetter;
55
+ }
56
+ const value = req.headers[name.toLowerCase()] ?? req.headers[name];
57
+ if (Array.isArray(value)) {
58
+ return value[0];
59
+ }
60
+ return value;
61
+ }
62
+ function truncate(value, max) {
63
+ return value.length > max ? value.slice(0, max) : value;
64
+ }
65
+
66
+ // src/middleware.ts
67
+ async function analyzeRequest(template, req, options = {}) {
68
+ const guardian = template.fork();
69
+ if (!template.getInstalledPlugins().includes("guardian-risk-express")) {
70
+ fromRequest(req, guardian, options);
71
+ }
72
+ const report = await guardian.analyzeAsync(req);
73
+ return { guardian, report };
74
+ }
75
+ function guardianMiddleware(template, options = {}) {
76
+ const {
77
+ attachToRequest = true,
78
+ blockAboveScore,
79
+ trustProxy = false,
80
+ exposeBlockDetails = false
81
+ } = options;
82
+ const onAnalyzeError = options.onAnalyzeError ?? (blockAboveScore !== void 0 ? "block" : "pass");
83
+ return (req, res, next) => {
84
+ void (async () => {
85
+ try {
86
+ const guardian = template.fork();
87
+ if (!template.getInstalledPlugins().includes("guardian-risk-express")) {
88
+ fromRequest(req, guardian, { trustProxy });
89
+ }
90
+ const report = await guardian.analyzeAsync(req);
91
+ if (attachToRequest) {
92
+ req.guardian = guardian;
93
+ req.riskReport = report;
94
+ }
95
+ if (blockAboveScore !== void 0 && report.score >= blockAboveScore) {
96
+ sendBlocked(res, report, exposeBlockDetails);
97
+ return;
98
+ }
99
+ next();
100
+ } catch (error) {
101
+ if (onAnalyzeError === "block") {
102
+ res.status(503).json({ error: "Risk analysis unavailable" });
103
+ return;
104
+ }
105
+ next(error);
106
+ }
107
+ })();
108
+ };
109
+ }
110
+ function sendBlocked(res, report, exposeDetails) {
111
+ if (exposeDetails) {
112
+ res.status(403).json({
113
+ error: "Request blocked",
114
+ score: report.score,
115
+ level: report.level,
116
+ reasons: report.reasons
117
+ });
118
+ return;
119
+ }
120
+ res.status(403).json({ error: "Request blocked" });
121
+ }
122
+
1
123
  // src/index.ts
2
124
  function expressPlugin(options = {}) {
3
- const { trustProxy = false } = options;
125
+ const hook = expressBeforeAnalyze(options);
4
126
  return {
5
127
  name: "guardian-risk-express",
6
- install(_guardian) {
128
+ install(guardian) {
129
+ guardian.beforeAnalyze(hook);
7
130
  }
8
131
  };
9
132
  }
10
- function fromRequest(_req, guardian) {
11
- return guardian.signal("requestSource", "express").signal("expressPlugin", "stub");
12
- }
13
133
 
14
- export { expressPlugin, fromRequest };
15
- //# sourceMappingURL=index.js.map
16
- //# sourceMappingURL=index.js.map
134
+ export { analyzeRequest, expressBeforeAnalyze, expressPlugin, fromRequest, guardianMiddleware, resolveClientIp };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardian-risk-express",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Express middleware plugin for guardian-risk — adds request signals",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -20,7 +20,10 @@
20
20
  },
21
21
  "sideEffects": false,
22
22
  "files": [
23
- "dist",
23
+ "dist/index.js",
24
+ "dist/index.cjs",
25
+ "dist/index.d.ts",
26
+ "dist/index.d.cts",
24
27
  "README.md",
25
28
  "LICENSE"
26
29
  ],
@@ -35,6 +38,7 @@
35
38
  "risk",
36
39
  "security"
37
40
  ],
41
+ "author": "himanshuusinghh",
38
42
  "license": "MIT",
39
43
  "repository": {
40
44
  "type": "git",
@@ -45,11 +49,12 @@
45
49
  "bugs": {
46
50
  "url": "https://github.com/himanshu6306singh/guardian-risk/issues"
47
51
  },
52
+ "security": "https://github.com/himanshu6306singh/guardian-risk/security/policy",
48
53
  "publishConfig": {
49
54
  "access": "public"
50
55
  },
51
56
  "peerDependencies": {
52
- "guardian-risk": "^0.2.0",
57
+ "guardian-risk": "^0.3.0",
53
58
  "express": ">=4"
54
59
  },
55
60
  "peerDependenciesMeta": {
@@ -60,10 +65,12 @@
60
65
  "devDependencies": {
61
66
  "tsup": "^8.3.5",
62
67
  "typescript": "^5.7.2",
63
- "guardian-risk": "0.2.0"
68
+ "vitest": "^4.1.9",
69
+ "guardian-risk": "0.3.0"
64
70
  },
65
71
  "scripts": {
66
72
  "build": "tsup",
73
+ "test": "vitest run",
67
74
  "typecheck": "tsc --noEmit"
68
75
  }
69
76
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;AAeO,SAAS,aAAA,CAAc,OAAA,GAAgC,EAAC,EAAW;AACxE,EAAA,MAAM,EAAE,UAAA,GAAa,KAAA,EAAM,GAAI,OAAA;AAE/B,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,uBAAA;AAAA,IACN,QAAQ,SAAA,EAAW;AACZ,IAEP;AAAA,GACF;AACF;AAKO,SAAS,WAAA,CACd,MACA,QAAA,EACkC;AAClC,EAAA,OAAO,SACJ,MAAA,CAAO,eAAA,EAAiB,SAAS,CAAA,CACjC,MAAA,CAAO,iBAAiB,MAAM,CAAA;AACnC","file":"index.cjs","sourcesContent":["import type { Plugin } from 'guardian-risk';\n\n/** Options for the Express plugin (stub). */\nexport interface ExpressPluginOptions {\n /** Trust X-Forwarded-* headers when reading client IP. */\n readonly trustProxy?: boolean;\n}\n\n/**\n * Express plugin for guardian-risk.\n *\n * @stub This plugin is a stub. Future versions will read Express requests\n * and add signals such as `clientIp`, `userAgent`, `requestMethod`, and\n * `requestsPerMinute`.\n */\nexport function expressPlugin(options: ExpressPluginOptions = {}): Plugin {\n const { trustProxy = false } = options;\n\n return {\n name: 'guardian-risk-express',\n install(_guardian) {\n void trustProxy;\n // Stub: middleware will attach signals from req before analyze()\n },\n };\n}\n\n/**\n * @stub Future middleware that enriches a Guardian instance from an Express request.\n */\nexport function fromRequest(\n _req: unknown,\n guardian: import('guardian-risk').Guardian,\n): import('guardian-risk').Guardian {\n return guardian\n .signal('requestSource', 'express')\n .signal('expressPlugin', 'stub');\n}\n"]}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";AAeO,SAAS,aAAA,CAAc,OAAA,GAAgC,EAAC,EAAW;AACxE,EAAA,MAAM,EAAE,UAAA,GAAa,KAAA,EAAM,GAAI,OAAA;AAE/B,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,uBAAA;AAAA,IACN,QAAQ,SAAA,EAAW;AACZ,IAEP;AAAA,GACF;AACF;AAKO,SAAS,WAAA,CACd,MACA,QAAA,EACkC;AAClC,EAAA,OAAO,SACJ,MAAA,CAAO,eAAA,EAAiB,SAAS,CAAA,CACjC,MAAA,CAAO,iBAAiB,MAAM,CAAA;AACnC","file":"index.js","sourcesContent":["import type { Plugin } from 'guardian-risk';\n\n/** Options for the Express plugin (stub). */\nexport interface ExpressPluginOptions {\n /** Trust X-Forwarded-* headers when reading client IP. */\n readonly trustProxy?: boolean;\n}\n\n/**\n * Express plugin for guardian-risk.\n *\n * @stub This plugin is a stub. Future versions will read Express requests\n * and add signals such as `clientIp`, `userAgent`, `requestMethod`, and\n * `requestsPerMinute`.\n */\nexport function expressPlugin(options: ExpressPluginOptions = {}): Plugin {\n const { trustProxy = false } = options;\n\n return {\n name: 'guardian-risk-express',\n install(_guardian) {\n void trustProxy;\n // Stub: middleware will attach signals from req before analyze()\n },\n };\n}\n\n/**\n * @stub Future middleware that enriches a Guardian instance from an Express request.\n */\nexport function fromRequest(\n _req: unknown,\n guardian: import('guardian-risk').Guardian,\n): import('guardian-risk').Guardian {\n return guardian\n .signal('requestSource', 'express')\n .signal('expressPlugin', 'stub');\n}\n"]}