x-risk-recommendation
v1.0.3
Published
A Risk Recommendation System built on nx-rules for analyzing data exposure risks and producing actionable recommendations
Maintainers
Readme
Risk Recommendation System
A production-grade recommendation system built on nx-rules for analyzing data exposure risks and producing actionable recommendations.
Overview
Organizations track data exposure risks through "risk counts"—aggregated metrics describing what sensitive information was found, how much, and its severity. This system determines whether risks can be automatically remediated via existing policies (like Microsoft Information Protection or Google Workspace labels) or require manual review.
┌─────────────────────────────────────────────────────────────────┐
│ Risk Recommendation System │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ Input │────▶│ nx-rules │────▶│ Output │ │
│ │RiskCount │ │ Engine │ │ Recommendation │ │
│ └──────────┘ └──────┬───────┘ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ RuleSet │ │
│ │ - route │ │
│ │ - actions │ │
│ │ - templates │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘Features
- 🎯 Simple Decision Logic - Two paths: policy (automated) or recommendation (manual)
- 📝 Template-Based Output - Configurable recommendation content
- 🔍 Full Traceability - Track exactly which rule matched and why
- 📊 JSON-Based Rules - Business logic as data, not code
- 🔧 Extensible - Add new templates and rules without code changes
Installation
npm installQuick Start
import { createRecommendationEngine, type RiskCount } from 'risk-recommendation-system';
// Create the engine
const engine = createRecommendationEngine();
// Evaluate a risk with an external label → Policy recommendation
const riskWithLabel: RiskCount = {
riskType: 'PII_EXPOSURE',
count: 15,
contentLabel: 'SSN',
externalLabel: 'Confidential', // Has label mapping
severity: 'high'
};
const result1 = await engine.evaluate(riskWithLabel);
console.log(result1.recommendation.action); // 'policy'
console.log(result1.recommendation.content);
// "Apply Confidential policy to automatically remediate 15 SSN exposure(s). Severity: high | Risk Type: PII_EXPOSURE"
// Evaluate a risk without an external label → Manual recommendation
const riskWithoutLabel: RiskCount = {
riskType: 'CREDENTIAL_LEAK',
count: 8,
contentLabel: 'API Key',
severity: 'critical'
// No externalLabel
};
const result2 = await engine.evaluate(riskWithoutLabel);
console.log(result2.recommendation.action); // 'recommendation'
console.log(result2.recommendation.content);
// "Review and manually label 8 API Key item(s) flagged as CREDENTIAL_LEAK. Severity: critical. No automated policy available—manual classification required."Data Structures
Input: RiskCount
interface RiskCount {
riskType: string; // Category of risk (e.g., "PII_EXPOSURE")
count: number; // Number of instances detected
contentLabel: string; // What information is at risk (e.g., "SSN")
externalLabel?: string; // Mapped MIP/Google label, if any
severity: 'low' | 'medium' | 'high' | 'critical';
}Output: Recommendation
interface Recommendation {
riskInput: RiskCount; // Original input (preserved)
action: 'policy' | 'recommendation';
content: string; // Human-readable recommendation
}Decision Logic
The system implements a simple routing decision:
IF risk has an external label mapped
THEN action = "policy"
AND content = policy remediation instructions
IF risk has no external label mapped
THEN action = "recommendation"
AND content = manual review instructionsRules
Two rules are configured:
1. Policy Path (Priority: 100)
Routes to automated remediation when externalLabel exists:
{
"ruleId": "risk-with-label-to-policy",
"priority": 100,
"route": {
"exists": { "path": "input.externalLabel" }
},
"actions": {
"onMatch": [{
"type": "transform",
"args": { "action": "policy", "template": "policy-recommendation" }
}]
}
}2. Recommendation Path (Priority: 90)
Routes to manual review when no externalLabel:
{
"ruleId": "risk-without-label-to-recommendation",
"priority": 90,
"route": {
"not": { "exists": { "path": "input.externalLabel" } }
},
"actions": {
"onMatch": [{
"type": "transform",
"args": { "action": "recommendation", "template": "manual-recommendation" }
}]
}
}Templates
Policy Recommendation
Apply {{externalLabel}} policy to automatically remediate {{count}} {{contentLabel}} exposure(s).
Severity: {{severity}} | Risk Type: {{riskType}}Manual Recommendation
Review and manually label {{count}} {{contentLabel}} item(s) flagged as {{riskType}}.
Severity: {{severity}}. No automated policy available—manual classification required.With Scope
const result = await engine.evaluate(risk, {
tenantId: 'tenant-123',
environment: 'production'
});
// Only rules matching this scope (or global rules) will be evaluatedConfiguration
const engine = createRecommendationEngine({
matchPolicy: 'FIRST_MATCH', // Stop after first rule matches
conflictPolicy: 'ERROR', // Only one rule should match
trace: true // Enable for audit trail
});Tracing
Every evaluation includes trace events for debugging and auditing:
const result = await engine.evaluate(risk);
if (result.trace) {
result.trace.forEach(event => {
console.log(event.type, event.ruleId, event.timestamp);
});
}Trace event types:
evaluation_start/evaluation_endroute_endcondition_endaction_start/action_endwriteerror
API Reference
createRecommendationEngine(config?)
Creates a new recommendation engine.
const engine = createRecommendationEngine({
matchPolicy?: 'FIRST_MATCH' | 'ALL_MATCH',
conflictPolicy?: 'FIRST_WRITE_WINS' | 'LAST_WRITE_WINS' | 'MERGE' | 'ERROR',
trace?: boolean
});engine.evaluate(riskCount)
Evaluates a risk and produces a recommendation.
const result = await engine.evaluate(riskCount);
// Returns: RecommendationResultengine.getRules()
Returns all configured rules.
engine.addRule(rule)
Adds a new rule to the engine.
evaluateRisk(riskCount)
Quick helper function for one-time evaluations.
import { evaluateRisk } from 'risk-recommendation-system';
const result = await evaluateRisk(riskCount);Testing
npm testDevelopment
# Run demo
npm run dev
# Build
npm run build
# Watch tests
npm run test:watchProject Structure
risk-recommendation-system/
├── src/
│ ├── index.ts # Main exports
│ ├── types.ts # TypeScript interfaces
│ ├── engine.ts # Rule engine implementation
│ ├── demo.ts # Demo script
│ ├── templates/
│ │ └── index.ts # Template registry
│ ├── functions/
│ │ └── render.ts # Render function
│ └── rules/
│ └── index.ts # Rule definitions
├── tests/
│ └── recommendation.test.ts
├── package.json
├── tsconfig.json
└── vitest.config.tsExtension Points
| To Add... | Configure... | |-----------|--------------| | New risk type | No change needed (generic) | | New severity level | No change needed (pass-through) | | New recommendation path | Add new rule with route condition | | New template | Register template, reference in rule action | | Custom content logic | Register new function, reference in action |
Error Handling
| Condition | Behavior | |-----------|----------| | No rule matches | Returns error: "No recommendation path for input" | | Template not found | Returns error: "Template {id} not registered" | | Missing required field | Validation error before evaluation |
License
MIT
Based On
- nx-rules - Production-grade rules engine for Node.js/TypeScript
