Unicode Normalization in Adonisjs with Dynamodb
Unicode Normalization in Adonisjs with Dynamodb — how this specific combination creates or exposes the vulnerability
When an AdonisJS application interacts with DynamoDB, Unicode normalization inconsistencies can create security risks that are not obvious in typical application code. AdonisJS, a Node.js web framework, handles strings as JavaScript values, while DynamoDB stores and indexes strings in a way that preserves the exact byte sequence provided. If an attacker supplies a specially crafted Unicode identifier—such as a user handle, API key name, or record key—that appears visually identical to an authorized value but has a different normalization form, the application may treat them as distinct keys while security logic incorrectly treats them as equivalent.
For example, consider a user profile lookup using a username. The legitimate username jose can be represented in composed form (NFC) as a single code point, or in decomposed form (NFD) as j followed by combining acute accents. Both render identically in a UI but have different binary representations. If AdonisJS normalizes incoming HTTP request parameters to NFC before business logic checks, but passes the raw user-supplied value to DynamoDB for a GetItem or Query, the lookup may fail to match the stored record. Conversely, if authorization checks compare normalized values while DynamoDB operations use the raw input, an attacker can bypass intended access controls by switching normalization forms.
This becomes an injection or privilege escalation vector when such identifiers are used in DynamoDB condition expressions or key schemas. If an application uses a composite key like PK = USER#username and SK = TOKEN#sessionId, and relies on exact key matches for isolation, a normalization mismatch can allow one user to inadvertently access another’s data if the comparison layer normalizes but the persistence layer does not. The risk is particularly pronounced in unauthenticated or weakly authenticated endpoints where input validation does not enforce a canonical normalization form before data reaches DynamoDB.
Because middleBrick tests unauthenticated attack surfaces and includes checks such as Input Validation and Property Authorization, it can surface these inconsistencies by probing endpoints that use DynamoDB-backed identifiers. The scanner does not fix the normalization mismatch, but its findings highlight where canonicalization should be enforced consistently across HTTP input, application logic, and DynamoDB key construction.
Dynamodb-Specific Remediation in Adonisjs — concrete code fixes
To mitigate Unicode normalization issues when using DynamoDB with AdonisJS, enforce a single canonical normalization form at the earliest boundary—typically before any business logic or database interaction—and ensure the same form is used for all DynamoDB operations. The following examples demonstrate how to apply this consistently in an AdonisJS resource controller that stores and retrieves user-specific data in a DynamoDB table.
Normalization Strategy and DynamoDB Interaction
Use Node’s built-in normalize method from the string prototype to convert all identifiers to NFC (or NFD, provided you apply it everywhere). Combine this with explicit key construction so that the value stored and the value queried are guaranteed to match.
import { normalize } from 'unorm';
export default class UsersController {
async getUser({ request, params }) {
// Normalize incoming identifier once at the edge
const normalizedUsername = normalize('NFC', params.username);
// Construct DynamoDB key using the normalized value
const params = {
TableName: process.env.DYNAMODB_TABLE,
Key: {
PK: `USER#${normalizedUsername}`,
SK: `PROFILE#self`
}
};
const dynamoDb = use('Adonis/Addons/DynamoDb');
const data = await dynamoDb.get(params).promise();
if (!data.Item) {
return response.notFound({ error: 'User not found' });
}
return data.Item;
}
async updateUser({ request, params }) {
const normalizedUsername = normalize('NFC', params.username);
const body = request.only(['email', 'displayName']);
const updateParams = {
TableName: process.env.DYNAMODB_TABLE,
Key: {
PK: `USER#${normalizedUsername}`,
SK: `PROFILE#self`
},
UpdateExpression: 'set email = :email, displayName = :displayName',
ExpressionAttributeValues: {
':email': body.email,
':displayName': body.displayName
},
ReturnValues: 'UPDATED_NEW'
};
const dynamoDb = use('Adonis/Addons/DynamoDb');
await dynamoDb.update(updateParams).promise();
return { message: 'updated' };
}
}
In this pattern, normalization is applied before the key is built and before the value is sent to DynamoDB. This ensures that lookups, condition expressions, and any secondary index queries use the same binary representation. If your application accepts identifiers in headers, cookies, or JSON bodies, normalize each before using them as part of a DynamoDB key or condition.
Preventing Cross-User Access with Canonical Keys
When designing key schemas that include user-controlled values, always normalize before concatenation. The following example shows a more complex scenario where a composite key includes a normalized tenant identifier and a normalized resource name, reducing the risk of one tenant’s data being exposed to another due to encoding differences.
import { normalize } from 'unorm';
export default class RecordsController {
async getRecord({ request, params }) {
const tenantId = normalize('NFC', params.tenantId);
const resourceName = normalize('NFC', params.resourceName);
const queryParams = {
TableName: process.env.DYNAMODB_TABLE,
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :sk)',
ExpressionAttributeValues: {
':pk': `TENANT#${tenantId}`,
':sk': `RESOURCE#${resourceName}`
}
};
const dynamoDb = use('Adonis/Addons/DynamoDb');
const results = await dynamoDb.query(queryParams).promise();
return results.Items;
}
}
For continuous protection in production, integrate the normalization step into input validation layers or route middleware so that every request enforces the canonical form before reaching service logic. middleBrick’s checks, such as Input Validation and Property Authorization, can highlight endpoints where identifiers are used in DynamoDB key constructions without consistent canonicalization.