Overview
Broken Object Property Level Authorization vulnerabilities occur when an API uses object-level authorization (for example, checking ownership of a resource) but fails to enforce access controls on individual properties within that object. In Hapi.js this often shows up when a route returns a resource containing sensitive fields or allows clients to influence which properties are returned, without validating that the requester should have access to those specific fields. Properly scoped property exposure is essential; otherwise, an attacker authenticated to a valid user account could view or manipulate fields they should not access. The CVE-2026-33180 case illustrates a related data exposure risk in HTTP workflows: HAPI FHIR (a Java implementation) prior to 6.9.0 could forward sensitive headers across redirects, underscoring how mismanaging boundaries and data exposure can occur in framework code paths. While CVE-2026-33180 targets a Java library, it highlights the broader risk pattern of data leakage across transport or object boundaries and the need for strict per-property controls in API backends. In Hapi.js, the vulnerability manifests when a route returns an object to the client without enforcing property-level authorization or filtering sensitive fields, enabling information disclosure or unintended modification of nested properties.
Code Fix Example
Hapi API Security Remediation
Vulnerable pattern (Hapi.js):
const Hapi = require('@hapi/hapi');
const Boom = require('@hapi/boom');
async function start() {
const server = Hapi.server({ port: 3000, host: '0.0.0.0' });
// Vulnerable: returns full document including sensitive fields
server.route({
method: 'GET',
path: '/documents/{id}',
handler: async (request) => {
const id = request.params.id;
// suppose this fetches: { id, ownerId, title, content, sensitiveData }
const doc = await getDocumentById(id);
return doc; // no per-property access control
}
});
// Authentication strategy assumed (e.g., token-based) and userId available at request.auth.credentials.userId
await server.start();
console.log('Server running');
}
start();
// Fixed: enforce object ownership and shape response to allowed properties only
const Hapi2 = require('@hapi/hapi');
const Boom2 = require('@hapi/boom');
async function startFixed() {
const server = Hapi2.server({ port: 3001, host: '0.0.0.0' });
server.route({
method: 'GET',
path: '/documents/{id}',
handler: async (request) => {
const id = request.params.id;
const userId = request.auth.credentials.userId;
const doc = await getDocumentById(id);
if (!doc) throw Boom2.notFound('Document not found');
// Property-level authorization: ensure requester owns the resource
if (doc.ownerId !== userId) throw Boom2.forbidden('Not authorized to access this document');
// Route-level DTO: expose only non-sensitive fields
return {
id: doc.id,
title: doc.title,
content: doc.content // content may be allowed, but ensure explicit policy on each field
};
}
});
await server.start();
console.log('Fixed server running');
}
startFixed();
function getDocumentById(id) {
// Placeholder for data fetch
return {
id,
ownerId: 'user-123',
title: 'Sample',
content: 'Confidential content',
sensitiveData: 'secret'
};
}