guardian-risk 0.2.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 +50 -3
- package/dist/index.cjs +125 -7
- package/dist/index.d.cts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +125 -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,7 +34,41 @@ console.log(report.score); // 35
|
|
|
36
34
|
console.log(report.level); // MEDIUM
|
|
37
35
|
```
|
|
38
36
|
|
|
39
|
-
##
|
|
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
|
|
40
72
|
|
|
41
73
|
```typescript
|
|
42
74
|
import type { Plugin } from 'guardian-risk';
|
|
@@ -62,6 +94,21 @@ new Guardian().use(myPlugin);
|
|
|
62
94
|
| `guardian.reset()` | Clear signals (rules + plugins persist) |
|
|
63
95
|
| `guardian.getInstalledPlugins()` | List installed plugin names |
|
|
64
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)
|
|
111
|
+
|
|
65
112
|
## License
|
|
66
113
|
|
|
67
114
|
MIT
|
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,
|
|
@@ -178,6 +282,13 @@ var RuleBuilder = class {
|
|
|
178
282
|
};
|
|
179
283
|
|
|
180
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
|
+
};
|
|
181
292
|
var PluginAlreadyInstalledError = class extends Error {
|
|
182
293
|
constructor(pluginName) {
|
|
183
294
|
super(`Plugin "${pluginName}" is already installed`);
|
|
@@ -191,10 +302,15 @@ var PluginRegistry = class {
|
|
|
191
302
|
* Each plugin name may only be installed once per Guardian instance.
|
|
192
303
|
*/
|
|
193
304
|
install(plugin, guardian) {
|
|
305
|
+
validatePlugin(plugin);
|
|
194
306
|
if (this.installed.has(plugin.name)) {
|
|
195
307
|
throw new PluginAlreadyInstalledError(plugin.name);
|
|
196
308
|
}
|
|
197
|
-
|
|
309
|
+
try {
|
|
310
|
+
plugin.install(guardian);
|
|
311
|
+
} catch (error) {
|
|
312
|
+
throw new PluginInstallError(plugin.name, error);
|
|
313
|
+
}
|
|
198
314
|
this.installed.add(plugin.name);
|
|
199
315
|
}
|
|
200
316
|
/**
|
|
@@ -218,6 +334,9 @@ var Guardian = class {
|
|
|
218
334
|
pluginRegistry = new PluginRegistry();
|
|
219
335
|
constructor(config = {}) {
|
|
220
336
|
const thresholds = config.levels ?? DEFAULT_RISK_LEVELS;
|
|
337
|
+
if (config.levels !== void 0) {
|
|
338
|
+
validateRiskLevels(config.levels);
|
|
339
|
+
}
|
|
221
340
|
this.signalStore = new SignalStore();
|
|
222
341
|
const deps = {
|
|
223
342
|
signalStore: this.signalStore,
|
|
@@ -273,6 +392,7 @@ var Guardian = class {
|
|
|
273
392
|
exports.DEFAULT_RISK_LEVELS = DEFAULT_RISK_LEVELS;
|
|
274
393
|
exports.Guardian = Guardian;
|
|
275
394
|
exports.PluginAlreadyInstalledError = PluginAlreadyInstalledError;
|
|
395
|
+
exports.PluginInstallError = PluginInstallError;
|
|
276
396
|
exports.PluginRegistry = PluginRegistry;
|
|
277
397
|
exports.RiskEngine = RiskEngine;
|
|
278
398
|
exports.RuleBuilder = RuleBuilder;
|
|
@@ -280,5 +400,3 @@ exports.RuleEvaluator = RuleEvaluator;
|
|
|
280
400
|
exports.ScoreCalculator = ScoreCalculator;
|
|
281
401
|
exports.SignalStore = SignalStore;
|
|
282
402
|
exports.resolveLevel = resolveLevel;
|
|
283
|
-
//# sourceMappingURL=index.cjs.map
|
|
284
|
-
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.cts
CHANGED
|
@@ -6,6 +6,8 @@ interface Plugin {
|
|
|
6
6
|
readonly name: string;
|
|
7
7
|
install(guardian: Guardian): void;
|
|
8
8
|
}
|
|
9
|
+
/** Public alias for {@link Plugin}. */
|
|
10
|
+
type GuardianPlugin = Plugin;
|
|
9
11
|
|
|
10
12
|
/** Primitive signal value types supported by Guardian. */
|
|
11
13
|
type SignalValue = string | number | boolean | null;
|
|
@@ -121,7 +123,7 @@ declare class RuleEvaluator {
|
|
|
121
123
|
*/
|
|
122
124
|
declare class ScoreCalculator {
|
|
123
125
|
/**
|
|
124
|
-
* Sum scores from all matched rules.
|
|
126
|
+
* Sum scores from all matched rules, clamped to a safe maximum.
|
|
125
127
|
*/
|
|
126
128
|
calculate(matchedRules: readonly MatchedRule[]): number;
|
|
127
129
|
}
|
|
@@ -182,6 +184,12 @@ declare class RiskEngine {
|
|
|
182
184
|
analyze(): RiskReport;
|
|
183
185
|
}
|
|
184
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
|
+
}
|
|
185
193
|
/**
|
|
186
194
|
* Thrown when attempting to register a plugin that is already installed.
|
|
187
195
|
*/
|
|
@@ -227,4 +235,4 @@ declare function resolveLevel(score: number, thresholds?: readonly RiskLevelThre
|
|
|
227
235
|
/** Default risk level thresholds used when none are configured. */
|
|
228
236
|
declare const DEFAULT_RISK_LEVELS: readonly RiskLevelThreshold[];
|
|
229
237
|
|
|
230
|
-
export { type CreateRuleInput, DEFAULT_RISK_LEVELS, Guardian, type GuardianConfig, type MatchedRule, type Plugin, PluginAlreadyInstalledError, PluginRegistry, 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
|
@@ -6,6 +6,8 @@ interface Plugin {
|
|
|
6
6
|
readonly name: string;
|
|
7
7
|
install(guardian: Guardian): void;
|
|
8
8
|
}
|
|
9
|
+
/** Public alias for {@link Plugin}. */
|
|
10
|
+
type GuardianPlugin = Plugin;
|
|
9
11
|
|
|
10
12
|
/** Primitive signal value types supported by Guardian. */
|
|
11
13
|
type SignalValue = string | number | boolean | null;
|
|
@@ -121,7 +123,7 @@ declare class RuleEvaluator {
|
|
|
121
123
|
*/
|
|
122
124
|
declare class ScoreCalculator {
|
|
123
125
|
/**
|
|
124
|
-
* Sum scores from all matched rules.
|
|
126
|
+
* Sum scores from all matched rules, clamped to a safe maximum.
|
|
125
127
|
*/
|
|
126
128
|
calculate(matchedRules: readonly MatchedRule[]): number;
|
|
127
129
|
}
|
|
@@ -182,6 +184,12 @@ declare class RiskEngine {
|
|
|
182
184
|
analyze(): RiskReport;
|
|
183
185
|
}
|
|
184
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
|
+
}
|
|
185
193
|
/**
|
|
186
194
|
* Thrown when attempting to register a plugin that is already installed.
|
|
187
195
|
*/
|
|
@@ -227,4 +235,4 @@ declare function resolveLevel(score: number, thresholds?: readonly RiskLevelThre
|
|
|
227
235
|
/** Default risk level thresholds used when none are configured. */
|
|
228
236
|
declare const DEFAULT_RISK_LEVELS: readonly RiskLevelThreshold[];
|
|
229
237
|
|
|
230
|
-
export { type CreateRuleInput, DEFAULT_RISK_LEVELS, Guardian, type GuardianConfig, type MatchedRule, type Plugin, PluginAlreadyInstalledError, PluginRegistry, 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,
|
|
@@ -176,6 +280,13 @@ var RuleBuilder = class {
|
|
|
176
280
|
};
|
|
177
281
|
|
|
178
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
|
+
};
|
|
179
290
|
var PluginAlreadyInstalledError = class extends Error {
|
|
180
291
|
constructor(pluginName) {
|
|
181
292
|
super(`Plugin "${pluginName}" is already installed`);
|
|
@@ -189,10 +300,15 @@ var PluginRegistry = class {
|
|
|
189
300
|
* Each plugin name may only be installed once per Guardian instance.
|
|
190
301
|
*/
|
|
191
302
|
install(plugin, guardian) {
|
|
303
|
+
validatePlugin(plugin);
|
|
192
304
|
if (this.installed.has(plugin.name)) {
|
|
193
305
|
throw new PluginAlreadyInstalledError(plugin.name);
|
|
194
306
|
}
|
|
195
|
-
|
|
307
|
+
try {
|
|
308
|
+
plugin.install(guardian);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
throw new PluginInstallError(plugin.name, error);
|
|
311
|
+
}
|
|
196
312
|
this.installed.add(plugin.name);
|
|
197
313
|
}
|
|
198
314
|
/**
|
|
@@ -216,6 +332,9 @@ var Guardian = class {
|
|
|
216
332
|
pluginRegistry = new PluginRegistry();
|
|
217
333
|
constructor(config = {}) {
|
|
218
334
|
const thresholds = config.levels ?? DEFAULT_RISK_LEVELS;
|
|
335
|
+
if (config.levels !== void 0) {
|
|
336
|
+
validateRiskLevels(config.levels);
|
|
337
|
+
}
|
|
219
338
|
this.signalStore = new SignalStore();
|
|
220
339
|
const deps = {
|
|
221
340
|
signalStore: this.signalStore,
|
|
@@ -268,6 +387,4 @@ var Guardian = class {
|
|
|
268
387
|
}
|
|
269
388
|
};
|
|
270
389
|
|
|
271
|
-
export { DEFAULT_RISK_LEVELS, Guardian, PluginAlreadyInstalledError, PluginRegistry, RiskEngine, RuleBuilder, RuleEvaluator, ScoreCalculator, SignalStore, resolveLevel };
|
|
272
|
-
//# sourceMappingURL=index.js.map
|
|
273
|
-
//# 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.2.
|
|
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/plugins/PluginRegistry.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 type { Guardian } from '../engine/Guardian.js';\nimport type { Plugin } from './Plugin.js';\n\n/**\n * Thrown when attempting to register a plugin that is already installed.\n */\nexport class PluginAlreadyInstalledError extends Error {\n constructor(pluginName: string) {\n super(`Plugin \"${pluginName}\" is already installed`);\n this.name = 'PluginAlreadyInstalledError';\n }\n}\n\n/**\n * Manages plugin lifecycle: register once, track installed plugins.\n */\nexport class PluginRegistry {\n private readonly installed = new Set<string>();\n\n /**\n * Install a plugin on the given Guardian instance.\n * Each plugin name may only be installed once per Guardian instance.\n */\n install(plugin: Plugin, guardian: Guardian): void {\n if (this.installed.has(plugin.name)) {\n throw new PluginAlreadyInstalledError(plugin.name);\n }\n\n plugin.install(guardian);\n this.installed.add(plugin.name);\n }\n\n /**\n * Check if a plugin is installed by name.\n */\n has(name: string): boolean {\n return this.installed.has(name);\n }\n\n /**\n * Get names of all installed plugins in registration order.\n */\n getInstalled(): readonly string[] {\n return [...this.installed];\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 { PluginRegistry } from '../plugins/PluginRegistry.js';\nimport type { Plugin } from '../plugins/Plugin.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 private readonly pluginRegistry = new PluginRegistry();\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 * Install a plugin. Each plugin name may only be registered once.\n */\n use(plugin: Plugin): this {\n this.pluginRegistry.install(plugin, this);\n return this;\n }\n\n /**\n * Returns names of installed plugins.\n */\n getInstalledPlugins(): readonly string[] {\n return this.pluginRegistry.getInstalled();\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 and installed plugins 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/plugins/PluginRegistry.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 type { Guardian } from '../engine/Guardian.js';\nimport type { Plugin } from './Plugin.js';\n\n/**\n * Thrown when attempting to register a plugin that is already installed.\n */\nexport class PluginAlreadyInstalledError extends Error {\n constructor(pluginName: string) {\n super(`Plugin \"${pluginName}\" is already installed`);\n this.name = 'PluginAlreadyInstalledError';\n }\n}\n\n/**\n * Manages plugin lifecycle: register once, track installed plugins.\n */\nexport class PluginRegistry {\n private readonly installed = new Set<string>();\n\n /**\n * Install a plugin on the given Guardian instance.\n * Each plugin name may only be installed once per Guardian instance.\n */\n install(plugin: Plugin, guardian: Guardian): void {\n if (this.installed.has(plugin.name)) {\n throw new PluginAlreadyInstalledError(plugin.name);\n }\n\n plugin.install(guardian);\n this.installed.add(plugin.name);\n }\n\n /**\n * Check if a plugin is installed by name.\n */\n has(name: string): boolean {\n return this.installed.has(name);\n }\n\n /**\n * Get names of all installed plugins in registration order.\n */\n getInstalled(): readonly string[] {\n return [...this.installed];\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 { PluginRegistry } from '../plugins/PluginRegistry.js';\nimport type { Plugin } from '../plugins/Plugin.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 private readonly pluginRegistry = new PluginRegistry();\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 * Install a plugin. Each plugin name may only be registered once.\n */\n use(plugin: Plugin): this {\n this.pluginRegistry.install(plugin, this);\n return this;\n }\n\n /**\n * Returns names of installed plugins.\n */\n getInstalledPlugins(): readonly string[] {\n return this.pluginRegistry.getInstalled();\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 and installed plugins persist across resets.\n */\n reset(): this {\n this.signalStore.clear();\n return this;\n }\n}\n"]}
|