HIGH replay attackadonisjscockroachdb

Replay Attack in Adonisjs with Cockroachdb

Replay Attack in Adonisjs with Cockroachdb — how this specific combination creates or exposes the vulnerability

A replay attack in the context of Adonisjs communicating with Cockroachdb occurs when an attacker intercepts a valid request—such as an authenticated API call or a database query—and re-sends it at a later time to achieve unauthorized effects. Because Adonisjs applications often rely on stateless tokens (e.g., JWT) or session identifiers, and Cockroachdb serves as the distributed SQL backend, the risk centers around idempotent operations and lack of per-request uniqueness.

Specific to this stack, replay exposure commonly arises when endpoints that mutate state (POST/PUT/DELETE) do not enforce strict request uniqueness. For example, if an Adonisjs controller processes a payment or state change by issuing SQL against Cockroachdb without a nonce or timestamp, an attacker can capture a valid HTTP payload and replay it, causing duplicate transactions or unexpected state changes. Cockroachdb’s strong consistency guarantees mean that repeated, valid-looking writes are applied deterministically, which can amplify the impact of a replayed request.

Another vector involves token or session replay. If Adonisjs issues a JWT with a long lifetime and does not maintain server-side revocation state, an attacker can reuse the token to repeatedly access Cockroachdb-backed resources. Since Cockroachdb does not inherently understand application-level tokens, the database will honor requests that present valid credentials or connection parameters, making server-side enforcement in Adonisjs essential.

Additionally, without idempotency keys or request identifiers, retry logic in clients (e.g., HTTP libraries or ORM query retries) can inadvertently enable replay behavior. Adonisjs applications that use services like Database from @ioc:Adonis/Lucid may see retries on network glitches; if those retries are not guarded, the same mutation is applied multiple times against Cockroachdb.

To detect such issues, middleBrick scans an Adonisjs endpoint set against a Cockroachdb instance in black-box mode, exercising unauthenticated and authenticated paths to uncover missing nonce checks, absent idempotency mechanisms, and overly permissive token lifetimes. Findings include severity-ranked guidance on introducing request identifiers, short-lived tokens, and server-side replay detection.

Cockroachdb-Specific Remediation in Adonisjs — concrete code fixes

Remediation focuses on ensuring each request to Adonisjs is unique and verifiable before it reaches Cockroachdb, and on tightening token/session handling. Below are concrete, idiomatic Adonisjs code examples that integrate with Cockroachdb safely.

1. Idempotency key with UUID and database check

Require an Idempotency-Key header on mutating requests and store processed keys in Cockroachdb to reject duplicates.

import { DateTime } from 'luxon'
import { schema } from '@ioc:Adonis/Core/Validator'
import Database from '@ioc:Adonis/Lucid/Database'

export default class IdempotencyController {
  public async store({ request, response }) {
    const keySchema = schema.create({
      idempotencyKey: schema.string.optional(),
      payload: schema.object({}),
    })
    const { idempotencyKey, payload } = await request.validate({ schema: keySchema })
    const key = idempotencyKey || request.uuid()

    // Check if key already processed in Cockroachdb
    const existing = await Database.from('idempotency_keys').where('key', key).limit(1).first()
    if (existing) {
      return response.status(200).json({ cached: true, result: existing.response })
    }

    // Process the request (example: insert into a transactions table)
    const tx = await Database.transaction(async (trx) => {
      const result = await trx
        .from('transactions')
        .insert({ data: payload, createdAt: DateTime.local().toISO() })

      // Record idempotency key
      await trx.from('idempotency_keys').insert({
        key,
        response: result,
        expiresAt: DateTime.local().plus({ hours: 24 }).toISO(),
      })
      return result
    })

    return response.created({ transaction: tx })
  }
}

2. Short-lived JWTs and refresh token rotation

Issue short-lived access tokens and use rotating refresh tokens stored server-side (e.g., in Cockroachdb) to prevent token reuse.

import { BaseProvider, DatabaseProvider } from '@ioc:Adonis/Addons/Auth'
import Database from '@ioc:Adonis/Lucid/Database'
import { jwtVerify } from 'jose'

export class CustomSessionProvider extends BaseProvider {
  public async verify(token: string) {
    const { payload, protectedHeader } = await jwtVerify(token, new TextEncoder().encode(process.env.JWT_SECRET))
    const stored = await Database.from('refresh_tokens')
      .where('jti', payload.jti)
      .where('expires_at', '>', DateTime.local().toSQLDateTime())
      .first()

    if (!stored || stored.revoked) {
      throw new Error('Invalid or revoked token')
    }
    return { user_id: payload.sub, session_id: stored.id }
  }
}

3. Request timestamp and nonce validation

Require a recent timestamp and a one-time nonce to mitigate replays within the Adonisjs middleware layer.

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Database from '@ioc:Adonis/Lucid/Database'

export default async function replayProtection(ctx: HttpContextContract) {
  const timestamp = ctx.request.header('x-timestamp')
  const nonce = ctx.request.header('x-nonce')
  if (!timestamp || !nonce) {
    ctx.response.badRequest({ error: 'Missing replay protection headers' })
    return false
  }
  const windowMs = 5 * 60 * 1000 // 5 minutes
  const now = Date.now()
  if (Math.abs(now - parseInt(timestamp, 10)) > windowMs) {
    ctx.response.status(400).json({ error: 'Stale request' })
    return false
  }
  const seen = await Database.from('nonces').where('nonce', nonce).limit(1).first()
  if (seen) {
    ctx.response.status(400).json({ error: 'Replay detected' })
    return false
  }
  await Database.table('nonces').insert({ nonce, expiresAt: new Date(now + windowMs) })
  return true
}

4. Cockroachdb schema considerations

Define tables that support replay detection with appropriate TTLs to avoid unbounded growth.

-- idempotency_keys table
CREATE TABLE idempotency_keys (
  key STRING PRIMARY KEY,
  response JSONB,
  expires_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX ON idempotency_keys (expires_at);

-- nonces table with TTL via Cockroachdb's row-level TTL
CREATE TABLE nonces (
  nonce STRING PRIMARY KEY,
  expires_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX ON nonces (expires_at);

5. Middleware integration in Adonisjs routes

Apply the replay protection middleware to sensitive routes in your router file.

import Route from '@ioc:Adonis/Core/Route'
import replayProtection from 'App/Middleware/replay_protection'

Route.post('/payment', 'PaymentController.store').middleware([replayProtection])
Route.delete('/account/:id', 'AccountController.destroy').middleware([replayProtection])

Frequently Asked Questions

How does middleBrick detect replay vulnerabilities in an Adonisjs + Cockroachdb setup?
middleBrick performs black-box scans against your Adonisjs endpoints, exercising unauthenticated and authenticated flows to identify missing nonce checks, absent idempotency mechanisms, and long-lived tokens that enable replay against Cockroachdb.
Can replay attacks happen even if Cockroachdb enforces strong consistency?
Yes. Strong consistency ensures deterministic application of writes, but if Adonisjs does not guard against duplicate requests (missing idempotency or nonce checks), replayed mutations are applied reliably, leading to duplicate transactions or state changes.