Information Disclosure
Description
The commit changes util.inspect's handling of Error objects to only consider own properties for the 'cause' and 'errors' fields. Previously, non-own (prototype-inherited) properties named 'cause' or 'errors' could be revealed by util.inspect due to the use of the 'in' operator. This could leak internal or prototype-level information when debugging errors. The fix aligns inspect behavior with other parts of the codebase by ensuring only own properties are considered for these fields, reducing the risk of information disclosure via error objects.
Proof of Concept
// PoC: Demonstrate information leakage via util.inspect before the fix
const util = require('util');
// Create an object with an inherited 'cause' property (not own property)
const inherited = { cause: [new Error('secret')] };
const err = Object.create(inherited);
err.message = 'test';
// Before the fix, util.inspect(err) could reveal the inherited 'cause'
console.log(util.inspect(err));
// After the fix (in Node.js versions containing this commit), the inherited 'cause' would not be shown
Commit Details
Author: Ruben Bridgewater
Date: 2025-12-22 13:06 UTC
Message:
util: limit `inspect` to only show own properties
`Error`'s `cause` and `errors` properties would be visible even if these
were not own properties. This is changed to align with all other
parts of the `inspect` handling.
Fixes: https://github.com/nodejs/node/issues/60717
PR-URL: https://github.com/nodejs/node/pull/61032
Reviewed-By: Jordan Harband <ljharb@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Triage Assessment
Vulnerability Type: Information Disclosure
Confidence: HIGH
Reasoning:
The change prevents util.inspect from displaying non-own (prototype-inherited) cause and errors properties of Error objects, reducing unintended information disclosure from error objects. This aligns with security best practices to avoid leaking internal properties during debugging.
Verification Assessment
Vulnerability Type: Information Disclosure
Confidence: HIGH
Affected Versions: pre-25.9.0
Code Diff
diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js
index d5da624ed5a16e..bd9be20548eabd 100644
--- a/lib/internal/util/inspect.js
+++ b/lib/internal/util/inspect.js
@@ -1961,7 +1961,7 @@ function formatError(err, constructor, tag, ctx, keys) {
}
name ??= 'Error';
- if ('cause' in err &&
+ if (ObjectPrototypeHasOwnProperty(err, 'cause') &&
(keys.length === 0 || !ArrayPrototypeIncludes(keys, 'cause'))) {
ArrayPrototypePush(keys, 'cause');
}
@@ -1969,7 +1969,7 @@ function formatError(err, constructor, tag, ctx, keys) {
// Print errors aggregated into AggregateError
try {
const errors = err.errors;
- if (ArrayIsArray(errors) &&
+ if (ArrayIsArray(errors) && ObjectPrototypeHasOwnProperty(err, 'errors') &&
(keys.length === 0 || !ArrayPrototypeIncludes(keys, 'errors'))) {
ArrayPrototypePush(keys, 'errors');
}
diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js
index d64c676d6403f9..e60320d0591233 100644
--- a/test/parallel/test-util-inspect.js
+++ b/test/parallel/test-util-inspect.js
@@ -688,6 +688,53 @@ assert.strictEqual(util.inspect(-5e-324), '-5e-324');
);
}
+{
+ // No own errors or cause property.
+ const { stackTraceLimit } = Error;
+ Error.stackTraceLimit = 0;
+
+ const e1 = new Error('e1');
+ const e2 = new TypeError('e2');
+ const e3 = false;
+
+ const errors = [e1, e2, e3];
+ const aggregateError = new AggregateError(errors, 'Foobar');
+
+ assert.deepStrictEqual(aggregateError.errors, errors);
+ assert.strictEqual(
+ util.inspect(aggregateError),
+ '[AggregateError: Foobar] {\n [errors]: [ [Error: e1], [TypeError: e2], false ]\n}'
+ );
+
+
+ const custom = new Error('No own errors property');
+ Object.setPrototypeOf(custom, aggregateError);
+
+ assert.strictEqual(
+ util.inspect(custom),
+ '[AggregateError: No own errors property]'
+ );
+
+ const cause = [new Error('cause')];
+ const causeError = new TypeError('Foobar', { cause: [new Error('cause')] });
+
+ assert.strictEqual(
+ util.inspect(causeError),
+ '[TypeError: Foobar] { [cause]: [ [Error: cause] ] }'
+ );
+
+ const custom2 = new Error('No own cause property');
+ Object.setPrototypeOf(custom2, causeError);
+
+ assert.deepStrictEqual(custom2.cause, cause);
+ assert.strictEqual(
+ util.inspect(custom2),
+ '[TypeError: No own cause property]'
+ );
+
+ Error.stackTraceLimit = stackTraceLimit;
+}
+
{
const tmp = Error.stackTraceLimit;
// Force stackTraceLimit = 0 for this test, but make it non-enumerable