HIGH replay attackadonisjsdynamodb

Replay Attack in Adonisjs with Dynamodb

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

A replay attack occurs when an attacker intercepts a valid request and retransmits it to reproduce the original effect. In an AdonisJS application that uses DynamoDB as its primary data store, this risk is heightened when authentication or state verification is not tightly bound to the request context. AdonisJS does not enforce automatic request uniqueness, and DynamoDB’s idempotent write operations (e.g., PutItem with the same primary key) can make replayed mutations appear legitimate if the server cannot distinguish them from fresh requests.

The typical attack flow involves an authenticated user performing an action—such as transferring funds or updating a profile—that results in a signed HTTP request. An attacker captures the request, including headers, body, and signature, then replays it later. If the server relies only on static API keys, session cookies without nonce/timestamp validation, or lacks idempotency controls, the replay may succeed. DynamoDB streams or conditional writes that do not include a client-supplied nonce or timestamp can further enable replay because the service processes the request without detecting duplication.

AdonisJS middleware can inadvertently expose replay surfaces when it parses JSON payloads or cookies without validating a one-time value. For example, an endpoint that accepts a payment request via POST to /payments and writes to a DynamoDB table without a request_id uniqueness check may process the same payment multiple times. The framework’s body parser will accept the replayed payload, and the DynamoDB PutItem or UpdateItem operation may succeed if the primary key does not change, even though the business logic should reject duplicates.

Additionally, unauthenticated or weakly authenticated endpoints, such as public webhook URLs, increase risk. If an attacker knows or guesses a webhook signature format, they can replay crafted events to trigger side effects. The combination of AdonisJS route handling and DynamoDB’s eventual consistency model means that without explicit deduplication logic, the system may apply the same operation more than once, leading to double writes or state corruption.

Compliance frameworks such as OWASP API Top 10 (2023) A07:2021 — Identification and Authentication Failures highlight the need for replay resistance in authentication and transaction flows. Systems that store sensitive data in DynamoDB must ensure that requests are bound to a nonce or timestamp validated server-side to meet security expectations and audit requirements.

Dynamodb-Specific Remediation in Adonisjs — concrete code fixes

To mitigate replay attacks in an AdonisJS application using DynamoDB, implement server-side idempotency controls and strict request validation. The core strategy is to require a unique client-generated identifier (nonce or idempotency key) on each request and enforce its one-time use in DynamoDB before performing state-changing operations.

First, define a DynamoDB table that tracks processed idempotency keys with an appropriate TTL (time-to-live) to avoid indefinite storage. Use the AWS SDK for JavaScript within your AdonisJS provider or service layer.

import { DynamoDB } from "@aws-sdk/client-dynamodb";
import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";

const ddb = new DynamoDB({ region: "us-east-1" });

export async function recordIdempotency(key, ttl) {
  const now = Math.floor(Date.now() / 1000);
  await ddb.putItem({
    TableName: process.env.IDEMPOTENCY_TABLE,
    Item: {
      request_id: { S: key },
      expires_at: { N: String(now + ttl) },
    },
    ConditionExpression: "attribute_not_exists(request_id)",
  });
}

export async function isReplay(key) {
  const out = await ddb.getItem({
    TableName: process.env.IDEMPOTENCY_TABLE,
    Key: marshall({ request_id: key }),
  });
  return out.Item !== undefined;
}

In your route handler, validate the idempotency key before proceeding. For JSON payloads, expect a header such as Idempotency-Key; for forms, include it as a hidden field or part of the request body.

import { HttpContextContract } from "@ioc:Adonis/Core/HttpContext";
import { isReplay, recordIdempotency } from "../services/dynamodbIdempotency";

export default class PaymentsController {
  public async store({ request, response }: HttpContextContract) {
    const idempotencyKey = request.header("Idempotency-Key");
    if (!idempotencyKey) {
      return response.badRequest({ error: "Idempotency-Key header is required" });
    }

    const isDuplicate = await isReplay(idempotencyKey);
    if (isDuplicate) {
      return response.conflict({ error: "Duplicate request detected" });
    }

    // Record the key before performing side effects to avoid race conditions
    await recordIdempotency(idempotencyKey, 86400); // 24 hours TTL

    const payload = request.body();
    // Proceed with business logic, e.g., write to another DynamoDB table
    await ddb.putItem({
      TableName: process.env.TRANSACTIONS_TABLE,
      Item: marshall({
        transaction_id: `txn_${Date.now()}`,
        amount: { N: String(payload.amount) },
        currency: { S: payload.currency },
        request_id: { S: idempotencyKey },
      }),
    });

    return response.created({ status: "processed", request_id: idempotencyKey });
  }
}

For endpoints that rely on cookies or session tokens, bind a nonce to the session and require it in the request body or header. Rotate nonces after use and store them with a short expiration to reduce storage overhead. Also enforce HTTPS to prevent interception, and use short request time windows (e.g., reject requests with timestamps older than 2 minutes) to limit the window for replay.

When using DynamoDB conditional writes, include a client-supplied value such as a hash of the payload combined with a nonce. This ensures that even if the same primary key is used, the condition fails on replay if the payload differs. Combine this with CloudWatch metrics to detect unusual spikes in conditional check failures, which may indicate replay attempts.

Frequently Asked Questions

How does idempotency protect against replay attacks in AdonisJS with DynamoDB?
Idempotency requires a unique key per request; the server records the key in DynamoDB and rejects duplicates. This prevents the same request from being processed more than once, even if an attacker replays the exact payload and headers.
What headers or fields should I include to defend against replay attacks in AdonisJS APIs?
Include an Idempotency-Key header with a cryptographically random value per request, and optionally a timestamp or nonce in the body. Validate both server-side and store the key in DynamoDB with a TTL to enforce one-time use.