Skip to content

Commit 5672ab7

Browse files
committed
util: prevent tampering with internals in inspect()
This makes sure user options passed to `util.inspect()` will not override any internal properties (besides `stylize`). PR-URL: #26577 Fixes: #24765 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent 32853c0 commit 5672ab7

File tree

2 files changed

+31
-9
lines changed

2 files changed

+31
-9
lines changed

lib/internal/util/inspect.js

+21-7
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,16 @@ const meta = [
150150
'', '', '', '', '', '', '', '\\\\'
151151
];
152152

153+
function getUserOptions(ctx) {
154+
const obj = { stylize: ctx.stylize };
155+
for (const key of Object.keys(inspectDefaultOptions)) {
156+
obj[key] = ctx[key];
157+
}
158+
if (ctx.userOptions === undefined)
159+
return obj;
160+
return { ...obj, ...ctx.userOptions };
161+
}
162+
153163
/**
154164
* Echos the value of any input. Tries to print the value out
155165
* in the best way possible given the different types.
@@ -192,8 +202,16 @@ function inspect(value, opts) {
192202
ctx.showHidden = opts;
193203
} else if (opts) {
194204
const optKeys = Object.keys(opts);
195-
for (var i = 0; i < optKeys.length; i++) {
196-
ctx[optKeys[i]] = opts[optKeys[i]];
205+
for (const key of optKeys) {
206+
// TODO(BridgeAR): Find a solution what to do about stylize. Either make
207+
// this function public or add a new API with a similar or better
208+
// functionality.
209+
if (hasOwnProperty(inspectDefaultOptions, key) || key === 'stylize') {
210+
ctx[key] = opts[key];
211+
} else if (ctx.userOptions === undefined) {
212+
// This is required to pass through the actual user input.
213+
ctx.userOptions = opts;
214+
}
197215
}
198216
}
199217
}
@@ -522,14 +540,10 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
522540
maybeCustom !== inspect &&
523541
// Also filter out any prototype objects using the circular check.
524542
!(value.constructor && value.constructor.prototype === value)) {
525-
// Remove some internal properties from the options before passing it
526-
// through to the user function. This also prevents option manipulation.
527-
// eslint-disable-next-line no-unused-vars
528-
const { budget, seen, indentationLvl, ...plainCtx } = ctx;
529543
// This makes sure the recurseTimes are reported as before while using
530544
// a counter internally.
531545
const depth = ctx.depth === null ? null : ctx.depth - recurseTimes;
532-
const ret = maybeCustom.call(context, depth, plainCtx);
546+
const ret = maybeCustom.call(context, depth, getUserOptions(ctx));
533547
// If the custom inspection method returned `this`, don't go into
534548
// infinite recursion.
535549
if (ret !== context) {

test/parallel/test-util-inspect.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ util.inspect({ hasOwnProperty: null });
791791
}) };
792792
});
793793

794-
util.inspect(subject, { customInspectOptions: true });
794+
util.inspect(subject);
795795

796796
// util.inspect.custom is a shared symbol which can be accessed as
797797
// Symbol.for("nodejs.util.inspect.custom").
@@ -803,9 +803,11 @@ util.inspect({ hasOwnProperty: null });
803803

804804
subject[inspect] = (depth, opts) => {
805805
assert.strictEqual(opts.customInspectOptions, true);
806+
assert.strictEqual(opts.seen, null);
807+
return {};
806808
};
807809

808-
util.inspect(subject, { customInspectOptions: true });
810+
util.inspect(subject, { customInspectOptions: true, seen: null });
809811
}
810812

811813
{
@@ -816,6 +818,12 @@ util.inspect({ hasOwnProperty: null });
816818
`{ a: 123,\n [Symbol(${UIC})]: [Function: [${UIC}]] }`);
817819
}
818820

821+
// Verify that it's possible to use the stylize function to manipulate input.
822+
assert.strictEqual(
823+
util.inspect([1, 2, 3], { stylize() { return 'x'; } }),
824+
'[ x, x, x ]'
825+
);
826+
819827
// Using `util.inspect` with "colors" option should produce as many lines as
820828
// without it.
821829
{

0 commit comments

Comments
 (0)