GRMustache ships with security features that help preventing untrusted templates and data to threaten your application.
The Runtime Guide describes how GRMustache looks for a key in your data objects:
- If the object responds to the
objectForKeyedSubscript:
method, this method is used. - Otherwise, if the key is safe, then the
valueForKey:
method is used. - Otherwise, the key is considered missed.
By default, a key is safe if it is backed by a declared Objective-C property, or a Core Data attribute (for managed objects).
The goal is to prevent valueForKey:
from accessing dangerous methods. Consider the code below:
@interface DBRecord : NSObject
- (void)deleteRecord;
@end
@implementation DBRecord
- (void)deleteRecord
{
NSLog(@"Oooops, your record was just deleted!");
}
@end
// Render a vilain template:
NSString *templateString = @"{{# records }}{{ deleteRecord }}{{/ records }}";
NSString *rendering = [GRMustacheTemplate renderObject:document
fromString:templateString
error:NULL];
Not being declared as a property, the deleteRecord
key is considered unsafe. The deleteRecord
method is not called.
If this default secure behavior does not fit your need, you can implement the safeMustacheKeys
method of the GRMustacheSafeKeyAccess
protocol in your object class:
+ (NSSet *)safeMustacheKeys;
This method returns the set of all keys you want to allow access to.
GRMustache ships with built-in implementation of safeMustacheKeys
for most immutable Foundation classes, so that you can freely use them in your templates: {{ array.count }}
, {{ set.anyObject}}
, {{ url.host }}
, etc. render as expected.
The full list of handled Foundation classes are: NSArray, NSAttributedString, NSData, NSDate, NSDateComponents, NSDecimalNumber, NSError, NSHashTable, NSIndexPath, NSIndexSet, NSMapTable, NSNotification, NSException, NSNumber, NSOrderedSet, NSPointerArray, NSSet, NSString, NSURL, and NSValue.
The objectForKeyedSubscript:
method is another way to go: it is considered safe, and there is no limitation on keys that can be accessed through this method.
If you know what you are doing, you can disable safe key access altogether, removing all limitations on the keys that can be accessed via the valueForKey:
method.
This can be done globally for all renderings:
// Grant GRMustache unsafe access to `valueForKey:`, so that we do not have
// to explicitly declare properties on objects that feed templates.
GRMustacheConfiguration *configuration = [GRMustacheConfiguration defaultConfiguration];
configuration.baseContext = [configuration.baseContext contextWithUnsafeKeyAccess];
GRMustacheConfiguration
is described in the Configuration Guide.
Safe key access can be disabled for a single template as well:
GRMustacheTemplate *template = [GRMustacheTemplate templateFrom...];
template.baseContext = [template.baseContext contextWithUnsafeKeyAccess];
See the GRMustacheContext Class Reference for a full documentation of the GRMustacheContext class.
The safe key access mechanism is directly inspired by fotonauts/handlebars-objc. Many thanks to Bertrand Guiheneuf.
As Mustache sections get nested, the context stack expands:
{{#person}}
{{#pet}}
{{name}} {{! the name of the pet of the person }}
{{/pet}}
{{/person}}
This is all good. However, the children contexts shadow their parents: keys get "redefined" as sections get nested:
{{#person}}
{{name}} {{! the person's name }}
{{#pet}}
{{name}} {{! the pet's name }}
{{/pet}}
{{/person}}
Key shadowing is a threat on robust and/or reusable partials, filters, rendering objects that process untrusted data in untrusted templates.
Because of untrusted data, you can not be sure that your precious keys won't be shadowed.
Because of untrusted templates, you can not be sure that your precious keys will be invoked with the correct syntax, should a syntax for navigating the context stack exist.
GRMustache addresses this concern by letting you store priority objects in the base context of a template.
The base context contains context stack values and tag delegates that are always available for the template rendering. It contains all the ready for use tools of the standard library, for example. Context objects are detailed in the Rendering Objects Guide.
You can extend it with a priority object with the extendBaseContextWithProtectedObject:
method:
id object = @{
@"safe": @"important",
};
GRMustacheTemplate *template = [GRMustacheTemplate templateFrom...];
[template extendBaseContextWithProtectedObject:object];
Now the safe
key can not be shadowed: it will always evaluate to the important
value.
See the GRMustacheTemplate Class Reference for a full discussion of extendBaseContextWithProtectedObject:
.
In order to explain how GRMustache behaves when you give priority to an object than contains other objects, let's use a metaphor:
Think of a priority object as a module in a programming language, and consider this Python snippet:
import string
print string.digits # 0123456789
print digits # NameError: "name 'digits' is not defined"
In Python, you need to provide the full path to an object inside a module, or you get an error. With GRMustache, access to objects inside priority objects is similar. Deep priority objects must be accessed via their full path:
Document.mustache
- {{string.digits}} {{! full path }}
- {{#string}}{{.digits}}{{/string}} {{! another kind of full path }}
- {{digits}} {{! digits? which digits? }}
- {{#string}}{{digits}}{{/string}} {{! digits? which digits? }}
Render.m
:
id modules = @{
@"string": @{
@"digits": @"0123456789"
},
};
GRMustacheTemplate *template = [GRMustacheTemplate templateFromResource:@"Document" bundle:nil error:NULL];
// "import string"
[template extendBaseContextWithProtectedObject:modules];
NSString *rendering = [template renderObject:nil error:NULL];
Final rendering:
- 0123456789
- 0123456789
-
-
See how the digits
key, alone on the third and fourth line, has not been rendered.
Conclusion: you must use full paths to your deep priority objects, or they won't be found.
The Mustache specification does not have any concept of security.
In particular, if your goal is to design templates that are compatible with other Mustache implementations, use priority objects with great care.