When a financial correction or reversal is handled by deleting or modifying the original log record, the audit trail loses continuity — an examiner reviewing the log will see the final state but not the error and its correction, which is exactly the information they need to evaluate whether the process is operating with integrity. SOX §404 requires that corrections to financial records follow a controlled, documented process. NIST 800-53 AU-10 (Non-Repudiation) requires that the organization protect against individuals falsely denying having performed particular actions — an undocumented reversal creates the ability to deny that an original transaction ever occurred. FINRA Rule 4511 requires that corrections be documented as corrections, not as silent replacements. CWE-284 (Improper Access Control) covers the case where any operator can issue a reversal without a separate approver checking the request.
Low because correction logging is a process-integrity control — its absence enables unilateral reversals without a second-party check, but the financial impact depends on the specific transaction value involved.
Implement corrections as a two-record pattern in src/services/corrections.ts: one record for the approval event, one for the reversal transaction, neither touching the original row:
async function approveAndReverseTransaction(
correctionId: string, approverUserId: string
) {
const correction = await db('corrections').where({ id: correctionId }).first();
await db('transaction_logs').insert([
{
user_id: approverUserId,
operation_type: 'correction_approved',
details: JSON.stringify({
correction_id: correctionId,
original_transaction_id: correction.original_transaction_id,
reason: correction.reason,
requested_by: correction.requested_by
}),
timestamp: new Date().toISOString()
},
{
user_id: approverUserId,
operation_type: 'reversal',
details: JSON.stringify({
original_transaction_id: correction.original_transaction_id,
approved_by: approverUserId,
approved_at: new Date().toISOString()
}),
timestamp: new Date().toISOString()
}
]);
}
Verify requester_id !== approver_id before calling this function — a single person should never be able to self-approve a correction.
ID: finserv-audit-trail.tamper-evidence.correction-approval-chain
Severity: low
What to look for: Count all reversal/correction endpoints. For each, verify the correction is logged as a new operation (not a deletion of the original). Count the approval workflow fields: at minimum requester, approver, approval_timestamp, and original_transaction_id must be present. Quote the actual operation_type values used for corrections. Deleting the original transaction record does not count as pass — do not pass if any correction path deletes or modifies the original log entry.
Pass criteria: Corrections or reversals are logged as new operations with at least 4 fields: original_transaction_id reference, requester, approver, and approval_timestamp. At least 1 documented approval workflow requires a separate approver role. Report the count even on pass (e.g., "2 correction endpoints found, both log 5 approval fields").
Fail criteria: Corrections are applied without logging, or reversals are handled as deletions, or fewer than 4 approval fields are recorded, or no approval workflow exists.
Skip (N/A) when: Never — reversals are critical audit trail events.
Detail on fail: "Reversals logged but 0 of 4 approval fields present — any operator can reverse without approval." or "Reversed transactions deleted from log — original record modified instead of new correction entry.".
Remediation: Implement correction logging with approval (in src/services/corrections.ts or src/app/api/corrections/route.ts):
async function requestCorrection(originalTransactionId, reason) {
const correction = await db.corrections.create({
originalTransactionId,
reason,
requestedBy: userId,
requestedAt: new Date(),
status: 'pending_approval'
});
// Notify compliance for approval
await notificationService.requestApproval(correction.id);
}
async function approveCorrection(correctionId, approverUserId) {
const correction = await db.corrections.findById(correctionId);
// Log the approval
await db.transactionLogs.create({
userId: approverUserId,
operationType: 'correction_approved',
details: {
originalTransactionId: correction.originalTransactionId,
correctionId: correction.id,
reason: correction.reason,
approvedAt: new Date()
}
});
// Execute reversal as new transaction
await db.transactionLogs.create({
userId: approverUserId,
operationType: 'reversal',
details: {
originalTransactionId: correction.originalTransactionId,
reversalReason: correction.reason
}
});
correction.status = 'approved';
correction.approvedBy = approverUserId;
correction.approvedAt = new Date();
await correction.save();
}