jashkenas/underscore · error · Error
variable is not a bare identifier:
Error message
variable is not a bare identifier:
What it means
When you compile an Underscore template with a {variable: 'name'} setting, the name is interpolated verbatim into generated JS, so it must be a single bare identifier (e.g. 'data'), not a dotted path ('a.b') or an expression ('data; process.exit(1)'). The bareIdentifier test guards against code injection via the variable name (CVE-2021-23358), and throws an Error when it fails.
Source
Thrown at modules/template.js:72
index = offset + match.length;
if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
} else if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
} else if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}
// Adobe VMs need the match returned to produce the correct offset.
return match;
});
source += "';\n";
var argument = settings.variable;
if (argument) {
// Insure against third-party code injection. (CVE-2021-23358)
if (!bareIdentifier.test(argument)) throw new Error(
'variable is not a bare identifier: ' + argument
);
} else {
// If a variable is not specified, place data values in local scope.
source = 'with(obj||{}){\n' + source + '}\n';
argument = 'obj';
}
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + 'return __p;\n';
var render;
try {
render = new Function(argument, '_', source);
} catch (e) {
e.source = source;
throw e;View on GitHub (pinned to e70d5bd070)
Solutions
- Use a single valid JavaScript identifier for the variable setting, e.g. _.template(t, {variable: 'data'}).
- If you need a nested path, pre-resolve it yourself: pass {variable: 'data'} and read data.prop inside the template, or compute the object before compiling.
- Sanitize or validate the variable name with /^[A-Za-z_$][A-Za-z0-9_$]*$/ before passing it, especially when it comes from user input.
- Never interpolate user input into the variable setting; treat it as code, not data.
- For trusted static templates, hardcode the variable name in source rather than deriving it from runtime config.
Example fix
// before
_.template(html, {variable: 'ctx.model'});
// Error: variable is not a bare identifier: ctx.model
// after
const t = _.template(html, {variable: 'ctx'});
t({ model: ctx.model }); Defensive patterns
Strategy: validation
Validate before calling
const BARE_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
function compileTemplate(text, settings = {}) {
if (settings.variable && !BARE_IDENTIFIER.test(settings.variable)) {
throw new Error('variable must be a bare identifier: ' + settings.variable);
}
return _.template(text, settings);
} Type guard
const isSafeTemplateVariable = (v) => typeof v === 'string' && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(v);
Try / catch
try {
return _.template(source, { variable: name });
} catch (e) {
if (e instanceof Error && e.message.startsWith('variable is not a bare identifier')) {
return _.template(source, { variable: 'data' }); // safe default
}
throw e;
} Prevention
- Never pass user input as the variable setting; validate with /^[A-Za-z_$][\w$]*$/ first.
- Use a single identifier and access nested properties inside the template (data.prop) instead of 'a.b'.
- For untrusted template sources, omit variable entirely and accept the with(obj) sandbox, or use a hardened template engine.
- Code-review any template settings built from config or environment values.
- Pin Underscore >= 1.13.0-2 so the CVE-2021-23358 guard is present.
When it happens
Trigger: Calling _.template(text, {variable: 'some.path'}) or {variable: 'data, other'} — any variable string that is not a plain [A-Za-z_$][\w$]* identifier, including names containing dots, brackets, dashes, spaces, or semicolons (typically from untrusted input).
Common situations: Passing a config-derived or user-supplied variable name (e.g. a dotted property path) straight into the template settings; copying the {variable:'data'} idiom but substituting 'obj.prop' hoping to scope the data; an attacker-controlled variable name triggering the CVE-2021-23358 injection guard.
AI-assisted analysis of jashkenas/underscore@e70d5bd070 (2026-08-29).
Data as JSON: /api/errors/eccc9a9158cb7a37.
Report an issue: GitHub.