# ARGUS-A10: Unisolated Critical Table Transactions > **Rule Code:** `ARGUS-A10` > **Identifier:** `ISOLATION_LEVEL` > **Severity:** `HIGH / CRITICAL` (Financial Write-Skew & Silent Data Corruption Blocker) > **Category:** `Data Integrity, Concurrency & Financial Correctness` > **Target Standards:** CWE-362 (Race Condition / Concurrent Execution), CWE-662 (Improper Synchronization), OWASP ASVS v4.0.3/v5.0 §V11.1.4, ACID Isolation Standards --- ## 1. Overview & Core Invariant Database transactions mutating **critical tables-such as financial accounts, balances, inventory quotas, or sequential numbering** (`balances`, `accounts`, `inventory`, `wallets`, `ledger`, `sequence_counters`)-**must not implicitly rely on default `ReadCommitted` isolation**. Every transaction writing to critical tables must enforce at least one of 3 concurrency safeguards: 1. Specifying **`pgx.RepeatableRead`** or **`pgx.Serializable`** explicitly via `pgx.TxOptions`, **OR** 2. Acquiring explicit pessimistic row locks (**`SELECT ... FOR UPDATE`**) on the target rows before mutation under `ReadCommitted`, **OR** 3. Enclosing the transaction within a transaction-level advisory lock (**`argus.WithAdvisoryLock`**). --- ## 2. Technical Grounding & PostgreSQL Engine Realities ### 2.1. The Write-Skew Anomaly in Default `ReadCommitted` Under PostgreSQL's default `ReadCommitted` isolation level, each SQL statement within a transaction executes against a fresh MVCC snapshot: 1. **Concurrent Overlapping Reads:** Two concurrent transactions (Tx A and Tx B) read the current ledger balance of $10,000 simultaneously. 2. **False Precondition Validation:** Tx A validates a $8,000 withdrawal (`10,000 >= 8,000` -> valid). Tx B validates a $7,000 withdrawal (`10,000 >= 7,000` -> valid). 3. **Sequential Commit Catastrophe:** Both transactions commit sequential `UPDATE` statements. The resulting balance drops to -$5,000, creating silent financial corruption (CWE-362). ```mermaid flowchart TD subgraph SKEW ["Write-Skew Catastrophe (ReadCommitted)"] direction TB A1["Tx A: Reads Balance ($10,000)"] --> V1["Validates $8k Payout (PASS)"] B1["Tx B: Reads Balance ($10,000)"] --> V2["Validates $7k Payout (PASS)"] V1 --> U1["Tx A Updates Balance to $2,000 & COMMITS"] V2 --> U2["Tx B Updates Balance to -$5,000 & COMMITS"] U2 --> Outage["Silent Financial Corruption & Ledger Deficit (CWE-362)"] end subgraph SAFE ["Serializable Snapshot Isolation (COMPLIANT)"] direction TB A2["Tx A (Serializable): Reads Balance ($10,000)"] --> S1["Validates $8k & Updates to $2k"] B2["Tx B (Serializable): Reads Balance ($10,000)"] --> S2["Validates $7k & Attempts Update"] S1 --> C1["Tx A Commits Successfully"] S2 --> Err["Tx B Aborted by PostgreSQL with SQLSTATE 40001 (SAFE)"] end ``` ### 2.2. PostgreSQL 18 Serializable Snapshot Isolation (SSI) PostgreSQL implements true mathematical SSI (`src/backend/storage/lmgr/predicate.c`, Cahill et al.). SSI tracks read-write anti-dependencies (`SIReadLock`) in shared memory without blocking reads. When an anomaly cycle is detected, PostgreSQL automatically aborts one transaction with SQLSTATE `40001` (serialization failure), guaranteeing 100% data integrity without manual lock coordination. --- ## 3. How Argus Detects Violations (Static Analysis Architecture) Argus inspects transaction scopes across database calls and helper closures: ```mermaid flowchart LR Scan["Inspect Go Functions
(Exclude _test.go)"] --> DetectTx{"Identifies Transaction Scope?
(Begin, BeginTx, WithTx)"} DetectTx -->|Yes| CheckSQL["Inspect Queries in Tx Body"] CheckSQL --> CriticalCheck{"Writes to Critical Table?
(critical_tables.go)"} CriticalCheck -->|Yes| GuardCheck{"Has Concurrency Guard?
1. Serializable/RepeatableRead
2. SELECT ... FOR UPDATE
3. argus.WithAdvisoryLock"} GuardCheck -->|No| Report["Report CRITICAL Violation:
Unisolated Critical Table Write"] GuardCheck -->|Yes| Pass["Pass (Safeguarded Mutation)"] CriticalCheck -->|No| Pass DetectTx -->|No| Pass ``` 1. **Critical Table Registry (`critical_tables.go`):** Analyzes SQL AST for `INSERT`, `UPDATE`, and `DELETE` statements targeting registered critical tables (`balances`, `accounts`, `inventory`, `wallets`, `ledger`, etc.). 2. **Pessimistic Row Lock Detector (`pessimistic_lock.go`):** Parses PostgreSQL AST `LockingClause` to verify `SELECT ... FOR UPDATE` or `FOR NO KEY UPDATE`. 3. **Transaction Options Validator (`tx_options_check.go`):** Analyzes `pgx.TxOptions` structs to verify `IsoLevel: Serializable` or `RepeatableRead`. --- ## 4. Vulnerability & Risk Taxonomy | Failure Mode | Technical Impact | Risk Severity | | :-------------------------------------- | :--------------------------------------------------------------------------------- | :------------ | | **Default `ReadCommitted` on Balances** | Triggers write-skew anomalies leading to negative balances and ledger corruption. | **CRITICAL** | | **Quota Allocation Race Conditions** | Over-allocates constrained inventory or subsidy quotas beyond authorized limits. | **HIGH** | | **Sequential Numbering Collision** | Concurrent counter reads generate duplicate invoice or certificate serial numbers. | **HIGH** | --- ## 5. Non-Compliant Code Patterns (Bad Examples) ### Example 1: Default `pool.Begin` Mutating Balance ```go // VIOLATION: Mutating balance under default ReadCommitted without row locking func DeductAccountBalance(ctx context.Context, pool *pgxpool.Pool, accountID int, amount int64) error { // Flagged: transaction writing to critical table without explicit isolation or row lock tx, err := pool.Begin(ctx) if err != nil { return err } defer tx.Rollback(ctx) _, err = tx.Exec(ctx, "UPDATE saldo SET amount = amount - $1 WHERE account_id = $2", amount, accountID) if err != nil { return err } return tx.Commit(ctx) } ``` ### Example 2: Helper `WithTx` Without Isolation Level ```go // VIOLATION: WithTx on critical table without TxOptions func AllocateQuota(ctx context.Context, pool *pgxpool.Pool, quotaID int) error { // Flagged: transaction writing to critical table without explicit isolation or row lock return argus.WithTx(ctx, pool, func(tx pgx.Tx) error { _, err := tx.Exec(ctx, "UPDATE kuota SET sisa = sisa - 1 WHERE id = $1", quotaID) return err }) } ``` --- ## 6. Compliant Implementation Patterns (Good Examples) ### Solution 1: Explicit `Serializable` Isolation ```go // COMPLIANT: Serializable isolation prevents write-skew mathematically func DeductAccountBalance(ctx context.Context, pool *pgxpool.Pool, accountID int, amount int64) error { opts := pgx.TxOptions{IsoLevel: pgx.Serializable} tx, err := pool.BeginTx(ctx, opts) if err != nil { return err } defer tx.Rollback(ctx) _, err = tx.Exec(ctx, "UPDATE saldo SET amount = amount - $1 WHERE account_id = $2", amount, accountID) if err != nil { return err } return tx.Commit(ctx) } ``` ### Solution 2: Pessimistic Row Lock (`SELECT ... FOR UPDATE`) ```go // COMPLIANT: Explicit row lock under ReadCommitted func TransferFunds(ctx context.Context, pool *pgxpool.Pool, fromID, toID int, amount int64) error { tx, err := pool.Begin(ctx) if err != nil { return err } defer tx.Rollback(ctx) // Lock rows exclusively before mutation const lockSQL = "SELECT amount FROM saldo WHERE id = $1 FOR UPDATE" var balance int64 if err := tx.QueryRow(ctx, lockSQL, fromID).Scan(&balance); err != nil { return err } _, err = tx.Exec(ctx, "UPDATE saldo SET amount = amount - $1 WHERE id = $2", amount, fromID) if err != nil { return err } return tx.Commit(ctx) } ``` ### Solution 3: Wrapped with Advisory Lock ```go // COMPLIANT: Protected by distributed advisory lock func AllocateQuota(ctx context.Context, pool *pgxpool.Pool, quotaID int) error { lockKey := fmt.Sprintf("quota:allocation:%d", quotaID) return argus.WithAdvisoryLock(ctx, lockKey, func() error { return argus.WithTx(ctx, pool, func(tx pgx.Tx) error { _, err := tx.Exec(ctx, "UPDATE kuota SET sisa = sisa - 1 WHERE id = $1", quotaID) return err }) }) } ``` --- ## 7. How to Suppress (Ignore Directives) For manual database migrations, admin reconciliation scripts, or batch repair tools: ```go // argus:ignore ARGUS-A10 manual single-operator data migration tx, err := pool.Begin(ctx) ``` Alternatively, use the identifier alias: ```go // argus:ignore ISOLATION_LEVEL single-tenant reconciliation batch err := argus.WithTx(ctx, pool, migrationFn) ``` --- ## 8. Configuration Reference (`.argus.yaml`) Configure additional custom critical tables in `.argus.yaml`: ```yaml rules: ARGUS-A10: enabled: true critical_tables: - ledger_entries - voucher_pool - order_sequence ```