Stack Overflow in Fiber with Cockroachdb
Stack Overflow in Fiber with Cockroachdb — how this specific combination creates or exposes the vulnerability
A Stack Overflow vulnerability in a Go Fiber service that uses CockroachDB typically arises when unbounded recursion or deeply nested data structures are generated from database queries and then serialized into JSON for HTTP responses. With CockroachDB, this can occur when recursive common table expressions (CTEs) or hierarchical data (such as organizational trees or category hierarchies) are traversed without depth limits, and the resulting rows are mapped into Go structs that reference themselves. If the JSON encoder traverses these structures, it can exhaust the stack because each level of nesting adds a frame to the call stack. The combination of Fiber’s lightweight runtime, CockroachDB’s support for complex recursive queries, and missing controls on data depth creates a path to stack exhaustion under uncontrolled recursion.
Consider an endpoint that returns a full category tree from CockroachDB. A recursive CTE can return many rows with parent_id references that, when assembled into a tree in Go, produce deeply nested structs. If an attacker can influence query parameters (e.g., starting node or depth), they may be able to drive unbounded recursion either via the database CTE or via the application’s tree-building logic. This leads to large stack consumption and eventual crashes, a classic Stack Overflow vector. Even without direct user-supplied depth, misconfigured or overly permissive CTEs in CockroachDB can return unexpectedly deep hierarchies, and Fiber handlers that serialize the full structure without safeguards risk exhausting the runtime stack. The unauthenticated attack surface that middleBrick scans exposes such endpoints, revealing missing input validation and unsafe consumption patterns that can contribute to stack-related issues.
middleBrick identifies this as an Unsafe Consumption and Input Validation finding, highlighting that the API processes untrusted data shapes without adequate guardrails. Because CockroachDB can efficiently return hierarchical data, developers must enforce depth limits, apply pagination, or transform recursive structures into bounded representations before JSON serialization. This prevents the stack from growing in proportion to data depth and mitigates the risk of Stack Overflow in production deployments.
Cockroachdb-Specific Remediation in Fiber — concrete code fixes
To remediate Stack Overflow risks when using CockroachDB with Fiber, bound recursion at both the query and application layers. Use CockroachDB’s CTE options to enforce a maximum depth, and validate or cap depth in Go before constructing nested structures. Below are concrete, working examples that demonstrate safe patterns.
1. Limit recursion depth in CockroachDB CTE
Set a max_depth parameter in your recursive CTE and stop recursion when the limit is reached. This ensures the database never returns excessively deep hierarchies that could overflow the stack.
-- Example: retrieve category tree up to a safe depth
WITH RECURSIVE category_path AS (
SELECT
id,
parent_id,
name,
0 AS depth
FROM categories
WHERE id = $1
UNION ALL
SELECT
c.id,
c.parent_id,
c.name,
cp.depth + 1
FROM categories c
INNER JOIN category_path cp ON c.parent_id = cp.id
WHERE cp.depth < $2 -- enforce max depth
)
SELECT id, parent_id, name, depth FROM category_path;
2. Use bounded structs and avoid self-referential marshaling in Go
Define DTOs that do not contain direct self-references and cap nested levels during tree building. This prevents the JSON encoder from walking unbounded stacks.
// Safe DTOs with bounded nesting
type CategoryNodeDTO struct {
ID int64 `json:"id"`
Name string `json:"name"`
Children []CategoryNodeDTO `json:"children,omitempty"`
Depth int `json:"depth"`
}
// Build a bounded tree from rows returned by CockroachDB
func buildBoundedTree(rows *sql.Rows, maxDepth int) ([]CategoryNodeDTO, error) {
// Map to reconstruct tree; ensure depth is capped
nodes := make(map[int64]*CategoryNodeDTO)
var roots []CategoryNodeDTO
for rows.Next() {
var id, parentID sql.NullInt64
var name string
var depth int
if err := rows.Scan(&id, &parentID, &name, &depth); err != nil {
return nil, err
}
if depth > maxDepth {
continue // skip nodes beyond allowed depth
}
dto := &CategoryNodeDTO{
ID: id.Int64,
Name: name,
Depth: depth,
}
nodes[id.Int64] = dto
if parentID.Valid {
if parent, ok := nodes[parentID.Int64]; ok {
parent.Children = append(parent.Children, *dto)
}
} else {
roots = append(roots, *dto)
}
}
return roots, nil
}
// Handler using the bounded tree
app.Get("/categories/tree", func(c *fiber.Ctx) error {
const maxDepth = 5
rows, err := db.Query(`
WITH RECURSIVE category_path AS (
SELECT id, parent_id, name, 0 AS depth FROM categories WHERE id = $1
UNION ALL
SELECT c.id, c.parent_id, c.name, cp.depth + 1
FROM categories c INNER JOIN category_path cp ON c.parent_id = cp.id
WHERE cp.depth < $2
) SELECT id, parent_id, name, depth FROM category_path;
`, fiber.Locals(c).Get("categoryID"), maxDepth)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
defer rows.Close()
tree, err := buildBoundedTree(rows, maxDepth)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(tree)
});
3. Enforce pagination and field selection
Avoid returning entire subtrees. Use page-based limits and select only required columns to reduce payload and recursion depth.
// Paginated children query with depth limit
app.Get("/categories/children", func(c *fiber.Ctx) error {
parentID := c.QueryInt("parent_id")
page := c.QueryInt("page")
pageSize := c.QueryInt("page_size")
if page < 1 { page = 1 }
if pageSize < 1 || pageSize > 100 { pageSize = 20 }
offset := (page - 1) * pageSize
rows, err := db.Query(`
WITH RECURSIVE category_path AS (
SELECT id, parent_id, name, 0 AS depth
FROM categories
WHERE id = $1
UNION ALL
SELECT c.id, c.parent_id, c.name, cp.depth + 1
FROM categories c
INNER JOIN category_path cp ON c.parent_id = cp.id
WHERE cp.depth < $2
)
SELECT id, parent_id, name FROM category_path
ORDER BY id OFFSET $3 LIMIT $4;
`, parentID, 10, offset, pageSize)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
defer rows.Close()
var nodes []CategoryNodeDTO
for rows.Next() {
var dto CategoryNodeDTO
if err := rows.Scan(&dto.ID, &dto.Name); err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
nodes = append(nodes, dto)
}
return c.JSON(nodes)
});
By combining CockroachDB-level depth limits, bounded Go structs, and pagination, you mitigate Stack Overflow risks while preserving the ability to work with hierarchical data. middleBrick can help detect missing depth controls and unsafe consumption patterns, guiding you toward safer API designs.