The security restrictions of template expressions
Security Restrictions in Template Expressions
Vue 3's template expressions are designed with a series of security restrictions that balance developer experience and protection against potential vulnerabilities. During template compilation, expressions undergo static analysis and transformation, ultimately generating optimized render function code.
Difference Between Expressions and Statements
Templates can only contain expressions, not statements. This design limits the logical complexity of templates, forcing complex logic to be moved into component options or the Composition API. Below are typical error examples:
<!-- Error: Contains statements -->
<div v-if="let a = 1; a > 0"></div>
<!-- Correct: Only expressions -->
<div v-if="a > 0"></div>
The compiler detects such issues through regex matching and AST analysis, throwing errors during development. The core differences between expressions and statements are:
- Expressions produce values (e.g.,
a + 1
) - Statements perform operations but do not return values (e.g.,
if(a){...}
)
Restricted Global Access
Template expressions can only access a limited whitelist of global objects, including:
- Native JavaScript globals (
Math
,Date
, etc.) - Vue built-in globals (
$refs
,$emit
, etc.) - Properties injected by users via
app.config.globalProperties
Attempting to access unauthorized globals triggers warnings:
<!-- Assuming 'location' is not injected -->
<div>{{ location.href }}</div>
<!-- Console warning: Property "location" is not available on instance... -->
The compiler implements scope control using with
blocks, combined with Proxy
for access interception. The logic is handled in compile-core/src/transforms/transformExpression.ts
:
const globalsWhitelist = new Set([
'Infinity','undefined','NaN','isFinite','isNaN','parseFloat',...Vue built-ins
])
function isGloballyAllowed(identifier: string) {
return globalsWhitelist.has(identifier)
}
Prohibited Function Calls
Certain function calls that may cause side effects are explicitly prohibited:
- Constructors:
new Date()
is allowed, butnew Function()
is not. - Potentially dangerous functions:
eval()
,Function()
,alert()
. - Property assignments: Operations like
obj.a = 1
are forbidden.
<!-- Error example -->
<button @click="eval('alert(1)')">Dangerous operation</button>
<!-- Compilation error: Avoid using JavaScript unary expression as statement -->
In compiler-core/src/validateExpression.ts
, restrictions are enforced by checking AST node types:
if (node.type === NodeTypes.CALL_EXPRESSION) {
const fnName = getStaticPropertyName(node.callee)
if (fnName === 'eval') {
context.onError(createCompilerError(ErrorCodes.X_DISALLOWED_FUNCTION))
}
}
Property Access Path Restrictions
Property access must follow explicit paths; dynamically computed properties are not allowed (these should be moved to JavaScript):
<!-- Not allowed -->
<div>{{ user[propertyName] }}</div>
<!-- Should be changed to -->
<div>{{ getUserProperty(propertyName) }}</div>
The source code controls this by checking the computed
property of MemberExpression
nodes:
if (node.computed && !isLiteral(node.property)) {
context.onError(createCompilerError(ErrorCodes.X_DYNAMIC_PROPERTY_ACCESS))
}
Static Analysis of Directive Arguments
Directive arguments must be static strings; dynamic arguments require square bracket syntax:
<!-- Static argument -->
<comp :msg="hello"/>
<!-- Dynamic argument -->
<comp :[dynamicKey]="value"/>
During compilation, dynamic arguments are transformed into toHandlerKey
calls:
// Compiled result
_createVNode(comp, { [dynamicKey]: value })
Custom Whitelist Extension
Developers can extend the allowed globals via compiler options:
const app = createApp(App)
app.config.compilerOptions.whitelist = ['CustomGlobal']
However, extensions should be used cautiously to avoid introducing XSS vulnerabilities. The logic is handled in compiler-core/src/options.ts
:
function mergeWhitelist(config: CompilerConfig) {
return new Set([...DEFAULT_WHITELIST, ...(config.whitelist || [])])
}
Special Handling for Server-Side Rendering
SSR mode imposes stricter restrictions:
- Browser-specific APIs (
window
,document
) are blocked. - Lifecycle hooks (
onMounted
, etc.) are disabled. - Asynchronous operations are limited.
// compiler-ssr/src/transforms/ssrTransform.ts
if (isBrowserEnvAccess(node)) {
context.onError(createCompilerError(ErrorCodes.X_SSR_BROWSER_API_ACCESS))
}
Performance Optimization Restrictions
Certain expressions that may impact performance trigger warnings:
- Deeply nested object access (beyond 3 levels).
- Chained operations with multiple method calls.
- In-place operations on large arrays.
<!-- Not recommended -->
<div>{{ articles.filter(a => a.published).map(a => a.title).join(',') }}</div>
The compiler generates optimization hints:
[Vue warn]: Avoid complex expressions in templates. Consider moving this to a computed property.
Integration with TypeScript
Template expression type checking is implemented via @vue/compiler-sfc
, with restrictions including:
- Prohibiting type assertion syntax (
as
). - Blocking type imports (
type
keyword). - Enforcing explicit type declarations.
<script setup lang="ts">
interface User { name: string }
</script>
<template>
<!-- Error: Cannot directly use interface types -->
<div>{{ (user as User).name }}</div>
</template>
Type checking is implemented in compiler-sfc/src/typeCheck.ts
:
if (ts.isTypeReference(node)) {
ctx.emitError(`Type references are not allowed in templates`)
}
Security Vulnerability Protection
Protection against common attacks:
- HTML injection: Automatic escaping of interpolated content.
- Event injection: Validation of event handlers.
- URL injection: Filtering of
javascript:
protocols.
<!-- User input is escaped -->
<div>{{ userControlledContent }}</div>
<!-- Renders as <script>alert(1)</script> -->
Escaping logic is in runtime-core/src/helpers/escapeHtml.ts
:
const escapeRE = /["'&<>]/
function escapeHtml(string: unknown) {
if (typeof string !== 'string') return string
return string.replace(escapeRE, match => escapeMap[match])
}
Development vs. Production Mode Differences
Restrictions are stricter in development mode:
- Unused variables are checked.
- Warning codes are removed in production.
- Certain performance optimizations are allowed in production.
// compiler-core/src/errors.ts
if (__DEV__ && !context.prefixIdentifiers) {
checkMissingIdentifiers(node)
}
Custom Directive Restrictions
Custom directives have special constraints:
- Arguments must be static properties.
- Modifiers must be pre-declared.
- Native DOM properties cannot be overridden.
<button v-my-directive:arg.modifier="value"></button>
The compiler validates directive validity:
// compiler-core/src/validateDirectives.ts
if (directive.arg && !isStaticExp(directive.arg)) {
warn(`v-${directive.name} directive argument must be static`)
}
Special Rules for Slot Content
Slot content expressions have additional restrictions:
- Scoped variables are automatically limited.
- Modifying slot props is prohibited.
- CamelCase naming is enforced.
<slot name="header" :user="currentUser">
<!-- Cannot modify 'user' -->
{{ user.name.toUpperCase() }}
</slot>
Relevant checks are implemented in compiler-core/src/transforms/transformSlotOutlet.ts
.
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn