Skip to content

Commit 7af1c12

Browse files
committed
feat: default options for controlling proto access
This commmit adds the runtime options - `allowProtoPropertiesByDefault` (boolean, default: false) and - `allowProtoMethodsByDefault` (boolean, default: false)` which can be used to allow access to prototype properties and functions in general. Specific properties and methods can still be disabled from access via `allowedProtoProperties` and `allowedProtoMethods` by setting the corresponding values to false. The methods `constructor`, `__defineGetter__`, `__defineSetter__`, `__lookupGetter__` and the property `__proto__` will be disabled, even if the allow...ByDefault-options are set to true. In order to allow access to those properties and methods, they have to be explicitly set to true in the 'allowedProto...'-options. A warning is logged when the a proto-access it attempted and denied by default (i.e. if no option is set by the user to make the access decision explicit)
1 parent 91a1b5d commit 7af1c12

File tree

6 files changed

+251
-120
lines changed

6 files changed

+251
-120
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { createNewLookupObject } from './create-new-lookup-object';
2+
3+
export function createProtoAccessControl(runtimeOptions) {
4+
let defaultMethodWhiteList = Object.create(null);
5+
defaultMethodWhiteList['constructor'] = false;
6+
defaultMethodWhiteList['__defineGetter__'] = false;
7+
defaultMethodWhiteList['__defineSetter__'] = false;
8+
defaultMethodWhiteList['__lookupGetter__'] = false;
9+
10+
let defaultPropertyWhiteList = Object.create(null);
11+
// eslint-disable-next-line no-proto
12+
defaultPropertyWhiteList['__proto__'] = false;
13+
14+
return {
15+
properties: {
16+
whitelist: createNewLookupObject(
17+
defaultPropertyWhiteList,
18+
runtimeOptions.allowedProtoProperties
19+
),
20+
defaultValue: runtimeOptions.allowProtoPropertiesByDefault
21+
},
22+
methods: {
23+
whitelist: createNewLookupObject(
24+
defaultMethodWhiteList,
25+
runtimeOptions.allowedProtoMethods
26+
),
27+
defaultValue: runtimeOptions.allowProtoMethodsByDefault
28+
}
29+
};
30+
}
31+
32+
export function resultIsAllowed(result, protoAccessControl, propertyName) {
33+
if (typeof result === 'function') {
34+
return checkWhiteList(protoAccessControl.methods, propertyName);
35+
} else {
36+
return checkWhiteList(protoAccessControl.properties, propertyName);
37+
}
38+
}
39+
40+
function checkWhiteList(protoAccessControlForType, propertyName) {
41+
if (protoAccessControlForType.whitelist[propertyName] !== undefined) {
42+
return protoAccessControlForType.whitelist[propertyName] === true;
43+
}
44+
if (protoAccessControlForType.defaultValue !== undefined) {
45+
return protoAccessControlForType.defaultValue;
46+
}
47+
48+
// eslint-disable-next-line no-console
49+
console.error(
50+
`Handlebars: Access has been denied to resolve the property "${propertyName}" because it is not an "own property" of its parent.\n` +
51+
`You can add a runtime option to disable the check or this warning:\n` +
52+
`See http://localhost:8080/api-reference/runtime-options.html#options-to-control-prototype-access for details`
53+
);
54+
return false;
55+
}

lib/handlebars/runtime.js

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import {
88
} from './base';
99
import { moveHelperToHooks } from './helpers';
1010
import { wrapHelper } from './internal/wrapHelper';
11-
import { createNewLookupObject } from './internal/createNewLookupObject';
11+
import {
12+
createProtoAccessControl,
13+
resultIsAllowed
14+
} from './internal/proto-access';
1215

1316
export function checkRevision(compilerInfo) {
1417
const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,
@@ -73,8 +76,7 @@ export function template(templateSpec, env) {
7376

7477
let extendedOptions = Utils.extend({}, options, {
7578
hooks: this.hooks,
76-
allowedProtoMethods: this.allowedProtoMethods,
77-
allowedProtoProperties: this.allowedProtoProperties
79+
protoAccessControl: this.protoAccessControl
7880
});
7981

8082
let result = env.VM.invokePartial.call(
@@ -126,15 +128,14 @@ export function template(templateSpec, env) {
126128
},
127129
lookupProperty: function(parent, propertyName) {
128130
let result = parent[propertyName];
131+
if (result == null) {
132+
return result;
133+
}
129134
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
130135
return result;
131136
}
132-
const whitelist =
133-
typeof result === 'function'
134-
? container.allowedProtoMethods
135-
: container.allowedProtoProperties;
136137

137-
if (whitelist[propertyName] === true) {
138+
if (resultIsAllowed(result, container.protoAccessControl, propertyName)) {
138139
return result;
139140
}
140141
return undefined;
@@ -237,6 +238,7 @@ export function template(templateSpec, env) {
237238
)
238239
);
239240
}
241+
240242
main = executeDecorators(
241243
templateSpec.main,
242244
main,
@@ -247,6 +249,7 @@ export function template(templateSpec, env) {
247249
);
248250
return main(context, options);
249251
}
252+
250253
ret.isTop = true;
251254

252255
ret._setup = function(options) {
@@ -271,21 +274,15 @@ export function template(templateSpec, env) {
271274
}
272275

273276
container.hooks = {};
274-
container.allowedProtoProperties = createNewLookupObject(
275-
options.allowedProtoProperties
276-
);
277-
container.allowedProtoMethods = createNewLookupObject(
278-
options.allowedProtoMethods
279-
);
277+
container.protoAccessControl = createProtoAccessControl(options);
280278

281279
let keepHelperInHelpers =
282280
options.allowCallsToHelperMissing ||
283281
templateWasPrecompiledWithCompilerV7;
284282
moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
285283
moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
286284
} else {
287-
container.allowedProtoProperties = options.allowedProtoProperties;
288-
container.allowedProtoMethods = options.allowedProtoMethods;
285+
container.protoAccessControl = options.protoAccessControl; // internal option
289286
container.helpers = options.helpers;
290287
container.partials = options.partials;
291288
container.decorators = options.decorators;

0 commit comments

Comments
 (0)