SOX §302 requires that the principal executive and financial officers certify in each quarterly and annual report that the disclosure controls and procedures are effective — and effective controls include the ability to produce a summary of financial activity for the period. NIST 800-53 AU-6 requires that audit records be reviewed and that findings be reported to designated officials; automated monthly reports operationalize this requirement. FINRA Rule 4511 requires that books and records be available on demand. A system that cannot generate a monthly transaction summary report on demand fails both the SOX certification requirement and the FINRA examination readiness requirement. Without summary-plus-detail reports, management has no structured way to spot anomalies across the portfolio, and compliance officers cannot produce required periodic disclosures.
Low because report generation is a detective control rather than a preventive one — its absence delays anomaly detection but does not itself create an exploitable vulnerability.
Add a monthly report endpoint at src/app/api/admin/reports/monthly/route.ts that returns both a summary block and full transaction detail in a single response:
export async function GET(req: Request) {
const { month, year } = Object.fromEntries(new URL(req.url).searchParams);
const start = new Date(`${year}-${month.padStart(2,'0')}-01`);
const end = new Date(start.getFullYear(), start.getMonth() + 1, 0, 23, 59, 59);
const rows = await db('transaction_logs')
.whereBetween('timestamp', [start, end]).orderBy('timestamp');
const summary = rows.reduce((acc, r) => {
acc.total_transactions++;
acc.by_type[r.operation_type] = (acc.by_type[r.operation_type] ?? 0) + 1;
acc.total_amount += Number(r.amount ?? 0);
return acc;
}, { total_transactions: 0, by_type: {} as Record<string,number>, total_amount: 0 });
return Response.json({ summary, transactions: rows });
}
Schedule automated delivery via src/jobs/monthly-report.ts on the first of each month so compliance officers receive the report without manual intervention.
ID: finserv-audit-trail.retention-compliance.audit-reports
Severity: low
What to look for: Count all report generation endpoints, scheduled jobs, or UI pages. Enumerate the data fields each report includes and classify as "summary" or "detail" type. Verify reports include at least 3 summary fields (total transactions, by-type counts, total amount) and at least 1 detail section (individual transaction records). Quote the actual report endpoint paths found.
Pass criteria: At least 1 audit report can be generated monthly (automated or on-demand) with at least 3 summary fields and transaction-level detail records. Report the count even on pass (e.g., "1 report endpoint at /api/admin/reports/monthly with 4 summary fields + full transaction detail").
Fail criteria: No audit report generation capability (0 endpoints), or reports only provide summaries without transaction details (or vice versa), or fewer than 3 summary fields are included.
Skip (N/A) when: Audit report generation is verifiably outsourced to a third-party processor (e.g., Stripe reporting, external audit firm — cite the actual integration found).
Detail on fail: "No audit report generation — 0 report endpoints found. Compliance cannot produce monthly reports." or "Report at /api/reports provides 2 summary fields but 0 transaction-level detail records.".
Remediation: Add report generation (in src/app/api/admin/reports/monthly/route.ts):
app.get('/api/admin/reports/monthly', authenticate, requireRole('compliance'), async (req, res) => {
const { month, year } = req.query;
const startDate = new Date(`${year}-${String(month).padStart(2, '0')}-01`);
const endDate = new Date(startDate.getFullYear(), startDate.getMonth() + 1, 0);
const logs = await db.transactionLogs
.find({ timestamp: { $gte: startDate, $lte: endDate } })
.lean();
const summary = {
month,
year,
totalTransactions: logs.length,
byType: {},
byStatus: { pass: 0, fail: 0, skip: 0 },
totalAmount: 0
};
logs.forEach(log => {
summary.byType[log.operationType] = (summary.byType[log.operationType] || 0) + 1;
summary.byStatus[log.result] = (summary.byStatus[log.result] || 0) + 1;
if (log.amount) summary.totalAmount += log.amount;
});
res.json({
summary,
transactions: logs
});
});