@way_marks/server 0.8.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.
- package/dist/api/server.js +598 -0
- package/dist/approval/manager.js +249 -0
- package/dist/approval/manager.test.js +315 -0
- package/dist/db/database.js +544 -1
- package/dist/escalation/manager.js +205 -0
- package/dist/escalation/manager.test.js +519 -0
- package/dist/notifications/service.js +457 -0
- package/dist/policy/engine.js +420 -0
- package/dist/remediation/recommender.js +309 -0
- package/dist/risk/analyzer.js +442 -0
- package/dist/risk/analyzer.test.js +482 -0
- package/dist/rollback/blocker.js +222 -0
- package/dist/rollback/manager.js +245 -0
- package/dist/rollback/manager.test.js +552 -0
- package/package.json +1 -1
- package/src/ui/index.html +862 -0
|
@@ -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
|
+
};
|