botshield-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 +91 -2
- package/dist/client.d.ts +3 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +79 -3
- package/dist/client.js.map +1 -1
- package/dist/clientIp.d.ts +56 -0
- package/dist/clientIp.d.ts.map +1 -0
- package/dist/clientIp.js +164 -0
- package/dist/clientIp.js.map +1 -0
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +31 -1
- package/dist/middleware.js.map +1 -1
- package/dist/types.d.ts +46 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,19 +52,108 @@ app.get('/login', (req, res) => {
|
|
|
52
52
|
| `protectedRoutes` | `[]` | Patterns the middleware scores; every other request passes through untouched |
|
|
53
53
|
| `fingerprintCookie` | – | Overrides the cookie name the browser sensor sets |
|
|
54
54
|
| `timeoutMs` | – | Bounded time budget for the engine call |
|
|
55
|
+
| `tls` | – | `{ ca, rejectUnauthorized, servername }` for an engine with a private cert |
|
|
55
56
|
| `cacheMaxEntries` | `5000` | Entries retained in the in-process verdict cache |
|
|
57
|
+
| `trustedProxies` | `[]` | Addresses/CIDRs of the hops in front of the app whose IP header is believed |
|
|
58
|
+
| `clientIpHeader` | `x-real-ip` | Header a trusted proxy asserts the client in |
|
|
59
|
+
| `trustForwardedChain` | `false` | Also walk `X-Forwarded-For` from the nearest hop outward |
|
|
56
60
|
| `onVerdict` | – | `(context, req) => void` hook |
|
|
57
61
|
| `logger` | – | `{ warn(obj, msg?), error(obj, msg?) }` |
|
|
58
62
|
|
|
63
|
+
## Client IP resolution
|
|
64
|
+
|
|
65
|
+
The address the engine records is used for rate limiting, the blocklist, honeypot
|
|
66
|
+
flags, the verdict cache, sessions and alerts. Only the TCP peer address cannot
|
|
67
|
+
be forged — completing the handshake requires a routable source — so a header is
|
|
68
|
+
believed **only when it arrives from a proxy you name**:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
app.use(
|
|
72
|
+
botShield({
|
|
73
|
+
engineUrl: 'http://engine:4000',
|
|
74
|
+
appName: 'my-app',
|
|
75
|
+
// The BotShield gateway (or your own ingress) — nothing else may assert an IP.
|
|
76
|
+
trustedProxies: ['10.0.0.10/32'],
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
With `trustedProxies` empty (the default) every header is ignored and the peer
|
|
82
|
+
address is used. That is deliberate: a believed-but-forged address is
|
|
83
|
+
indistinguishable from a real one once stored, and an attacker who can write the
|
|
84
|
+
header can also name a third party and have them blocked.
|
|
85
|
+
|
|
86
|
+
`clientIpHeader` defaults to `x-real-ip`, which the BotShield gateway always
|
|
87
|
+
overwrites with its own resolved observation. Point it at `cf-connecting-ip`
|
|
88
|
+
when a Cloudflare-fronted ingress is the trusted hop.
|
|
89
|
+
|
|
90
|
+
`trustForwardedChain` additionally honours an `X-Forwarded-For` chain, walking
|
|
91
|
+
from the nearest hop outward and stopping at the first untrusted address. Enable
|
|
92
|
+
it only when every trusted hop *appends* its own observation: a hop that relays
|
|
93
|
+
the client's chain verbatim makes this a spoofing hole, because a relayed claim
|
|
94
|
+
and an asserted one look identical.
|
|
95
|
+
|
|
96
|
+
Do not rely on Express's `trust proxy: true`. It makes `req.ip` whatever the
|
|
97
|
+
client sent. The SDK warns once when it detects that setting.
|
|
98
|
+
|
|
99
|
+
## TLS and self-signed certificates
|
|
100
|
+
|
|
101
|
+
Self-hosted BotShield serves the gateway with a self-signed certificate whose SAN
|
|
102
|
+
covers `localhost` and `127.0.0.1`, so a client reaching it over HTTPS sees two
|
|
103
|
+
separate failures: an untrusted chain, and — whenever the engine is dialled by an
|
|
104
|
+
address the certificate does not name — a hostname mismatch. Node's `fetch`
|
|
105
|
+
takes no TLS options, so **both look identical from your side: the middleware
|
|
106
|
+
fails open and records nothing.** A verdict of `allow` with
|
|
107
|
+
`req.botshield.requestId === 'unavailable'` is the signature.
|
|
108
|
+
|
|
109
|
+
Pass `tls` to fix it. Trusting the certificate is not enough on its own: the
|
|
110
|
+
hostname check still applies, so supply `servername` when the engine is reached
|
|
111
|
+
by IP or an alias.
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
const fs = require('node:fs');
|
|
115
|
+
|
|
116
|
+
app.use(
|
|
117
|
+
botShield({
|
|
118
|
+
engineUrl: 'https://10.0.0.10:8443/botshield',
|
|
119
|
+
appName: 'my-app',
|
|
120
|
+
tls: {
|
|
121
|
+
ca: fs.readFileSync('./certs/dev.crt'), // the gateway's certificate
|
|
122
|
+
servername: 'localhost', // name on the certificate you dial past
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
| field | meaning |
|
|
129
|
+
| -------------------- | ------------------------------------------------------------------------------------------- |
|
|
130
|
+
| `ca` | PEM certificate(s) to trust. Required for a private or self-signed CA. |
|
|
131
|
+
| `servername` | Name to verify against the certificate and send as SNI. Required when dialling an IP. |
|
|
132
|
+
| `rejectUnauthorized` | Defaults to `true`. `false` skips chain verification entirely — a development escape hatch. |
|
|
133
|
+
|
|
134
|
+
Supplying `tls` routes the engine call through `node:https`; without it the
|
|
135
|
+
leaner `fetch` path is used unchanged. A dispatcher from the separately installed
|
|
136
|
+
`undici` package cannot be used instead: Node's built-in fetch rejects it with
|
|
137
|
+
`UND_ERR_INVALID_ARG`.
|
|
138
|
+
|
|
139
|
+
A certificate trusted by the host is a cleaner answer than `rejectUnauthorized`:
|
|
140
|
+
prefer adding the deployment's real address to the certificate's SAN, or
|
|
141
|
+
installing a real CA-issued certificate.
|
|
142
|
+
|
|
59
143
|
## Exports
|
|
60
144
|
|
|
61
145
|
`botShield`, `VerdictCache`, `matchesProtected`, `createEngineClient`, and the
|
|
62
146
|
types `BotShieldConfig`, `BotShieldContext`, `BotShieldMiddleware`, `Verdict`,
|
|
63
|
-
`VerifyRequest`, `EngineClient`, `Action`, `NextFunction`,
|
|
64
|
-
`RequestHandler`, `Response`.
|
|
147
|
+
`VerifyRequest`, `TlsOptions`, `EngineClient`, `Action`, `NextFunction`,
|
|
148
|
+
`Request`, `RequestHandler`, `Response`.
|
|
65
149
|
|
|
66
150
|
## Notes
|
|
67
151
|
|
|
152
|
+
- **Upgrading from 0.1.0:** client IP resolution is now scoped to
|
|
153
|
+
`trustedProxies`, so a header is only believed when it arrives from a hop you
|
|
154
|
+
name. Deployments that relied on the old blind `X-Real-IP` trust must set
|
|
155
|
+
`trustedProxies` to their gateway/ingress addresses, or every request will
|
|
156
|
+
fall back to the peer address.
|
|
68
157
|
- The engine must be reachable. From a container outside the BotShield compose
|
|
69
158
|
project, join its network first (`docker network ls | grep botshield`).
|
|
70
159
|
- Verdicts are cached for the TTL the engine returns, and a verdict supplied by
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Verdict, VerifyRequest } from './types';
|
|
1
|
+
import type { TlsOptions, Verdict, VerifyRequest } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Engine client for the SDK.
|
|
4
4
|
*
|
|
@@ -12,6 +12,8 @@ import type { Verdict, VerifyRequest } from './types';
|
|
|
12
12
|
export interface EngineClientOptions {
|
|
13
13
|
engineUrl: string;
|
|
14
14
|
timeoutMs?: number;
|
|
15
|
+
/** TLS settings, for an engine served with a private or self-signed certificate. */
|
|
16
|
+
tls?: TlsOptions;
|
|
15
17
|
}
|
|
16
18
|
export declare const DEFAULT_TIMEOUT_MS = 1000;
|
|
17
19
|
export interface EngineClient {
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAElE;;;;;;;;;GASG;AAEH,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,GAAG,CAAC,EAAE,UAAU,CAAC;CAClB;AAED,eAAO,MAAM,kBAAkB,OAAQ,CAAC;AAExC,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACtD,WAAW,CAAC,KAAK,EAAE;QACjB,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/D,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACrC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAsID,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAiB7E"}
|
package/dist/client.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DEFAULT_TIMEOUT_MS = void 0;
|
|
4
4
|
exports.createEngineClient = createEngineClient;
|
|
5
|
+
const node_http_1 = require("node:http");
|
|
6
|
+
const node_https_1 = require("node:https");
|
|
5
7
|
exports.DEFAULT_TIMEOUT_MS = 1_000;
|
|
6
8
|
function isVerdict(value) {
|
|
7
9
|
if (typeof value !== 'object' || value === null)
|
|
@@ -11,7 +13,7 @@ function isVerdict(value) {
|
|
|
11
13
|
typeof candidate.score === 'number' &&
|
|
12
14
|
typeof candidate.verdict === 'string');
|
|
13
15
|
}
|
|
14
|
-
async function
|
|
16
|
+
async function postJsonWithFetch(url, body, timeoutMs) {
|
|
15
17
|
const controller = new AbortController();
|
|
16
18
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
17
19
|
try {
|
|
@@ -38,12 +40,86 @@ async function postJson(url, body, timeoutMs) {
|
|
|
38
40
|
clearTimeout(timer);
|
|
39
41
|
}
|
|
40
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* HTTPS transport for clients that need TLS options.
|
|
45
|
+
*
|
|
46
|
+
* `fetch` cannot express them: it takes no `ca`, and Node's own fetch rejects a
|
|
47
|
+
* dispatcher built from the separately installed `undici` package with
|
|
48
|
+
* `UND_ERR_INVALID_ARG`, because the two copies of undici are different classes.
|
|
49
|
+
* `node:https` is the only transport in the runtime that accepts a trusted CA and
|
|
50
|
+
* an SNI override, so it is used here and nowhere else.
|
|
51
|
+
*/
|
|
52
|
+
async function postJsonWithTls(url, body, timeoutMs, tls) {
|
|
53
|
+
const target = new URL(url);
|
|
54
|
+
const secure = target.protocol === 'https:';
|
|
55
|
+
const send = secure ? node_https_1.request : node_http_1.request;
|
|
56
|
+
const payload = JSON.stringify(body);
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
let settled = false;
|
|
59
|
+
const finish = (result) => {
|
|
60
|
+
if (settled)
|
|
61
|
+
return;
|
|
62
|
+
settled = true;
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
resolve(result);
|
|
65
|
+
};
|
|
66
|
+
const request = send({
|
|
67
|
+
protocol: target.protocol,
|
|
68
|
+
hostname: target.hostname,
|
|
69
|
+
port: target.port === '' ? (secure ? 443 : 80) : Number(target.port),
|
|
70
|
+
path: `${target.pathname}${target.search}`,
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: {
|
|
73
|
+
'Content-Type': 'application/json',
|
|
74
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
75
|
+
},
|
|
76
|
+
...(secure && tls.ca !== undefined ? { ca: tls.ca } : {}),
|
|
77
|
+
...(secure && tls.rejectUnauthorized !== undefined
|
|
78
|
+
? { rejectUnauthorized: tls.rejectUnauthorized }
|
|
79
|
+
: {}),
|
|
80
|
+
...(secure && tls.servername !== undefined ? { servername: tls.servername } : {}),
|
|
81
|
+
}, (response) => {
|
|
82
|
+
const chunks = [];
|
|
83
|
+
response.on('data', (chunk) => chunks.push(chunk));
|
|
84
|
+
response.on('error', () => finish(null));
|
|
85
|
+
response.on('end', () => {
|
|
86
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
87
|
+
let parsed = null;
|
|
88
|
+
try {
|
|
89
|
+
parsed = text === '' ? null : JSON.parse(text);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
parsed = null;
|
|
93
|
+
}
|
|
94
|
+
const status = response.statusCode ?? 0;
|
|
95
|
+
finish({ ok: status >= 200 && status < 300, status, body: parsed });
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
const timer = setTimeout(() => {
|
|
99
|
+
finish(null);
|
|
100
|
+
request.destroy();
|
|
101
|
+
}, timeoutMs);
|
|
102
|
+
// One failure mode, reported once: DNS, refused connection, TLS rejection.
|
|
103
|
+
request.on('error', () => finish(null));
|
|
104
|
+
request.end(payload);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Picks the transport. `fetch` stays the default because it is the leanest path
|
|
109
|
+
* and covers deployments whose certificate is publicly trusted; only clients that
|
|
110
|
+
* need TLS options pay for the dedicated one.
|
|
111
|
+
*/
|
|
112
|
+
async function postJson(url, body, timeoutMs, tls) {
|
|
113
|
+
if (tls === undefined)
|
|
114
|
+
return postJsonWithFetch(url, body, timeoutMs);
|
|
115
|
+
return postJsonWithTls(url, body, timeoutMs, tls);
|
|
116
|
+
}
|
|
41
117
|
function createEngineClient(options) {
|
|
42
118
|
const timeoutMs = options.timeoutMs ?? exports.DEFAULT_TIMEOUT_MS;
|
|
43
119
|
const base = options.engineUrl.replace(/\/+$/, '');
|
|
44
120
|
return {
|
|
45
121
|
async verify(input) {
|
|
46
|
-
const result = await postJson(`${base}/verify`, input, timeoutMs);
|
|
122
|
+
const result = await postJson(`${base}/verify`, input, timeoutMs, options.tls);
|
|
47
123
|
if (!result || !result.ok)
|
|
48
124
|
return null;
|
|
49
125
|
return isVerdict(result.body) ? result.body : null;
|
|
@@ -51,7 +127,7 @@ function createEngineClient(options) {
|
|
|
51
127
|
async recordEvent(input) {
|
|
52
128
|
// Business-event reporting is explicitly fire-and-forget. The /events
|
|
53
129
|
// endpoint lands in Phase 3; until then this is a tolerated 404.
|
|
54
|
-
await postJson(`${base}/events`, input, timeoutMs);
|
|
130
|
+
await postJson(`${base}/events`, input, timeoutMs, options.tls);
|
|
55
131
|
},
|
|
56
132
|
};
|
|
57
133
|
}
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client IP resolution (§13.4).
|
|
3
|
+
*
|
|
4
|
+
* The TCP peer address is the only fact a request carries that cannot be forged:
|
|
5
|
+
* completing the handshake requires the source to be routable. Every header is
|
|
6
|
+
* merely a *claim*, so the question is never "what does the header say" but
|
|
7
|
+
* "whose claim is this".
|
|
8
|
+
*
|
|
9
|
+
* This module answers that question with one rule:
|
|
10
|
+
*
|
|
11
|
+
* a claim is believed only when it arrives from a configured trusted hop.
|
|
12
|
+
*
|
|
13
|
+
* That matters more than it looks. A laundered header is indistinguishable from
|
|
14
|
+
* a real address once it is stored, so a wrong answer here silently corrupts
|
|
15
|
+
* every IP-keyed decision downstream — rate limits, the blocklist, honeypot
|
|
16
|
+
* flags, the verdict cache, sessions and alerts. Worse, an attacker can name a
|
|
17
|
+
* third party and have *them* blocked.
|
|
18
|
+
*/
|
|
19
|
+
export interface ClientIpInput {
|
|
20
|
+
/** `req.socket.remoteAddress` — the only unforgeable input. */
|
|
21
|
+
peer: string | undefined;
|
|
22
|
+
/** Lower-cased request headers. */
|
|
23
|
+
headers: Record<string, string>;
|
|
24
|
+
/**
|
|
25
|
+
* Addresses (bare IP or CIDR) of the hops that sit in front of the app. Empty
|
|
26
|
+
* or absent means "trust nobody", which is the safe default.
|
|
27
|
+
*/
|
|
28
|
+
trustedProxies?: readonly string[] | undefined;
|
|
29
|
+
/** Header a trusted hop asserts the client in. Defaults to `x-real-ip`. */
|
|
30
|
+
clientIpHeader?: string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Additionally honour an `X-Forwarded-For` chain, walking from the nearest hop
|
|
33
|
+
* outward and skipping trusted hops. Off by default: it is only correct when
|
|
34
|
+
* every trusted hop appends rather than relays what it received.
|
|
35
|
+
*/
|
|
36
|
+
trustForwardedChain?: boolean | undefined;
|
|
37
|
+
/** Called at most once per resolution when a claim is present but unusable. */
|
|
38
|
+
onWarn?: ((message: string) => void) | undefined;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Canonical form of an address, or null when it is not one.
|
|
42
|
+
*
|
|
43
|
+
* Nodes are keyed by these strings (Redis sets, rate-limit buckets, session
|
|
44
|
+
* lookup), so `::ffff:203.0.113.45` and `203.0.113.45` must never be allowed to
|
|
45
|
+
* become two identities for one client. IPv6 is lower-cased for the same reason.
|
|
46
|
+
*/
|
|
47
|
+
export declare function normalizeIp(raw: string | undefined | null): string | null;
|
|
48
|
+
/** True when `peer` is one of the configured hops. Cross-family rules never match. */
|
|
49
|
+
export declare function isTrustedPeer(peer: string | undefined, trustedProxies: readonly string[] | undefined): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Resolves the client address for a request.
|
|
52
|
+
*
|
|
53
|
+
* Order is deliberate and load-bearing — see the module comment.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveClientIp(input: ClientIpInput): string;
|
|
56
|
+
//# sourceMappingURL=clientIp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clientIp.d.ts","sourceRoot":"","sources":["../src/clientIp.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC;;;OAGG;IACH,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAC/C,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1C,+EAA+E;IAC/E,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CAClD;AASD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAkBzE;AAED,sFAAsF;AACtF,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,cAAc,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAC5C,OAAO,CAOT;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CA0B5D"}
|
package/dist/clientIp.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeIp = normalizeIp;
|
|
4
|
+
exports.isTrustedPeer = isTrustedPeer;
|
|
5
|
+
exports.resolveClientIp = resolveClientIp;
|
|
6
|
+
const node_net_1 = require("node:net");
|
|
7
|
+
/** Stands in for "we could not establish an address" — never a real client. */
|
|
8
|
+
const UNKNOWN_IP = '0.0.0.0';
|
|
9
|
+
const DEFAULT_CLIENT_IP_HEADER = 'x-real-ip';
|
|
10
|
+
const FORWARDED_FOR = 'x-forwarded-for';
|
|
11
|
+
const IPV4_MAPPED = /^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i;
|
|
12
|
+
/**
|
|
13
|
+
* Canonical form of an address, or null when it is not one.
|
|
14
|
+
*
|
|
15
|
+
* Nodes are keyed by these strings (Redis sets, rate-limit buckets, session
|
|
16
|
+
* lookup), so `::ffff:203.0.113.45` and `203.0.113.45` must never be allowed to
|
|
17
|
+
* become two identities for one client. IPv6 is lower-cased for the same reason.
|
|
18
|
+
*/
|
|
19
|
+
function normalizeIp(raw) {
|
|
20
|
+
if (typeof raw !== 'string')
|
|
21
|
+
return null;
|
|
22
|
+
let value = raw.trim();
|
|
23
|
+
if (value === '')
|
|
24
|
+
return null;
|
|
25
|
+
// IPv4-mapped IPv6 arrives whenever a v4 client reaches a dual-stack listener.
|
|
26
|
+
const mapped = IPV4_MAPPED.exec(value);
|
|
27
|
+
if (mapped?.[1] !== undefined)
|
|
28
|
+
value = mapped[1];
|
|
29
|
+
value = value.toLowerCase();
|
|
30
|
+
// A scope/zone id (`fe80::1%eth0`) is link-local routing information, not part
|
|
31
|
+
// of the address identity.
|
|
32
|
+
const zone = value.indexOf('%');
|
|
33
|
+
if (zone !== -1)
|
|
34
|
+
value = value.slice(0, zone);
|
|
35
|
+
return (0, node_net_1.isIP)(value) === 0 ? null : value;
|
|
36
|
+
}
|
|
37
|
+
/** True when `peer` is one of the configured hops. Cross-family rules never match. */
|
|
38
|
+
function isTrustedPeer(peer, trustedProxies) {
|
|
39
|
+
if (trustedProxies === undefined || trustedProxies.length === 0)
|
|
40
|
+
return false;
|
|
41
|
+
const normalized = normalizeIp(peer);
|
|
42
|
+
if (normalized === null)
|
|
43
|
+
return false;
|
|
44
|
+
return trustedProxies.some((rule) => matchesRule(normalized, rule));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolves the client address for a request.
|
|
48
|
+
*
|
|
49
|
+
* Order is deliberate and load-bearing — see the module comment.
|
|
50
|
+
*/
|
|
51
|
+
function resolveClientIp(input) {
|
|
52
|
+
const peer = normalizeIp(input.peer) ?? UNKNOWN_IP;
|
|
53
|
+
// Untrusted peer (or no configuration): headers are attacker-controlled text.
|
|
54
|
+
if (!isTrustedPeer(peer, input.trustedProxies))
|
|
55
|
+
return peer;
|
|
56
|
+
const headerName = (input.clientIpHeader ?? DEFAULT_CLIENT_IP_HEADER).toLowerCase();
|
|
57
|
+
const raw = input.headers[headerName];
|
|
58
|
+
const claimed = normalizeIp(raw);
|
|
59
|
+
if (claimed !== null)
|
|
60
|
+
return claimed;
|
|
61
|
+
// A claim was present but unusable. Silent fallback here would hide a
|
|
62
|
+
// misconfigured hop, which is exactly the class of bug this module exists to
|
|
63
|
+
// catch, so it is reported once.
|
|
64
|
+
if (raw !== undefined && raw.trim() !== '') {
|
|
65
|
+
input.onWarn?.(`[botshield] ignoring unusable ${headerName} value from trusted proxy ${peer}: ${JSON.stringify(raw)}`);
|
|
66
|
+
}
|
|
67
|
+
if (input.trustForwardedChain === true) {
|
|
68
|
+
const walked = walkForwardedChain(input.headers[FORWARDED_FOR], input.trustedProxies);
|
|
69
|
+
if (walked !== null)
|
|
70
|
+
return walked;
|
|
71
|
+
}
|
|
72
|
+
return peer;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Walks `X-Forwarded-For` from the right (nearest hop) to the left, skipping
|
|
76
|
+
* trusted hops, and returns the first untrusted address.
|
|
77
|
+
*
|
|
78
|
+
* Addresses further left are whatever the client sent, so they are only reached
|
|
79
|
+
* if the intervening hops are themselves trusted — which is the whole point of
|
|
80
|
+
* scoping by peer.
|
|
81
|
+
*/
|
|
82
|
+
function walkForwardedChain(value, trustedProxies) {
|
|
83
|
+
if (typeof value !== 'string' || value.trim() === '')
|
|
84
|
+
return null;
|
|
85
|
+
const hops = value
|
|
86
|
+
.split(',')
|
|
87
|
+
.map((part) => normalizeIp(part))
|
|
88
|
+
.filter((part) => part !== null);
|
|
89
|
+
for (let index = hops.length - 1; index >= 0; index -= 1) {
|
|
90
|
+
const hop = hops[index];
|
|
91
|
+
if (hop === undefined)
|
|
92
|
+
continue;
|
|
93
|
+
if (isTrustedPeer(hop, trustedProxies))
|
|
94
|
+
continue;
|
|
95
|
+
return hop;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
/** Matches a bare address or a CIDR, never across address families. */
|
|
100
|
+
function matchesRule(ip, rule) {
|
|
101
|
+
const slash = rule.indexOf('/');
|
|
102
|
+
const base = normalizeIp(slash === -1 ? rule : rule.slice(0, slash));
|
|
103
|
+
if (base === null)
|
|
104
|
+
return false;
|
|
105
|
+
const family = (0, node_net_1.isIP)(ip);
|
|
106
|
+
if (family === 0 || family !== (0, node_net_1.isIP)(base))
|
|
107
|
+
return false;
|
|
108
|
+
const bits = family === 4 ? 32 : 128;
|
|
109
|
+
let prefix = bits;
|
|
110
|
+
if (slash !== -1) {
|
|
111
|
+
const raw = rule.slice(slash + 1);
|
|
112
|
+
if (!/^\d+$/.test(raw))
|
|
113
|
+
return false;
|
|
114
|
+
prefix = Number(raw);
|
|
115
|
+
if (prefix > bits)
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const candidate = toBigInt(ip, family);
|
|
119
|
+
const network = toBigInt(base, family);
|
|
120
|
+
if (candidate === null || network === null)
|
|
121
|
+
return false;
|
|
122
|
+
if (prefix === 0)
|
|
123
|
+
return true;
|
|
124
|
+
const shift = BigInt(bits - prefix);
|
|
125
|
+
return candidate >> shift === network >> shift;
|
|
126
|
+
}
|
|
127
|
+
function toBigInt(ip, family) {
|
|
128
|
+
return family === 4 ? ipv4ToBigInt(ip) : ipv6ToBigInt(ip);
|
|
129
|
+
}
|
|
130
|
+
function ipv4ToBigInt(ip) {
|
|
131
|
+
const parts = ip.split('.');
|
|
132
|
+
if (parts.length !== 4)
|
|
133
|
+
return null;
|
|
134
|
+
let value = 0n;
|
|
135
|
+
for (const part of parts) {
|
|
136
|
+
const octet = Number(part);
|
|
137
|
+
if (!Number.isInteger(octet) || octet < 0 || octet > 255)
|
|
138
|
+
return null;
|
|
139
|
+
value = (value << 8n) | BigInt(octet);
|
|
140
|
+
}
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
/** Expands `::` shorthand to eight groups; no embedded-IPv4 form is accepted here. */
|
|
144
|
+
function ipv6ToBigInt(ip) {
|
|
145
|
+
const doubleColon = ip.indexOf('::');
|
|
146
|
+
const head = doubleColon === -1 ? ip : ip.slice(0, doubleColon);
|
|
147
|
+
const tail = doubleColon === -1 ? '' : ip.slice(doubleColon + 2);
|
|
148
|
+
const headGroups = head === '' ? [] : head.split(':');
|
|
149
|
+
const tailGroups = tail === '' ? [] : tail.split(':');
|
|
150
|
+
const missing = 8 - (headGroups.length + tailGroups.length);
|
|
151
|
+
if (missing < 0 || (doubleColon === -1 && missing !== 0))
|
|
152
|
+
return null;
|
|
153
|
+
const groups = [...headGroups, ...new Array(Math.max(missing, 0)).fill('0'), ...tailGroups];
|
|
154
|
+
if (groups.length !== 8)
|
|
155
|
+
return null;
|
|
156
|
+
let value = 0n;
|
|
157
|
+
for (const group of groups) {
|
|
158
|
+
if (!/^[0-9a-f]{1,4}$/.test(group))
|
|
159
|
+
return null;
|
|
160
|
+
value = (value << 16n) | BigInt(Number.parseInt(group, 16));
|
|
161
|
+
}
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=clientIp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clientIp.js","sourceRoot":"","sources":["../src/clientIp.ts"],"names":[],"mappings":"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"}
|
package/dist/middleware.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAEjE,OAAO,KAAK,EAEV,eAAe,EAEf,mBAAmB,EAKnB,OAAO,EACR,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,0EAA0E;AAC1E,qBAAa,YAAY;IAGX,OAAO,CAAC,QAAQ,CAAC,UAAU;IAFvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwC;gBAEnC,UAAU,SAAQ;IAE/C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,OAAO,GAAG,IAAI;IAYlD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,SAAa,GAAG,IAAI;IAU1D,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAsCD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAKnF;AAQD,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,mBAAmB,CAsKtE;AAED,OAAO,EAAE,kBAAkB,EAAE,YAAY,IAAI,gBAAgB,EAAE,CAAC;AAChE,YAAY,EAAE,YAAY,EAAE,CAAC"}
|
package/dist/middleware.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.matchesProtected = matchesProtected;
|
|
|
5
5
|
exports.botShield = botShield;
|
|
6
6
|
const client_1 = require("./client");
|
|
7
7
|
Object.defineProperty(exports, "createEngineClient", { enumerable: true, get: function () { return client_1.createEngineClient; } });
|
|
8
|
+
const clientIp_1 = require("./clientIp");
|
|
8
9
|
/** Small bounded cache; Map insertion order doubles as eviction order. */
|
|
9
10
|
class VerdictCache {
|
|
10
11
|
maxEntries;
|
|
@@ -98,7 +99,20 @@ function botShield(config) {
|
|
|
98
99
|
const client = (0, client_1.createEngineClient)({
|
|
99
100
|
engineUrl: config.engineUrl,
|
|
100
101
|
timeoutMs: config.timeoutMs,
|
|
102
|
+
tls: config.tls,
|
|
101
103
|
});
|
|
104
|
+
/**
|
|
105
|
+
* Client-IP problems are configuration faults, not per-request events: a
|
|
106
|
+
* misconfigured hop would otherwise log on every request and turn a detection
|
|
107
|
+
* gap into a log flood. One line per distinct cause is enough to find it.
|
|
108
|
+
*/
|
|
109
|
+
const reported = new Set();
|
|
110
|
+
function reportOnce(message) {
|
|
111
|
+
if (reported.has(message))
|
|
112
|
+
return;
|
|
113
|
+
reported.add(message);
|
|
114
|
+
config.logger?.warn({ reason: 'client_ip' }, message);
|
|
115
|
+
}
|
|
102
116
|
function buildContext(verdict, score, requestId, cached, scope) {
|
|
103
117
|
return {
|
|
104
118
|
verdict,
|
|
@@ -126,8 +140,24 @@ function botShield(config) {
|
|
|
126
140
|
const handler = async (req, res, next) => {
|
|
127
141
|
const headers = toHeaderRecord(req.headers);
|
|
128
142
|
const cookies = parseCookies(headerValue(headers, 'cookie'));
|
|
143
|
+
// Express's own `trust proxy: true` makes `req.ip` whatever the client sent.
|
|
144
|
+
// We resolve the address ourselves, so the host's setting cannot loosen our
|
|
145
|
+
// rule — but a host that enabled it is worth one warning, because its own
|
|
146
|
+
// rate limiting and logging are then using a forgeable value.
|
|
147
|
+
if (req.app?.get?.('trust proxy') === true) {
|
|
148
|
+
reportOnce('[botshield] host app sets `trust proxy: true`, which trusts X-Forwarded-For from any peer. ' +
|
|
149
|
+
'BotShield resolves the client IP from its own trustedProxies config, but Express `req.ip` ' +
|
|
150
|
+
'elsewhere in the app is client-controlled.');
|
|
151
|
+
}
|
|
129
152
|
const scope = {
|
|
130
|
-
ip:
|
|
153
|
+
ip: (0, clientIp_1.resolveClientIp)({
|
|
154
|
+
peer: req.socket?.remoteAddress,
|
|
155
|
+
headers,
|
|
156
|
+
trustedProxies: config.trustedProxies,
|
|
157
|
+
clientIpHeader: config.clientIpHeader,
|
|
158
|
+
trustForwardedChain: config.trustForwardedChain,
|
|
159
|
+
onWarn: reportOnce,
|
|
160
|
+
}),
|
|
131
161
|
fingerprint: cookies[fingerprintCookie],
|
|
132
162
|
sessionId: cookies.bs_sid,
|
|
133
163
|
userId: req.user?.id,
|
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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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -25,6 +25,27 @@ export interface BotShieldContext {
|
|
|
25
25
|
*/
|
|
26
26
|
record(event: string, attributes: Record<string, unknown>): void;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* TLS settings for the engine connection (§3.3).
|
|
30
|
+
*
|
|
31
|
+
* Node's `fetch` accepts no TLS options, so supplying any of these routes the
|
|
32
|
+
* engine call through `node:https` instead. Self-hosted BotShield ships a
|
|
33
|
+
* self-signed gateway certificate, so a deployment addressing it over HTTPS needs
|
|
34
|
+
* at least `ca`. When the engine is reached by an address the certificate does not
|
|
35
|
+
* name — a LAN IP, say — `servername` is required as well: trusting a CA does not,
|
|
36
|
+
* and must not, stand in for the hostname check.
|
|
37
|
+
*/
|
|
38
|
+
export interface TlsOptions {
|
|
39
|
+
/** PEM certificate(s) to trust, for a private or self-signed CA. */
|
|
40
|
+
ca?: string | string[];
|
|
41
|
+
/**
|
|
42
|
+
* Verify the server certificate. Defaults to true. `false` disables
|
|
43
|
+
* verification for the engine connection and is a development escape hatch.
|
|
44
|
+
*/
|
|
45
|
+
rejectUnauthorized?: boolean;
|
|
46
|
+
/** Name to verify against the certificate and to send as SNI. */
|
|
47
|
+
servername?: string;
|
|
48
|
+
}
|
|
28
49
|
export interface BotShieldConfig {
|
|
29
50
|
engineUrl: string;
|
|
30
51
|
appName: string;
|
|
@@ -35,8 +56,33 @@ export interface BotShieldConfig {
|
|
|
35
56
|
fingerprintCookie?: string;
|
|
36
57
|
/** Bounded time budget for the engine call, ms. */
|
|
37
58
|
timeoutMs?: number;
|
|
59
|
+
/** TLS settings, for an engine served with a private or self-signed certificate. */
|
|
60
|
+
tls?: TlsOptions;
|
|
38
61
|
/** Entries retained in the in-process verdict cache. */
|
|
39
62
|
cacheMaxEntries?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Addresses (bare IP or CIDR) of the reverse proxies that sit in front of the
|
|
65
|
+
* app — the BotShield gateway, an ingress, an ALB.
|
|
66
|
+
*
|
|
67
|
+
* Headers asserting a client IP are believed **only** when the request's TCP
|
|
68
|
+
* peer is listed here; anything else falls back to the peer address, which is
|
|
69
|
+
* the one thing a client cannot forge. Empty by default, and empty means
|
|
70
|
+
* "trust nobody", because a believed-but-forged address silently corrupts
|
|
71
|
+
* every IP-keyed decision downstream, and can get a third party blocked.
|
|
72
|
+
*/
|
|
73
|
+
trustedProxies?: string[];
|
|
74
|
+
/** Header a trusted proxy asserts the client in. Defaults to `x-real-ip`. */
|
|
75
|
+
clientIpHeader?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Also honour an `X-Forwarded-For` chain, walking from the nearest hop outward
|
|
78
|
+
* and skipping trusted hops, stopping at the first untrusted address.
|
|
79
|
+
*
|
|
80
|
+
* Off by default: it is only correct when every trusted hop *appends* its own
|
|
81
|
+
* observation. A hop that forwards the client's chain verbatim turns this into
|
|
82
|
+
* a spoofing hole, because a relayed claim is indistinguishable from an
|
|
83
|
+
* asserted one.
|
|
84
|
+
*/
|
|
85
|
+
trustForwardedChain?: boolean;
|
|
40
86
|
onVerdict?: (context: BotShieldContext, req: Request) => void;
|
|
41
87
|
logger?: {
|
|
42
88
|
warn(obj: unknown, msg?: string): void;
|
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,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE/E,uCAAuC;AACvC,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;AAEhE,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAClE;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9D,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CAC9F;AAED,OAAO,QAAQ,2BAA2B,CAAC;IACzC,UAAU,OAAO;QACf,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAC9B;CACF;AAED,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG;IACjD,yBAAyB;IACzB,QAAQ,CAAC,MAAM,EAAE;QAAE,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;KAAE,CAAC;CAC5E,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAED,YAAY,EAAE,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,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE/E,uCAAuC;AACvC,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;AAEhE,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAClE;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAU;IACzB,oEAAoE;IACpE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACvB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;;OAQG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9D,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CAC9F;AAED,OAAO,QAAQ,2BAA2B,CAAC;IACzC,UAAU,OAAO;QACf,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAC9B;CACF;AAED,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG;IACjD,yBAAyB;IACzB,QAAQ,CAAC,MAAM,EAAE;QAAE,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;KAAE,CAAC;CAC5E,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAED,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC"}
|
package/package.json
CHANGED