@sentriflow/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +86 -0
  3. package/package.json +60 -0
  4. package/src/constants.ts +77 -0
  5. package/src/engine/RuleExecutor.ts +256 -0
  6. package/src/engine/Runner.ts +312 -0
  7. package/src/engine/SandboxedExecutor.ts +208 -0
  8. package/src/errors.ts +88 -0
  9. package/src/helpers/arista/helpers.ts +1220 -0
  10. package/src/helpers/arista/index.ts +12 -0
  11. package/src/helpers/aruba/helpers.ts +637 -0
  12. package/src/helpers/aruba/index.ts +13 -0
  13. package/src/helpers/cisco/helpers.ts +534 -0
  14. package/src/helpers/cisco/index.ts +11 -0
  15. package/src/helpers/common/helpers.ts +265 -0
  16. package/src/helpers/common/index.ts +5 -0
  17. package/src/helpers/common/validation.ts +280 -0
  18. package/src/helpers/cumulus/helpers.ts +676 -0
  19. package/src/helpers/cumulus/index.ts +12 -0
  20. package/src/helpers/extreme/helpers.ts +422 -0
  21. package/src/helpers/extreme/index.ts +12 -0
  22. package/src/helpers/fortinet/helpers.ts +892 -0
  23. package/src/helpers/fortinet/index.ts +12 -0
  24. package/src/helpers/huawei/helpers.ts +790 -0
  25. package/src/helpers/huawei/index.ts +11 -0
  26. package/src/helpers/index.ts +53 -0
  27. package/src/helpers/juniper/helpers.ts +756 -0
  28. package/src/helpers/juniper/index.ts +12 -0
  29. package/src/helpers/mikrotik/helpers.ts +722 -0
  30. package/src/helpers/mikrotik/index.ts +12 -0
  31. package/src/helpers/nokia/helpers.ts +856 -0
  32. package/src/helpers/nokia/index.ts +11 -0
  33. package/src/helpers/paloalto/helpers.ts +939 -0
  34. package/src/helpers/paloalto/index.ts +12 -0
  35. package/src/helpers/vyos/helpers.ts +429 -0
  36. package/src/helpers/vyos/index.ts +12 -0
  37. package/src/index.ts +30 -0
  38. package/src/json-rules/ExpressionEvaluator.ts +292 -0
  39. package/src/json-rules/HelperRegistry.ts +177 -0
  40. package/src/json-rules/JsonRuleCompiler.ts +339 -0
  41. package/src/json-rules/JsonRuleValidator.ts +371 -0
  42. package/src/json-rules/index.ts +97 -0
  43. package/src/json-rules/schema.json +350 -0
  44. package/src/json-rules/types.ts +303 -0
  45. package/src/pack-loader/PackLoader.ts +332 -0
  46. package/src/pack-loader/index.ts +17 -0
  47. package/src/pack-loader/types.ts +135 -0
  48. package/src/parser/IncrementalParser.ts +527 -0
  49. package/src/parser/Sanitizer.ts +104 -0
  50. package/src/parser/SchemaAwareParser.ts +504 -0
  51. package/src/parser/VendorSchema.ts +72 -0
  52. package/src/parser/vendors/arista-eos.ts +206 -0
  53. package/src/parser/vendors/aruba-aoscx.ts +123 -0
  54. package/src/parser/vendors/aruba-aosswitch.ts +113 -0
  55. package/src/parser/vendors/aruba-wlc.ts +173 -0
  56. package/src/parser/vendors/cisco-ios.ts +110 -0
  57. package/src/parser/vendors/cisco-nxos.ts +107 -0
  58. package/src/parser/vendors/cumulus-linux.ts +161 -0
  59. package/src/parser/vendors/extreme-exos.ts +154 -0
  60. package/src/parser/vendors/extreme-voss.ts +167 -0
  61. package/src/parser/vendors/fortinet-fortigate.ts +217 -0
  62. package/src/parser/vendors/huawei-vrp.ts +192 -0
  63. package/src/parser/vendors/index.ts +1521 -0
  64. package/src/parser/vendors/juniper-junos.ts +230 -0
  65. package/src/parser/vendors/mikrotik-routeros.ts +274 -0
  66. package/src/parser/vendors/nokia-sros.ts +251 -0
  67. package/src/parser/vendors/paloalto-panos.ts +264 -0
  68. package/src/parser/vendors/vyos-vyos.ts +454 -0
  69. package/src/types/ConfigNode.ts +72 -0
  70. package/src/types/DeclarativeRule.ts +158 -0
  71. package/src/types/IRule.ts +270 -0
@@ -0,0 +1,292 @@
1
+ // packages/core/src/json-rules/ExpressionEvaluator.ts
2
+
3
+ /**
4
+ * Sandboxed Expression Evaluator for JSON Rules
5
+ *
6
+ * Evaluates simple JavaScript expressions in a secure sandbox.
7
+ * Provides access to helper functions while blocking dangerous operations.
8
+ */
9
+
10
+ import { createContext, Script, type Context as VMContext } from 'vm';
11
+ import type { ConfigNode } from '../types/ConfigNode';
12
+ import { type HelperRegistry, getHelperRegistry, VENDOR_NAMESPACES } from './HelperRegistry';
13
+
14
+ /** Maximum expression length to prevent DoS */
15
+ const MAX_EXPR_LENGTH = 1000;
16
+
17
+ /** Timeout for expression evaluation in milliseconds */
18
+ const EXPR_TIMEOUT_MS = 50;
19
+
20
+ /** Patterns that are blocked for security */
21
+ const BLOCKED_PATTERNS = [
22
+ 'require',
23
+ 'import',
24
+ 'eval',
25
+ 'Function',
26
+ 'process',
27
+ 'global',
28
+ 'globalThis',
29
+ 'window',
30
+ '__proto__',
31
+ 'constructor',
32
+ 'prototype',
33
+ 'Reflect',
34
+ 'Proxy',
35
+ 'module',
36
+ 'exports',
37
+ 'Buffer',
38
+ 'setTimeout',
39
+ 'setInterval',
40
+ 'setImmediate',
41
+ 'clearTimeout',
42
+ 'clearInterval',
43
+ 'clearImmediate',
44
+ 'fetch',
45
+ 'XMLHttpRequest',
46
+ 'WebSocket',
47
+ // Additional security patterns
48
+ 'arguments', // Special object in functions
49
+ 'this', // Context access
50
+ 'with', // Scope manipulation
51
+ ];
52
+
53
+ /**
54
+ * Pre-compiled script cache for performance.
55
+ */
56
+ const scriptCache = new Map<string, Script>();
57
+
58
+ /**
59
+ * Freeze an object deeply to prevent modification.
60
+ */
61
+ function deepFreeze<T>(obj: T): T {
62
+ if (obj === null || typeof obj !== 'object') {
63
+ return obj;
64
+ }
65
+
66
+ Object.freeze(obj);
67
+
68
+ for (const key of Object.keys(obj)) {
69
+ const value = (obj as Record<string, unknown>)[key];
70
+ if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
71
+ deepFreeze(value);
72
+ }
73
+ }
74
+
75
+ return obj;
76
+ }
77
+
78
+ /**
79
+ * Create a frozen, safe copy of a ConfigNode for sandbox use.
80
+ * Only exposes safe properties, no methods or circular references.
81
+ */
82
+ function createSafeNode(node: ConfigNode): Readonly<{
83
+ id: string;
84
+ type: string;
85
+ rawText: string;
86
+ params: readonly string[];
87
+ children: readonly ReturnType<typeof createSafeNode>[];
88
+ }> {
89
+ return deepFreeze({
90
+ id: node.id,
91
+ type: node.type,
92
+ rawText: node.rawText,
93
+ params: [...node.params],
94
+ children: node.children.map(createSafeNode),
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Validate an expression for security.
100
+ * Returns true if the expression is safe to evaluate.
101
+ */
102
+ export function isValidExpression(expr: string): boolean {
103
+ // Check length
104
+ if (expr.length > MAX_EXPR_LENGTH) {
105
+ return false;
106
+ }
107
+
108
+ // Check for blocked patterns
109
+ const exprLower = expr.toLowerCase();
110
+ for (const pattern of BLOCKED_PATTERNS) {
111
+ if (exprLower.includes(pattern.toLowerCase())) {
112
+ return false;
113
+ }
114
+ }
115
+
116
+ // Block template literals with expressions
117
+ if (expr.includes('${')) {
118
+ return false;
119
+ }
120
+
121
+ // Block assignment operators
122
+ if (/[^=!<>]=[^=]/.test(expr)) {
123
+ return false;
124
+ }
125
+
126
+ return true;
127
+ }
128
+
129
+ /**
130
+ * Expression Evaluator class for sandboxed expression evaluation.
131
+ * Provides pre-compilation and caching for performance.
132
+ */
133
+ export class ExpressionEvaluator {
134
+ private readonly sandbox: VMContext;
135
+ private readonly registry: HelperRegistry;
136
+
137
+ constructor(registry?: HelperRegistry) {
138
+ this.registry = registry ?? getHelperRegistry();
139
+ this.sandbox = this.createSandbox();
140
+ }
141
+
142
+ /**
143
+ * Create a sandboxed VM context with helpers available.
144
+ */
145
+ private createSandbox(): VMContext {
146
+ // Start with safe primitives
147
+ const sandboxObj: Record<string, unknown> = {
148
+ // Safe primitives
149
+ true: true,
150
+ false: false,
151
+ undefined: undefined,
152
+ null: null,
153
+ NaN: NaN,
154
+ Infinity: Infinity,
155
+
156
+ // Safe built-in constructors (frozen)
157
+ Boolean: Object.freeze(Boolean),
158
+ Number: Object.freeze(Number),
159
+ String: Object.freeze(String),
160
+ Array: Object.freeze(Array),
161
+ Object: Object.freeze(Object),
162
+ RegExp: Object.freeze(RegExp),
163
+ JSON: Object.freeze(JSON),
164
+ Math: Object.freeze(Math),
165
+
166
+ // Node placeholder (set per evaluation)
167
+ node: null,
168
+ };
169
+
170
+ // Add common helpers at top level
171
+ for (const [key, value] of Object.entries(this.registry)) {
172
+ if (typeof value === 'function') {
173
+ sandboxObj[key] = value;
174
+ }
175
+ }
176
+
177
+ // Add vendor namespaces
178
+ for (const namespace of VENDOR_NAMESPACES) {
179
+ const vendorHelpers = this.registry[namespace];
180
+ if (vendorHelpers && typeof vendorHelpers === 'object') {
181
+ sandboxObj[namespace] = Object.freeze({ ...vendorHelpers });
182
+ }
183
+ }
184
+
185
+ return createContext(Object.freeze(sandboxObj));
186
+ }
187
+
188
+ /**
189
+ * Pre-compile an expression for later evaluation.
190
+ * Call this at rule load time for performance.
191
+ *
192
+ * @param expr The expression to compile
193
+ * @returns true if compilation succeeded
194
+ */
195
+ precompile(expr: string): boolean {
196
+ if (!isValidExpression(expr)) {
197
+ return false;
198
+ }
199
+
200
+ if (scriptCache.has(expr)) {
201
+ return true;
202
+ }
203
+
204
+ try {
205
+ const script = new Script(`(${expr})`, {
206
+ filename: 'expr.js',
207
+ });
208
+ scriptCache.set(expr, script);
209
+ return true;
210
+ } catch {
211
+ return false;
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Evaluate an expression against a node.
217
+ *
218
+ * @param expr The expression to evaluate
219
+ * @param node The ConfigNode to evaluate against
220
+ * @returns The evaluation result as a boolean
221
+ */
222
+ evaluate(expr: string, node: ConfigNode): boolean {
223
+ // Validate expression
224
+ if (!isValidExpression(expr)) {
225
+ return false;
226
+ }
227
+
228
+ // Get or compile script
229
+ let script = scriptCache.get(expr);
230
+ if (!script) {
231
+ try {
232
+ script = new Script(`(${expr})`, {
233
+ filename: 'expr.js',
234
+ });
235
+ scriptCache.set(expr, script);
236
+ } catch {
237
+ return false; // Compilation error
238
+ }
239
+ }
240
+
241
+ // Set node in sandbox (create a new context each time for isolation)
242
+ const evalContext = createContext({
243
+ ...this.sandbox,
244
+ node: createSafeNode(node),
245
+ });
246
+
247
+ try {
248
+ const result = script.runInContext(evalContext, {
249
+ timeout: EXPR_TIMEOUT_MS,
250
+ });
251
+ return Boolean(result);
252
+ } catch {
253
+ return false; // Runtime error or timeout
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Clear the script cache (useful for testing).
259
+ */
260
+ static clearCache(): void {
261
+ scriptCache.clear();
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Create a new ExpressionEvaluator instance.
267
+ * The evaluator can be reused across multiple rule evaluations.
268
+ */
269
+ export function createExpressionEvaluator(registry?: HelperRegistry): ExpressionEvaluator {
270
+ return new ExpressionEvaluator(registry);
271
+ }
272
+
273
+ // Default singleton evaluator for convenience
274
+ let defaultEvaluator: ExpressionEvaluator | null = null;
275
+
276
+ /**
277
+ * Get the default expression evaluator (singleton).
278
+ */
279
+ export function getExpressionEvaluator(): ExpressionEvaluator {
280
+ if (!defaultEvaluator) {
281
+ defaultEvaluator = new ExpressionEvaluator();
282
+ }
283
+ return defaultEvaluator;
284
+ }
285
+
286
+ /**
287
+ * Clear the default evaluator (useful for testing).
288
+ */
289
+ export function clearExpressionEvaluator(): void {
290
+ defaultEvaluator = null;
291
+ ExpressionEvaluator.clearCache();
292
+ }
@@ -0,0 +1,177 @@
1
+ // packages/core/src/json-rules/HelperRegistry.ts
2
+
3
+ /**
4
+ * Helper Registry for JSON Rules
5
+ *
6
+ * Provides a registry of all helper functions available to JSON rules.
7
+ * Helpers are organized by namespace (vendor) for clarity.
8
+ * Vendor namespaces are derived dynamically from the helpers module.
9
+ */
10
+
11
+ import * as helpers from '../helpers';
12
+ import { VENDOR_NAMESPACES as HELPER_VENDOR_NAMESPACES, getAllVendorModules } from '../helpers';
13
+
14
+ /**
15
+ * Type representing any helper function.
16
+ */
17
+ export type HelperFunction = (...args: unknown[]) => unknown;
18
+
19
+ /**
20
+ * Vendor namespace containing helper functions.
21
+ */
22
+ export type VendorHelpers = Record<string, HelperFunction>;
23
+
24
+ /**
25
+ * Vendor namespaces for helper organization.
26
+ * Dynamically derived from the helpers module - single source of truth.
27
+ */
28
+ export const VENDOR_NAMESPACES = HELPER_VENDOR_NAMESPACES;
29
+
30
+ export type VendorNamespace = (typeof VENDOR_NAMESPACES)[number];
31
+
32
+ /**
33
+ * Complete helper registry with common helpers and vendor namespaces.
34
+ * Vendor properties are dynamically typed based on VendorNamespace.
35
+ */
36
+ export type HelperRegistry = {
37
+ // Common helpers (no namespace required)
38
+ [key: string]: HelperFunction | VendorHelpers;
39
+ } & {
40
+ // Vendor namespaces - dynamically typed from VendorNamespace
41
+ [K in VendorNamespace]: VendorHelpers;
42
+ };
43
+
44
+ /**
45
+ * Extract only function exports from a module object.
46
+ */
47
+ function extractFunctions(module: Record<string, unknown>): VendorHelpers {
48
+ const result: VendorHelpers = {};
49
+ for (const [key, value] of Object.entries(module)) {
50
+ if (typeof value === 'function') {
51
+ result[key] = value as HelperFunction;
52
+ }
53
+ }
54
+ return result;
55
+ }
56
+
57
+ /**
58
+ * Create a helper registry with all available helpers.
59
+ * Common helpers are available at the top level.
60
+ * Vendor-specific helpers are namespaced (e.g., "cisco.isTrunkPort").
61
+ */
62
+ export function createHelperRegistry(): HelperRegistry {
63
+ // Extract common helpers (these are re-exported at the top level)
64
+ const commonHelpers = extractFunctions(helpers as unknown as Record<string, unknown>);
65
+
66
+ // Get all vendor modules dynamically
67
+ const vendorModules = getAllVendorModules();
68
+
69
+ // Build registry with common helpers and vendor namespaces
70
+ const registry = { ...commonHelpers } as HelperRegistry;
71
+
72
+ // Add each vendor namespace dynamically
73
+ for (const namespace of VENDOR_NAMESPACES) {
74
+ const vendorModule = vendorModules[namespace];
75
+ if (vendorModule) {
76
+ registry[namespace] = extractFunctions(vendorModule as unknown as Record<string, unknown>);
77
+ }
78
+ }
79
+
80
+ return registry;
81
+ }
82
+
83
+ /**
84
+ * Resolve a helper function by name.
85
+ * Supports both flat names (e.g., "hasChildCommand") and
86
+ * namespaced names (e.g., "cisco.isTrunkPort").
87
+ *
88
+ * @param registry The helper registry
89
+ * @param helperName The helper name to resolve
90
+ * @returns The helper function, or undefined if not found
91
+ */
92
+ export function resolveHelper(
93
+ registry: HelperRegistry,
94
+ helperName: string
95
+ ): HelperFunction | undefined {
96
+ // Check for namespaced helper (e.g., "cisco.isTrunkPort")
97
+ if (helperName.includes('.')) {
98
+ const [namespace, name] = helperName.split('.', 2);
99
+ if (!namespace || !name) return undefined;
100
+
101
+ const vendorHelpers = registry[namespace];
102
+ if (vendorHelpers && typeof vendorHelpers === 'object') {
103
+ const helper = vendorHelpers[name];
104
+ return typeof helper === 'function' ? helper : undefined;
105
+ }
106
+ return undefined;
107
+ }
108
+
109
+ // Try common helpers at top level
110
+ const helper = registry[helperName];
111
+ if (typeof helper === 'function') {
112
+ return helper;
113
+ }
114
+
115
+ return undefined;
116
+ }
117
+
118
+ /**
119
+ * Get a list of all available helper names.
120
+ * Useful for validation and documentation.
121
+ *
122
+ * @param registry The helper registry
123
+ * @returns Array of helper names (both flat and namespaced)
124
+ */
125
+ export function getAvailableHelpers(registry: HelperRegistry): string[] {
126
+ const helpers: string[] = [];
127
+
128
+ // Add common helpers
129
+ for (const [key, value] of Object.entries(registry)) {
130
+ if (typeof value === 'function') {
131
+ helpers.push(key);
132
+ }
133
+ }
134
+
135
+ // Add namespaced helpers
136
+ for (const namespace of VENDOR_NAMESPACES) {
137
+ const vendorHelpers = registry[namespace];
138
+ if (vendorHelpers && typeof vendorHelpers === 'object') {
139
+ for (const key of Object.keys(vendorHelpers)) {
140
+ helpers.push(`${namespace}.${key}`);
141
+ }
142
+ }
143
+ }
144
+
145
+ return helpers.sort();
146
+ }
147
+
148
+ /**
149
+ * Check if a helper name exists in the registry.
150
+ *
151
+ * @param registry The helper registry
152
+ * @param helperName The helper name to check
153
+ * @returns true if the helper exists
154
+ */
155
+ export function hasHelper(registry: HelperRegistry, helperName: string): boolean {
156
+ return resolveHelper(registry, helperName) !== undefined;
157
+ }
158
+
159
+ // Singleton registry instance for performance
160
+ let cachedRegistry: HelperRegistry | null = null;
161
+
162
+ /**
163
+ * Get the global helper registry (cached for performance).
164
+ */
165
+ export function getHelperRegistry(): HelperRegistry {
166
+ if (!cachedRegistry) {
167
+ cachedRegistry = createHelperRegistry();
168
+ }
169
+ return cachedRegistry;
170
+ }
171
+
172
+ /**
173
+ * Clear the cached registry (useful for testing).
174
+ */
175
+ export function clearHelperRegistryCache(): void {
176
+ cachedRegistry = null;
177
+ }