# 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.
```mermaid
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
```
### 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):
```mermaid
flowchart LR
Call["Detect Database Call
(Query, Exec, BeginTx, etc.)"] --> DirectCheck{"Is Argument
context.Background()
or context.TODO()?"}
DirectCheck -->|Yes| Report["Report Violation:
Unbounded Context"]
DirectCheck -->|No| LocalCheck{"Does Variable Resolve
to Raw Context?"}
LocalCheck -->|Yes| Report
LocalCheck -->|No| Pass["Pass (Safe Bounded Context)"]
```
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
```go
// 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
```go
// 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
```go
// 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)
```go
// 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
```go
// 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
```go
// 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:
```go
// argus:ignore ARGUS-A03 dedicated maintenance worker with external process watchdog
row := pool.QueryRow(context.Background(), maintenanceQuery)
```
Alternatively, use the identifier alias:
```go
// 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`:
```yaml
rules:
ARGUS-A03:
enabled: true
```