guardian-risk 0.1.0 → 0.2.1
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 +72 -6
- package/dist/index.cjs +175 -7
- package/dist/index.d.cts +55 -3
- package/dist/index.d.ts +55 -3
- package/dist/index.js +173 -8
- package/package.json +21 -4
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -6,8 +6,6 @@ Configurable risk decision engine for TypeScript. Evaluate signals against rules
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install guardian-risk
|
|
9
|
-
# or
|
|
10
|
-
pnpm add guardian-risk
|
|
11
9
|
```
|
|
12
10
|
|
|
13
11
|
## Usage
|
|
@@ -36,12 +34,80 @@ console.log(report.score); // 35
|
|
|
36
34
|
console.log(report.level); // MEDIUM
|
|
37
35
|
```
|
|
38
36
|
|
|
37
|
+
## Official plugins
|
|
38
|
+
|
|
39
|
+
Install **core first**, then add only the plugins you need:
|
|
40
|
+
|
|
41
|
+
| Package | Install | Purpose |
|
|
42
|
+
|---------|---------|---------|
|
|
43
|
+
| **guardian-risk** (this) | `npm i guardian-risk` | Core engine |
|
|
44
|
+
| guardian-risk-express | `npm i guardian-risk-express` | Express request signals |
|
|
45
|
+
| guardian-risk-browser | `npm i guardian-risk-browser` | Browser behavioral signals |
|
|
46
|
+
| guardian-risk-redis | `npm i guardian-risk-redis` | Redis session counters |
|
|
47
|
+
| guardian-risk-vpn | `npm i guardian-risk-vpn` | VPN / proxy / Tor detection |
|
|
48
|
+
| guardian-risk-logger | `npm i guardian-risk-logger` | Audit logging |
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Example: Express + VPN + Logger stack
|
|
52
|
+
npm install guardian-risk guardian-risk-express guardian-risk-vpn guardian-risk-logger
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { Guardian } from 'guardian-risk';
|
|
57
|
+
import { expressPlugin } from 'guardian-risk-express';
|
|
58
|
+
import { vpnPlugin } from 'guardian-risk-vpn';
|
|
59
|
+
import { loggerPlugin, analyzeAndLog } from 'guardian-risk-logger';
|
|
60
|
+
|
|
61
|
+
const guardian = new Guardian()
|
|
62
|
+
.use(expressPlugin())
|
|
63
|
+
.use(vpnPlugin())
|
|
64
|
+
.use(loggerPlugin());
|
|
65
|
+
|
|
66
|
+
analyzeAndLog(guardian);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
> Plugin packages are early stubs — APIs may change before `1.0.0`.
|
|
70
|
+
|
|
71
|
+
## Plugins API
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import type { Plugin } from 'guardian-risk';
|
|
75
|
+
|
|
76
|
+
const myPlugin: Plugin = {
|
|
77
|
+
name: 'my-plugin',
|
|
78
|
+
install(guardian) {
|
|
79
|
+
guardian.rule({ name: 'Custom', when: () => true, score: 10 });
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
new Guardian().use(myPlugin);
|
|
84
|
+
```
|
|
85
|
+
|
|
39
86
|
## API
|
|
40
87
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
88
|
+
| Method | Description |
|
|
89
|
+
|--------|-------------|
|
|
90
|
+
| `guardian.signal(key, value)` | Add a signal |
|
|
91
|
+
| `guardian.rule({ name, when, score, reason? })` | Register a rule |
|
|
92
|
+
| `guardian.use(plugin)` | Install a plugin (once per name) |
|
|
93
|
+
| `guardian.analyze()` | Run evaluation, returns `RiskReport` |
|
|
94
|
+
| `guardian.reset()` | Clear signals (rules + plugins persist) |
|
|
95
|
+
| `guardian.getInstalledPlugins()` | List installed plugin names |
|
|
96
|
+
|
|
97
|
+
## Security
|
|
98
|
+
|
|
99
|
+
- **Zero runtime dependencies** — minimal supply chain risk
|
|
100
|
+
- **No install scripts** — nothing runs on `npm install`
|
|
101
|
+
- Prototype pollution protection on signal keys
|
|
102
|
+
- Rule `when()` errors isolated — engine stays stable
|
|
103
|
+
- Score bounds: ±10,000 per rule, ±1,000,000 total
|
|
104
|
+
- Plugin `install()` failures throw `PluginInstallError` without registering
|
|
105
|
+
- See [SECURITY.md](SECURITY.md) for vulnerability reporting
|
|
106
|
+
|
|
107
|
+
## Links
|
|
108
|
+
|
|
109
|
+
- [GitHub monorepo](https://github.com/himanshu6306singh/guardian-risk)
|
|
110
|
+
- [All packages guide](https://github.com/himanshu6306singh/guardian-risk/blob/main/ECOSYSTEM.md)
|
|
45
111
|
|
|
46
112
|
## License
|
|
47
113
|
|
package/dist/index.cjs
CHANGED
|
@@ -10,6 +10,18 @@ var DEFAULT_RISK_LEVELS = [
|
|
|
10
10
|
{ max: Infinity, level: "CRITICAL" }
|
|
11
11
|
];
|
|
12
12
|
|
|
13
|
+
// src/constants/security.ts
|
|
14
|
+
var MAX_RULES = 1e3;
|
|
15
|
+
var MAX_SIGNALS = 1e3;
|
|
16
|
+
var MAX_KEY_LENGTH = 256;
|
|
17
|
+
var MAX_RULE_SCORE = 1e4;
|
|
18
|
+
var MAX_TOTAL_SCORE = 1e6;
|
|
19
|
+
var BLOCKED_SIGNAL_KEYS = /* @__PURE__ */ new Set([
|
|
20
|
+
"__proto__",
|
|
21
|
+
"constructor",
|
|
22
|
+
"prototype"
|
|
23
|
+
]);
|
|
24
|
+
|
|
13
25
|
// src/utils/resolveLevel.ts
|
|
14
26
|
function resolveLevel(score, thresholds = DEFAULT_RISK_LEVELS) {
|
|
15
27
|
for (const threshold of thresholds) {
|
|
@@ -49,7 +61,13 @@ var RuleEvaluator = class {
|
|
|
49
61
|
evaluate(rules, signals) {
|
|
50
62
|
const matched = [];
|
|
51
63
|
for (const rule of rules) {
|
|
52
|
-
|
|
64
|
+
let isMatched = false;
|
|
65
|
+
try {
|
|
66
|
+
isMatched = rule.when(signals);
|
|
67
|
+
} catch {
|
|
68
|
+
isMatched = false;
|
|
69
|
+
}
|
|
70
|
+
if (isMatched) {
|
|
53
71
|
matched.push({
|
|
54
72
|
id: rule.id,
|
|
55
73
|
name: rule.name,
|
|
@@ -65,10 +83,20 @@ var RuleEvaluator = class {
|
|
|
65
83
|
// src/score/ScoreCalculator.ts
|
|
66
84
|
var ScoreCalculator = class {
|
|
67
85
|
/**
|
|
68
|
-
* Sum scores from all matched rules.
|
|
86
|
+
* Sum scores from all matched rules, clamped to a safe maximum.
|
|
69
87
|
*/
|
|
70
88
|
calculate(matchedRules) {
|
|
71
|
-
|
|
89
|
+
const total = matchedRules.reduce((sum, rule) => sum + rule.score, 0);
|
|
90
|
+
if (!Number.isFinite(total)) {
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
if (total > MAX_TOTAL_SCORE) {
|
|
94
|
+
return MAX_TOTAL_SCORE;
|
|
95
|
+
}
|
|
96
|
+
if (total < -MAX_TOTAL_SCORE) {
|
|
97
|
+
return -MAX_TOTAL_SCORE;
|
|
98
|
+
}
|
|
99
|
+
return total;
|
|
72
100
|
}
|
|
73
101
|
};
|
|
74
102
|
function validateSignalValue(value) {
|
|
@@ -78,6 +106,74 @@ function validateSignalValue(value) {
|
|
|
78
106
|
const type = typeof value;
|
|
79
107
|
return type === "string" || type === "number" || type === "boolean";
|
|
80
108
|
}
|
|
109
|
+
function validateSignalKey(key) {
|
|
110
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
111
|
+
throw new TypeError("Signal key must be a non-empty string");
|
|
112
|
+
}
|
|
113
|
+
if (key.length > MAX_KEY_LENGTH) {
|
|
114
|
+
throw new TypeError(`Signal key exceeds maximum length of ${MAX_KEY_LENGTH}`);
|
|
115
|
+
}
|
|
116
|
+
if (BLOCKED_SIGNAL_KEYS.has(key)) {
|
|
117
|
+
throw new TypeError(`Signal key "${key}" is not allowed`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function validateRuleInput(input) {
|
|
121
|
+
if (typeof input.name !== "string" || input.name.trim().length === 0) {
|
|
122
|
+
throw new TypeError("Rule name must be a non-empty string");
|
|
123
|
+
}
|
|
124
|
+
if (input.name.length > MAX_KEY_LENGTH) {
|
|
125
|
+
throw new TypeError(`Rule name exceeds maximum length of ${MAX_KEY_LENGTH}`);
|
|
126
|
+
}
|
|
127
|
+
if (typeof input.when !== "function") {
|
|
128
|
+
throw new TypeError("Rule when must be a function");
|
|
129
|
+
}
|
|
130
|
+
if (typeof input.score !== "number" || !Number.isFinite(input.score)) {
|
|
131
|
+
throw new TypeError("Rule score must be a finite number");
|
|
132
|
+
}
|
|
133
|
+
if (Math.abs(input.score) > MAX_RULE_SCORE) {
|
|
134
|
+
throw new TypeError(`Rule score must be between -${MAX_RULE_SCORE} and ${MAX_RULE_SCORE}`);
|
|
135
|
+
}
|
|
136
|
+
if (input.reason !== void 0) {
|
|
137
|
+
if (typeof input.reason !== "string") {
|
|
138
|
+
throw new TypeError("Rule reason must be a string");
|
|
139
|
+
}
|
|
140
|
+
if (input.reason.length > MAX_KEY_LENGTH) {
|
|
141
|
+
throw new TypeError(`Rule reason exceeds maximum length of ${MAX_KEY_LENGTH}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (input.description !== void 0) {
|
|
145
|
+
if (typeof input.description !== "string") {
|
|
146
|
+
throw new TypeError("Rule description must be a string");
|
|
147
|
+
}
|
|
148
|
+
if (input.description.length > MAX_KEY_LENGTH) {
|
|
149
|
+
throw new TypeError(`Rule description exceeds maximum length of ${MAX_KEY_LENGTH}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function validatePlugin(plugin) {
|
|
154
|
+
if (typeof plugin.name !== "string" || plugin.name.trim().length === 0) {
|
|
155
|
+
throw new TypeError("Plugin name must be a non-empty string");
|
|
156
|
+
}
|
|
157
|
+
if (plugin.name.length > MAX_KEY_LENGTH) {
|
|
158
|
+
throw new TypeError(`Plugin name exceeds maximum length of ${MAX_KEY_LENGTH}`);
|
|
159
|
+
}
|
|
160
|
+
if (typeof plugin.install !== "function") {
|
|
161
|
+
throw new TypeError("Plugin install must be a function");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function validateRiskLevels(levels) {
|
|
165
|
+
if (levels.length === 0) {
|
|
166
|
+
throw new TypeError("Risk levels must contain at least one threshold");
|
|
167
|
+
}
|
|
168
|
+
for (const threshold of levels) {
|
|
169
|
+
if (typeof threshold.level !== "string" || threshold.level.trim().length === 0) {
|
|
170
|
+
throw new TypeError("Risk level label must be a non-empty string");
|
|
171
|
+
}
|
|
172
|
+
if (typeof threshold.max !== "number" || !Number.isFinite(threshold.max) && threshold.max !== Infinity) {
|
|
173
|
+
throw new TypeError("Risk level max must be a finite number or Infinity");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
81
177
|
function generateId() {
|
|
82
178
|
return crypto.randomUUID();
|
|
83
179
|
}
|
|
@@ -89,11 +185,15 @@ var SignalStore = class {
|
|
|
89
185
|
* Set a signal value. Overwrites any existing value for the key.
|
|
90
186
|
*/
|
|
91
187
|
set(key, value) {
|
|
188
|
+
validateSignalKey(key);
|
|
92
189
|
if (!validateSignalValue(value)) {
|
|
93
190
|
throw new TypeError(
|
|
94
191
|
`Invalid signal value for "${key}": signals must be string, number, boolean, or null`
|
|
95
192
|
);
|
|
96
193
|
}
|
|
194
|
+
if (!this.signals.has(key) && this.signals.size >= MAX_SIGNALS) {
|
|
195
|
+
throw new RangeError(`Cannot exceed maximum of ${MAX_SIGNALS} signals`);
|
|
196
|
+
}
|
|
97
197
|
this.signals.set(key, value);
|
|
98
198
|
return this;
|
|
99
199
|
}
|
|
@@ -113,7 +213,7 @@ var SignalStore = class {
|
|
|
113
213
|
* Returns a frozen snapshot of all signals.
|
|
114
214
|
*/
|
|
115
215
|
getAll() {
|
|
116
|
-
const snapshot =
|
|
216
|
+
const snapshot = /* @__PURE__ */ Object.create(null);
|
|
117
217
|
for (const [key, value] of this.signals) {
|
|
118
218
|
snapshot[key] = value;
|
|
119
219
|
}
|
|
@@ -140,6 +240,9 @@ var RiskEngine = class {
|
|
|
140
240
|
* Register a rule for evaluation.
|
|
141
241
|
*/
|
|
142
242
|
addRule(rule) {
|
|
243
|
+
if (this.rules.length >= MAX_RULES) {
|
|
244
|
+
throw new RangeError(`Cannot exceed maximum of ${MAX_RULES} rules`);
|
|
245
|
+
}
|
|
143
246
|
this.rules.push(rule);
|
|
144
247
|
}
|
|
145
248
|
/**
|
|
@@ -165,6 +268,7 @@ var RuleBuilder = class {
|
|
|
165
268
|
* Create a new rule from input configuration.
|
|
166
269
|
*/
|
|
167
270
|
static create(input) {
|
|
271
|
+
validateRuleInput(input);
|
|
168
272
|
const rule = {
|
|
169
273
|
id: generateId(),
|
|
170
274
|
name: input.name,
|
|
@@ -177,12 +281,62 @@ var RuleBuilder = class {
|
|
|
177
281
|
}
|
|
178
282
|
};
|
|
179
283
|
|
|
284
|
+
// src/plugins/PluginRegistry.ts
|
|
285
|
+
var PluginInstallError = class extends Error {
|
|
286
|
+
constructor(pluginName, cause) {
|
|
287
|
+
const message = cause instanceof Error ? cause.message : "Unknown plugin install error";
|
|
288
|
+
super(`Plugin "${pluginName}" failed to install: ${message}`);
|
|
289
|
+
this.name = "PluginInstallError";
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
var PluginAlreadyInstalledError = class extends Error {
|
|
293
|
+
constructor(pluginName) {
|
|
294
|
+
super(`Plugin "${pluginName}" is already installed`);
|
|
295
|
+
this.name = "PluginAlreadyInstalledError";
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
var PluginRegistry = class {
|
|
299
|
+
installed = /* @__PURE__ */ new Set();
|
|
300
|
+
/**
|
|
301
|
+
* Install a plugin on the given Guardian instance.
|
|
302
|
+
* Each plugin name may only be installed once per Guardian instance.
|
|
303
|
+
*/
|
|
304
|
+
install(plugin, guardian) {
|
|
305
|
+
validatePlugin(plugin);
|
|
306
|
+
if (this.installed.has(plugin.name)) {
|
|
307
|
+
throw new PluginAlreadyInstalledError(plugin.name);
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
plugin.install(guardian);
|
|
311
|
+
} catch (error) {
|
|
312
|
+
throw new PluginInstallError(plugin.name, error);
|
|
313
|
+
}
|
|
314
|
+
this.installed.add(plugin.name);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Check if a plugin is installed by name.
|
|
318
|
+
*/
|
|
319
|
+
has(name) {
|
|
320
|
+
return this.installed.has(name);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Get names of all installed plugins in registration order.
|
|
324
|
+
*/
|
|
325
|
+
getInstalled() {
|
|
326
|
+
return [...this.installed];
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
180
330
|
// src/engine/Guardian.ts
|
|
181
331
|
var Guardian = class {
|
|
182
332
|
signalStore;
|
|
183
333
|
riskEngine;
|
|
334
|
+
pluginRegistry = new PluginRegistry();
|
|
184
335
|
constructor(config = {}) {
|
|
185
336
|
const thresholds = config.levels ?? DEFAULT_RISK_LEVELS;
|
|
337
|
+
if (config.levels !== void 0) {
|
|
338
|
+
validateRiskLevels(config.levels);
|
|
339
|
+
}
|
|
186
340
|
this.signalStore = new SignalStore();
|
|
187
341
|
const deps = {
|
|
188
342
|
signalStore: this.signalStore,
|
|
@@ -207,6 +361,19 @@ var Guardian = class {
|
|
|
207
361
|
this.riskEngine.addRule(rule);
|
|
208
362
|
return this;
|
|
209
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Install a plugin. Each plugin name may only be registered once.
|
|
366
|
+
*/
|
|
367
|
+
use(plugin) {
|
|
368
|
+
this.pluginRegistry.install(plugin, this);
|
|
369
|
+
return this;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Returns names of installed plugins.
|
|
373
|
+
*/
|
|
374
|
+
getInstalledPlugins() {
|
|
375
|
+
return this.pluginRegistry.getInstalled();
|
|
376
|
+
}
|
|
210
377
|
/**
|
|
211
378
|
* Run risk analysis and return an immutable report.
|
|
212
379
|
*/
|
|
@@ -214,7 +381,7 @@ var Guardian = class {
|
|
|
214
381
|
return this.riskEngine.analyze();
|
|
215
382
|
}
|
|
216
383
|
/**
|
|
217
|
-
* Clear all signals. Rules persist across resets.
|
|
384
|
+
* Clear all signals. Rules and installed plugins persist across resets.
|
|
218
385
|
*/
|
|
219
386
|
reset() {
|
|
220
387
|
this.signalStore.clear();
|
|
@@ -224,11 +391,12 @@ var Guardian = class {
|
|
|
224
391
|
|
|
225
392
|
exports.DEFAULT_RISK_LEVELS = DEFAULT_RISK_LEVELS;
|
|
226
393
|
exports.Guardian = Guardian;
|
|
394
|
+
exports.PluginAlreadyInstalledError = PluginAlreadyInstalledError;
|
|
395
|
+
exports.PluginInstallError = PluginInstallError;
|
|
396
|
+
exports.PluginRegistry = PluginRegistry;
|
|
227
397
|
exports.RiskEngine = RiskEngine;
|
|
228
398
|
exports.RuleBuilder = RuleBuilder;
|
|
229
399
|
exports.RuleEvaluator = RuleEvaluator;
|
|
230
400
|
exports.ScoreCalculator = ScoreCalculator;
|
|
231
401
|
exports.SignalStore = SignalStore;
|
|
232
402
|
exports.resolveLevel = resolveLevel;
|
|
233
|
-
//# sourceMappingURL=index.cjs.map
|
|
234
|
-
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension point for adding capabilities to Guardian without modifying core.
|
|
3
|
+
* Plugins may register signals, rules, or helpers during installation.
|
|
4
|
+
*/
|
|
5
|
+
interface Plugin {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
install(guardian: Guardian): void;
|
|
8
|
+
}
|
|
9
|
+
/** Public alias for {@link Plugin}. */
|
|
10
|
+
type GuardianPlugin = Plugin;
|
|
11
|
+
|
|
1
12
|
/** Primitive signal value types supported by Guardian. */
|
|
2
13
|
type SignalValue = string | number | boolean | null;
|
|
3
14
|
/** Read-only map of signal key to value. */
|
|
@@ -58,6 +69,7 @@ interface GuardianConfig {
|
|
|
58
69
|
declare class Guardian {
|
|
59
70
|
private readonly signalStore;
|
|
60
71
|
private readonly riskEngine;
|
|
72
|
+
private readonly pluginRegistry;
|
|
61
73
|
constructor(config?: GuardianConfig);
|
|
62
74
|
/**
|
|
63
75
|
* Add a signal value for risk evaluation.
|
|
@@ -67,12 +79,20 @@ declare class Guardian {
|
|
|
67
79
|
* Register a rule. ID is auto-generated.
|
|
68
80
|
*/
|
|
69
81
|
rule(input: CreateRuleInput): this;
|
|
82
|
+
/**
|
|
83
|
+
* Install a plugin. Each plugin name may only be registered once.
|
|
84
|
+
*/
|
|
85
|
+
use(plugin: Plugin): this;
|
|
86
|
+
/**
|
|
87
|
+
* Returns names of installed plugins.
|
|
88
|
+
*/
|
|
89
|
+
getInstalledPlugins(): readonly string[];
|
|
70
90
|
/**
|
|
71
91
|
* Run risk analysis and return an immutable report.
|
|
72
92
|
*/
|
|
73
93
|
analyze(): RiskReport;
|
|
74
94
|
/**
|
|
75
|
-
* Clear all signals. Rules persist across resets.
|
|
95
|
+
* Clear all signals. Rules and installed plugins persist across resets.
|
|
76
96
|
*/
|
|
77
97
|
reset(): this;
|
|
78
98
|
}
|
|
@@ -103,7 +123,7 @@ declare class RuleEvaluator {
|
|
|
103
123
|
*/
|
|
104
124
|
declare class ScoreCalculator {
|
|
105
125
|
/**
|
|
106
|
-
* Sum scores from all matched rules.
|
|
126
|
+
* Sum scores from all matched rules, clamped to a safe maximum.
|
|
107
127
|
*/
|
|
108
128
|
calculate(matchedRules: readonly MatchedRule[]): number;
|
|
109
129
|
}
|
|
@@ -164,6 +184,38 @@ declare class RiskEngine {
|
|
|
164
184
|
analyze(): RiskReport;
|
|
165
185
|
}
|
|
166
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Thrown when a plugin's install() callback fails.
|
|
189
|
+
*/
|
|
190
|
+
declare class PluginInstallError extends Error {
|
|
191
|
+
constructor(pluginName: string, cause: unknown);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Thrown when attempting to register a plugin that is already installed.
|
|
195
|
+
*/
|
|
196
|
+
declare class PluginAlreadyInstalledError extends Error {
|
|
197
|
+
constructor(pluginName: string);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Manages plugin lifecycle: register once, track installed plugins.
|
|
201
|
+
*/
|
|
202
|
+
declare class PluginRegistry {
|
|
203
|
+
private readonly installed;
|
|
204
|
+
/**
|
|
205
|
+
* Install a plugin on the given Guardian instance.
|
|
206
|
+
* Each plugin name may only be installed once per Guardian instance.
|
|
207
|
+
*/
|
|
208
|
+
install(plugin: Plugin, guardian: Guardian): void;
|
|
209
|
+
/**
|
|
210
|
+
* Check if a plugin is installed by name.
|
|
211
|
+
*/
|
|
212
|
+
has(name: string): boolean;
|
|
213
|
+
/**
|
|
214
|
+
* Get names of all installed plugins in registration order.
|
|
215
|
+
*/
|
|
216
|
+
getInstalled(): readonly string[];
|
|
217
|
+
}
|
|
218
|
+
|
|
167
219
|
/**
|
|
168
220
|
* Builds immutable rule definitions with auto-generated IDs.
|
|
169
221
|
*/
|
|
@@ -183,4 +235,4 @@ declare function resolveLevel(score: number, thresholds?: readonly RiskLevelThre
|
|
|
183
235
|
/** Default risk level thresholds used when none are configured. */
|
|
184
236
|
declare const DEFAULT_RISK_LEVELS: readonly RiskLevelThreshold[];
|
|
185
237
|
|
|
186
|
-
export { type CreateRuleInput, DEFAULT_RISK_LEVELS, Guardian, type GuardianConfig, type MatchedRule, RiskEngine, type RiskEngineDependencies, type RiskLevelThreshold, type RiskReport, type Rule, RuleBuilder, RuleEvaluator, ScoreCalculator, type SignalDefinition, type SignalMap, SignalStore, type SignalValue, resolveLevel };
|
|
238
|
+
export { type CreateRuleInput, DEFAULT_RISK_LEVELS, Guardian, type GuardianConfig, type GuardianPlugin, type MatchedRule, type Plugin, PluginAlreadyInstalledError, PluginInstallError, PluginRegistry, RiskEngine, type RiskEngineDependencies, type RiskLevelThreshold, type RiskReport, type Rule, RuleBuilder, RuleEvaluator, ScoreCalculator, type SignalDefinition, type SignalMap, SignalStore, type SignalValue, resolveLevel };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension point for adding capabilities to Guardian without modifying core.
|
|
3
|
+
* Plugins may register signals, rules, or helpers during installation.
|
|
4
|
+
*/
|
|
5
|
+
interface Plugin {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
install(guardian: Guardian): void;
|
|
8
|
+
}
|
|
9
|
+
/** Public alias for {@link Plugin}. */
|
|
10
|
+
type GuardianPlugin = Plugin;
|
|
11
|
+
|
|
1
12
|
/** Primitive signal value types supported by Guardian. */
|
|
2
13
|
type SignalValue = string | number | boolean | null;
|
|
3
14
|
/** Read-only map of signal key to value. */
|
|
@@ -58,6 +69,7 @@ interface GuardianConfig {
|
|
|
58
69
|
declare class Guardian {
|
|
59
70
|
private readonly signalStore;
|
|
60
71
|
private readonly riskEngine;
|
|
72
|
+
private readonly pluginRegistry;
|
|
61
73
|
constructor(config?: GuardianConfig);
|
|
62
74
|
/**
|
|
63
75
|
* Add a signal value for risk evaluation.
|
|
@@ -67,12 +79,20 @@ declare class Guardian {
|
|
|
67
79
|
* Register a rule. ID is auto-generated.
|
|
68
80
|
*/
|
|
69
81
|
rule(input: CreateRuleInput): this;
|
|
82
|
+
/**
|
|
83
|
+
* Install a plugin. Each plugin name may only be registered once.
|
|
84
|
+
*/
|
|
85
|
+
use(plugin: Plugin): this;
|
|
86
|
+
/**
|
|
87
|
+
* Returns names of installed plugins.
|
|
88
|
+
*/
|
|
89
|
+
getInstalledPlugins(): readonly string[];
|
|
70
90
|
/**
|
|
71
91
|
* Run risk analysis and return an immutable report.
|
|
72
92
|
*/
|
|
73
93
|
analyze(): RiskReport;
|
|
74
94
|
/**
|
|
75
|
-
* Clear all signals. Rules persist across resets.
|
|
95
|
+
* Clear all signals. Rules and installed plugins persist across resets.
|
|
76
96
|
*/
|
|
77
97
|
reset(): this;
|
|
78
98
|
}
|
|
@@ -103,7 +123,7 @@ declare class RuleEvaluator {
|
|
|
103
123
|
*/
|
|
104
124
|
declare class ScoreCalculator {
|
|
105
125
|
/**
|
|
106
|
-
* Sum scores from all matched rules.
|
|
126
|
+
* Sum scores from all matched rules, clamped to a safe maximum.
|
|
107
127
|
*/
|
|
108
128
|
calculate(matchedRules: readonly MatchedRule[]): number;
|
|
109
129
|
}
|
|
@@ -164,6 +184,38 @@ declare class RiskEngine {
|
|
|
164
184
|
analyze(): RiskReport;
|
|
165
185
|
}
|
|
166
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Thrown when a plugin's install() callback fails.
|
|
189
|
+
*/
|
|
190
|
+
declare class PluginInstallError extends Error {
|
|
191
|
+
constructor(pluginName: string, cause: unknown);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Thrown when attempting to register a plugin that is already installed.
|
|
195
|
+
*/
|
|
196
|
+
declare class PluginAlreadyInstalledError extends Error {
|
|
197
|
+
constructor(pluginName: string);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Manages plugin lifecycle: register once, track installed plugins.
|
|
201
|
+
*/
|
|
202
|
+
declare class PluginRegistry {
|
|
203
|
+
private readonly installed;
|
|
204
|
+
/**
|
|
205
|
+
* Install a plugin on the given Guardian instance.
|
|
206
|
+
* Each plugin name may only be installed once per Guardian instance.
|
|
207
|
+
*/
|
|
208
|
+
install(plugin: Plugin, guardian: Guardian): void;
|
|
209
|
+
/**
|
|
210
|
+
* Check if a plugin is installed by name.
|
|
211
|
+
*/
|
|
212
|
+
has(name: string): boolean;
|
|
213
|
+
/**
|
|
214
|
+
* Get names of all installed plugins in registration order.
|
|
215
|
+
*/
|
|
216
|
+
getInstalled(): readonly string[];
|
|
217
|
+
}
|
|
218
|
+
|
|
167
219
|
/**
|
|
168
220
|
* Builds immutable rule definitions with auto-generated IDs.
|
|
169
221
|
*/
|
|
@@ -183,4 +235,4 @@ declare function resolveLevel(score: number, thresholds?: readonly RiskLevelThre
|
|
|
183
235
|
/** Default risk level thresholds used when none are configured. */
|
|
184
236
|
declare const DEFAULT_RISK_LEVELS: readonly RiskLevelThreshold[];
|
|
185
237
|
|
|
186
|
-
export { type CreateRuleInput, DEFAULT_RISK_LEVELS, Guardian, type GuardianConfig, type MatchedRule, RiskEngine, type RiskEngineDependencies, type RiskLevelThreshold, type RiskReport, type Rule, RuleBuilder, RuleEvaluator, ScoreCalculator, type SignalDefinition, type SignalMap, SignalStore, type SignalValue, resolveLevel };
|
|
238
|
+
export { type CreateRuleInput, DEFAULT_RISK_LEVELS, Guardian, type GuardianConfig, type GuardianPlugin, type MatchedRule, type Plugin, PluginAlreadyInstalledError, PluginInstallError, PluginRegistry, RiskEngine, type RiskEngineDependencies, type RiskLevelThreshold, type RiskReport, type Rule, RuleBuilder, RuleEvaluator, ScoreCalculator, type SignalDefinition, type SignalMap, SignalStore, type SignalValue, resolveLevel };
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,18 @@ var DEFAULT_RISK_LEVELS = [
|
|
|
8
8
|
{ max: Infinity, level: "CRITICAL" }
|
|
9
9
|
];
|
|
10
10
|
|
|
11
|
+
// src/constants/security.ts
|
|
12
|
+
var MAX_RULES = 1e3;
|
|
13
|
+
var MAX_SIGNALS = 1e3;
|
|
14
|
+
var MAX_KEY_LENGTH = 256;
|
|
15
|
+
var MAX_RULE_SCORE = 1e4;
|
|
16
|
+
var MAX_TOTAL_SCORE = 1e6;
|
|
17
|
+
var BLOCKED_SIGNAL_KEYS = /* @__PURE__ */ new Set([
|
|
18
|
+
"__proto__",
|
|
19
|
+
"constructor",
|
|
20
|
+
"prototype"
|
|
21
|
+
]);
|
|
22
|
+
|
|
11
23
|
// src/utils/resolveLevel.ts
|
|
12
24
|
function resolveLevel(score, thresholds = DEFAULT_RISK_LEVELS) {
|
|
13
25
|
for (const threshold of thresholds) {
|
|
@@ -47,7 +59,13 @@ var RuleEvaluator = class {
|
|
|
47
59
|
evaluate(rules, signals) {
|
|
48
60
|
const matched = [];
|
|
49
61
|
for (const rule of rules) {
|
|
50
|
-
|
|
62
|
+
let isMatched = false;
|
|
63
|
+
try {
|
|
64
|
+
isMatched = rule.when(signals);
|
|
65
|
+
} catch {
|
|
66
|
+
isMatched = false;
|
|
67
|
+
}
|
|
68
|
+
if (isMatched) {
|
|
51
69
|
matched.push({
|
|
52
70
|
id: rule.id,
|
|
53
71
|
name: rule.name,
|
|
@@ -63,10 +81,20 @@ var RuleEvaluator = class {
|
|
|
63
81
|
// src/score/ScoreCalculator.ts
|
|
64
82
|
var ScoreCalculator = class {
|
|
65
83
|
/**
|
|
66
|
-
* Sum scores from all matched rules.
|
|
84
|
+
* Sum scores from all matched rules, clamped to a safe maximum.
|
|
67
85
|
*/
|
|
68
86
|
calculate(matchedRules) {
|
|
69
|
-
|
|
87
|
+
const total = matchedRules.reduce((sum, rule) => sum + rule.score, 0);
|
|
88
|
+
if (!Number.isFinite(total)) {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
if (total > MAX_TOTAL_SCORE) {
|
|
92
|
+
return MAX_TOTAL_SCORE;
|
|
93
|
+
}
|
|
94
|
+
if (total < -MAX_TOTAL_SCORE) {
|
|
95
|
+
return -MAX_TOTAL_SCORE;
|
|
96
|
+
}
|
|
97
|
+
return total;
|
|
70
98
|
}
|
|
71
99
|
};
|
|
72
100
|
function validateSignalValue(value) {
|
|
@@ -76,6 +104,74 @@ function validateSignalValue(value) {
|
|
|
76
104
|
const type = typeof value;
|
|
77
105
|
return type === "string" || type === "number" || type === "boolean";
|
|
78
106
|
}
|
|
107
|
+
function validateSignalKey(key) {
|
|
108
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
109
|
+
throw new TypeError("Signal key must be a non-empty string");
|
|
110
|
+
}
|
|
111
|
+
if (key.length > MAX_KEY_LENGTH) {
|
|
112
|
+
throw new TypeError(`Signal key exceeds maximum length of ${MAX_KEY_LENGTH}`);
|
|
113
|
+
}
|
|
114
|
+
if (BLOCKED_SIGNAL_KEYS.has(key)) {
|
|
115
|
+
throw new TypeError(`Signal key "${key}" is not allowed`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function validateRuleInput(input) {
|
|
119
|
+
if (typeof input.name !== "string" || input.name.trim().length === 0) {
|
|
120
|
+
throw new TypeError("Rule name must be a non-empty string");
|
|
121
|
+
}
|
|
122
|
+
if (input.name.length > MAX_KEY_LENGTH) {
|
|
123
|
+
throw new TypeError(`Rule name exceeds maximum length of ${MAX_KEY_LENGTH}`);
|
|
124
|
+
}
|
|
125
|
+
if (typeof input.when !== "function") {
|
|
126
|
+
throw new TypeError("Rule when must be a function");
|
|
127
|
+
}
|
|
128
|
+
if (typeof input.score !== "number" || !Number.isFinite(input.score)) {
|
|
129
|
+
throw new TypeError("Rule score must be a finite number");
|
|
130
|
+
}
|
|
131
|
+
if (Math.abs(input.score) > MAX_RULE_SCORE) {
|
|
132
|
+
throw new TypeError(`Rule score must be between -${MAX_RULE_SCORE} and ${MAX_RULE_SCORE}`);
|
|
133
|
+
}
|
|
134
|
+
if (input.reason !== void 0) {
|
|
135
|
+
if (typeof input.reason !== "string") {
|
|
136
|
+
throw new TypeError("Rule reason must be a string");
|
|
137
|
+
}
|
|
138
|
+
if (input.reason.length > MAX_KEY_LENGTH) {
|
|
139
|
+
throw new TypeError(`Rule reason exceeds maximum length of ${MAX_KEY_LENGTH}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (input.description !== void 0) {
|
|
143
|
+
if (typeof input.description !== "string") {
|
|
144
|
+
throw new TypeError("Rule description must be a string");
|
|
145
|
+
}
|
|
146
|
+
if (input.description.length > MAX_KEY_LENGTH) {
|
|
147
|
+
throw new TypeError(`Rule description exceeds maximum length of ${MAX_KEY_LENGTH}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function validatePlugin(plugin) {
|
|
152
|
+
if (typeof plugin.name !== "string" || plugin.name.trim().length === 0) {
|
|
153
|
+
throw new TypeError("Plugin name must be a non-empty string");
|
|
154
|
+
}
|
|
155
|
+
if (plugin.name.length > MAX_KEY_LENGTH) {
|
|
156
|
+
throw new TypeError(`Plugin name exceeds maximum length of ${MAX_KEY_LENGTH}`);
|
|
157
|
+
}
|
|
158
|
+
if (typeof plugin.install !== "function") {
|
|
159
|
+
throw new TypeError("Plugin install must be a function");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function validateRiskLevels(levels) {
|
|
163
|
+
if (levels.length === 0) {
|
|
164
|
+
throw new TypeError("Risk levels must contain at least one threshold");
|
|
165
|
+
}
|
|
166
|
+
for (const threshold of levels) {
|
|
167
|
+
if (typeof threshold.level !== "string" || threshold.level.trim().length === 0) {
|
|
168
|
+
throw new TypeError("Risk level label must be a non-empty string");
|
|
169
|
+
}
|
|
170
|
+
if (typeof threshold.max !== "number" || !Number.isFinite(threshold.max) && threshold.max !== Infinity) {
|
|
171
|
+
throw new TypeError("Risk level max must be a finite number or Infinity");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
79
175
|
function generateId() {
|
|
80
176
|
return randomUUID();
|
|
81
177
|
}
|
|
@@ -87,11 +183,15 @@ var SignalStore = class {
|
|
|
87
183
|
* Set a signal value. Overwrites any existing value for the key.
|
|
88
184
|
*/
|
|
89
185
|
set(key, value) {
|
|
186
|
+
validateSignalKey(key);
|
|
90
187
|
if (!validateSignalValue(value)) {
|
|
91
188
|
throw new TypeError(
|
|
92
189
|
`Invalid signal value for "${key}": signals must be string, number, boolean, or null`
|
|
93
190
|
);
|
|
94
191
|
}
|
|
192
|
+
if (!this.signals.has(key) && this.signals.size >= MAX_SIGNALS) {
|
|
193
|
+
throw new RangeError(`Cannot exceed maximum of ${MAX_SIGNALS} signals`);
|
|
194
|
+
}
|
|
95
195
|
this.signals.set(key, value);
|
|
96
196
|
return this;
|
|
97
197
|
}
|
|
@@ -111,7 +211,7 @@ var SignalStore = class {
|
|
|
111
211
|
* Returns a frozen snapshot of all signals.
|
|
112
212
|
*/
|
|
113
213
|
getAll() {
|
|
114
|
-
const snapshot =
|
|
214
|
+
const snapshot = /* @__PURE__ */ Object.create(null);
|
|
115
215
|
for (const [key, value] of this.signals) {
|
|
116
216
|
snapshot[key] = value;
|
|
117
217
|
}
|
|
@@ -138,6 +238,9 @@ var RiskEngine = class {
|
|
|
138
238
|
* Register a rule for evaluation.
|
|
139
239
|
*/
|
|
140
240
|
addRule(rule) {
|
|
241
|
+
if (this.rules.length >= MAX_RULES) {
|
|
242
|
+
throw new RangeError(`Cannot exceed maximum of ${MAX_RULES} rules`);
|
|
243
|
+
}
|
|
141
244
|
this.rules.push(rule);
|
|
142
245
|
}
|
|
143
246
|
/**
|
|
@@ -163,6 +266,7 @@ var RuleBuilder = class {
|
|
|
163
266
|
* Create a new rule from input configuration.
|
|
164
267
|
*/
|
|
165
268
|
static create(input) {
|
|
269
|
+
validateRuleInput(input);
|
|
166
270
|
const rule = {
|
|
167
271
|
id: generateId(),
|
|
168
272
|
name: input.name,
|
|
@@ -175,12 +279,62 @@ var RuleBuilder = class {
|
|
|
175
279
|
}
|
|
176
280
|
};
|
|
177
281
|
|
|
282
|
+
// src/plugins/PluginRegistry.ts
|
|
283
|
+
var PluginInstallError = class extends Error {
|
|
284
|
+
constructor(pluginName, cause) {
|
|
285
|
+
const message = cause instanceof Error ? cause.message : "Unknown plugin install error";
|
|
286
|
+
super(`Plugin "${pluginName}" failed to install: ${message}`);
|
|
287
|
+
this.name = "PluginInstallError";
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
var PluginAlreadyInstalledError = class extends Error {
|
|
291
|
+
constructor(pluginName) {
|
|
292
|
+
super(`Plugin "${pluginName}" is already installed`);
|
|
293
|
+
this.name = "PluginAlreadyInstalledError";
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
var PluginRegistry = class {
|
|
297
|
+
installed = /* @__PURE__ */ new Set();
|
|
298
|
+
/**
|
|
299
|
+
* Install a plugin on the given Guardian instance.
|
|
300
|
+
* Each plugin name may only be installed once per Guardian instance.
|
|
301
|
+
*/
|
|
302
|
+
install(plugin, guardian) {
|
|
303
|
+
validatePlugin(plugin);
|
|
304
|
+
if (this.installed.has(plugin.name)) {
|
|
305
|
+
throw new PluginAlreadyInstalledError(plugin.name);
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
plugin.install(guardian);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
throw new PluginInstallError(plugin.name, error);
|
|
311
|
+
}
|
|
312
|
+
this.installed.add(plugin.name);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Check if a plugin is installed by name.
|
|
316
|
+
*/
|
|
317
|
+
has(name) {
|
|
318
|
+
return this.installed.has(name);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Get names of all installed plugins in registration order.
|
|
322
|
+
*/
|
|
323
|
+
getInstalled() {
|
|
324
|
+
return [...this.installed];
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
178
328
|
// src/engine/Guardian.ts
|
|
179
329
|
var Guardian = class {
|
|
180
330
|
signalStore;
|
|
181
331
|
riskEngine;
|
|
332
|
+
pluginRegistry = new PluginRegistry();
|
|
182
333
|
constructor(config = {}) {
|
|
183
334
|
const thresholds = config.levels ?? DEFAULT_RISK_LEVELS;
|
|
335
|
+
if (config.levels !== void 0) {
|
|
336
|
+
validateRiskLevels(config.levels);
|
|
337
|
+
}
|
|
184
338
|
this.signalStore = new SignalStore();
|
|
185
339
|
const deps = {
|
|
186
340
|
signalStore: this.signalStore,
|
|
@@ -205,6 +359,19 @@ var Guardian = class {
|
|
|
205
359
|
this.riskEngine.addRule(rule);
|
|
206
360
|
return this;
|
|
207
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Install a plugin. Each plugin name may only be registered once.
|
|
364
|
+
*/
|
|
365
|
+
use(plugin) {
|
|
366
|
+
this.pluginRegistry.install(plugin, this);
|
|
367
|
+
return this;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Returns names of installed plugins.
|
|
371
|
+
*/
|
|
372
|
+
getInstalledPlugins() {
|
|
373
|
+
return this.pluginRegistry.getInstalled();
|
|
374
|
+
}
|
|
208
375
|
/**
|
|
209
376
|
* Run risk analysis and return an immutable report.
|
|
210
377
|
*/
|
|
@@ -212,7 +379,7 @@ var Guardian = class {
|
|
|
212
379
|
return this.riskEngine.analyze();
|
|
213
380
|
}
|
|
214
381
|
/**
|
|
215
|
-
* Clear all signals. Rules persist across resets.
|
|
382
|
+
* Clear all signals. Rules and installed plugins persist across resets.
|
|
216
383
|
*/
|
|
217
384
|
reset() {
|
|
218
385
|
this.signalStore.clear();
|
|
@@ -220,6 +387,4 @@ var Guardian = class {
|
|
|
220
387
|
}
|
|
221
388
|
};
|
|
222
389
|
|
|
223
|
-
export { DEFAULT_RISK_LEVELS, Guardian, RiskEngine, RuleBuilder, RuleEvaluator, ScoreCalculator, SignalStore, resolveLevel };
|
|
224
|
-
//# sourceMappingURL=index.js.map
|
|
225
|
-
//# sourceMappingURL=index.js.map
|
|
390
|
+
export { DEFAULT_RISK_LEVELS, Guardian, PluginAlreadyInstalledError, PluginInstallError, PluginRegistry, RiskEngine, RuleBuilder, RuleEvaluator, ScoreCalculator, SignalStore, resolveLevel };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardian-risk",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Configurable risk decision engine using signals, rules, and scoring",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Configurable risk decision engine using signals, rules, and scoring. Zero runtime dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -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
|
],
|
|
@@ -28,6 +31,7 @@
|
|
|
28
31
|
"node": ">=20"
|
|
29
32
|
},
|
|
30
33
|
"keywords": [
|
|
34
|
+
"guardian-risk",
|
|
31
35
|
"risk",
|
|
32
36
|
"risk-scoring",
|
|
33
37
|
"rules-engine",
|
|
@@ -36,14 +40,27 @@
|
|
|
36
40
|
"security",
|
|
37
41
|
"typescript"
|
|
38
42
|
],
|
|
43
|
+
"author": "himanshuusinghh",
|
|
39
44
|
"license": "MIT",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/himanshu6306singh/guardian-risk.git",
|
|
48
|
+
"directory": "packages/core"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/himanshu6306singh/guardian-risk#readme",
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/himanshu6306singh/guardian-risk/issues"
|
|
53
|
+
},
|
|
54
|
+
"security": "https://github.com/himanshu6306singh/guardian-risk/security/policy",
|
|
55
|
+
"funding": "https://github.com/sponsors/himanshu6306singh",
|
|
40
56
|
"publishConfig": {
|
|
41
57
|
"access": "public"
|
|
42
58
|
},
|
|
43
59
|
"devDependencies": {
|
|
60
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
44
61
|
"tsup": "^8.3.5",
|
|
45
62
|
"typescript": "^5.7.2",
|
|
46
|
-
"vitest": "^
|
|
63
|
+
"vitest": "^4.1.9"
|
|
47
64
|
},
|
|
48
65
|
"scripts": {
|
|
49
66
|
"build": "tsup",
|
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/constants/defaults.ts","../src/utils/resolveLevel.ts","../src/report/Report.ts","../src/rules/RuleEvaluator.ts","../src/score/ScoreCalculator.ts","../src/utils/validation.ts","../src/signals/SignalStore.ts","../src/engine/RiskEngine.ts","../src/rules/RuleBuilder.ts","../src/engine/Guardian.ts"],"names":["randomUUID"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { RiskLevelThreshold } from '../types/report.js';\n\n/** Default risk level thresholds used when none are configured. */\nexport const DEFAULT_RISK_LEVELS: readonly RiskLevelThreshold[] = [\n { max: 20, level: 'LOW' },\n { max: 40, level: 'MEDIUM' },\n { max: 60, level: 'HIGH' },\n { max: Infinity, level: 'CRITICAL' },\n] as const;\n","import type { RiskLevelThreshold } from '../types/report.js';\nimport { DEFAULT_RISK_LEVELS } from '../constants/defaults.js';\n\n/**\n * Resolves a risk level label from a score using configured thresholds.\n * Thresholds are evaluated in order; the first threshold where score <= max wins.\n */\nexport function resolveLevel(\n score: number,\n thresholds: readonly RiskLevelThreshold[] = DEFAULT_RISK_LEVELS,\n): string {\n for (const threshold of thresholds) {\n if (score <= threshold.max) {\n return threshold.level;\n }\n }\n\n const last = thresholds[thresholds.length - 1];\n return last?.level ?? 'UNKNOWN';\n}\n","import type { MatchedRule } from '../types/rules.js';\nimport type { RiskReport } from '../types/report.js';\nimport { resolveLevel } from '../utils/resolveLevel.js';\nimport type { RiskLevelThreshold } from '../types/report.js';\n\n/**\n * Builds immutable risk reports.\n */\nexport class ReportBuilder {\n /**\n * Build a frozen risk report from evaluation results.\n */\n build(\n score: number,\n matchedRules: readonly MatchedRule[],\n thresholds: readonly RiskLevelThreshold[],\n analyzedAt: string = new Date().toISOString(),\n ): RiskReport {\n const reasons = matchedRules.map((rule) => rule.reason);\n const level = resolveLevel(score, thresholds);\n\n const report: RiskReport = {\n score,\n level,\n reasons: Object.freeze([...reasons]),\n matchedRules: Object.freeze([...matchedRules]),\n analyzedAt,\n };\n\n return Object.freeze(report);\n }\n}\n","import type { MatchedRule, Rule } from '../types/rules.js';\nimport type { SignalMap } from '../types/signals.js';\n\n/**\n * Evaluates rules against a signal map and returns matched rules.\n */\nexport class RuleEvaluator {\n /**\n * Evaluate all rules against the given signals.\n * Rules are evaluated in registration order (exhaustive, no short-circuit).\n */\n evaluate<TSignals extends SignalMap>(\n rules: readonly Rule<TSignals>[],\n signals: TSignals,\n ): MatchedRule[] {\n const matched: MatchedRule[] = [];\n\n for (const rule of rules) {\n if (rule.when(signals)) {\n matched.push({\n id: rule.id,\n name: rule.name,\n score: rule.score,\n reason: rule.reason ?? rule.name,\n });\n }\n }\n\n return matched;\n }\n}\n","import type { MatchedRule } from '../types/rules.js';\n\n/**\n * Calculates risk score from matched rules.\n */\nexport class ScoreCalculator {\n /**\n * Sum scores from all matched rules.\n */\n calculate(matchedRules: readonly MatchedRule[]): number {\n return matchedRules.reduce((total, rule) => total + rule.score, 0);\n }\n}\n","import { randomUUID } from 'node:crypto';\nimport type { SignalValue } from '../types/signals.js';\n\n/**\n * Validates that a value is an allowed signal primitive.\n * Signals must be string, number, boolean, or null — not objects or arrays.\n */\nexport function validateSignalValue(value: unknown): value is SignalValue {\n if (value === null) {\n return true;\n }\n\n const type = typeof value;\n return type === 'string' || type === 'number' || type === 'boolean';\n}\n\n/**\n * Generates a unique identifier for rules.\n */\nexport function generateId(): string {\n return randomUUID();\n}\n","import type { SignalMap, SignalValue } from '../types/signals.js';\nimport { validateSignalValue } from '../utils/validation.js';\n\n/**\n * Stores and retrieves signal values for risk evaluation.\n */\nexport class SignalStore {\n private readonly signals = new Map<string, SignalValue>();\n\n /**\n * Set a signal value. Overwrites any existing value for the key.\n */\n set(key: string, value: SignalValue): this {\n if (!validateSignalValue(value)) {\n throw new TypeError(\n `Invalid signal value for \"${key}\": signals must be string, number, boolean, or null`,\n );\n }\n\n this.signals.set(key, value);\n return this;\n }\n\n /**\n * Get a signal value by key.\n */\n get(key: string): SignalValue | undefined {\n return this.signals.get(key);\n }\n\n /**\n * Check if a signal exists.\n */\n has(key: string): boolean {\n return this.signals.has(key);\n }\n\n /**\n * Returns a frozen snapshot of all signals.\n */\n getAll(): SignalMap {\n const snapshot: Record<string, SignalValue> = {};\n for (const [key, value] of this.signals) {\n snapshot[key] = value;\n }\n return Object.freeze(snapshot);\n }\n\n /**\n * Clear all signals.\n */\n clear(): void {\n this.signals.clear();\n }\n}\n","import { DEFAULT_RISK_LEVELS } from '../constants/defaults.js';\nimport { ReportBuilder } from '../report/Report.js';\nimport { RuleEvaluator } from '../rules/RuleEvaluator.js';\nimport { ScoreCalculator } from '../score/ScoreCalculator.js';\nimport { SignalStore } from '../signals/SignalStore.js';\nimport type { Rule } from '../types/rules.js';\nimport type { RiskReport, RiskLevelThreshold } from '../types/report.js';\nimport type { SignalMap } from '../types/signals.js';\n\n/** Dependencies injected into RiskEngine. */\nexport interface RiskEngineDependencies {\n readonly signalStore: SignalStore;\n readonly ruleEvaluator: RuleEvaluator;\n readonly scoreCalculator: ScoreCalculator;\n readonly reportBuilder: ReportBuilder;\n}\n\n/**\n * Orchestrates signal evaluation, scoring, and report generation.\n */\nexport class RiskEngine {\n private readonly rules: Rule<SignalMap>[] = [];\n private readonly thresholds: readonly RiskLevelThreshold[];\n\n constructor(\n private readonly deps: RiskEngineDependencies,\n thresholds: readonly RiskLevelThreshold[] = DEFAULT_RISK_LEVELS,\n ) {\n this.thresholds = thresholds;\n }\n\n /**\n * Register a rule for evaluation.\n */\n addRule(rule: Rule<SignalMap>): void {\n this.rules.push(rule);\n }\n\n /**\n * Get all registered rules.\n */\n getRules(): readonly Rule<SignalMap>[] {\n return this.rules;\n }\n\n /**\n * Run the full risk analysis pipeline.\n */\n analyze(): RiskReport {\n const signals = this.deps.signalStore.getAll();\n const matchedRules = this.deps.ruleEvaluator.evaluate(this.rules, signals);\n const score = this.deps.scoreCalculator.calculate(matchedRules);\n\n return this.deps.reportBuilder.build(score, matchedRules, this.thresholds);\n }\n}\n","import type { CreateRuleInput, Rule } from '../types/rules.js';\nimport type { SignalMap } from '../types/signals.js';\nimport { generateId } from '../utils/validation.js';\n\n/**\n * Builds immutable rule definitions with auto-generated IDs.\n */\nexport class RuleBuilder {\n /**\n * Create a new rule from input configuration.\n */\n static create<TSignals extends SignalMap = SignalMap>(\n input: CreateRuleInput<TSignals>,\n ): Rule<TSignals> {\n const rule: Rule<TSignals> = {\n id: generateId(),\n name: input.name,\n score: input.score,\n when: input.when,\n ...(input.description !== undefined ? { description: input.description } : {}),\n ...(input.reason !== undefined ? { reason: input.reason } : {}),\n };\n\n return rule;\n }\n}\n","import { DEFAULT_RISK_LEVELS } from '../constants/defaults.js';\nimport { RiskEngine, type RiskEngineDependencies } from './RiskEngine.js';\nimport { ReportBuilder } from '../report/Report.js';\nimport { RuleBuilder } from '../rules/RuleBuilder.js';\nimport { RuleEvaluator } from '../rules/RuleEvaluator.js';\nimport { ScoreCalculator } from '../score/ScoreCalculator.js';\nimport { SignalStore } from '../signals/SignalStore.js';\nimport type { CreateRuleInput } from '../types/rules.js';\nimport type { GuardianConfig, RiskReport } from '../types/report.js';\nimport type { SignalValue } from '../types/signals.js';\n\n/**\n * Fluent public API for risk analysis.\n * Collects signals and rules, then produces an immutable report.\n */\nexport class Guardian {\n private readonly signalStore: SignalStore;\n private readonly riskEngine: RiskEngine;\n\n constructor(config: GuardianConfig = {}) {\n const thresholds = config.levels ?? DEFAULT_RISK_LEVELS;\n this.signalStore = new SignalStore();\n\n const deps: RiskEngineDependencies = {\n signalStore: this.signalStore,\n ruleEvaluator: new RuleEvaluator(),\n scoreCalculator: new ScoreCalculator(),\n reportBuilder: new ReportBuilder(),\n };\n\n this.riskEngine = new RiskEngine(deps, thresholds);\n }\n\n /**\n * Add a signal value for risk evaluation.\n */\n signal(key: string, value: SignalValue): this {\n this.signalStore.set(key, value);\n return this;\n }\n\n /**\n * Register a rule. ID is auto-generated.\n */\n rule(input: CreateRuleInput): this {\n const rule = RuleBuilder.create(input);\n this.riskEngine.addRule(rule);\n return this;\n }\n\n /**\n * Run risk analysis and return an immutable report.\n */\n analyze(): RiskReport {\n return this.riskEngine.analyze();\n }\n\n /**\n * Clear all signals. Rules persist across resets.\n */\n reset(): this {\n this.signalStore.clear();\n return this;\n }\n}\n"]}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/constants/defaults.ts","../src/utils/resolveLevel.ts","../src/report/Report.ts","../src/rules/RuleEvaluator.ts","../src/score/ScoreCalculator.ts","../src/utils/validation.ts","../src/signals/SignalStore.ts","../src/engine/RiskEngine.ts","../src/rules/RuleBuilder.ts","../src/engine/Guardian.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { RiskLevelThreshold } from '../types/report.js';\n\n/** Default risk level thresholds used when none are configured. */\nexport const DEFAULT_RISK_LEVELS: readonly RiskLevelThreshold[] = [\n { max: 20, level: 'LOW' },\n { max: 40, level: 'MEDIUM' },\n { max: 60, level: 'HIGH' },\n { max: Infinity, level: 'CRITICAL' },\n] as const;\n","import type { RiskLevelThreshold } from '../types/report.js';\nimport { DEFAULT_RISK_LEVELS } from '../constants/defaults.js';\n\n/**\n * Resolves a risk level label from a score using configured thresholds.\n * Thresholds are evaluated in order; the first threshold where score <= max wins.\n */\nexport function resolveLevel(\n score: number,\n thresholds: readonly RiskLevelThreshold[] = DEFAULT_RISK_LEVELS,\n): string {\n for (const threshold of thresholds) {\n if (score <= threshold.max) {\n return threshold.level;\n }\n }\n\n const last = thresholds[thresholds.length - 1];\n return last?.level ?? 'UNKNOWN';\n}\n","import type { MatchedRule } from '../types/rules.js';\nimport type { RiskReport } from '../types/report.js';\nimport { resolveLevel } from '../utils/resolveLevel.js';\nimport type { RiskLevelThreshold } from '../types/report.js';\n\n/**\n * Builds immutable risk reports.\n */\nexport class ReportBuilder {\n /**\n * Build a frozen risk report from evaluation results.\n */\n build(\n score: number,\n matchedRules: readonly MatchedRule[],\n thresholds: readonly RiskLevelThreshold[],\n analyzedAt: string = new Date().toISOString(),\n ): RiskReport {\n const reasons = matchedRules.map((rule) => rule.reason);\n const level = resolveLevel(score, thresholds);\n\n const report: RiskReport = {\n score,\n level,\n reasons: Object.freeze([...reasons]),\n matchedRules: Object.freeze([...matchedRules]),\n analyzedAt,\n };\n\n return Object.freeze(report);\n }\n}\n","import type { MatchedRule, Rule } from '../types/rules.js';\nimport type { SignalMap } from '../types/signals.js';\n\n/**\n * Evaluates rules against a signal map and returns matched rules.\n */\nexport class RuleEvaluator {\n /**\n * Evaluate all rules against the given signals.\n * Rules are evaluated in registration order (exhaustive, no short-circuit).\n */\n evaluate<TSignals extends SignalMap>(\n rules: readonly Rule<TSignals>[],\n signals: TSignals,\n ): MatchedRule[] {\n const matched: MatchedRule[] = [];\n\n for (const rule of rules) {\n if (rule.when(signals)) {\n matched.push({\n id: rule.id,\n name: rule.name,\n score: rule.score,\n reason: rule.reason ?? rule.name,\n });\n }\n }\n\n return matched;\n }\n}\n","import type { MatchedRule } from '../types/rules.js';\n\n/**\n * Calculates risk score from matched rules.\n */\nexport class ScoreCalculator {\n /**\n * Sum scores from all matched rules.\n */\n calculate(matchedRules: readonly MatchedRule[]): number {\n return matchedRules.reduce((total, rule) => total + rule.score, 0);\n }\n}\n","import { randomUUID } from 'node:crypto';\nimport type { SignalValue } from '../types/signals.js';\n\n/**\n * Validates that a value is an allowed signal primitive.\n * Signals must be string, number, boolean, or null — not objects or arrays.\n */\nexport function validateSignalValue(value: unknown): value is SignalValue {\n if (value === null) {\n return true;\n }\n\n const type = typeof value;\n return type === 'string' || type === 'number' || type === 'boolean';\n}\n\n/**\n * Generates a unique identifier for rules.\n */\nexport function generateId(): string {\n return randomUUID();\n}\n","import type { SignalMap, SignalValue } from '../types/signals.js';\nimport { validateSignalValue } from '../utils/validation.js';\n\n/**\n * Stores and retrieves signal values for risk evaluation.\n */\nexport class SignalStore {\n private readonly signals = new Map<string, SignalValue>();\n\n /**\n * Set a signal value. Overwrites any existing value for the key.\n */\n set(key: string, value: SignalValue): this {\n if (!validateSignalValue(value)) {\n throw new TypeError(\n `Invalid signal value for \"${key}\": signals must be string, number, boolean, or null`,\n );\n }\n\n this.signals.set(key, value);\n return this;\n }\n\n /**\n * Get a signal value by key.\n */\n get(key: string): SignalValue | undefined {\n return this.signals.get(key);\n }\n\n /**\n * Check if a signal exists.\n */\n has(key: string): boolean {\n return this.signals.has(key);\n }\n\n /**\n * Returns a frozen snapshot of all signals.\n */\n getAll(): SignalMap {\n const snapshot: Record<string, SignalValue> = {};\n for (const [key, value] of this.signals) {\n snapshot[key] = value;\n }\n return Object.freeze(snapshot);\n }\n\n /**\n * Clear all signals.\n */\n clear(): void {\n this.signals.clear();\n }\n}\n","import { DEFAULT_RISK_LEVELS } from '../constants/defaults.js';\nimport { ReportBuilder } from '../report/Report.js';\nimport { RuleEvaluator } from '../rules/RuleEvaluator.js';\nimport { ScoreCalculator } from '../score/ScoreCalculator.js';\nimport { SignalStore } from '../signals/SignalStore.js';\nimport type { Rule } from '../types/rules.js';\nimport type { RiskReport, RiskLevelThreshold } from '../types/report.js';\nimport type { SignalMap } from '../types/signals.js';\n\n/** Dependencies injected into RiskEngine. */\nexport interface RiskEngineDependencies {\n readonly signalStore: SignalStore;\n readonly ruleEvaluator: RuleEvaluator;\n readonly scoreCalculator: ScoreCalculator;\n readonly reportBuilder: ReportBuilder;\n}\n\n/**\n * Orchestrates signal evaluation, scoring, and report generation.\n */\nexport class RiskEngine {\n private readonly rules: Rule<SignalMap>[] = [];\n private readonly thresholds: readonly RiskLevelThreshold[];\n\n constructor(\n private readonly deps: RiskEngineDependencies,\n thresholds: readonly RiskLevelThreshold[] = DEFAULT_RISK_LEVELS,\n ) {\n this.thresholds = thresholds;\n }\n\n /**\n * Register a rule for evaluation.\n */\n addRule(rule: Rule<SignalMap>): void {\n this.rules.push(rule);\n }\n\n /**\n * Get all registered rules.\n */\n getRules(): readonly Rule<SignalMap>[] {\n return this.rules;\n }\n\n /**\n * Run the full risk analysis pipeline.\n */\n analyze(): RiskReport {\n const signals = this.deps.signalStore.getAll();\n const matchedRules = this.deps.ruleEvaluator.evaluate(this.rules, signals);\n const score = this.deps.scoreCalculator.calculate(matchedRules);\n\n return this.deps.reportBuilder.build(score, matchedRules, this.thresholds);\n }\n}\n","import type { CreateRuleInput, Rule } from '../types/rules.js';\nimport type { SignalMap } from '../types/signals.js';\nimport { generateId } from '../utils/validation.js';\n\n/**\n * Builds immutable rule definitions with auto-generated IDs.\n */\nexport class RuleBuilder {\n /**\n * Create a new rule from input configuration.\n */\n static create<TSignals extends SignalMap = SignalMap>(\n input: CreateRuleInput<TSignals>,\n ): Rule<TSignals> {\n const rule: Rule<TSignals> = {\n id: generateId(),\n name: input.name,\n score: input.score,\n when: input.when,\n ...(input.description !== undefined ? { description: input.description } : {}),\n ...(input.reason !== undefined ? { reason: input.reason } : {}),\n };\n\n return rule;\n }\n}\n","import { DEFAULT_RISK_LEVELS } from '../constants/defaults.js';\nimport { RiskEngine, type RiskEngineDependencies } from './RiskEngine.js';\nimport { ReportBuilder } from '../report/Report.js';\nimport { RuleBuilder } from '../rules/RuleBuilder.js';\nimport { RuleEvaluator } from '../rules/RuleEvaluator.js';\nimport { ScoreCalculator } from '../score/ScoreCalculator.js';\nimport { SignalStore } from '../signals/SignalStore.js';\nimport type { CreateRuleInput } from '../types/rules.js';\nimport type { GuardianConfig, RiskReport } from '../types/report.js';\nimport type { SignalValue } from '../types/signals.js';\n\n/**\n * Fluent public API for risk analysis.\n * Collects signals and rules, then produces an immutable report.\n */\nexport class Guardian {\n private readonly signalStore: SignalStore;\n private readonly riskEngine: RiskEngine;\n\n constructor(config: GuardianConfig = {}) {\n const thresholds = config.levels ?? DEFAULT_RISK_LEVELS;\n this.signalStore = new SignalStore();\n\n const deps: RiskEngineDependencies = {\n signalStore: this.signalStore,\n ruleEvaluator: new RuleEvaluator(),\n scoreCalculator: new ScoreCalculator(),\n reportBuilder: new ReportBuilder(),\n };\n\n this.riskEngine = new RiskEngine(deps, thresholds);\n }\n\n /**\n * Add a signal value for risk evaluation.\n */\n signal(key: string, value: SignalValue): this {\n this.signalStore.set(key, value);\n return this;\n }\n\n /**\n * Register a rule. ID is auto-generated.\n */\n rule(input: CreateRuleInput): this {\n const rule = RuleBuilder.create(input);\n this.riskEngine.addRule(rule);\n return this;\n }\n\n /**\n * Run risk analysis and return an immutable report.\n */\n analyze(): RiskReport {\n return this.riskEngine.analyze();\n }\n\n /**\n * Clear all signals. Rules persist across resets.\n */\n reset(): this {\n this.signalStore.clear();\n return this;\n }\n}\n"]}
|