Skip to content

transform-wxs-code 安全脚本转换原理与关键代码

1. 概述

transform-wxs-code 是小程序框架中用于处理安全脚本(WXS)的转换工具。WXS(Secure JavaScript)是一种在小程序环境中使用的安全脚本语言,它具有与 JavaScript 相似的语法,但移除了一些不安全的特性,以确保小程序运行在一个受控的环境中。

transform-wxs-code 模块利用 Babel 工具链对 WXS 代码进行解析、转换和生成,实现了一系列安全性相关的代码转换。

2. 实现原理

transform-wxs-code 的核心工作原理是通过 Babel 的 AST(抽象语法树)转换来实现代码的安全性处理:

  • 解析(Parse):使用 @babel/parser 将 WXS 代码解析为 AST
  • 转换(Transform):使用 @babel/traverse 遍历 AST,对特定节点进行安全性转换
  • 生成(Generate):使用 @babel/generator 将转换后的 AST 重新生成为代码

3. 核心代码分析

3.1 主入口函数

typescript
export const transformWxsCode = (
  code: string
): GeneratorResult | { syntaxError?: Error } => {
  let ast: Node;
  try {
    // 解析代码为 AST
    ast = parse(code, {
      sourceType: "script",
      plugins: ["classProperties", "objectRestSpread"],
    });
  } catch (error) {
    return { syntaxError: error };
  }

  // 应用转换器
  traverse(ast, {
    Identifier: IdentifierTransformer,
    MemberExpression: MemberExpressionTransformer,
    StringLiteral: StringLiteralTransformer,
  });

  // 生成代码
  return generate(ast, {}, code);
};

这个函数是转换 WXS 代码的主入口,它执行了完整的解析-转换-生成流程,并处理了可能的语法错误。

3.2 标识符转换器

typescript
export const IdentifierTransformer = (path: NodePath<t.Identifier>) => {
  const { node } = path;
  // check is not in classMethods
  if (node.name === "constructor") {
    const isClassMethod = path.findParent((p) => p.isClassMethod());
    if (!isClassMethod) {
      path.replaceWithSourceString("v_constructor");
    }
  }
};

IdentifierTransformer 主要处理特殊标识符的安全性问题:

  • 检测并重命名全局的 constructor 函数,但保留类方法中的 constructor
  • 这是为了防止开发者在 WXS 中访问或修改全局的构造函数,可能导致安全问题

3.3 成员表达式转换器

typescript
export const MemberExpressionTransformer = (
  path: NodePath<t.MemberExpression>
) => {
  const { node } = path;
  const { property } = node;
  // a[b] => a[__REPLACE_PROPERTY__(b)]
  if (node.computed) {
    node.property = t.callExpression(t.identifier("**REPLACE_PROPERTY**"), [
      property as t.Expression,
    ]);
  }
};

MemberExpressionTransformer 处理计算属性访问的安全性:

  • 将计算属性访问 obj[prop] 转换为 obj[REPLACE_PROPERTY(prop)]
  • 这种转换确保在运行时可以过滤或拦截对敏感属性的访问
  • 例如,防止访问 proto、constructor 等敏感属性

3.4 字符串字面量转换器

typescript
export const StringLiteralTransformer = (path: NodePath<t.StringLiteral>) => {
  const { node } = path;
  const { extra } = node;
  if (extra && extra.raw) {
    // getRegExp('^-?\d+(\.\d+)?$') => getRegExp('/^-?\\d+(\\.\\d+)?$/')
    // 正则表达式对字符串的转义处理
    node.value = (extra.raw as string).slice(1, -1);
  }
};

StringLiteralTransformer 处理字符串的转义问题:

  • 通过获取原始字符串表示(raw value)来处理特殊的转义字符
  • 在正则表达式等场景下确保转义字符被正确处理
  • 这有助于防止由转义字符导致的安全问题,如注入攻击

4. 安全转换的关键点

4.1 防止访问特权 API

通过重命名 constructor 和替换计算属性访问,防止 WXS 代码访问 JavaScript 引擎的内部 API 和原型链。这些措施可以有效防止:

  • 原型链污染(prototype pollution)
  • 全局对象访问(如 window、global)
  • 敏感构造函数调用(如 Function、eval)

4.2 隔离不同环境的代码

WXS 转换的目的是确保小程序中的脚本在一个受控的沙箱环境中运行,不能破坏环境的安全性。主要实现了:

  • 代码隔离:确保 WXS 代码无法访问宿主环境中的敏感 API
  • 资源隔离:限制对系统资源的访问
  • 数据隔离:防止对其他小程序页面或组件数据的非法访问

4.3 运行时安全

转换后的代码会在运行时环境中加入额外的安全检查:

  • REPLACE_PROPERTY 函数在运行时过滤危险属性访问
  • 通过重命名和转换确保开发者无法绕过这些安全限制
  • 提供优雅的错误处理,而不是暴露底层实现细节

5. 转换示例

5.1 原始 WXS 代码

javascript
function test() {
  let obj = { name: "test" };
  let key = "constructor";
  return obj[key];
}

let constructor = "hello";

5.2 转换后的代码

javascript
function test() {
  let obj = { name: "test" };
  let key = "constructor";
  return obj[__REPLACE_PROPERTY__(key)];
}

let v_constructor = "hello";

在转换后的代码中:

  • 全局 constructor 变量被重命名为 v_constructor
  • 计算属性访问 obj[key] 被转换为 obj[REPLACE_PROPERTY(key)]
  • 这确保在运行时可以拦截对 constructor 的访问

6. 技术亮点

6.1 非侵入性转换

不需要开发者修改代码,自动进行安全转换

6.2 AST 级别操作

通过操作 AST 而非简单的文本替换,确保转换的精确性和全面性

6.3 可扩展架构

  • 基于 Babel 的插件架构,可以方便地添加更多的安全转换器
  • 沙箱隔离:与运行时环境结合,提供了全面的代码沙箱

7. 总结

transform-wxs-code 通过精巧的代码转换,实现了小程序 WXS 环境的安全性保障。它在不牺牲开发便利性的前提下,通过静态代码分析和转换,防止了潜在的安全风险,保障了小程序运行环境的稳定和安全。核心转换器针对不同类型的语法结构进行安全处理,实现了全面的代码安全转换。

这种基于 Babel 的转换方案,结合运行时的安全检查,构成了小程序安全脚本系统的重要基础设施,为小程序开发者提供了一个既安全又易用的脚本环境。

基于 MIT 许可发布