/* * 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;