Time Of Check Time Of Use in Fiber with Cockroachdb
Time Of Check Time Of Use in Fiber with Cockroachdb — how this specific combination creates or exposes the vulnerability
Time Of Check Time Of Use (TOCTOU) is a race condition pattern that occurs when a system checks a condition (such as permissions or resource state) and later uses the result, but the state may change between the check and the use. In a Fiber application using Cockroachdb, this commonly appears in authorization flows where an endpoint first verifies a resource ownership or permission (check), then performs a mutation (use). Because Cockroachdb is a distributed SQL database with strong consistency guarantees but no server-side locking across transactions, the window between the SELECT check and the subsequent INSERT/UPDATE/DELETE can be exploited if the request context or parameters are mutable.
Consider a typical route where a user requests to update their own profile record. The handler may first query Cockroachdb to confirm the record exists and belongs to the requesting user (check), then execute an UPDATE based on the same identifier (use). If the identifier is user-supplied and not bound to the session or request context immutably, an attacker can swap the identifier between the SELECT and UPDATE by making concurrent requests. This can lead to BOLA (Broken Object Level Authorization) where one user modifies another user’s record, a pattern often flagged by middleBrick in its BOLA/IDOR checks and mapped to OWASP API Top 10 A01:2023.
When combined with Cockroachdb’s transaction semantics, TOCTOU can manifest in less obvious ways. Cockroachdb supports serializable isolation by default, which prevents some write skews, but it does not prevent read phenomena like non-repeatable reads unless explicit SELECT FOR UPDATE or equivalent application-level locking is used. If the Fiber handler performs a read, computes a next state in Go, and then writes in a separate transaction, the computed state may be based on stale data. middleBrick’s checks for BFLA/Privilege Escalation and Property Authorization often surface these patterns when the scan observes inconsistent authorization decisions across requests.
Real-world attack chains may chain TOCTOU with other issues such as missing rate limiting or unsafe consumption of user input. For example, an attacker could flood the endpoint with rapid identifier swaps while the handler is between check and use, increasing the chance of a successful unauthorized update. Because middleBrick runs 12 security checks in parallel, including Rate Limiting and Property Authorization, such combinations are detectable in the scan findings, which provide severity and remediation guidance rather than attempting to fix the issue automatically.
Instrumentation and observability do not remove the race; they only help detect it. In a distributed deployment, logs may show successful transactions but will not reveal the narrow authorization window without explicit tracing of the check-use sequence. The scanner’s LLM/AI Security module does not apply here, as this is a classical concurrency flaw, not a prompt or system-pipe issue. Ultimately, the responsibility lies in designing handlers so that the decision and the action are indivisible, which Cockroachdb facilitates through upserts and explicit locking statements within a single transaction.
middleBrick can highlight these risks in two ways: via its Web Dashboard by tracking scores over time as endpoints are rescanned, and via the CLI tool (middlebrick scan
Cockroachdb-Specific Remediation in Fiber — concrete code fixes
To eliminate TOCTOU in Fiber with Cockroachdb, ensure that authorization checks and state mutations occur within a single, well-defined transaction. Avoid two-step patterns where a SELECT is followed by a separate UPDATE/DELETE based on application-layer logic. Instead, perform the verification and the write inside one Cockroachdb transaction using SQL conditions or upserts, so the database enforces consistency.
Example 1: Safe upsert with condition
Use an UPDATE with a WHERE clause that includes ownership, and check rows affected. This removes the separate check and makes the operation atomic.
router.put('/profile/:id', async (c *fiber.Ctx) => {
userID := c.Locals("userID").(int64)
var req struct {
DisplayName string `json:"display_name"`
}
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid body"})
}
result, err := db.Exec(c.Context(), `
UPDATE profiles SET display_name = $1 WHERE id = $2 AND user_id = $3
`, req.DisplayName, c.Params("id"), userID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
rows, _ := result.RowsAffected()
if rows == 0 {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "not authorized or not found"})
}
return c.JSON(fiber.Map{"ok": true})
})
Example 2: Transaction with explicit verification
If business logic requires a read before write, keep both steps inside a Cockroachdb transaction and use SELECT FOR UPDATE to lock the row. This prevents concurrent modifications between check and use.
router.post('/transfer', async (c *fiber.Ctx) => {
tx, err := db.Begin(c.Context())
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
defer tx.Rollback(c.Context())
var fromBalance int64
err = tx.QueryRow(c.Context(), `
SELECT balance FROM accounts WHERE id = $1 FOR UPDATE
`, c.Locals("accountID")).Scan(&fromBalance)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "account not found"})
}
var req struct {
Amount int64 `json:"amount"`
ToID int64 `json:"to_id"`
}
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid body"})
}
// Business rule check inside the same transaction
if fromBalance < req.Amount {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "insufficient funds"})
}
_, err = tx.Exec(c.Context(), `
UPDATE accounts SET balance = balance - $1 WHERE id = $2
`, req.Amount, c.Locals("accountID"))
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
_, err = tx.Exec(c.Context(), `
UPDATE accounts SET balance = balance + $1 WHERE id = $2
`, req.Amount, req.ToID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
if err := tx.Commit(c.Context()); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"ok": true})
})
General guidance
- Prefer upserts and conditional writes to avoid separate check phases.
- When a read is necessary, use SELECT FOR UPDATE within a transaction to lock rows until the write completes.
- Validate and bind identifiers to the authenticated context rather than trusting request parameters alone.
- Use middleBrick’s CLI (middlebrick scan <url>) and Web Dashboard to verify that endpoints no longer show BOLA/IDOR or Property Authorization findings for these routes.
These patterns align with OWASP API Top 10 and help ensure that authorization decisions cannot be tampered with between check and use, reducing the attack surface exposed by TOCTOU in distributed SQL environments.