# All financial transactions logged immutably with UTC timestamp, user ID, operation type

- **Pattern:** `ab-001379` (`finserv-audit-trail.transaction-logging.transaction-immutable-logging`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001379
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001379)

## Why it matters

Missing or mutable transaction logs are the single largest SOX §404 finding class in financial technology audits. Without an append-only record of every transfer, withdrawal, deposit, and adjustment — each stamped with a UTC timestamp, user ID, and operation type — you cannot reconstruct the sequence of events that produced a balance, respond to a regulatory examination, or prove to an auditor that your ledger is complete. PCI-DSS 4.0 Req-10.2 mandates logging of all individual user access to cardholder data and all financial transactions. NIST 800-53 AU-2 requires that the organization determine which events are auditable; AU-3 specifies the content those records must contain (user identity, time, type of event, and outcome). CWE-778 (Insufficient Logging) is the direct vulnerability class — an adversary who moves funds without triggering a log entry leaves no forensic trail to detect or prosecute the fraud.

## Severity rationale

Critical because gaps in transaction logging create undetectable embezzlement vectors and directly trigger SOX §404 material-weakness findings that can halt a public offering or trigger SEC enforcement.

## Remediation

Create a dedicated append-only `transaction_logs` table and instrument every financial endpoint to write a record before returning a success response. Place the DDL in `db/migrations/` or `prisma/schema.prisma`:

```sql
CREATE TABLE transaction_logs (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW() AT TIME ZONE 'UTC',
  user_id UUID NOT NULL REFERENCES users(id),
  operation_type VARCHAR(50) NOT NULL,
  amount DECIMAL(19,4),
  currency CHAR(3),
  details JSONB
);
REVOKE UPDATE, DELETE ON transaction_logs FROM app_role;
GRANT INSERT, SELECT ON transaction_logs TO app_role;
```

In your service layer, log before the side-effectful operation commits so a crash never produces an unlogged transaction.

## Detection

- **ID:** `transaction-immutable-logging`
- **Severity:** `critical`
- **What to look for:** Enumerate all financial operation endpoints (transfers, withdrawals, deposits, adjustments, cancellations, reversals, fee changes) and count every endpoint that writes to the audit/transaction log table. Examine database schema for a dedicated audit/transaction log table with at least 3 required columns: UTC timestamp, user identifier, and operation type. Quote the exact column names found. Look for INSERT-only queries (no UPDATE, no DELETE) or append-only table design. A table that stores transaction data but allows UPDATE or DELETE does not count as pass — do not pass if any mutation path exists.
- **Pass criteria:** A transaction log table exists with at least 3 columns for timestamp (UTC), user ID, and operation type. Count all financial transaction endpoints — at least 90% must contain calls to log transactions immediately upon execution. Report the count even on pass (e.g., "6 of 7 endpoints log to audit table"). Logging is INSERT-only with no modification mechanism found in at least 5 code paths examined.
- **Fail criteria:** No audit log table found, or fewer than 90% of financial transaction endpoints contain logging calls, or the audit log is mutable (allows UPDATE/DELETE).
- **Skip (N/A) when:** Never — every financial application must log transactions.
- **Detail on fail:** Name the transaction types that lack logging (e.g., `"Transfers logged but withdrawals are not — 3 of 5 endpoints missing logging"`), or specify that the audit log table allows modifications (`"audit_logs table has UPDATE and DELETE permissions — not append-only"`).
- **Cross-reference:** Check `finserv-audit-trail.retention-compliance.complete-action-logging` for comprehensive action coverage, and `finserv-audit-trail.transaction-logging.append-only-enforcement` for immutability enforcement details.
- **Remediation:** Create a dedicated, append-only transaction log table in your database (e.g., `db/schema/transaction_logs.sql` or `prisma/schema.prisma`):

  ```sql
  CREATE TABLE transaction_logs (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    timestamp TIMESTAMP NOT NULL DEFAULT NOW() AT TIME ZONE 'UTC',
    user_id UUID NOT NULL,
    operation_type VARCHAR(50) NOT NULL, -- 'transfer', 'withdraw', 'deposit', 'adjustment'
    amount DECIMAL(19, 2),
    currency VARCHAR(3),
    details JSONB,
    created_at TIMESTAMP NOT NULL DEFAULT NOW() AT TIME ZONE 'UTC'
  );

  -- Append-only: never allow UPDATE or DELETE
  REVOKE UPDATE, DELETE ON transaction_logs FROM app_role;
  GRANT INSERT, SELECT ON transaction_logs TO app_role;
  ```

  Then, log every financial operation immediately:

  ```ts
  async function executeTransfer(userId, toAccount, amount) {
    await db.transactionLogs.create({
      timestamp: new Date(Date.now()).toISOString(),
      userId,
      operationType: 'transfer',
      amount,
      details: { toAccount }
    });
    // ... execute transfer
  }
  ```

## External references

- cwe CWE-778
- nist AU-2
- nist AU-3
- sox Section 404
- pci-dss Req-10.2

Taxons: regulatory-conformance, data-integrity

HTML version: https://auditbuffet.com/patterns/ab-001379
