HIGH cache poisoningsailsdynamodb

Cache Poisoning in Sails with Dynamodb

Cache Poisoning in Sails with Dynamodb — how this specific combination creates or exposes the vulnerability

Cache poisoning in a Sails application that uses DynamoDB as a persistent or caching data store occurs when an attacker causes incorrect or malicious data to be written into the cache layer and subsequently served to other users or to the same user in different contexts. This typically maps to the OWASP API Top 10:2023 – Injection and Broken Object Level Authorization (BOLA) categories, especially when cache-control logic is coupled to DynamoDB query parameters or item keys.

In Sails, which follows a model-view-controller pattern, cache-related behavior often lives in policies or services that decide whether a request should be served from cache or should hit DynamoDB. If these decisions are based solely on user-supplied input—such as query parameters, path segments, or headers—without strict validation and canonicalization, an attacker can manipulate that input to change the cache key. A manipulated key may result in cache misses that forward the request to DynamoDB, or worse, write attacker-controlled values into the cache under keys that other users will later retrieve.

DynamoDB itself does not provide native cache poisoning protections; it stores and returns exactly what the application writes. Therefore, if Sails writes responses into DynamoDB based on tainted inputs, those tainted responses can persist and be reused. For example, an item ID derived from a URL parameter could allow an attacker to force writes to a shared item key, overwriting data that other users or services expect to see. Additionally, if Sails stores rendered API responses or authorization contexts in DynamoDB and uses unvalidated identifiers as keys, the poisoned cache can lead to privilege escalation or data leakage across users.

Because middleBrick tests unauthenticated attack surfaces and includes checks such as BOLA/IDOR and Input Validation, it can surface cache poisoning risks in this configuration by identifying weak key derivation, missing integrity checks, and insufficient authorization on data retrieval paths. The scanner also checks for excessive agency patterns and unsafe consumption, which can be relevant if Lambda functions or other automation write to DynamoDB on behalf of cached results.

Dynamodb-Specific Remediation in Sails — concrete code fixes

To mitigate cache poisoning when using DynamoDB with Sails, enforce strict input validation, canonicalize cache keys, and isolate data by tenant or user context. Below are concrete steps and code examples.

  • Validate and sanitize all inputs that influence cache keys or DynamoDB item identifiers. Use a validation library (e.g., joi) to enforce type, length, and pattern constraints.
// api/validators/cacheKey.js
'use strict';
const Joi = require('joi');

module.exports = {\n  friendlyName: 'Validate cache key',
  description: 'Ensure cache keys are safe and canonical.',
  inputs: {
    key: { type: 'string', required: true },
    userId: { type: 'string', required: true }
  },
  exits: {
    invalid: { statusCode: 400 },
    ok: { },
  },
  fn: function (inputs, exits) {
    const schema = Joi.object({
      key: Joi.string().pattern(/^[a-zA-Z0-9\-_]+$/).max(128).required(),
      userId: Joi.string().pattern(/^[a-zA-Z0-9\-]+$/).required()
    });
    const result = schema.validate(inputs);
    if (result.error) { return exits.invalid(); }
    // Canonical key format: userId:resourceType:normalizedKey
    const canonical = `${inputs.userId}:item:${result.value.key}`;
    return exits.ok({ canonical });
  }
};
  • Use canonical keys that incorporate user or tenant context to prevent key collisions and cross-user reads/writes. When reading from or writing to DynamoDB, derive keys from validated inputs combined with a user or tenant identifier.
// api/services/dynamo-cache.js
'use strict';
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient({ region: 'us-east-1' });

module.exports = {
  async getOrCreate(key, userId, fetchFromSource) {
    const { canonical } = await sails.helpers.validateCacheKey({ key, userId });
    const params = {
      TableName: 'AppCache',
      Key: { pk: canonical }
    };
    const cached = await docClient.get(params).promise();
    if (cached.Item && cached.Item.value != null) {
      return cached.Item.value;
    }
    const fresh = await fetchFromSource();
    await docClient.put({
      TableName: 'AppCache',
      Item: {
        pk: canonical,
        value: fresh,
        ttl: Math.floor(Date.now() / 1000) + 300 // 5 minutes
      }
    }).promise();
    return fresh;
  }
};
  • Enforce authorization on every DynamoDB operation by re-checking the relationship between the requesting user and the item’s owning user or tenant, rather than relying on cache presence alone.
// api/actions/get-user-item.js
module.exports = {
  friendlyName: 'Get user item with cache check',
  description: 'Retrieve an item from cache or DynamoDB with ownership verification.',
  inputs: {
    itemId: { type: 'string', required: true },
    requesterId: { type: 'string', required: true }
  },
  exits: {
    notFound: { statusCode: 404 },
    forbidden: { statusCode: 403 },
    ok: { },
  },
  fn: async function (inputs, exits) {
    const canonical = `${inputs.requesterId}:item:${inputs.itemId}`;
    const cached = await sails.helpers.getCacheEntry(canonical, inputs.requesterId);
    if (cached) { return exits.ok(cached); }
    // Direct DynamoDB fetch with ownership check
    const params = {
      TableName: 'UserItems',
      Key: { userId: inputs.requesterId, itemId: inputs.itemId }
    };
    const result = await docClient.get(params).promise();
    if (!result.Item) { return exits.notFound(); }
    // Ensure the item belongs to the requester (redundant check)
    if (result.Item.ownerId !== inputs.requesterId) { return exits.forbidden(); }
    await sails.helpers.cacheEntry(canonical, result.Item);
    return exits.ok(result.Item);
  }
};
  • Configure DynamoDB TTL to automatically expire stale entries and reduce the window for poisoned cache persistence.

By combining strict input validation, canonical and isolated keys, and re-verifying ownership on each DynamoDB access, Sails applications can avoid cache poisoning while still benefiting from performance gains.

Frequently Asked Questions

Why is cache key canonicalization important when using DynamoDB with Sails?
Canonicalization ensures that cache keys are deterministic and scoped to the correct user or tenant. Without it, attacker-controlled input can alter the key, causing cross-user cache reads/writes and enabling cache poisoning.
How does DynamoDB’s lack of native cache awareness increase risk in Sails applications?
DynamoDB stores and returns exactly what the application writes. If Sails writes responses based on tainted inputs into DynamoDB and later reuses those keys, poisoned data can be served to other users, leading to data leakage or privilege escalation.