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>
249 lines
5.7 KiB
JavaScript
249 lines
5.7 KiB
JavaScript
/**
|
|
* Missed Breach Controller
|
|
*
|
|
* Tracks governance framework false negatives for research integrity
|
|
*/
|
|
|
|
const MissedBreach = require('../models/MissedBreach.model');
|
|
const AuditLog = require('../models/AuditLog.model');
|
|
|
|
/**
|
|
* Report a new missed breach
|
|
* POST /api/admin/missed-breaches
|
|
*/
|
|
async function reportMissedBreach(req, res) {
|
|
try {
|
|
const {
|
|
title,
|
|
incidentDate,
|
|
severity,
|
|
description,
|
|
activityType,
|
|
missReason,
|
|
missReasonDetails,
|
|
linkedAuditLog,
|
|
sessionId,
|
|
actualCost,
|
|
estimatedCost,
|
|
environment,
|
|
tags
|
|
} = req.body;
|
|
|
|
// Validate required fields
|
|
if (!title || !incidentDate || !severity || !description || !activityType || !missReason) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Missing required fields: title, incidentDate, severity, description, activityType, missReason'
|
|
});
|
|
}
|
|
|
|
const missedBreach = new MissedBreach({
|
|
title,
|
|
incidentDate: new Date(incidentDate),
|
|
severity,
|
|
description,
|
|
activityType,
|
|
missReason,
|
|
missReasonDetails,
|
|
linkedAuditLog,
|
|
sessionId,
|
|
actualCost,
|
|
estimatedCost,
|
|
environment: environment || process.env.NODE_ENV || 'development',
|
|
tags: tags || [],
|
|
reportedBy: req.user?._id || null,
|
|
status: 'REPORTED'
|
|
});
|
|
|
|
await missedBreach.save();
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
missedBreach: missedBreach.toObject()
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error reporting missed breach:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to report missed breach'
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all missed breaches
|
|
* GET /api/admin/missed-breaches?status=VERIFIED&severity=HIGH
|
|
*/
|
|
async function getMissedBreaches(req, res) {
|
|
try {
|
|
const {
|
|
status,
|
|
severity,
|
|
missReason,
|
|
environment,
|
|
startDate,
|
|
endDate,
|
|
limit = 100
|
|
} = req.query;
|
|
|
|
const query = {};
|
|
|
|
if (status) query.status = status;
|
|
if (severity) query.severity = severity;
|
|
if (missReason) query.missReason = missReason;
|
|
if (environment && environment !== 'all') query.environment = environment;
|
|
|
|
if (startDate && endDate) {
|
|
query.incidentDate = {
|
|
$gte: new Date(startDate),
|
|
$lte: new Date(endDate)
|
|
};
|
|
}
|
|
|
|
const breaches = await MissedBreach.find(query)
|
|
.sort({ incidentDate: -1 })
|
|
.limit(parseInt(limit))
|
|
.populate('reportedBy', 'name email')
|
|
.populate('verifiedBy', 'name email')
|
|
.lean();
|
|
|
|
res.json({
|
|
success: true,
|
|
count: breaches.length,
|
|
breaches
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error fetching missed breaches:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch missed breaches'
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get missed breach statistics
|
|
* GET /api/admin/missed-breaches/statistics
|
|
*/
|
|
async function getMissedBreachStatistics(req, res) {
|
|
try {
|
|
const { startDate, endDate } = req.query;
|
|
|
|
const start = startDate ? new Date(startDate) : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
|
|
const end = endDate ? new Date(endDate) : new Date();
|
|
|
|
// Get missed breach stats
|
|
const missedStats = await MissedBreach.getStatistics(start, end);
|
|
|
|
// Get audit log stats for comparison
|
|
const auditStats = await AuditLog.getStatistics(start, end);
|
|
|
|
// Calculate effectiveness rate
|
|
const effectiveness = await MissedBreach.calculateEffectivenessRate(
|
|
start,
|
|
end,
|
|
auditStats
|
|
);
|
|
|
|
// Get breakdown by reason
|
|
const byReason = await MissedBreach.getBreakdownByReason(start, end);
|
|
|
|
res.json({
|
|
success: true,
|
|
dateRange: { start, end },
|
|
missedBreaches: missedStats,
|
|
auditLogs: auditStats,
|
|
effectiveness,
|
|
breakdownByReason: byReason
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error fetching missed breach statistics:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch statistics'
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update a missed breach (verify, remediate, etc.)
|
|
* PATCH /api/admin/missed-breaches/:id
|
|
*/
|
|
async function updateMissedBreach(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
const updates = req.body;
|
|
|
|
// If verifying, record who verified it
|
|
if (updates.status === 'VERIFIED' && req.user) {
|
|
updates.verifiedBy = req.user._id;
|
|
}
|
|
|
|
const missedBreach = await MissedBreach.findByIdAndUpdate(
|
|
id,
|
|
updates,
|
|
{ new: true, runValidators: true }
|
|
);
|
|
|
|
if (!missedBreach) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Missed breach not found'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
missedBreach: missedBreach.toObject()
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error updating missed breach:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to update missed breach'
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete a missed breach (if it was a false positive)
|
|
* DELETE /api/admin/missed-breaches/:id
|
|
*/
|
|
async function deleteMissedBreach(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const missedBreach = await MissedBreach.findByIdAndDelete(id);
|
|
|
|
if (!missedBreach) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Missed breach not found'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Missed breach deleted'
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error deleting missed breach:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to delete missed breach'
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
reportMissedBreach,
|
|
getMissedBreaches,
|
|
getMissedBreachStatistics,
|
|
updateMissedBreach,
|
|
deleteMissedBreach
|
|
};
|