Socket
Socket
Sign inDemoInstall

secure-handlebars

Package Overview
Dependencies
174
Maintainers
4
Versions
13
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.0.6 to 1.1.0

tests/samples/files/handlebarsjs_template_friend_filters.hbs

2

bower.json
{
"name": "secure-handlebars",
"version": "1.0.6",
"version": "1.1.0",
"main": "dist/secure-handlebars.min.js",

@@ -5,0 +5,0 @@ "authors": [

{
"name": "secure-handlebars",
"version": "1.0.6",
"version": "1.1.0",
"licenses": [

@@ -47,3 +47,3 @@ {

"html-decoder": "^1.0.2",
"xss-filters": "^1.2.0"
"xss-filters": "^1.2.2"
},

@@ -50,0 +50,0 @@ "devDependencies": {

@@ -106,4 +106,5 @@ SecureHandlebars

When output expressions are found inside dangerous (yet-to-be-supported) contexts, we echo warnings and gracefully fallback to apply the default Handlebars [`escapeExpression()`](http://handlebarsjs.com/#html-escaping). These warnings are indications of potential security exploits, and thus require closer inspections. Instead of simply abusing `{{{raw_expression}}}` to suppress the warnings, here are some alternative suggestions to secure your applications.
- Output expression in the `<script>` tag: <br/>`[WARNING] SecureHandlebars: Unsafe output expression found at scriptable <script> tag`
```html
- [WARNING] SecureHandlebars: Unsafe output expression found at scriptable `<script>` tag
```html
<!-- Rewrite <script>var strJS = {{strJS}};</script> as: -->

@@ -113,4 +114,7 @@ <input type="hidden" id="strJS" value="{{strJS}}">

```
- Output expression in an event attribute (e.g., `onclick=""`): <br/>`[WARNING] SecureHandlebars: Unsafe output expression found at onclick JavaScript event attribute`
```html
- [WARNING] SecureHandlebars: Unsafe output expression found at onclick JavaScript event attribute
- *Case 1.* the data is trusted, or will not be used as URI/HTML output
```html
<!-- Rewrite <div onclick="hello({{name}})"> as: -->

@@ -120,3 +124,32 @@ <div onclick="hello(this.getAttribute('data-name'))" data-name="{{name}}">

- *Case 2A.* the data will be used as URI/HTML output<br/>The contextual analyzer does not (cannot) evaluate your JavaScript code, and thus lacks the information on which contexts the data will be ultimately used. Therefore, you must manually apply the escaping filters including `uriData` (a patched `encodeURI()`), `uriComponentData` (alias of `encodeURIComponent()`), and the [xss-filters](https://github.com/yahoo/xss-filters#the-api) that are already registered as Handlebars helpers.
```html
<script>
function search(url, keyword) {
var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); // ...
document.getElementById('status').innerHTML = 'Searching for ' + keyword;
}
</script>
<!-- Rewrite <div onclick="search('/query?q={{keyword}}&lang=us', '{{keyword}}')"> as: -->
<div onclick="search(this.getAttribute('data-url'), this.getAttribute('data-keyword'))"
data-url="/query?q={{uriComponentData keyword}}&lang=us"
data-keyword="{{inHTMLData keyword}}">
```
The manually-applied filters here are to pre-escape `{{keyword}}` depending on the ultimate output contexts, while the `{{` `}}` is still needed (**NOT** `{{{ }}}`) to let `secure-handlebars` automatically applies the escaping filter for the immediate attribute value context.
- *Case 2B.* Alternatively, just in case the output pre-escaping is what you want to avoid, please embed the [xss-filters](https://github.com/yahoo/xss-filters/#client-side-browser) on the client-side for filtering.
```html
<script src="dist/xss-filters.min.js"></script>
<script>
function search(keyword) {
// ...
document.getElementById('status').innerHTML = 'Searching for ' + xssFilters.inHTMLData(keyword);
}
</script>
<div onclick="search(this.getAttribute('data-keyword'))" data-keyword="{{keyword}}">
```
## License

@@ -123,0 +156,0 @@

@@ -24,27 +24,3 @@ /*

/////////////////////////////////////////////////////
//
// TODO: need to move this code back to filter module
// the main concern is the update of the xss-filters
// does not reflect the change below.
//
/////////////////////////////////////////////////////
var filter = {
FILTER_NOT_HANDLE: 'y',
FILTER_DATA: 'yd',
FILTER_COMMENT: 'yc',
FILTER_ATTRIBUTE_VALUE_DOUBLE_QUOTED: 'yavd',
FILTER_ATTRIBUTE_VALUE_SINGLE_QUOTED: 'yavs',
FILTER_ATTRIBUTE_VALUE_UNQUOTED: 'yavu',
FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_DOUBLE_QUOTED: 'yced',
FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_SINGLE_QUOTED: 'yces',
FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_UNQUOTED: 'yceu',
FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_URL_UNQUOTED: 'yceuu',
FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_URL_DOUBLE_QUOTED: 'yceud',
FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_URL_SINGLE_QUOTED: 'yceus',
FILTER_ENCODE_URI: 'yu',
FILTER_ENCODE_URI_COMPONENT: 'yuc',
FILTER_URI_SCHEME_BLACKLIST: 'yubl',
FILTER_FULL_URI: 'yufull'
};
var filterMap = handlebarsUtils.filterMap;

@@ -515,3 +491,3 @@ // extracted from xss-filters

case stateMachine.State.STATE_RCDATA: // 3
return [filter.FILTER_DATA];
return [filterMap.DATA];

@@ -522,3 +498,3 @@ case stateMachine.State.STATE_RAWTEXT: // 5

if (tagName === 'xmp' || tagName === 'noembed' || tagName === 'noframes') {
return [filter.FILTER_NOT_HANDLE];
return [filterMap.NOT_HANDLE];
}

@@ -543,5 +519,5 @@

isFullUri = true;
f = filter.FILTER_FULL_URI;
f = filterMap.FULL_URI;
} else {
f = reEqualSign.test(attributeValue) ? filter.FILTER_ENCODE_URI_COMPONENT : filter.FILTER_ENCODE_URI;
f = reEqualSign.test(attributeValue) ? filterMap.ENCODE_URI_COMPONENT : filterMap.ENCODE_URI;
}

@@ -560,21 +536,21 @@ filters.push(f);

case cssParserUtils.STYLE_ATTRIBUTE_URL_UNQUOTED:
filters.push(filter.FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_URL_UNQUOTED);
filters.push(filterMap.ATTRIBUTE_VALUE_STYLE_EXPR_URL_UNQUOTED);
isFullUri = true;
break;
case cssParserUtils.STYLE_ATTRIBUTE_URL_SINGLE_QUOTED:
filters.push(filter.FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_URL_SINGLE_QUOTED);
filters.push(filterMap.ATTRIBUTE_VALUE_STYLE_EXPR_URL_SINGLE_QUOTED);
isFullUri = true;
break;
case cssParserUtils.STYLE_ATTRIBUTE_URL_DOUBLE_QUOTED:
filters.push(filter.FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_URL_DOUBLE_QUOTED);
filters.push(filterMap.ATTRIBUTE_VALUE_STYLE_EXPR_URL_DOUBLE_QUOTED);
isFullUri = true;
break;
case cssParserUtils.STYLE_ATTRIBUTE_UNQUOTED:
filters.push(filter.FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_UNQUOTED);
filters.push(filterMap.ATTRIBUTE_VALUE_STYLE_EXPR_UNQUOTED);
break;
case cssParserUtils.STYLE_ATTRIBUTE_SINGLE_QUOTED:
filters.push(filter.FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_SINGLE_QUOTED);
filters.push(filterMap.ATTRIBUTE_VALUE_STYLE_EXPR_SINGLE_QUOTED);
break;
case cssParserUtils.STYLE_ATTRIBUTE_DOUBLE_QUOTED:
filters.push(filter.FILTER_ATTRIBUTE_VALUE_STYLE_EXPR_DOUBLE_QUOTED);
filters.push(filterMap.ATTRIBUTE_VALUE_STYLE_EXPR_DOUBLE_QUOTED);
break;

@@ -588,9 +564,9 @@ case cssParserUtils.STYLE_ATTRIBUTE_ERROR:

case stateMachine.State.STATE_ATTRIBUTE_VALUE_DOUBLE_QUOTED:
f = filter.FILTER_ATTRIBUTE_VALUE_DOUBLE_QUOTED;
f = filterMap.ATTRIBUTE_VALUE_DOUBLE_QUOTED;
break;
case stateMachine.State.STATE_ATTRIBUTE_VALUE_SINGLE_QUOTED:
f = filter.FILTER_ATTRIBUTE_VALUE_SINGLE_QUOTED;
f = filterMap.ATTRIBUTE_VALUE_SINGLE_QUOTED;
break;
default: // stateMachine.State.STATE_ATTRIBUTE_VALUE_UNQUOTED:
f = filter.FILTER_ATTRIBUTE_VALUE_UNQUOTED;
f = filterMap.ATTRIBUTE_VALUE_UNQUOTED;
break;

@@ -603,3 +579,3 @@ }

/* blacklist the URI scheme for full uri */
filters.push(filter.FILTER_URI_SCHEME_BLACKLIST);
filters.push(filterMap.URI_SCHEME_BLACKLIST);
}

@@ -610,3 +586,3 @@ return filters;

/* we don't support js parser yet
* we use filter.FILTER_NOT_HANDLE to warn the developers for unsafe output expression,
* we use filterMap.NOT_HANDLE to warn the developers for unsafe output expression,
* and we fall back to default Handlebars escaping filter. IT IS UNSAFE.

@@ -621,9 +597,9 @@ */

case stateMachine.State.STATE_ATTRIBUTE_VALUE_DOUBLE_QUOTED:
f = filter.FILTER_ATTRIBUTE_VALUE_DOUBLE_QUOTED;
f = filterMap.ATTRIBUTE_VALUE_DOUBLE_QUOTED;
break;
case stateMachine.State.STATE_ATTRIBUTE_VALUE_SINGLE_QUOTED:
f = filter.FILTER_ATTRIBUTE_VALUE_SINGLE_QUOTED;
f = filterMap.ATTRIBUTE_VALUE_SINGLE_QUOTED;
break;
default: // stateMachine.State.STATE_ATTRIBUTE_VALUE_UNQUOTED:
f = filter.FILTER_ATTRIBUTE_VALUE_UNQUOTED;
f = filterMap.ATTRIBUTE_VALUE_UNQUOTED;
}

@@ -635,3 +611,3 @@ filters.push(f);

/* blacklist the URI scheme for full uri */
filters.push(filter.FILTER_URI_SCHEME_BLACKLIST);
filters.push(filterMap.URI_SCHEME_BLACKLIST);
}

@@ -642,7 +618,7 @@ return filters;

case stateMachine.State.STATE_COMMENT: // 48
return [filter.FILTER_COMMENT];
return [filterMap.COMMENT];
/* the following are those unsafe contexts that we have no plans to support (yet?)
* we use filter.FILTER_NOT_HANDLE to warn the developers for unsafe output expression,
* we use filterMap.NOT_HANDLE to warn the developers for unsafe output expression,
* and we fall back to default Handlebars escaping filter. IT IS UNSAFE.

@@ -690,3 +666,3 @@ */

}
return [filter.FILTER_NOT_HANDLE];
return [filterMap.NOT_HANDLE];
}

@@ -753,3 +729,3 @@ };

*/
ContextParserHandlebars.prototype.handleEscapeAndRawTemplate = function(input, i, stateObj) {
ContextParserHandlebars.prototype.handleEscapeAndRawTemplate = function(input, i, parser) {

@@ -777,3 +753,3 @@ /* the max length of the input string */

/* handleEscapeExpression and no validation need, it is safe guard in buildAst function */
obj = this.handleEscapeExpression(input, i, len, stateObj, true);
obj = this.handleEscapeExpression(input, i, len, parser, true);
return;

@@ -805,5 +781,5 @@ default:

*/
ContextParserHandlebars.prototype.handleEscapeExpression = function(input, i, len, stateObj, saveToBuffer) {
ContextParserHandlebars.prototype.handleEscapeExpression = function(input, i, len, parser, saveToBuffer) {
var msg, exceptionObj,
obj = {};
obj = {}, filters, re;

@@ -817,7 +793,26 @@ obj.str = '';

/* parse expression */
var re = handlebarsUtils.isValidExpression(input, i, handlebarsUtils.ESCAPE_EXPRESSION),
filters = [];
re = handlebarsUtils.isValidExpression(input, i, handlebarsUtils.ESCAPE_EXPRESSION);
/* get the customized filter based on the current HTML5 state before the Handlebars template expression. */
filters = this.addFilters(stateObj, input);
/* add the private filters according to the contextual analysis.
* no existing filters customized by developers will be found here */
filters = this.addFilters(parser, input);
/* if any of the provided manual filters is used as the last helper of an output expression,
* and it is residing in a dbl/sgl-quoted attr
* then, insert the ampersand filter (ya) to 'html encode' the value
*/
if (re.isSingleID === false && re[1] &&
(parser.getCurrentState() === stateMachine.State.STATE_ATTRIBUTE_VALUE_DOUBLE_QUOTED ||
parser.getCurrentState() === stateMachine.State.STATE_ATTRIBUTE_VALUE_SINGLE_QUOTED)
) {
var friendsFilters = handlebarsUtils.mFilterList,
m, ll = friendsFilters.length;
for(m=0; m<ll; ++m) {
if (friendsFilters[m] === re[1]) {
filters.unshift(filterMap.AMPERSAND);
break;
}
}
}
for(var k=filters.length-1;k>=0;--k) {

@@ -824,0 +819,0 @@ //

@@ -23,2 +23,39 @@ /*

HandlebarsUtils.pFilterList = ['y', 'ya', 'yd', 'yc', 'yavd', 'yavs', 'yavu', 'yu', 'yuc', 'yubl', 'yufull', 'yceu', 'yced', 'yces', 'yceuu', 'yceud', 'yceus'];
HandlebarsUtils.mFilterList = (function(){
// the handlebars-specific uriData and uriComponent are excluded here since it's safe for them to skip any further process relying on mFilterList
var baseContexts = ['HTMLData', 'HTMLComment', 'SingleQuotedAttr', 'DoubleQuotedAttr', 'UnQuotedAttr'],
contextPrefixes = ['in', 'uriIn', 'uriPathIn', 'uriQueryIn', 'uriComponentIn', 'uriFragmentIn'],
filters = [], prefix, baseContext, i, j;
// register below the filters that might be manually applied by developers
for (i = 0; (prefix = contextPrefixes[i]); i++) {
for (j = 0; (baseContext = baseContexts[j]); j++) {
filters.push(prefix + baseContext);
}
}
return filters;
}());
HandlebarsUtils.filterMap = {
NOT_HANDLE: 'y',
DATA: 'yd',
COMMENT: 'yc',
AMPERSAND: 'ya',
ATTRIBUTE_VALUE_DOUBLE_QUOTED: 'yavd',
ATTRIBUTE_VALUE_SINGLE_QUOTED: 'yavs',
ATTRIBUTE_VALUE_UNQUOTED: 'yavu',
ATTRIBUTE_VALUE_STYLE_EXPR_DOUBLE_QUOTED: 'yced',
ATTRIBUTE_VALUE_STYLE_EXPR_SINGLE_QUOTED: 'yces',
ATTRIBUTE_VALUE_STYLE_EXPR_UNQUOTED: 'yceu',
ATTRIBUTE_VALUE_STYLE_EXPR_URL_UNQUOTED: 'yceuu',
ATTRIBUTE_VALUE_STYLE_EXPR_URL_DOUBLE_QUOTED: 'yceud',
ATTRIBUTE_VALUE_STYLE_EXPR_URL_SINGLE_QUOTED: 'yceus',
ENCODE_URI: 'yu',
ENCODE_URI_COMPONENT: 'yuc',
URI_SCHEME_BLACKLIST: 'yubl',
FULL_URI: 'yufull'
};
/* type of expression */

@@ -299,4 +336,5 @@ HandlebarsUtils.UNHANDLED_EXPRESSION = -1;

module.exports = HandlebarsUtils;
})();

@@ -77,2 +77,2 @@ if (typeof Object.create != 'function') {

};
}
}

@@ -108,2 +108,2 @@ /*! http://mths.be/codepointat v0.1.0 by @mathias */

}());
}
}

@@ -13,8 +13,17 @@ /*

xssFilters = require('xss-filters'),
handlebarsUtils = require('./handlebars-utils.js');
handlebarsUtils = require('./handlebars-utils.js'),
privateFilterList = handlebarsUtils.pFilterList,
manualFilterList = handlebarsUtils.mFilterList,
hbsCreate = Handlebars.create;
var hbsCreate = Handlebars.create,
privateFilters = ['yd', 'yc', 'yavd', 'yavs', 'yavu', 'yu', 'yuc', 'yubl', 'yufull', 'yceu', 'yced', 'yces', 'yceuu', 'yceud', 'yceus'],
baseContexts = ['HTMLData', 'HTMLComment', 'SingleQuotedAttr', 'DoubleQuotedAttr', 'UnQuotedAttr'],
contextPrefixes = ['in', 'uriIn', 'uriPathIn', 'uriQueryIn', 'uriComponentIn', 'uriFragmentIn'];
// don't escape SafeStrings, since they're already safe according to Handlebars
// Reference: https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/utils.js#L63-L82
function safeStringCompatibleFilter (filterName) {
return function (s) {
// Unlike escapeExpression(), return s instead of s.toHTML() since downstream
// filters of the same chain has to be disabled too.
// Handlebars will invoke SafeString.toString() at last during data binding
return (s && s.toHTML) ? s : xssFilters._privFilters[filterName](s);
};
}

@@ -25,6 +34,4 @@ function overrideHbsCreate() {

pc = h.precompile,
privFilters = xssFilters._privFilters,
i, j, filterName, prefix, baseContext;
i, filterName;
// expose preprocess function

@@ -64,29 +71,16 @@ h.preprocess = function (template, options) {

// make y refer to the default escape function
h.registerHelper('y', Handlebars.escapeExpression);
// don't escape SafeStrings, since they're already safe according to Handlebars
// Reference: https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/utils.js#L63-L82
function safeStringCompatibleFilter (filterName) {
return function (s) {
// Unlike escapeExpression(), return s instead of s.toHTML() since downstream
// filters of the same chain has to be disabled too.
// Handlebars will invoke SafeString.toString() at last during data binding
return (s && s.toHTML) ? s : privFilters[filterName](s);
};
}
/*jshint -W083 */
// register below the filters that are automatically applied by context parser
for (i = 0; (filterName = privateFilters[i]); i++) {
for (i = 0; (filterName = privateFilterList[i]); i++) {
h.registerHelper(filterName, safeStringCompatibleFilter(filterName));
}
// register below the filters that might be manually applied by developers
for (i = 0; (prefix = contextPrefixes[i]); i++) {
for (j = 0; (baseContext = baseContexts[j]); j++) {
filterName = prefix + baseContext;
h.registerHelper(filterName, xssFilters[filterName]);
}
// override the default y to refer to the Handlebars escape function
h.registerHelper('y', Handlebars.escapeExpression);
// register below the filters that are designed for manual application
for (i = 0; (filterName = manualFilterList[i]); i++) {
h.registerHelper(filterName, xssFilters[filterName]);
}
h.registerHelper('uriComponentData', xssFilters._privFilters.yuc);
h.registerHelper('uriData', xssFilters._privFilters.yublf);

@@ -93,0 +87,0 @@ return h;

@@ -36,3 +36,3 @@ /*

'uriFragmentInSingleQuotedAttr', 'uriFragmentInDoubleQuotedAttr', 'uriFragmentInUnQuotedAttr', 'uriFragmentInHTMLData', 'uriFragmentInHTMLComment',
'y',
'y', 'ya',
'yd', 'yc',

@@ -42,3 +42,4 @@ 'yavd', 'yavs', 'yavu',

'yubl', 'yufull',
'yceu', 'yced', 'yces', 'yceuu', 'yceud', 'yceus'
'yceu', 'yced', 'yces', 'yceuu', 'yceud', 'yceus',
'uriData', 'uriComponentData'

@@ -45,0 +46,0 @@ ].forEach(function (filterName) {

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc