HIGH unicode normalizationadonisjsapi keys

Unicode Normalization in Adonisjs with Api Keys

Unicode Normalization in Adonisjs with Api Keys — how this combination creates or exposes the vulnerability

Unicode normalization inconsistencies can cause security issues when API keys are handled in AdonisJS applications. An API key that appears identical may have different Unicode representations (e.g., composed vs. decomposed forms) due to the use of combining characters. If an application compares raw input strings without normalization, a key like café (where é is a single code point U+00E9) may not match a stored key café (where é is decomposed into e followed by U+0301 combining acute accent). This mismatch can be exploited to bypass authentication checks that rely on string equality, effectively allowing an attacker to use a visually identical but differently normalized key.

In AdonisJS, API keys are often stored in a database and compared during request authentication, for example in route middleware or via an auth:api style guard. If the stored key is normalized using one form (such as NFC) and the incoming request header or query parameter contains the key in another form (such as NFD), the comparison may evaluate to false, leading to either a denial of service for valid keys or, worse, an acceptance of a malformed key if normalization is applied inconsistently across storage and verification steps. Attackers can leverage this to attempt authentication bypass by submitting crafted payloads that exploit normalization differences.

This issue intersects with AdonisJS API key handling in several ways. AdonisJS does not automatically normalize incoming request data, including headers and query parameters, before using them in security-sensitive operations. If developers normalize stored keys but do not also normalize incoming values—or vice versa—they introduce a subtle vulnerability. For example, a middleware that normalizes only the database value but compares directly against the raw request header can be bypassed. Additionally, logging or error messages that reflect the raw key may inadvertently expose differently normalized forms, aiding an attacker in refining their payload. The combination of Unicode normalization edge cases and the use of static API keys in AdonisJS applications can therefore expose authentication mechanisms to bypass or enumeration attacks if not handled consistently.

Api Keys-Specific Remediation in Adonisjs — concrete code fixes

To mitigate Unicode normalization issues with API keys in AdonisJS, enforce a consistent normalization form both when storing keys and when comparing incoming requests. Use a standard Unicode normalization library to process keys before storage and before comparison. Below are concrete code examples demonstrating how to implement this remediation within an AdonisJS project.

First, install a Unicode normalization package such as unorm via npm:

npm install unorm

Next, create a reusable normalization utility in your project, for example at app/Helpers/unicode.ts:

import unorm from 'unorm';

export const normalizeApiKey = (input: string): string => {
  // Use NFC normalization to ensure composed form
  return unorm.nfkc(input);
};

When storing or indexing API keys, apply this normalization. For instance, in a controller that accepts a new API key or during a migration:

import { normalizeApiKey } from 'App/Helpers/unicode';

// Example: normalizing before save
const rawKey = request.input('api_key');
const safeKey = normalizeApiKey(rawKey);
await ApiKey.create({ key: safeKey, userId: user.id });

During authentication, normalize the incoming key before comparison. In an authentication provider or middleware:

import { normalizeApiKey } from 'App/Helpers/unicode';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';

export default class ApiKeyAuthProvider {
  public async authenticate(request: HttpContextContract) {
    const rawKey = request.header('X-API-Key') || request.input('api_key');
    if (!rawKey) {
      throw new Error('API key missing');
    }
    const normalizedKey = normalizeApiKey(rawKey);
    const apiKey = await ApiKey.query().where('key', normalizedKey).preload('user').first();
    if (!apiKey) {
      throw new Error('Invalid API key');
    }
    request.authUser = apiKey.user;
  }
}

Additionally, ensure that any logging or error handling does not leak differently normalized forms. For example, avoid echoing the raw key in responses or logs; instead, log a hashed or truncated representation if necessary. By normalizing at both storage and comparison points, you eliminate the risk of Unicode-based bypasses for API keys in AdonisJS applications.

Frequently Asked Questions

Why does Unicode normalization matter for API key security in AdonisJS?
Because visually identical keys can have different Unicode byte sequences. If stored and compared in different normalization forms, an attacker can bypass authentication by submitting a crafted equivalent key.
Does AdonisJS automatically normalize incoming request data?
No. AdonisJS does not normalize headers, query parameters, or body values by default. Developers must explicitly normalize input before using it in security-sensitive operations like API key comparison.