You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3 Composition API无eval实现模板动态替换ref值的安全方案

问题说明
  • 开发支持用户自定义文档模板的系统,设计了动态数据注入语法,占位符格式为#属性名 from ref变量名#,示例:#firstName from client#,其中client是通过ref()定义的响应式变量,firstName是该响应式对象的属性。
  • 需求是实现占位符到实际数据的替换,因用户输入不可信,已配置allowedTemplateTags白名单数组,仅解析正则匹配到的#..#格式内容,非白名单片段不做解析。
  • 当前实现基于eval+白名单,需要确认该方案是否存在安全隐患,以及是否有更优的基于ref值的动态替换方案。

现有实现代码如下:

function processTemplate(text: string): string {
  const re = new RegExp(/#\w+ from \w+#/, 'g')
  const matches = text.match(re) ?? []

  for (const match of matches) {
    if (!allowedTemplateTags.includes(match)) {
      continue
    }
    const split = match.replace(/#/g, '').split(' from ')
    const replaceRegEx = new RegExp(`(${match})`, 'g')
    // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
    text = text.replace(replaceRegEx, eval(`${split[1]}?.value.${split[0]}`))
  }

  return text
}
现有方案的安全隐患
  • 核心风险来自eval的动态执行特性:即使配置了白名单,一旦出现正则绕过(比如特殊Unicode字符绕过\w匹配规则、白名单配置失误),攻击者可以构造恶意占位符执行上下文中的任意代码,触发XSS、敏感数据泄露等问题。
  • 性能表现差:eval无法被JS引擎做JIT优化,同时现有逻辑每次匹配都要重新构造正则、多次遍历替换文本,大模板场景下执行效率很低。
  • 可维护性差:TypeScript无法对eval内的动态代码做静态类型校验,很容易出现隐式类型错误、属性名拼写错误无法提前发现的问题。
无eval的安全实现方案

核心思路是完全隔离可访问的变量范围,把所有允许模板访问的ref显式注册到独立的上下文对象中,通过键值匹配取值,全程不做动态代码执行:

// 1. 显式注册所有允许模板访问的ref,不要自动收集作用域内的全部变量,从根源缩小攻击面
const templateSafeContext: Record<string, { value: Record<string, string | number | boolean> }> = {
  client: client, // 示例:注册client这个ref
  // 其他需要暴露给模板的ref都在这里手动添加
}

function processTemplate(text: string): string {
  const placeholderRe = /#(\w+) from (\w+)#/g
  // 白名单转Set提升查询性能
  const allowedTags = new Set(allowedTemplateTags)

  // 单次正则遍历完成全部替换,不需要多次循环
  return text.replace(placeholderRe, (match, propKey, refKey) => {
    // 第一道校验:白名单过滤
    if (!allowedTags.has(match)) return match
    // 第二道校验:从安全上下文取ref,不存在就原样返回
    const targetRef = templateSafeContext[refKey]
    if (!targetRef?.value) return match
    // 取对应属性值
    const propValue = targetRef.value[propKey]
    if (!['string', 'number', 'boolean'].includes(typeof propValue)) return ''
    // 第三道防护:XSS转义,避免输出内容带恶意脚本
    return String(propValue).replace(/[&<>"']/g, (char) => ({
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#39;'
    }[char]))
  })
}

方案优势

  • 零eval无代码执行风险:所有可访问的变量必须手动注册到安全上下文,攻击者无法通过构造占位符访问全局对象、未注册的内部变量。
  • 性能更高:单次正则遍历完成替换,没有动态代码执行开销,大文本场景下替换速度比原方案高一个数量级。
  • 类型安全:可以给templateSafeContext配置明确的TypeScript类型,编译阶段就能发现ref名、属性名拼写错误的问题。
  • 内置多层防护:白名单校验、上下文隔离、输出转义三层防护,即使某一层配置失误也不会出现严重安全问题。

扩展注意事项

如果后续需要支持嵌套属性取值(比如#address.city from client#),不要用eval解析属性路径,可以写一个独立的安全取值函数,按.拆分路径后逐层从对象上取值,全程避免动态代码执行。

内容的提问来源于stack exchange,提问作者Trent

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 17:54:30