# ARGUS-A16: Missing or Unbounded Pool MaxConns Configuration > **Rule Code:** `ARGUS-A16` > **Identifier:** `MAX_CONNS_CONFIG` > **Severity:** `HIGH` (Process Thrashing, OOM & Connection Rejection Outage) > **Category:** `Resource & Connection Lifecycle Management` > **Target Standards:** CWE-400 (Uncontrolled Resource Consumption), OWASP ASVS v4.0.3/v5.0 §V1.4.3, PostgreSQL Capacity Sizing Standards --- ## 1. Overview & Core Invariant Every database connection pool initialization (`pgxpool.NewWithConfig` or `pgxpool.ParseConfig`) **must explicitly declare a safe upper bound on `MaxConns`**. Unbounded pools, omitted `MaxConns` properties (which default implicitly or scale dangerously with core count), and excessively large limits ($> 100$ per application instance) are strictly prohibited. Production application instances must constrain pool sizes between **10 and 50** connections unless routed through a dedicated transaction pooler (such as PgBouncer). --- ## 2. Technical Grounding & PostgreSQL Engine Realities ### 2.1. Process-Per-Connection Memory Footprint PostgreSQL allocates a dedicated OS process for every client connection (`postgres: user db host [idle]`). Each process consumes **5-10 MB** of RAM for session state, `work_mem`, and query execution buffers. ### 2.2. The Connection Saturation Catastrophe In Kubernetes or horizontally scaled container environments: $$\text{Total Active Connections} = \text{Replica Pod Count} \times \text{MaxConns Per Pod}$$ If 10 application pods run with `MaxConns = 100`, the PostgreSQL cluster can be subjected to 1,000 simultaneous backend processes. During traffic surges: 1. **CPU Context-Switching Thrashing:** The OS kernel spends more CPU cycles managing process context switches than executing SQL queries. 2. **Buffer Cache Contention:** Memory exhausts rapidly, triggering the Linux kernel Out-Of-Memory (OOM) killer. 3. **Connection Rejection:** Client connections are dropped with `FATAL: remaining connection slots are reserved for non-replication superuser connections`. ```mermaid flowchart TD subgraph OUTAGE ["Unbounded Connection Pool Explosion (CWE-400)"] direction TB Pods1["10 Application Pods (MaxConns = 100 / Unset)"] -->|Surge| DB1["1,000 PostgreSQL Backend Processes Spawned"] DB1 --> RAM["Memory Exhaustion (1000 x 10MB = 10GB private RAM)"] RAM --> Thrash["Severe CPU Lock Contention & Context-Switch Thrashing"] Thrash --> OOM["Linux OOM-Killer Terminates PostgreSQL Primary -> TOTAL OUTAGE!"] end subgraph STABLE ["Bounded Pool Configuration (COMPLIANT)"] direction TB Pods2["10 Application Pods (MaxConns = 20)"] -->|Surge| DB2["200 Total Backend Processes (Well within 300 limit)"] DB2 --> Fast["Optimal CPU Cache Locality & Zero Context Thrashing"] DB2 --> Predictable["Predictable Memory Footprint & 100% Request Stability"] end ``` --- ## 3. How Argus Detects Violations (Static Analysis Architecture) Argus evaluates connection pool initialization and assignment flow: ```mermaid flowchart LR Scan["Scan Go Files
(Exclude _test.go)"] --> Tracker["config_tracker.go:
Track pgxpool.Config Initialization"] Tracker --> DSNCheck{"Configured via DSN string
(pool_max_conns)?"} DSNCheck -->|Yes| EvalDSN["size_evaluator.go:
Evaluate Bound (1 <= N <= 100)"] DSNCheck -->|No| StructCheck{"cfg.MaxConns Assigned
in Go AST?"} StructCheck -->|No| ReportMissing["Report HIGH Violation:
MaxConns Not Configured"] StructCheck -->|Yes| EvalVal["size_evaluator.go:
Evaluate Integer Bound"] EvalVal --> BoundCheck{"1 <= MaxConns <= 100?"} BoundCheck -->|No| ReportExcess["Report HIGH Violation:
MaxConns Out of Safe Bounds"] BoundCheck -->|Yes| Pass["Pass (Safe Connection Bound)"] EvalDSN --> Pass ``` 1. **Configuration Tracker (`config_tracker.go`):** Tracks `pgxpool.Config` declarations, `ParseConfig` calls, and AST assignments. 2. **Size Evaluator (`size_evaluator.go`):** Asserts that configured `MaxConns` values reside within safe operational limits ($1 \le \text{MaxConns} \le 100$). --- ## 4. Vulnerability & Risk Taxonomy | Failure Mode | Technical Impact | Risk Severity | | :------------------------------ | :---------------------------------------------------------------------------------- | :------------ | | **Unset `MaxConns`** | Pool inherits default settings, risking uncontrolled scaling during traffic bursts. | **HIGH** | | **Giant Pool Limit ($> 100$)** | Squeezes database host memory and triggers CPU context-switching starvation. | **HIGH** | | **Zero or Negative `MaxConns`** | Causes driver initialization errors or unbounded connection growth. | **HIGH** | --- ## 5. Non-Compliant Code Patterns (Bad Examples) ### Example 1: Missing MaxConns Configuration ```go // VIOLATION: Configured timeouts without bounding MaxConns func NewPool(ctx context.Context, dsn string) (*pgxpool.Pool, error) { cfg, err := pgxpool.ParseConfig(dsn) if err != nil { return nil, err } // Flagged: Database connection pool does not configure safe MaxConns return pgxpool.NewWithConfig(ctx, cfg) } ``` ### Example 2: Excessively Large Pool Limit ```go // VIOLATION: Pool size exceeds safe multi-instance limits func NewPool(ctx context.Context, dsn string) (*pgxpool.Pool, error) { cfg, _ := pgxpool.ParseConfig(dsn) // Flagged: MaxConns (500) exceeds maximum safe limit (100) cfg.MaxConns = 500 return pgxpool.NewWithConfig(ctx, cfg) } ``` --- ## 6. Compliant Implementation Patterns (Good Examples) ### Solution 1: Explicit Bounded Pool Assignment ```go // COMPLIANT: Explicitly limits pool connections to a safe size func NewPool(ctx context.Context, dsn string) (*pgxpool.Pool, error) { cfg, err := pgxpool.ParseConfig(dsn) if err != nil { return nil, err } cfg.MaxConns = 25 return pgxpool.NewWithConfig(ctx, cfg) } ``` ### Solution 2: Environment-Based Pool Sizing with Clamping ```go // COMPLIANT: Reads environment variable with fallback upper limit func NewPool(ctx context.Context, dsn string) (*pgxpool.Pool, error) { cfg, err := pgxpool.ParseConfig(dsn) if err != nil { return nil, err } maxConns := int32(20) if val := os.Getenv("DB_MAX_CONNS"); val != "" { if parsed, err := strconv.Atoi(val); err == nil && parsed > 0 && parsed <= 50 { maxConns = int32(parsed) } } cfg.MaxConns = maxConns return pgxpool.NewWithConfig(ctx, cfg) } ``` --- ## 7. How to Suppress (Ignore Directives) For applications communicating via dedicated PgBouncer transaction poolers: ```go // argus:ignore-a16 routed via dedicated pgbouncer transaction pooler cfg.MaxConns = 250 ``` Alternatively, use the canonical identifier alias: ```go // argus:ignore MAX_CONNS_CONFIG stress test harness dedicated benchmark cfg.MaxConns = 500 ``` --- ## 8. Configuration Reference (`.argus.yaml`) Configure maximum permissible pool connection limits in `.argus.yaml`: ```yaml rules: ARGUS-A16: enabled: true max_allowed_conns: 100 ```