HIGH time of check time of useadonisjscockroachdb

Time Of Check Time Of Use in Adonisjs with Cockroachdb

Time Of Check Time Of Use in Adonisjs with Cockroachdb — how this specific combination creates or exposes the permissions

Time Of Check Time Of Use (TOCTOU) occurs in AdonisJS when authorization checks and subsequent database operations are not performed as a single, atomic action. With CockroachDB, this can surface through its serializable isolation and the way AdonisJS handles transactions and model state. A typical pattern involves checking a user's role or ownership (e.g., via a policy or guard) and then performing a query that uses the checked identifier without re-verifying context immediately before the write.

In AdonisJS, this often appears in controller methods that first authorize a resource using a policy (e.g., authorize('PostPolicy', post)) and then execute a CockroachDB query using that resource’s ID. Because AdonisJS does not enforce that the authorization decision remains tied to the database operation, an attacker can manipulate state between the check and the use. For example, a user may be authorized to view one record, but before the CockroachDB query executes, they swap the ID parameter to access or modify another record that the policy would have denied.

CockroachDB’s distributed nature and support for strongly consistent serializable snapshots do not prevent TOCTOU; they only affect how concurrent writes are resolved. If AdonisJS issues separate authorization logic and SQL statements without a transaction or row-level predicate, the window remains. A concrete example is an endpoint that updates a document’s owner. The policy may confirm the user owns the document at check time, but if the update uses a raw query or a model merge that does not re-evaluate ownership with the same isolation context, the user can change the document ID in the request and gain unauthorized write access.

To map this to real-world attack patterns, consider an endpoint like PUT /documents/:id. An authorization check may verify that the requesting user is in the document’s team_id. However, if the handler uses the ID from the URL directly in a CockroachDB UPDATE without re-embedding the team constraint in the SQL predicate, a malicious user can change :id to point to another document in a different team. The check passed, but the use operates on unauthorized data.

Key contributing factors in this combination include reliance on pre-check attributes, lack of row-level security predicates in SQL, and missing transaction-scoped revalidation. AdonisJS policies are useful for request gating but must be augmented with CockroachDB-level constraints to ensure the check and use are inseparable.

Cockroachdb-Specific Remediation in Adonisjs — concrete code fixes

Remediation centers on binding authorization checks to the database operation so that the check and use occur within the same transactional context and cannot be decoupled by an attacker. In AdonisJS, use database transactions with explicit CockroachDB SQL or an ORM query that embeds ownership predicates directly in the WHERE clause, avoiding separate authorization followed by a raw identifier-based update.

1. Use a transaction to re-validate ownership immediately before the write. Begin a transaction, fetch the record with a team_id match, and proceed only if the record exists and belongs to the user. This ensures the check and use are atomic with respect to CockroachDB’s serializable guarantees.

import Base from '@ioc:Adonis/Lucid/Orm'
import Database from '@ioc:Adonis/Lucid/Database'

export default class DocumentsController {
  public async update({ params, request, auth }) {
    const user = await auth.getUser()
    const tx = Database.transaction()
    try {
      const doc = await tx
        .query()
        .table('documents')
        .where('id', params.id)
        .where('team_id', user.teamId)
        .limit(1)
        .forUpdate()
      if (!doc[0]) {
        tx.rollback()
        return Response.badRequest('Not authorized')
      }
      await tx
        .query()
        .table('documents')
        .where('id', params.id)
        .update({ title: request.input('title') })
      await tx.commit()
    } catch (error) {
      await tx.rollback()
      throw error
    }
  }
}

2. Embed ownership in the WHERE clause for direct updates, avoiding a prior SELECT check. This approach uses CockroachDB’s ability to match rows with both identifier and tenant/team context in a single statement, eliminating the gap between check and use.

import Database from '@ioc:Adonis/Lucid/Database'

export default class DocumentsController {
  public async update({ params, request, auth }) {
    const user = await auth.getUser()
    const result = await Database
      .update()
      .table('documents')
      .set({ title: request.input('title') })
      .where('id', params.id)
      .where('team_id', user.teamId)
      .returning(['id'])
    if (result.length === 0) {
      throw Response.forbidden('Document not found or access denied')
    }
  }
}

3. Combine AdonisJS policies with CockroachDB row-level constraints by ensuring policies do not simply return true/false but also inform the shape of the SQL predicate. For instance, a policy can compute a base query scope that includes team_id, which the controller then uses without further authorization checks.

// Policy method returning a scope helper
class PostPolicy {
  public scopeForUser(user, query) {
    return query.where('team_id', user.teamId)
  }
}

// Controller usage
export default class PostsController {
  public async index({ auth, view }) {
    const user = await auth.getUser()
    const scope = PostPolicy.prototype.scopeForUser(user, Database.query().table('posts'))
    const posts = await scope
    return view.render('posts/index', { posts })
  }
}

These patterns ensure that authorization is enforced at the point of data access on CockroachDB, closing the TOCTOU window by making the check inseparable from the use.

Frequently Asked Questions

Why doesn't CockroachDB's serializable isolation prevent TOCTOU on its own in AdonisJS?
CockroachDB's serializable isolation prevents write skew and concurrent anomalies, but it does not enforce that an application-level check and subsequent use are atomic. If AdonisJS performs authorization separately from the SQL operation, an attacker can alter input (such as an ID) between the check and the transaction, leading to privilege escalation or unauthorized access. The isolation level protects data consistency, not application logic sequencing.
Does using AdonisJS policies alone remove TOCTOU risk with CockroachDB?
No. AdonisJS policies are useful for request gating but do not bind the authorization decision to the database operation. Without embedding ownership predicates directly in the SQL WHERE clause or performing the check and use within the same transaction, there remains a time-of-check to time-of-use gap that can be exploited.