阿里云主机折上折
  • 微信号
Current Site:Index > The security restrictions of template expressions

The security restrictions of template expressions

Author:Chuan Chen 阅读数:44425人阅读 分类: Vue.js

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:

  1. Constructors: new Date() is allowed, but new Function() is not.
  2. Potentially dangerous functions: eval(), Function(), alert().
  3. 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:

  1. Browser-specific APIs (window, document) are blocked.
  2. Lifecycle hooks (onMounted, etc.) are disabled.
  3. 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:

  1. Deeply nested object access (beyond 3 levels).
  2. Chained operations with multiple method calls.
  3. 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:

  1. Prohibiting type assertion syntax (as).
  2. Blocking type imports (type keyword).
  3. 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:

  1. HTML injection: Automatic escaping of interpolated content.
  2. Event injection: Validation of event handlers.
  3. URL injection: Filtering of javascript: protocols.
<!-- User input is escaped -->
<div>{{ userControlledContent }}</div>
<!-- Renders as &lt;script&gt;alert(1)&lt;/script&gt; -->

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:

  1. Unused variables are checked.
  2. Warning codes are removed in production.
  3. 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:

  1. Arguments must be static properties.
  2. Modifiers must be pre-declared.
  3. 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:

  1. Scoped variables are automatically limited.
  2. Modifying slot props is prohibited.
  3. 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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.