@way_marks/server 0.9.0 → 1.0.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.
@@ -0,0 +1,420 @@
1
+ "use strict";
2
+ /**
3
+ * Policy Engine — Phase 4B
4
+ *
5
+ * Implements policy matching and evaluation for:
6
+ * - Compliance policies (HIPAA, SOC2, PCI-DSS, GDPR)
7
+ * - Operational policies (business hours, action limits)
8
+ * - Security policies (auth changes, patch downgrades)
9
+ * - Data policies (backup protection, schema migration)
10
+ *
11
+ * Evaluates whether actions violate any active policies
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.evaluatePolicies = evaluatePolicies;
15
+ exports.evaluatePolicy = evaluatePolicy;
16
+ exports.evaluateCondition = evaluateCondition;
17
+ exports.getDefaultCompliancePolicies = getDefaultCompliancePolicies;
18
+ exports.getDefaultOperationalPolicies = getDefaultOperationalPolicies;
19
+ exports.getDefaultSecurityPolicies = getDefaultSecurityPolicies;
20
+ exports.getDefaultDataPolicies = getDefaultDataPolicies;
21
+ exports.createPolicy = createPolicy;
22
+ /**
23
+ * Evaluate actions against all policies
24
+ */
25
+ function evaluatePolicies(session, actions, policies) {
26
+ const violations = [];
27
+ const log_entries = [];
28
+ // Filter enabled policies
29
+ const enabledPolicies = policies.filter(p => p.enabled);
30
+ for (const policy of enabledPolicies) {
31
+ const policyViolations = evaluatePolicy(session, actions, policy);
32
+ violations.push(...policyViolations);
33
+ if (policyViolations.length > 0) {
34
+ log_entries.push(`[${policy.category.toUpperCase()}] ${policy.name}: ${policyViolations.length} violation(s)`);
35
+ }
36
+ }
37
+ const has_blocks = violations.some(v => v.action === 'block');
38
+ const has_warnings = violations.some(v => v.severity === 'warning');
39
+ const requires_approval = violations.some(v => v.action === 'require_approval');
40
+ const requires_remediation = violations.some(v => v.action === 'require_remediation');
41
+ return {
42
+ violations,
43
+ has_blocks,
44
+ has_warnings,
45
+ requires_approval,
46
+ requires_remediation,
47
+ log_entries,
48
+ };
49
+ }
50
+ /**
51
+ * Evaluate actions against a single policy
52
+ */
53
+ function evaluatePolicy(session, actions, policy) {
54
+ const violations = [];
55
+ const timestamp = new Date().toISOString();
56
+ for (let ruleIndex = 0; ruleIndex < policy.rules.length; ruleIndex++) {
57
+ const rule = policy.rules[ruleIndex];
58
+ // Check if any action matches this rule
59
+ for (const action of actions) {
60
+ if (evaluateCondition(rule.condition, action, session)) {
61
+ violations.push({
62
+ policy_id: policy.policy_id,
63
+ policy_name: policy.name,
64
+ category: policy.category,
65
+ rule_index: ruleIndex,
66
+ action: rule.action,
67
+ severity: rule.severity,
68
+ message: rule.message,
69
+ violated_at: timestamp,
70
+ });
71
+ // Only record once per rule (not per action)
72
+ break;
73
+ }
74
+ }
75
+ }
76
+ return violations;
77
+ }
78
+ /**
79
+ * Evaluate a single condition against an action
80
+ */
81
+ function evaluateCondition(condition, action, session) {
82
+ const value = condition.value;
83
+ const op = condition.operator;
84
+ switch (condition.type) {
85
+ case 'operation_type':
86
+ return evaluateEquals(action.tool_name, value, op, condition.caseSensitive);
87
+ case 'tool_name':
88
+ return evaluateEquals(action.tool_name, value, op, condition.caseSensitive);
89
+ case 'file_pattern':
90
+ return evaluatePattern(action.target || '', value, op, condition.caseSensitive);
91
+ case 'action_count':
92
+ return evaluateNumeric(session.action_count, value, op);
93
+ case 'data_type':
94
+ return evaluateDataType(action, value);
95
+ case 'time_of_day':
96
+ return evaluateTimeOfDay(value);
97
+ case 'error_present':
98
+ return evaluateErrorPresent(action, op === 'equals' ? (value === 'true') : (value !== 'true'));
99
+ default:
100
+ return false;
101
+ }
102
+ }
103
+ /**
104
+ * Evaluate equality/contains conditions
105
+ */
106
+ function evaluateEquals(actual, expected, operator, caseSensitive) {
107
+ const a = caseSensitive ? actual : actual.toLowerCase();
108
+ const normalizeValue = (v) => {
109
+ if (Array.isArray(v)) {
110
+ return v.map(x => (caseSensitive ? String(x) : String(x).toLowerCase()));
111
+ }
112
+ return caseSensitive ? String(v) : String(v).toLowerCase();
113
+ };
114
+ const e = normalizeValue(expected);
115
+ if (operator === 'equals') {
116
+ if (Array.isArray(e)) {
117
+ return e.includes(a);
118
+ }
119
+ return a === e;
120
+ }
121
+ if (operator === 'contains') {
122
+ if (Array.isArray(e)) {
123
+ return e.some(x => a.includes(x));
124
+ }
125
+ return a.includes(String(e));
126
+ }
127
+ if (operator === 'in') {
128
+ if (Array.isArray(e)) {
129
+ return e.includes(a);
130
+ }
131
+ return a === String(e);
132
+ }
133
+ return false;
134
+ }
135
+ /**
136
+ * Evaluate pattern matching (file paths, regex)
137
+ */
138
+ function evaluatePattern(actual, pattern, operator, caseSensitive) {
139
+ const patternStr = Array.isArray(pattern) ? pattern[0] : String(pattern);
140
+ if (operator === 'matches_regex') {
141
+ const flags = caseSensitive ? '' : 'i';
142
+ try {
143
+ const regex = new RegExp(patternStr, flags);
144
+ return regex.test(actual);
145
+ }
146
+ catch {
147
+ return false;
148
+ }
149
+ }
150
+ if (operator === 'contains') {
151
+ const a = caseSensitive ? actual : actual.toLowerCase();
152
+ const p = caseSensitive ? patternStr : patternStr.toLowerCase();
153
+ return a.includes(p);
154
+ }
155
+ return false;
156
+ }
157
+ /**
158
+ * Evaluate numeric comparisons
159
+ */
160
+ function evaluateNumeric(actual, expected, operator) {
161
+ const expectedNum = typeof expected === 'number' ? expected : parseInt(String(expected), 10);
162
+ if (isNaN(expectedNum))
163
+ return false;
164
+ switch (operator) {
165
+ case 'equals':
166
+ return actual === expectedNum;
167
+ case 'greater_than':
168
+ return actual > expectedNum;
169
+ case 'less_than':
170
+ return actual < expectedNum;
171
+ default:
172
+ return false;
173
+ }
174
+ }
175
+ /**
176
+ * Evaluate data type conditions
177
+ */
178
+ function evaluateDataType(action, dataType) {
179
+ const typeStr = Array.isArray(dataType) ? dataType[0] : String(dataType).toLowerCase();
180
+ const target = (action.target || '').toLowerCase();
181
+ const typePatterns = {
182
+ sensitive_data: [
183
+ '/password',
184
+ '/api_key',
185
+ '/secret',
186
+ '/token',
187
+ '/credential',
188
+ '/pii',
189
+ '/users',
190
+ '/email',
191
+ '/ssn',
192
+ '/credit_card',
193
+ ],
194
+ database: ['.sql', '.db', '/migrations', '/schema', 'database'],
195
+ config: ['.env', 'config.json', 'secrets', '.key', '.pem'],
196
+ authentication: ['/auth', '/login', '/users', '/password', '/oauth'],
197
+ };
198
+ const patterns = typePatterns[typeStr] || [];
199
+ return patterns.some(p => target.includes(p));
200
+ }
201
+ /**
202
+ * Evaluate time of day conditions
203
+ */
204
+ function evaluateTimeOfDay(timeRange) {
205
+ const now = new Date();
206
+ const hour = now.getHours();
207
+ const dayOfWeek = now.getDay(); // 0 = Sunday, 6 = Saturday
208
+ // Supported formats: 'business_hours', 'off_hours', 'weekday', 'weekend', 'night'
209
+ switch (timeRange.toLowerCase()) {
210
+ case 'business_hours':
211
+ return hour >= 9 && hour < 17 && dayOfWeek >= 1 && dayOfWeek <= 5;
212
+ case 'off_hours':
213
+ return hour < 9 || hour >= 17 || dayOfWeek === 0 || dayOfWeek === 6;
214
+ case 'weekday':
215
+ return dayOfWeek >= 1 && dayOfWeek <= 5;
216
+ case 'weekend':
217
+ return dayOfWeek === 0 || dayOfWeek === 6;
218
+ case 'night':
219
+ return hour >= 22 || hour < 6;
220
+ default:
221
+ return false;
222
+ }
223
+ }
224
+ /**
225
+ * Evaluate error presence conditions
226
+ */
227
+ function evaluateErrorPresent(action, shouldHaveError) {
228
+ const hasError = action.status === 'error';
229
+ return hasError === shouldHaveError;
230
+ }
231
+ /**
232
+ * Get default compliance policies
233
+ */
234
+ function getDefaultCompliancePolicies() {
235
+ return [
236
+ {
237
+ policy_id: 'compliance-hipaa',
238
+ name: 'HIPAA Compliance',
239
+ description: 'Protect health information data',
240
+ category: 'compliance',
241
+ enabled: false, // Opt-in
242
+ created_at: new Date().toISOString(),
243
+ created_by: 'system',
244
+ rules: [
245
+ {
246
+ condition: {
247
+ type: 'data_type',
248
+ operator: 'equals',
249
+ value: 'sensitive_data',
250
+ },
251
+ action: 'require_approval',
252
+ severity: 'error',
253
+ message: 'HIPAA: Protected health information requires additional approval',
254
+ },
255
+ {
256
+ condition: {
257
+ type: 'operation_type',
258
+ operator: 'equals',
259
+ value: 'delete_file',
260
+ },
261
+ action: 'require_approval',
262
+ severity: 'error',
263
+ message: 'HIPAA: Deletion of records requires audit trail verification',
264
+ },
265
+ ],
266
+ },
267
+ {
268
+ policy_id: 'compliance-soc2',
269
+ name: 'SOC2 Compliance',
270
+ description: 'Ensure system security and availability',
271
+ category: 'compliance',
272
+ enabled: false, // Opt-in
273
+ created_at: new Date().toISOString(),
274
+ created_by: 'system',
275
+ rules: [
276
+ {
277
+ condition: {
278
+ type: 'time_of_day',
279
+ operator: 'equals',
280
+ value: 'off_hours',
281
+ },
282
+ action: 'require_approval',
283
+ severity: 'warning',
284
+ message: 'SOC2: Production changes outside business hours require approval',
285
+ },
286
+ {
287
+ condition: {
288
+ type: 'operation_type',
289
+ operator: 'equals',
290
+ value: 'bash',
291
+ },
292
+ action: 'require_approval',
293
+ severity: 'warning',
294
+ message: 'SOC2: System commands must be logged and approved',
295
+ },
296
+ ],
297
+ },
298
+ ];
299
+ }
300
+ /**
301
+ * Get default operational policies
302
+ */
303
+ function getDefaultOperationalPolicies() {
304
+ return [
305
+ {
306
+ policy_id: 'ops-scale-limit',
307
+ name: 'Rollback Scale Limit',
308
+ description: 'Prevent rollback of too many operations at once',
309
+ category: 'operational',
310
+ enabled: true,
311
+ created_at: new Date().toISOString(),
312
+ created_by: 'system',
313
+ rules: [
314
+ {
315
+ condition: {
316
+ type: 'action_count',
317
+ operator: 'greater_than',
318
+ value: 50,
319
+ },
320
+ action: 'require_remediation',
321
+ severity: 'warning',
322
+ message: 'Cannot rollback more than 50 actions at once; consider partial rollback',
323
+ },
324
+ {
325
+ condition: {
326
+ type: 'action_count',
327
+ operator: 'greater_than',
328
+ value: 100,
329
+ },
330
+ action: 'block',
331
+ severity: 'error',
332
+ message: 'Cannot rollback more than 100 actions; use staged rollback approach',
333
+ },
334
+ ],
335
+ },
336
+ ];
337
+ }
338
+ /**
339
+ * Get default security policies
340
+ */
341
+ function getDefaultSecurityPolicies() {
342
+ return [
343
+ {
344
+ policy_id: 'sec-auth-changes',
345
+ name: 'Authentication Changes Protection',
346
+ description: 'Require approval for authentication modifications',
347
+ category: 'security',
348
+ enabled: true,
349
+ created_at: new Date().toISOString(),
350
+ created_by: 'system',
351
+ rules: [
352
+ {
353
+ condition: {
354
+ type: 'file_pattern',
355
+ operator: 'matches_regex',
356
+ value: '/(auth|login|password|oauth|jwt)',
357
+ caseSensitive: false,
358
+ },
359
+ action: 'require_approval',
360
+ severity: 'error',
361
+ message: 'Security: Authentication changes require security team approval',
362
+ },
363
+ ],
364
+ },
365
+ ];
366
+ }
367
+ /**
368
+ * Get default data policies
369
+ */
370
+ function getDefaultDataPolicies() {
371
+ return [
372
+ {
373
+ policy_id: 'data-schema-changes',
374
+ name: 'Schema Change Protection',
375
+ description: 'Require DBA approval for database schema changes',
376
+ category: 'data',
377
+ enabled: true,
378
+ created_at: new Date().toISOString(),
379
+ created_by: 'system',
380
+ rules: [
381
+ {
382
+ condition: {
383
+ type: 'file_pattern',
384
+ operator: 'matches_regex',
385
+ value: '/(migrations|schema|\.sql)$',
386
+ caseSensitive: false,
387
+ },
388
+ action: 'require_approval',
389
+ severity: 'error',
390
+ message: 'Data: Database schema changes require DBA review',
391
+ },
392
+ ],
393
+ },
394
+ ];
395
+ }
396
+ /**
397
+ * Create a custom policy
398
+ */
399
+ function createPolicy(name, description, category, rules, createdBy = 'system') {
400
+ return {
401
+ policy_id: `policy-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
402
+ name,
403
+ description,
404
+ category,
405
+ rules,
406
+ enabled: true,
407
+ created_at: new Date().toISOString(),
408
+ created_by: createdBy,
409
+ };
410
+ }
411
+ exports.default = {
412
+ evaluatePolicies,
413
+ evaluatePolicy,
414
+ evaluateCondition,
415
+ getDefaultCompliancePolicies,
416
+ getDefaultOperationalPolicies,
417
+ getDefaultSecurityPolicies,
418
+ getDefaultDataPolicies,
419
+ createPolicy,
420
+ };