-
Notifications
You must be signed in to change notification settings - Fork 0
ARGUS A16
Rule Code:
ARGUS-A16Identifier:MAX_CONNS_CONFIGSeverity:HIGH(Process Thrashing, OOM & Connection Rejection Outage) Category:Resource & Connection Lifecycle ManagementTarget Standards: CWE-400 (Uncontrolled Resource Consumption), OWASP ASVS v4.0.3/v5.0 §V1.4.3, PostgreSQL Capacity Sizing Standards
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 (
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.
In Kubernetes or horizontally scaled container environments:
If 10 application pods run with MaxConns = 100, the PostgreSQL cluster can be subjected to 1,000 simultaneous backend processes. During traffic surges:
- CPU Context-Switching Thrashing: The OS kernel spends more CPU cycles managing process context switches than executing SQL queries.
- Buffer Cache Contention: Memory exhausts rapidly, triggering the Linux kernel Out-Of-Memory (OOM) killer.
-
Connection Rejection: Client connections are dropped with
FATAL: remaining connection slots are reserved for non-replication superuser connections.
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
Argus evaluates connection pool initialization and assignment flow:
flowchart LR
Scan["Scan Go Files<br/>(Exclude _test.go)"] --> Tracker["config_tracker.go:<br/>Track pgxpool.Config Initialization"]
Tracker --> DSNCheck{"Configured via DSN string<br/>(pool_max_conns)?"}
DSNCheck -->|Yes| EvalDSN["size_evaluator.go:<br/>Evaluate Bound (1 <= N <= 100)"]
DSNCheck -->|No| StructCheck{"cfg.MaxConns Assigned<br/>in Go AST?"}
StructCheck -->|No| ReportMissing["Report HIGH Violation:<br/>MaxConns Not Configured"]
StructCheck -->|Yes| EvalVal["size_evaluator.go:<br/>Evaluate Integer Bound"]
EvalVal --> BoundCheck{"1 <= MaxConns <= 100?"}
BoundCheck -->|No| ReportExcess["Report HIGH Violation:<br/>MaxConns Out of Safe Bounds"]
BoundCheck -->|Yes| Pass["Pass (Safe Connection Bound)"]
EvalDSN --> Pass
-
Configuration Tracker (
config_tracker.go): Trackspgxpool.Configdeclarations,ParseConfigcalls, and AST assignments. -
Size Evaluator (
size_evaluator.go): Asserts that configuredMaxConnsvalues reside within safe operational limits ($1 \le \text{MaxConns} \le 100$ ).
| Failure Mode | Technical Impact | Risk Severity |
|---|---|---|
Unset MaxConns |
Pool inherits default settings, risking uncontrolled scaling during traffic bursts. | HIGH |
| Giant Pool Limit ( |
Squeezes database host memory and triggers CPU context-switching starvation. | HIGH |
Zero or Negative MaxConns |
Causes driver initialization errors or unbounded connection growth. | HIGH |
// 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)
}// 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)
}// 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)
}// 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)
}For applications communicating via dedicated PgBouncer transaction poolers:
// argus:ignore-a16 routed via dedicated pgbouncer transaction pooler
cfg.MaxConns = 250Alternatively, use the canonical identifier alias:
// argus:ignore MAX_CONNS_CONFIG stress test harness dedicated benchmark
cfg.MaxConns = 500Configure maximum permissible pool connection limits in .argus.yaml:
rules:
ARGUS-A16:
enabled: true
max_allowed_conns: 100