SOX §802 requires that covered companies preserve audit records for 7 years, and regulators during examinations will ask not just whether archives exist but whether they can actually be restored to a readable state. Backup processes that have never been tested are not controls — they are theoretical controls. NIST 800-53 AU-11 requires that organizations retain audit logs for a defined period and that archived records remain accessible. NIST SP 800-34 (Contingency Planning Guide) requires that recovery procedures be tested. A compressed, encrypted archive on Glacier that has never been decompressed, decrypted, and verified is an unknown quantity — and discovering at examination time that the archive is corrupted or the decryption key is lost is a SOX material weakness.
Info because untested restoration is a latent risk rather than an immediate vulnerability — the failure mode only becomes critical when an actual restoration is required during an incident or examination.
Schedule an annual restoration test via cron in src/jobs/archive-test.ts and log each run to archive_restoration_tests:
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import zlib from 'zlib';
async function testArchiveRestoration(targetYear: number) {
const s3 = new S3Client({ region: process.env.AWS_REGION });
const obj = await s3.send(new GetObjectCommand({
Bucket: process.env.AUDIT_ARCHIVE_BUCKET!,
Key: `transactions_${targetYear}.json.gz`
}));
const body = await obj.Body!.transformToByteArray();
const decompressed = zlib.gunzipSync(body);
const records: AuditRecord[] = JSON.parse(decompressed.toString());
// Sample-verify 100 random records
const sample = records.sort(() => Math.random() - 0.5).slice(0, 100);
const allValid = sample.every(r => verifyLogSignature(r, process.env.AUDIT_SIGNING_KEY!));
await db('archive_restoration_tests').insert({
test_date: new Date(), archive_year: targetYear,
records_verified: records.length, status: allValid ? 'success' : 'failed'
});
if (!allValid) await alertOps('Archive integrity check failed', { targetYear });
}
// Run once per year (first Monday of January)
cron.schedule('0 8 1-7 1 1', () => testArchiveRestoration(new Date().getFullYear() - 2));
ID: finserv-audit-trail.tamper-evidence.archive-restoration-test
Severity: info
What to look for: Count all backup/restore playbooks, scheduled restoration tests, or monitoring jobs. Look for test result logs or records of restoration attempts. Quote the actual test schedule or last-run date found. Verify restoration tests occur at least once every 365 days.
Pass criteria: At least 1 documented archive restoration process exists, tested at least once per year (every 365 days). Test results are logged with at least 2 fields: test date and pass/fail status. Report the count even on pass (e.g., "1 annual restoration test found, last run 2025-12-15, status: success").
Fail criteria: No archive restoration tests documented (0 playbooks or test records found), or last test was more than 365 days ago.
Skip (N/A) when: Application has been in production for fewer than 12 months (no archives old enough to test) — cite the actual deployment date found.
Detail on fail: "No documentation of archive restoration tests — 0 playbooks, 0 test records found.".
Remediation: Schedule annual restoration testing (in docs/runbooks/archive-restore.md or src/jobs/archive-test.ts):
// In your operations runbook or cron job
async function annualArchiveRestorationTest() {
const testDate = new Date();
testDate.setFullYear(testDate.getFullYear() - 2); // Test 2-year-old archive
try {
// Retrieve archived logs from cold storage (S3, Glacier, etc.)
const archivedLogs = await s3.getObject({
Bucket: 'audit-logs-archive',
Key: `transactions_${testDate.getFullYear()}-${testDate.getMonth()}.json.gz`
});
// Decompress and verify
const decompressed = zlib.gunzipSync(archivedLogs.Body);
const logs = JSON.parse(decompressed);
// Verify integrity
for (const log of logs.slice(0, 100)) { // Sample check
if (!verifyLogSignature(log, AUDIT_KEY)) {
throw new Error('Archive integrity check failed');
}
}
// Log successful test
await db.archiveRestorationTests.create({
testDate: new Date(),
archiveYear: testDate.getFullYear(),
recordsVerified: logs.length,
status: 'success'
});
} catch (error) {
await db.archiveRestorationTests.create({
testDate: new Date(),
archiveYear: testDate.getFullYear(),
status: 'failed',
errorMessage: error.message
});
// Alert ops
await notificationService.alertOps('Archive restoration test failed', error);
}
}