Implements comprehensive system for tracking governance framework false negatives: Backend: - src/models/MissedBreach.model.js - Schema with severity, cost tracking, miss reasons - src/controllers/missedBreach.controller.js - CRUD operations and statistics - src/routes/missedBreach.routes.js - Admin-only API endpoints - src/routes/index.js - Route integration at /api/admin/missed-breaches Functionality: - Report missed breaches with classification (NO_RULE_EXISTS, RULE_TOO_NARROW, etc.) - Track actual/estimated costs of missed violations - Calculate effectiveness rate: detected / (detected + missed) - Breakdown by miss reason with examples - Link to original audit logs where available Statistics: - Total missed breaches by severity - Average time to detection - Cost impact analysis - Effectiveness comparison vs audit logs Purpose: - Measure true framework detection rate (not just blocked actions) - Identify blind spots in governance rules - Calculate realistic cost avoidance (avoiding "framework theater") - Support research integrity claims with empirical data Related: Cross-environment audit sync (production metrics) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
346 lines
8.2 KiB
JavaScript
346 lines
8.2 KiB
JavaScript
/*
|
|
* Copyright 2025 John G Stroh
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/**
|
|
* MissedBreach Model
|
|
*
|
|
* Tracks incidents where the governance framework FAILED to catch a violation
|
|
* Critical for research integrity and framework improvement
|
|
*
|
|
* Purpose:
|
|
* - Measure framework effectiveness (true positive rate)
|
|
* - Identify blind spots in governance rules
|
|
* - Calculate realistic cost avoidance (detected vs missed)
|
|
* - Improve framework through pattern analysis
|
|
*/
|
|
|
|
const mongoose = require('mongoose');
|
|
|
|
const missedBreachSchema = new mongoose.Schema({
|
|
// Incident identification
|
|
title: {
|
|
type: String,
|
|
required: true,
|
|
description: 'Brief description of the missed breach'
|
|
},
|
|
|
|
incidentDate: {
|
|
type: Date,
|
|
required: true,
|
|
index: true,
|
|
description: 'When the breach occurred (not when it was discovered)'
|
|
},
|
|
|
|
discoveredDate: {
|
|
type: Date,
|
|
default: Date.now,
|
|
index: true,
|
|
description: 'When the breach was discovered'
|
|
},
|
|
|
|
// Severity and impact
|
|
severity: {
|
|
type: String,
|
|
enum: ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'],
|
|
required: true,
|
|
index: true,
|
|
description: 'Actual severity of the breach'
|
|
},
|
|
|
|
actualCost: {
|
|
type: Number,
|
|
default: null,
|
|
description: 'Actual cost incurred (if known), in dollars'
|
|
},
|
|
|
|
estimatedCost: {
|
|
type: Number,
|
|
default: null,
|
|
description: 'Estimated cost if actual unknown, in dollars'
|
|
},
|
|
|
|
// What happened
|
|
description: {
|
|
type: String,
|
|
required: true,
|
|
description: 'Detailed description of what went wrong'
|
|
},
|
|
|
|
activityType: {
|
|
type: String,
|
|
enum: [
|
|
'CLIENT_COMMUNICATION',
|
|
'CODE_GENERATION',
|
|
'DOCUMENTATION',
|
|
'DEPLOYMENT',
|
|
'COMPLIANCE_REVIEW',
|
|
'DATA_MANAGEMENT',
|
|
'INFRASTRUCTURE',
|
|
'OTHER'
|
|
],
|
|
required: true,
|
|
description: 'Type of activity that caused the breach'
|
|
},
|
|
|
|
// Why it was missed
|
|
missReason: {
|
|
type: String,
|
|
enum: [
|
|
'NO_RULE_EXISTS', // No governance rule covered this case
|
|
'RULE_TOO_NARROW', // Rule exists but didn't match this case
|
|
'CLASSIFICATION_ERROR', // Activity classifier misclassified
|
|
'ENFORCEMENT_GAP', // Rule exists but wasn't enforced
|
|
'USER_OVERRIDE', // User bypassed framework (--no-verify)
|
|
'UNKNOWN'
|
|
],
|
|
required: true,
|
|
index: true,
|
|
description: 'Why the framework missed this breach'
|
|
},
|
|
|
|
missReasonDetails: {
|
|
type: String,
|
|
default: null,
|
|
description: 'Additional context on why it was missed'
|
|
},
|
|
|
|
// Link to original decision (if available)
|
|
linkedAuditLog: {
|
|
type: mongoose.Schema.Types.ObjectId,
|
|
ref: 'AuditLog',
|
|
default: null,
|
|
description: 'Audit log entry where framework allowed this action'
|
|
},
|
|
|
|
sessionId: {
|
|
type: String,
|
|
default: null,
|
|
index: true,
|
|
description: 'Session where the breach occurred (if known)'
|
|
},
|
|
|
|
// Remediation
|
|
remediation: {
|
|
type: String,
|
|
default: null,
|
|
description: 'What was done to fix the breach'
|
|
},
|
|
|
|
preventionAdded: {
|
|
type: Boolean,
|
|
default: false,
|
|
description: 'Whether a new rule/fix was added to prevent recurrence'
|
|
},
|
|
|
|
newRuleId: {
|
|
type: String,
|
|
default: null,
|
|
description: 'ID of new governance rule added (e.g., inst_085)'
|
|
},
|
|
|
|
// Reporting
|
|
reportedBy: {
|
|
type: mongoose.Schema.Types.ObjectId,
|
|
ref: 'User',
|
|
default: null,
|
|
description: 'User who reported the missed breach'
|
|
},
|
|
|
|
verifiedBy: {
|
|
type: mongoose.Schema.Types.ObjectId,
|
|
ref: 'User',
|
|
default: null,
|
|
description: 'Admin who verified this was truly a missed breach'
|
|
},
|
|
|
|
status: {
|
|
type: String,
|
|
enum: ['REPORTED', 'VERIFIED', 'REMEDIATED', 'FALSE_POSITIVE'],
|
|
default: 'REPORTED',
|
|
index: true,
|
|
description: 'Current status of the breach report'
|
|
},
|
|
|
|
// Metadata
|
|
environment: {
|
|
type: String,
|
|
enum: ['development', 'production', 'staging'],
|
|
default: 'development',
|
|
index: true
|
|
},
|
|
|
|
tags: {
|
|
type: [String],
|
|
default: [],
|
|
description: 'Tags for categorization and searching'
|
|
}
|
|
|
|
}, {
|
|
timestamps: true,
|
|
collection: 'missedBreaches'
|
|
});
|
|
|
|
// Indexes for common queries
|
|
missedBreachSchema.index({ incidentDate: -1 });
|
|
missedBreachSchema.index({ severity: 1, incidentDate: -1 });
|
|
missedBreachSchema.index({ missReason: 1, incidentDate: -1 });
|
|
missedBreachSchema.index({ status: 1, incidentDate: -1 });
|
|
missedBreachSchema.index({ environment: 1, incidentDate: -1 });
|
|
|
|
// Virtual for time to detection
|
|
missedBreachSchema.virtual('timeToDetection').get(function() {
|
|
if (!this.discoveredDate || !this.incidentDate) return null;
|
|
return Math.floor((this.discoveredDate - this.incidentDate) / (1000 * 60 * 60 * 24)); // Days
|
|
});
|
|
|
|
// Static methods
|
|
|
|
/**
|
|
* Get missed breach statistics
|
|
*/
|
|
missedBreachSchema.statics.getStatistics = async function(startDate, endDate) {
|
|
const matchStage = {};
|
|
|
|
if (startDate && endDate) {
|
|
matchStage.incidentDate = { $gte: startDate, $lte: endDate };
|
|
}
|
|
|
|
const stats = await this.aggregate([
|
|
{ $match: matchStage },
|
|
{
|
|
$group: {
|
|
_id: null,
|
|
totalMissed: { $sum: 1 },
|
|
bySeverity: {
|
|
$push: '$severity'
|
|
},
|
|
byReason: {
|
|
$push: '$missReason'
|
|
},
|
|
totalActualCost: {
|
|
$sum: { $ifNull: ['$actualCost', 0] }
|
|
},
|
|
totalEstimatedCost: {
|
|
$sum: { $ifNull: ['$estimatedCost', 0] }
|
|
},
|
|
avgTimeToDetection: {
|
|
$avg: {
|
|
$divide: [
|
|
{ $subtract: ['$discoveredDate', '$incidentDate'] },
|
|
1000 * 60 * 60 * 24 // Convert to days
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]);
|
|
|
|
return stats[0] || null;
|
|
};
|
|
|
|
/**
|
|
* Calculate framework effectiveness rate
|
|
* Requires audit log stats for comparison
|
|
*/
|
|
missedBreachSchema.statics.calculateEffectivenessRate = async function(startDate, endDate, auditStats) {
|
|
const missedStats = await this.getStatistics(startDate, endDate);
|
|
|
|
if (!missedStats || !auditStats) {
|
|
return null;
|
|
}
|
|
|
|
const detected = auditStats.blocked || 0;
|
|
const missed = missedStats.totalMissed || 0;
|
|
const total = detected + missed;
|
|
|
|
if (total === 0) return null;
|
|
|
|
return {
|
|
detected,
|
|
missed,
|
|
total,
|
|
detectionRate: (detected / total) * 100,
|
|
missRate: (missed / total) * 100,
|
|
effectiveness: detected / total
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Find breaches by reason
|
|
*/
|
|
missedBreachSchema.statics.findByReason = function(reason, options = {}) {
|
|
const query = { missReason: reason };
|
|
|
|
if (options.status) {
|
|
query.status = options.status;
|
|
}
|
|
|
|
return this.find(query)
|
|
.sort({ incidentDate: -1 })
|
|
.limit(options.limit || 0);
|
|
};
|
|
|
|
/**
|
|
* Get breach breakdown by reason
|
|
*/
|
|
missedBreachSchema.statics.getBreakdownByReason = async function(startDate, endDate) {
|
|
const matchStage = {};
|
|
|
|
if (startDate && endDate) {
|
|
matchStage.incidentDate = { $gte: startDate, $lte: endDate };
|
|
}
|
|
|
|
const breakdown = await this.aggregate([
|
|
{ $match: matchStage },
|
|
{
|
|
$group: {
|
|
_id: '$missReason',
|
|
count: { $sum: 1 },
|
|
totalCost: {
|
|
$sum: {
|
|
$ifNull: [
|
|
'$actualCost',
|
|
{ $ifNull: ['$estimatedCost', 0] }
|
|
]
|
|
}
|
|
},
|
|
examples: {
|
|
$push: {
|
|
title: '$title',
|
|
severity: '$severity',
|
|
incidentDate: '$incidentDate'
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
$project: {
|
|
_id: 0,
|
|
reason: '$_id',
|
|
count: 1,
|
|
totalCost: 1,
|
|
recentExamples: { $slice: ['$examples', 3] }
|
|
}
|
|
},
|
|
{ $sort: { count: -1 } }
|
|
]);
|
|
|
|
return breakdown;
|
|
};
|
|
|
|
const MissedBreach = mongoose.model('MissedBreach', missedBreachSchema);
|
|
|
|
module.exports = MissedBreach;
|