Skip to content

ARGUS A03

will2469 edited this page Aug 29, 2026 · 1 revision

ARGUS-A03: Unbounded Context in Database Operations

Rule Code: ARGUS-A03 Identifier: UNBOUNDED_CONTEXT Severity: HIGH (Zombie Query & CPU/Memory Saturation Blocker) Category: Resource & Connection Lifecycle Target Standards: CWE-400 (Uncontrolled Resource Consumption), OWASP ASVS v4.0.3/v5.0 §V1.5.1


1. Overview & Core Invariant

Every database I/O invocation (Query, QueryRow, Exec, Begin, BeginTx, SendBatch, CopyFrom, Ping) must receive and propagate a context.Context configured with an explicit timeout/deadline or bound to a request lifecycle (such as r.Context() from HTTP requests or worker queue envelopes).

Executing database calls with raw, unbounded contexts-specifically direct or indirect usages of context.Background() or context.TODO()-is strictly prohibited in production application code.


2. Technical Grounding & PostgreSQL Engine Realities

PostgreSQL utilizes an asynchronous, out-of-band wire protocol cancellation mechanism defined in PostgreSQL Protocol Formats §54.2 (CancelRequest):

  1. Out-of-Band Cancellation: When a Go context.Context expires or cancels (for instance, when an HTTP client terminates an in-flight request), driver engines like pgx do not sever the primary query socket.
  2. Auxiliary Socket Dispatch: Instead, pgx immediately establishes an auxiliary, lightweight TCP connection to PostgreSQL and transmits a CancelRequest message containing the target backend process PID and its session Secret Key.
  3. Instant Engine Abortion: The PostgreSQL engine traps this signal via ProcessInterrupts(), terminating the active statement instantly, releasing work_mem sort/hash buffers, and relinquishing relation lock pins.
flowchart TD
    subgraph SAFE ["Bounded Context: r.Context() or WithTimeout (SAFE)"]
        direction TB
        Client1["HTTP Client / Browser"] -->|"Drops Connection Early"| App1["Go App: r.Context().Done() Fired"]
        App1 -->|"Auxiliary Socket"| Cancel1["Sends CancelRequest (PID, Key)"]
        Cancel1 -->|"ProcessInterrupts() Trapped"| PG1["PostgreSQL Aborts Query Instantly"]
        PG1 -->|"work_mem & CPU freed"| Healthy["Cluster Remains Resilient (SAFE)"]
    end

    subgraph ZOMBIE ["Unbounded Raw Context: context.Background() (CATASTROPHIC RISK)"]
        direction TB
        Client2["HTTP Client / Browser"] -->|"Drops Connection Early"| App2["Go App: Ignores Disconnect"]
        App2 -->|"context.Background() Never Cancels"| PG2["PostgreSQL Executes for Hours"]
        PG2 -->|"CPU at 100% & Memory Saturation"| Deadlock["Cluster Starvation & Crash (CWE-400)"]
    end
Loading

2.1. Defense-in-Depth vs. Server Timeouts

While server-side safety guards such as statement_timeout (ARGUS-A12) and transaction_timeout (ARGUS-A23) exist, PostgreSQL cannot autonomously detect when a downstream HTTP client or mobile user has abandoned a request. Propagating a bounded Go context.Context is the only reactive mechanism capable of neutralizing zombie queries at the exact millisecond of abandonment.


3. How Argus Detects Violations (Static Analysis Architecture)

Argus inspects all function declarations (*ast.FuncDecl) outside test files (_test.go are exempted):

flowchart LR
    Call["Detect Database Call<br/>(Query, Exec, BeginTx, etc.)"] --> DirectCheck{"Is Argument<br/>context.Background()<br/>or context.TODO()?"}
    DirectCheck -->|Yes| Report["Report Violation:<br/>Unbounded Context"]
    DirectCheck -->|No| LocalCheck{"Does Variable Resolve<br/>to Raw Context?"}
    LocalCheck -->|Yes| Report
    LocalCheck -->|No| Pass["Pass (Safe Bounded Context)"]
Loading
  1. Target Method Filter: Matches database I/O methods: Query, QueryRow, Exec, Begin, BeginTx, SendBatch, CopyFrom, and Ping (excluding non-database selector methods like r.URL.Query()).
  2. Direct Call Detection: Identifies *ast.CallExpr directly calling context.Background() or context.TODO().
  3. Lexical Variable Resolution: Evaluates local identifier assignments (*ast.AssignStmt). If ctx := context.Background() is passed to a database call, the resolver identifies the raw origin and flags the violation.
  4. Bounded Whitelist: Recognizes valid context creators:
    • r.Context()
    • context.WithTimeout(...)
    • context.WithDeadline(...)

4. Vulnerability & Risk Taxonomy

Failure Mode Technical Impact Risk Severity
Zombie Query Accumulation Abandoned client queries continue burning database CPU and memory for extended durations. HIGH
Connection Pool Blockade Pool connections remain checked out by orphaned queries, blocking incoming requests. HIGH
Uncontrolled Resource Consumption (CWE-400) Exhaustion of work_mem and temp disk space causing database cluster instability. HIGH

5. Non-Compliant Code Patterns (Bad Examples)

Example 1: Direct context.Background() in Query

// VIOLATION: Using context.Background() directly in database call
func GetUserProfile(pool *pgxpool.Pool, userID string) (*User, error) {
    query := "SELECT id, username, email FROM users WHERE id = $1"
    row := pool.QueryRow(context.Background(), query, userID) // Flagged!
    // ...
}

Example 2: Direct context.TODO() in Execution

// VIOLATION: Using context.TODO() as a placeholder in production
func DeleteExpiredSessions(pool *pgxpool.Pool) error {
    _, err := pool.Exec(context.TODO(), "DELETE FROM sessions WHERE expires_at < NOW()")
    return err
}

Example 3: Indirect Raw Context via Local Assignment

// VIOLATION: Initializing raw context in variable and passing to BeginTx
func TransferFunds(pool *pgxpool.Pool, fromID, toID string, amount int64) error {
    ctx := context.Background() // Tracked by context_resolver
    tx, err := pool.BeginTx(ctx, pgx.TxOptions{})
    if err != nil {
        return err
    }
    defer tx.Rollback(ctx)
    // ...
}

6. Compliant Implementation Patterns (Good Examples)

Solution 1: Bound to Request Lifecycle (HTTP / gRPC Handlers)

// COMPLIANT: Propagating request context from HTTP server
func (h *UserHandler) HandleGetProfile(w http.ResponseWriter, r *http.Request) {
    user, err := h.repo.FindByID(r.Context(), r.PathValue("id"))
    if err != nil {
        http.Error(w, "Not found", http.StatusNotFound)
        return
    }
    json.NewEncoder(w).Encode(user)
}

Solution 2: Explicit Timeout for Background Workers / Cron Jobs

// COMPLIANT: Explicit timeout with deferred cancellation
func (w *CleanupWorker) RunHourlyCleanup(pool *pgxpool.Pool) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    _, err := pool.Exec(ctx, "DELETE FROM audit_temp WHERE created_at < NOW() - INTERVAL '7 days'")
    return err
}

Solution 3: Context Propagation Across Repository Layer

// COMPLIANT: Context passed as first parameter across domain boundaries
func (r *UserRepository) FindByID(ctx context.Context, id string) (*User, error) {
    const query = "SELECT id, username, email FROM users WHERE id = $1"
    row := r.pool.QueryRow(ctx, query, id)
    // ...
}

7. How to Suppress (Ignore Directives)

For maintenance daemons with custom internal watchdog cancellation loops:

// argus:ignore ARGUS-A03 dedicated maintenance worker with external process watchdog
row := pool.QueryRow(context.Background(), maintenanceQuery)

Alternatively, use the identifier alias:

// argus:ignore UNBOUNDED_CONTEXT verified internal background migration loop
_, err := pool.Exec(context.Background(), bootstrapQuery)

8. Configuration Reference (.argus.yaml)

Enable or configure this rule globally in .argus.yaml:

rules:
  ARGUS-A03:
    enabled: true

Clone this wiki locally