guardian-risk-vpn 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,44 +6,52 @@
6
6
  npm install guardian-risk guardian-risk-vpn
7
7
  ```
8
8
 
9
- > **Stub package** API may change before `1.0.0`.
9
+ VPN, proxy, hosting, and Tor detection for [guardian-risk](https://www.npmjs.com/package/guardian-risk).
10
10
 
11
- VPN, proxy, and Tor detection for [guardian-risk](https://www.npmjs.com/package/guardian-risk). Resolves client IP and adds network risk signals.
12
-
13
- ## Planned signals
11
+ ## Signals
14
12
 
15
13
  | Signal | Description |
16
14
  |--------|-------------|
17
15
  | `vpn` | VPN exit node detected |
18
16
  | `proxy` | Public proxy detected |
19
17
  | `tor` | Tor exit node detected |
20
- | `country` | ISO country code from IP |
18
+ | `hosting` | Datacenter / hosting ASN |
19
+ | `country` | ISO country code from provider |
21
20
  | `asn` | Autonomous system number |
22
21
 
23
- ## Usage (stub)
22
+ ## Production usage
24
23
 
25
24
  ```typescript
26
25
  import { Guardian } from 'guardian-risk';
27
- import { vpnPlugin, checkIp } from 'guardian-risk-vpn';
28
-
29
- const guardian = new Guardian().use(
30
- vpnPlugin({ provider: 'maxmind', vpnScore: 20, registerDefaultRules: true }),
26
+ import { vpnPlugin, StaticIpProvider } from 'guardian-risk-vpn';
27
+
28
+ // Use your own IP intelligence — MaxMind, IPinfo, etc.
29
+ const provider = new StaticIpProvider({
30
+ '203.0.113.10': { vpn: false, proxy: false, tor: false, country: 'US' },
31
+ });
32
+
33
+ const template = new Guardian().use(
34
+ vpnPlugin({
35
+ provider,
36
+ registerDefaultRules: true,
37
+ vpnScore: 20,
38
+ }),
31
39
  );
32
-
33
- await checkIp('203.0.113.10', guardian);
34
- const report = guardian.analyze();
35
40
  ```
36
41
 
37
- ## Default rules (optional)
42
+ For development only, `IpApiProvider` is available (HTTPS, 5s timeout) — **not recommended in production**.
43
+
44
+ ## Security notes
38
45
 
39
- When `registerDefaultRules: true`, the plugin registers:
46
+ - **No external provider by default** — you must supply `provider` for lookups.
47
+ - Reads **`clientIp` from guardian signals first** (set by express plugin).
48
+ - Only **validated public IPs** are looked up; private/reserved ranges are skipped.
49
+ - VPN signals are **hints** — use your own threat intel feed in production.
40
50
 
41
- | Rule | Condition | Default score |
42
- |------|-----------|---------------|
43
- | VpnDetected | `vpn === true` | `vpnScore` (20) |
44
- | ProxyDetected | `proxy === true` | `vpnScore` (20) |
45
- | TorDetected | `tor === true` | `vpnScore + 10` (30) |
51
+ ## API
46
52
 
47
- ## Status
53
+ - `vpnPlugin(options)` — `beforeAnalyze` hook + optional default rules
54
+ - `checkIp(ip, guardian, options?)` — manual lookup
55
+ - `StaticIpProvider`, `IpApiProvider` — built-in providers
48
56
 
49
- Not yet published. Implementation in progress.
57
+ See [SECURITY.md](../../SECURITY.md).
package/dist/index.cjs CHANGED
@@ -1,42 +1,166 @@
1
1
  'use strict';
2
2
 
3
+ var guardianRisk = require('guardian-risk');
4
+
5
+ // src/index.ts
6
+ var DEFAULT_TIMEOUT_MS = 5e3;
7
+ var LOOKUP_URL = "https://ip-api.com/json";
8
+ var IpApiProvider = class {
9
+ constructor(options = {}) {
10
+ this.options = options;
11
+ }
12
+ options;
13
+ async lookup(ip) {
14
+ const parsed = guardianRisk.parseIpAddress(ip);
15
+ if (!parsed || guardianRisk.isPrivateIp(parsed)) {
16
+ return localResult();
17
+ }
18
+ const controller = new AbortController();
19
+ const timeoutMs = this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
20
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
21
+ try {
22
+ const response = await fetch(
23
+ `${LOOKUP_URL}/${encodeURIComponent(parsed)}?fields=status,country,proxy,hosting`,
24
+ { signal: controller.signal }
25
+ );
26
+ if (!response.ok) {
27
+ throw new Error(`IP lookup failed with status ${response.status}`);
28
+ }
29
+ const data = await response.json();
30
+ if (data.status !== "success") {
31
+ return unknownResult();
32
+ }
33
+ return {
34
+ vpn: false,
35
+ proxy: data.proxy === true,
36
+ tor: false,
37
+ country: data.country ?? "unknown",
38
+ hosting: data.hosting === true
39
+ };
40
+ } finally {
41
+ clearTimeout(timer);
42
+ }
43
+ }
44
+ };
45
+ var StaticIpProvider = class {
46
+ constructor(results) {
47
+ this.results = results;
48
+ }
49
+ results;
50
+ async lookup(ip) {
51
+ const parsed = guardianRisk.parseIpAddress(ip);
52
+ if (!parsed) {
53
+ return unknownResult();
54
+ }
55
+ return this.results[parsed] ?? unknownResult();
56
+ }
57
+ };
58
+ function localResult() {
59
+ return { vpn: false, proxy: false, tor: false, country: "local", hosting: false };
60
+ }
61
+ function unknownResult() {
62
+ return { vpn: false, proxy: false, tor: false, country: "unknown", hosting: false };
63
+ }
64
+
3
65
  // src/index.ts
66
+ var EMPTY_PROVIDER = new StaticIpProvider({});
4
67
  function vpnPlugin(options = {}) {
5
68
  const {
6
- provider = "maxmind",
69
+ provider = EMPTY_PROVIDER,
7
70
  vpnScore = 20,
8
71
  registerDefaultRules = true
9
72
  } = options;
10
73
  return {
11
74
  name: "guardian-risk-vpn",
12
75
  install(guardian) {
13
- if (!registerDefaultRules) {
14
- return;
76
+ if (registerDefaultRules) {
77
+ registerVpnRules(guardian, vpnScore);
15
78
  }
16
- guardian.rule({
17
- name: "VpnDetected",
18
- reason: "Connection appears to use a VPN",
19
- when: (s) => s.vpn === true,
20
- score: vpnScore
21
- });
22
- guardian.rule({
23
- name: "ProxyDetected",
24
- reason: "Connection appears to use a proxy",
25
- when: (s) => s.proxy === true,
26
- score: vpnScore
27
- });
28
- guardian.rule({
29
- name: "TorDetected",
30
- reason: "Connection appears to use Tor",
31
- when: (s) => s.tor === true,
32
- score: vpnScore + 10
79
+ guardian.beforeAnalyze(async ({ data, guardian: g }) => {
80
+ const ip = resolveIpForLookup(g, data);
81
+ if (!ip) {
82
+ return;
83
+ }
84
+ await applyIpSignals(ip, g, provider);
33
85
  });
34
86
  }
35
87
  };
36
88
  }
37
- async function checkIp(_ip, guardian) {
38
- return guardian.signal("signalSource", "vpn").signal("vpn", false).signal("proxy", false).signal("tor", false).signal("country", "unknown");
89
+ async function checkIp(ip, guardian, provider = EMPTY_PROVIDER) {
90
+ const parsed = guardianRisk.parseIpAddress(ip);
91
+ if (!parsed) {
92
+ throw new TypeError("Invalid IP address");
93
+ }
94
+ await applyIpSignals(parsed, guardian, provider);
95
+ return guardian;
96
+ }
97
+ function registerVpnRules(guardian, vpnScore) {
98
+ guardian.rule({
99
+ name: "VpnDetected",
100
+ reason: "Connection appears to use a VPN or datacenter",
101
+ when: (s) => s.vpn === true || s.hosting === true,
102
+ score: vpnScore
103
+ }).rule({
104
+ name: "ProxyDetected",
105
+ reason: "Connection appears to use a proxy",
106
+ when: (s) => s.proxy === true,
107
+ score: vpnScore
108
+ }).rule({
109
+ name: "TorDetected",
110
+ reason: "Connection appears to use Tor",
111
+ when: (s) => s.tor === true,
112
+ score: vpnScore + 10
113
+ });
114
+ }
115
+ async function applyIpSignals(ip, guardian, provider) {
116
+ const intel = guardianRisk.isPrivateIp(ip) ? localIntel() : await provider.lookup(ip);
117
+ guardian.signal("clientIp", ip).signal("vpn", intel.vpn).signal("proxy", intel.proxy).signal("tor", intel.tor).signal("country", intel.country).signal("hosting", intel.hosting).signal("signalSource", "vpn");
118
+ }
119
+ function localIntel() {
120
+ return { vpn: false, proxy: false, tor: false, country: "local", hosting: false };
121
+ }
122
+ function resolveIpForLookup(guardian, data) {
123
+ const fromSignal = guardian.getSignal("clientIp");
124
+ if (typeof fromSignal === "string") {
125
+ const parsed = guardianRisk.parseIpAddress(fromSignal);
126
+ if (parsed) {
127
+ return parsed;
128
+ }
129
+ }
130
+ if (data !== null && typeof data === "object" && "clientIp" in data) {
131
+ const ip = data.clientIp;
132
+ const parsed = typeof ip === "string" ? guardianRisk.parseIpAddress(ip) : null;
133
+ if (parsed) {
134
+ return parsed;
135
+ }
136
+ }
137
+ if (data !== null && typeof data === "object" && "headers" in data) {
138
+ const req = data;
139
+ if (req.ips) {
140
+ for (const candidate of req.ips) {
141
+ const parsed = guardianRisk.parseIpAddress(candidate);
142
+ if (parsed) {
143
+ return parsed;
144
+ }
145
+ }
146
+ }
147
+ if (req.ip) {
148
+ const parsed = guardianRisk.parseIpAddress(req.ip);
149
+ if (parsed) {
150
+ return parsed;
151
+ }
152
+ }
153
+ if (req.socket?.remoteAddress) {
154
+ return guardianRisk.parseIpAddress(req.socket.remoteAddress);
155
+ }
156
+ }
157
+ if (typeof data === "string") {
158
+ return guardianRisk.parseIpAddress(data);
159
+ }
160
+ return null;
39
161
  }
40
162
 
163
+ exports.IpApiProvider = IpApiProvider;
164
+ exports.StaticIpProvider = StaticIpProvider;
41
165
  exports.checkIp = checkIp;
42
166
  exports.vpnPlugin = vpnPlugin;
package/dist/index.d.cts CHANGED
@@ -1,27 +1,50 @@
1
1
  import * as guardian_risk from 'guardian-risk';
2
2
  import { Plugin } from 'guardian-risk';
3
3
 
4
- /** IP intelligence provider (stub). */
5
- type VpnProvider = 'maxmind' | 'ipinfo' | 'custom';
6
- /** Options for the VPN plugin (stub). */
4
+ /** IP intelligence result used for VPN/proxy/Tor signals. */
5
+ interface IpIntelligence {
6
+ readonly vpn: boolean;
7
+ readonly proxy: boolean;
8
+ readonly tor: boolean;
9
+ readonly country: string;
10
+ readonly hosting: boolean;
11
+ }
12
+ /** Pluggable IP lookup for VPN detection. */
13
+ interface IpProvider {
14
+ lookup(ip: string): Promise<IpIntelligence>;
15
+ }
16
+ interface IpApiProviderOptions {
17
+ readonly timeoutMs?: number;
18
+ }
19
+ /**
20
+ * ip-api.com provider (rate-limited). For production use MaxMind or IPinfo.
21
+ */
22
+ declare class IpApiProvider implements IpProvider {
23
+ private readonly options;
24
+ constructor(options?: IpApiProviderOptions);
25
+ lookup(ip: string): Promise<IpIntelligence>;
26
+ }
27
+ /** Static provider for tests and offline use. */
28
+ declare class StaticIpProvider implements IpProvider {
29
+ private readonly results;
30
+ constructor(results: Readonly<Record<string, IpIntelligence>>);
31
+ lookup(ip: string): Promise<IpIntelligence>;
32
+ }
33
+
34
+ /** Options for the VPN plugin. */
7
35
  interface VpnPluginOptions {
8
- /** Provider used to classify IP addresses. */
9
- readonly provider?: VpnProvider;
10
- /** Default risk score added when VPN rules are registered by the plugin. */
36
+ /** Required for production. Defaults to empty static provider (no external calls). */
37
+ readonly provider?: IpProvider;
11
38
  readonly vpnScore?: number;
12
- /** Register default VPN/proxy/Tor rules on install. */
13
39
  readonly registerDefaultRules?: boolean;
14
40
  }
15
41
  /**
16
42
  * VPN / proxy detection plugin for guardian-risk.
17
- *
18
- * @stub Future versions will resolve client IP via MaxMind, IPinfo, or a custom
19
- * provider and add signals such as `vpn`, `proxy`, `tor`, and `country`.
20
43
  */
21
44
  declare function vpnPlugin(options?: VpnPluginOptions): Plugin;
22
- /**
23
- * @stub Future helper to resolve an IP and add VPN-related signals.
24
- */
25
- declare function checkIp(_ip: string, guardian: guardian_risk.Guardian): Promise<guardian_risk.Guardian>;
45
+ interface IpContext {
46
+ readonly clientIp: string;
47
+ }
48
+ declare function checkIp(ip: string, guardian: guardian_risk.Guardian, provider?: IpProvider): Promise<guardian_risk.Guardian>;
26
49
 
27
- export { type VpnPluginOptions, type VpnProvider, checkIp, vpnPlugin };
50
+ export { IpApiProvider, type IpContext, type IpIntelligence, type IpProvider, StaticIpProvider, type VpnPluginOptions, checkIp, vpnPlugin };
package/dist/index.d.ts CHANGED
@@ -1,27 +1,50 @@
1
1
  import * as guardian_risk from 'guardian-risk';
2
2
  import { Plugin } from 'guardian-risk';
3
3
 
4
- /** IP intelligence provider (stub). */
5
- type VpnProvider = 'maxmind' | 'ipinfo' | 'custom';
6
- /** Options for the VPN plugin (stub). */
4
+ /** IP intelligence result used for VPN/proxy/Tor signals. */
5
+ interface IpIntelligence {
6
+ readonly vpn: boolean;
7
+ readonly proxy: boolean;
8
+ readonly tor: boolean;
9
+ readonly country: string;
10
+ readonly hosting: boolean;
11
+ }
12
+ /** Pluggable IP lookup for VPN detection. */
13
+ interface IpProvider {
14
+ lookup(ip: string): Promise<IpIntelligence>;
15
+ }
16
+ interface IpApiProviderOptions {
17
+ readonly timeoutMs?: number;
18
+ }
19
+ /**
20
+ * ip-api.com provider (rate-limited). For production use MaxMind or IPinfo.
21
+ */
22
+ declare class IpApiProvider implements IpProvider {
23
+ private readonly options;
24
+ constructor(options?: IpApiProviderOptions);
25
+ lookup(ip: string): Promise<IpIntelligence>;
26
+ }
27
+ /** Static provider for tests and offline use. */
28
+ declare class StaticIpProvider implements IpProvider {
29
+ private readonly results;
30
+ constructor(results: Readonly<Record<string, IpIntelligence>>);
31
+ lookup(ip: string): Promise<IpIntelligence>;
32
+ }
33
+
34
+ /** Options for the VPN plugin. */
7
35
  interface VpnPluginOptions {
8
- /** Provider used to classify IP addresses. */
9
- readonly provider?: VpnProvider;
10
- /** Default risk score added when VPN rules are registered by the plugin. */
36
+ /** Required for production. Defaults to empty static provider (no external calls). */
37
+ readonly provider?: IpProvider;
11
38
  readonly vpnScore?: number;
12
- /** Register default VPN/proxy/Tor rules on install. */
13
39
  readonly registerDefaultRules?: boolean;
14
40
  }
15
41
  /**
16
42
  * VPN / proxy detection plugin for guardian-risk.
17
- *
18
- * @stub Future versions will resolve client IP via MaxMind, IPinfo, or a custom
19
- * provider and add signals such as `vpn`, `proxy`, `tor`, and `country`.
20
43
  */
21
44
  declare function vpnPlugin(options?: VpnPluginOptions): Plugin;
22
- /**
23
- * @stub Future helper to resolve an IP and add VPN-related signals.
24
- */
25
- declare function checkIp(_ip: string, guardian: guardian_risk.Guardian): Promise<guardian_risk.Guardian>;
45
+ interface IpContext {
46
+ readonly clientIp: string;
47
+ }
48
+ declare function checkIp(ip: string, guardian: guardian_risk.Guardian, provider?: IpProvider): Promise<guardian_risk.Guardian>;
26
49
 
27
- export { type VpnPluginOptions, type VpnProvider, checkIp, vpnPlugin };
50
+ export { IpApiProvider, type IpContext, type IpIntelligence, type IpProvider, StaticIpProvider, type VpnPluginOptions, checkIp, vpnPlugin };
package/dist/index.js CHANGED
@@ -1,39 +1,161 @@
1
+ import { parseIpAddress, isPrivateIp } from 'guardian-risk';
2
+
3
+ // src/index.ts
4
+ var DEFAULT_TIMEOUT_MS = 5e3;
5
+ var LOOKUP_URL = "https://ip-api.com/json";
6
+ var IpApiProvider = class {
7
+ constructor(options = {}) {
8
+ this.options = options;
9
+ }
10
+ options;
11
+ async lookup(ip) {
12
+ const parsed = parseIpAddress(ip);
13
+ if (!parsed || isPrivateIp(parsed)) {
14
+ return localResult();
15
+ }
16
+ const controller = new AbortController();
17
+ const timeoutMs = this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
18
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
19
+ try {
20
+ const response = await fetch(
21
+ `${LOOKUP_URL}/${encodeURIComponent(parsed)}?fields=status,country,proxy,hosting`,
22
+ { signal: controller.signal }
23
+ );
24
+ if (!response.ok) {
25
+ throw new Error(`IP lookup failed with status ${response.status}`);
26
+ }
27
+ const data = await response.json();
28
+ if (data.status !== "success") {
29
+ return unknownResult();
30
+ }
31
+ return {
32
+ vpn: false,
33
+ proxy: data.proxy === true,
34
+ tor: false,
35
+ country: data.country ?? "unknown",
36
+ hosting: data.hosting === true
37
+ };
38
+ } finally {
39
+ clearTimeout(timer);
40
+ }
41
+ }
42
+ };
43
+ var StaticIpProvider = class {
44
+ constructor(results) {
45
+ this.results = results;
46
+ }
47
+ results;
48
+ async lookup(ip) {
49
+ const parsed = parseIpAddress(ip);
50
+ if (!parsed) {
51
+ return unknownResult();
52
+ }
53
+ return this.results[parsed] ?? unknownResult();
54
+ }
55
+ };
56
+ function localResult() {
57
+ return { vpn: false, proxy: false, tor: false, country: "local", hosting: false };
58
+ }
59
+ function unknownResult() {
60
+ return { vpn: false, proxy: false, tor: false, country: "unknown", hosting: false };
61
+ }
62
+
1
63
  // src/index.ts
64
+ var EMPTY_PROVIDER = new StaticIpProvider({});
2
65
  function vpnPlugin(options = {}) {
3
66
  const {
4
- provider = "maxmind",
67
+ provider = EMPTY_PROVIDER,
5
68
  vpnScore = 20,
6
69
  registerDefaultRules = true
7
70
  } = options;
8
71
  return {
9
72
  name: "guardian-risk-vpn",
10
73
  install(guardian) {
11
- if (!registerDefaultRules) {
12
- return;
74
+ if (registerDefaultRules) {
75
+ registerVpnRules(guardian, vpnScore);
13
76
  }
14
- guardian.rule({
15
- name: "VpnDetected",
16
- reason: "Connection appears to use a VPN",
17
- when: (s) => s.vpn === true,
18
- score: vpnScore
19
- });
20
- guardian.rule({
21
- name: "ProxyDetected",
22
- reason: "Connection appears to use a proxy",
23
- when: (s) => s.proxy === true,
24
- score: vpnScore
25
- });
26
- guardian.rule({
27
- name: "TorDetected",
28
- reason: "Connection appears to use Tor",
29
- when: (s) => s.tor === true,
30
- score: vpnScore + 10
77
+ guardian.beforeAnalyze(async ({ data, guardian: g }) => {
78
+ const ip = resolveIpForLookup(g, data);
79
+ if (!ip) {
80
+ return;
81
+ }
82
+ await applyIpSignals(ip, g, provider);
31
83
  });
32
84
  }
33
85
  };
34
86
  }
35
- async function checkIp(_ip, guardian) {
36
- return guardian.signal("signalSource", "vpn").signal("vpn", false).signal("proxy", false).signal("tor", false).signal("country", "unknown");
87
+ async function checkIp(ip, guardian, provider = EMPTY_PROVIDER) {
88
+ const parsed = parseIpAddress(ip);
89
+ if (!parsed) {
90
+ throw new TypeError("Invalid IP address");
91
+ }
92
+ await applyIpSignals(parsed, guardian, provider);
93
+ return guardian;
94
+ }
95
+ function registerVpnRules(guardian, vpnScore) {
96
+ guardian.rule({
97
+ name: "VpnDetected",
98
+ reason: "Connection appears to use a VPN or datacenter",
99
+ when: (s) => s.vpn === true || s.hosting === true,
100
+ score: vpnScore
101
+ }).rule({
102
+ name: "ProxyDetected",
103
+ reason: "Connection appears to use a proxy",
104
+ when: (s) => s.proxy === true,
105
+ score: vpnScore
106
+ }).rule({
107
+ name: "TorDetected",
108
+ reason: "Connection appears to use Tor",
109
+ when: (s) => s.tor === true,
110
+ score: vpnScore + 10
111
+ });
112
+ }
113
+ async function applyIpSignals(ip, guardian, provider) {
114
+ const intel = isPrivateIp(ip) ? localIntel() : await provider.lookup(ip);
115
+ guardian.signal("clientIp", ip).signal("vpn", intel.vpn).signal("proxy", intel.proxy).signal("tor", intel.tor).signal("country", intel.country).signal("hosting", intel.hosting).signal("signalSource", "vpn");
116
+ }
117
+ function localIntel() {
118
+ return { vpn: false, proxy: false, tor: false, country: "local", hosting: false };
119
+ }
120
+ function resolveIpForLookup(guardian, data) {
121
+ const fromSignal = guardian.getSignal("clientIp");
122
+ if (typeof fromSignal === "string") {
123
+ const parsed = parseIpAddress(fromSignal);
124
+ if (parsed) {
125
+ return parsed;
126
+ }
127
+ }
128
+ if (data !== null && typeof data === "object" && "clientIp" in data) {
129
+ const ip = data.clientIp;
130
+ const parsed = typeof ip === "string" ? parseIpAddress(ip) : null;
131
+ if (parsed) {
132
+ return parsed;
133
+ }
134
+ }
135
+ if (data !== null && typeof data === "object" && "headers" in data) {
136
+ const req = data;
137
+ if (req.ips) {
138
+ for (const candidate of req.ips) {
139
+ const parsed = parseIpAddress(candidate);
140
+ if (parsed) {
141
+ return parsed;
142
+ }
143
+ }
144
+ }
145
+ if (req.ip) {
146
+ const parsed = parseIpAddress(req.ip);
147
+ if (parsed) {
148
+ return parsed;
149
+ }
150
+ }
151
+ if (req.socket?.remoteAddress) {
152
+ return parseIpAddress(req.socket.remoteAddress);
153
+ }
154
+ }
155
+ if (typeof data === "string") {
156
+ return parseIpAddress(data);
157
+ }
158
+ return null;
37
159
  }
38
160
 
39
- export { checkIp, vpnPlugin };
161
+ export { IpApiProvider, StaticIpProvider, checkIp, vpnPlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardian-risk-vpn",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "VPN and proxy detection plugin for guardian-risk — adds network risk signals",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -56,15 +56,17 @@
56
56
  "access": "public"
57
57
  },
58
58
  "peerDependencies": {
59
- "guardian-risk": "^0.2.0"
59
+ "guardian-risk": "^0.3.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "tsup": "^8.3.5",
63
63
  "typescript": "^5.7.2",
64
- "guardian-risk": "0.2.1"
64
+ "vitest": "^4.1.9",
65
+ "guardian-risk": "0.3.0"
65
66
  },
66
67
  "scripts": {
67
68
  "build": "tsup",
69
+ "test": "vitest run",
68
70
  "typecheck": "tsc --noEmit"
69
71
  }
70
72
  }