项目实训(九):JavaScript 四类核心风险轻量检测
改动背景
上一篇里,我主要补齐了 Python XSS 检测。到那一轮结束后,Python 侧基本已经覆盖了任务书里的四类核心风险:
SQL 注入
XSS
危险函数调用
硬编码密钥
这一篇开始往 JavaScript 方向扩展。
不过这次不是把 JavaScript 做成和 Python 一样深的分析器。Python 仍然是当前项目里的主线,前面已经做了 AST、flow analyzer、函数调用传播、返回值追踪、作用域处理、Web 入口上下文提示等能力。
JavaScript 这一轮的目标更明确:用轻量规则覆盖四类核心风险的常见写法,让 JS 侧也能进入统一的 detector / engine / evaluator 链路,从而证明当前架构可以扩展到第二种语言。
一、为什么这一篇做 JavaScript 轻量扩展
前面几篇主要都围绕 Python 展开。SQL 注入、危险函数、硬编码密钥、XSS 这几类风险已经陆续接入了统一分析链路,其中 SQL 注入、危险函数、XSS 还做了不同程度的数据流分析。
由于我们计划做多语言的扩展,所以这一篇开始接入 JavaScript。
这里需要先明确一点:JS 不是这一阶段的深度分析主线。它更像是一个轻量验证版,用来回答一个问题:
当前 detector / engine / evaluator 架构能不能自然扩展到第二种语言?
所以这次 JS 侧主要做三件事:
覆盖四类核心风险的常见写法
接入统一 detector 链路
保持输出结构和 Python 一致
这样既能证明多语言扩展是可行的,也不会把 JavaScript 扩展成另一条很重的静态分析主线。
二、本轮新增了什么
这次新增了 JavaScript 四类核心风险的轻量 detector:
JavaScript SQLInjection
JavaScript XSS
JavaScript HardcodedSecret
JavaScript DangerousFunction
整体仍然接入之前的结构:
rules
-> engine
-> evaluator
-> detector
-> registry
也就是说,JavaScript 没有单独另起一套流程,而是放进了现有分析链路。
后端 /analyze 主流程也不需要大改。请求里如果是:
{
"language": "python"
}
就走 Python detector;如果是:
{
"language": "javascript"
}
就走 JavaScript detector。
这样插件展示、结果转换、后续解释功能都可以继续复用原来的结构。
三、为什么 JS 这次采用行扫描和正则规则
这次 JavaScript 没有引入完整 AST parser,而是采用了文本级的轻量分析方式。
当前整体流程大概是:
源码文本
-> 按行读取
-> 去掉 // 和 /* */ 注释
-> 用正则识别 source / sink / 赋值 / 字符串
-> 维护少量变量状态
-> 生成风险候选
它属于“文本级 + 简单变量级”的分析。
比如下面这段代码:
const id = req.query.id;
const sql = `SELECT * FROM users WHERE id = ${id}`;
db.query(sql);
当前逻辑大概会理解成:
id 来自 req.query.id,是 taint
sql 是包含 id 的动态 SQL
sql 进入 db.query
=> SQLInjection
如果使用 AST,分析器会先把 JavaScript 代码解析成语法树。比如变量声明、成员访问、函数调用、模板字符串、函数参数都会变成明确的语法节点。这样更适合做跨行表达式、复杂函数传播、作用域遮蔽、import / require 解析等更深层分析。
但这个项目里,JS 侧没有选择这条路线,主要是因为定位不同。
JavaScript 生态比 Python 更分散:既有浏览器 DOM,也有 Node.js 后端;既有 CommonJS 的 require,也有 ESM 的 import;还可能涉及 React、Vue、Express、Koa、回调、异步函数、对象链式调用等写法。
如果一开始就引入完整 AST,并继续处理这些语法和框架场景,JS 分析很容易变成另一条很重的主线。
而当前项目的重点是 Python 深度分析,JS 侧主要承担多语言验证。因此这次选择行扫描、正则匹配和简单变量状态,覆盖高频、确定性强、容易解释的风险写法。
简单说,JS 不是 Python 深度分析的复制版,而是为了多语言验证设计的轻量规则扫描版。
四、JavaScript SQL 注入
JS SQL 注入的判断思路还是:
source -> dynamic SQL -> sink
也就是说,不是看到 query(...) 就直接报,而是尽量确认用户输入进入了动态 SQL 字符串,并且这个 SQL 又进入数据库执行点。
目前支持一些常见输入来源:
req.query.id
req.params.id
req.body.name
req.headers["x-user"]
ctx.query.id
event.queryStringParameters.id
模板字符串 SQL 可以识别:
const id = req.query.id;
const sql = `SELECT * FROM users WHERE id = ${id}`;
db.query(sql);
字符串拼接也可以识别:
const name = req.body.name;
connection.query("SELECT * FROM users WHERE name = '" + name + "'");
常见数据库执行点也做了轻量覆盖:
db.query(sql);
connection.execute(sql);
db.raw(sql);
db.run(sql);
db.all(sql);
db.get(sql);
prisma.$queryRawUnsafe(sql);
prisma.$executeRawUnsafe(sql);
其中 run / all / get 主要覆盖 SQLite 一类写法,$queryRawUnsafe / $executeRawUnsafe 主要覆盖 Prisma 中比较明确的 unsafe raw SQL 调用。
参数化查询会放行:
const id = req.query.id;
db.query("SELECT * FROM users WHERE id = ?", [id]);
这里虽然 id 仍然来自用户输入,但没有直接拼进 SQL 字符串里,所以当前不会报 SQL 注入。
这部分目前是轻量变量级传播,不做复杂函数传播。
五、JavaScript XSS
JS XSS 的判断思路还是:
source -> HTML build -> HTML sink
不过 JS XSS 和 Python XSS 有一点区别。Python XSS 更偏服务端返回 HTML;JS XSS 既可能出现在服务端响应,也可能出现在浏览器 DOM 写入里。
常见输入来源包括:
req.query.q
req.body.name
location.search
document.cookie
localStorage.getItem("x")
DOM HTML 写入可以识别:
const q = req.query.q;
element.innerHTML = `<h1>${q}</h1>`;
也支持这些 HTML sink:
element.outerHTML = `<div>${q}</div>`;
document.write(`<p>${q}</p>`);
element.insertAdjacentHTML("beforeend", `<span>${q}</span>`);
这里需要稍微区分一下:对于 innerHTML、document.write 这类 DOM sink,sink 本身会把字符串当作 HTML 解析;对于 res.send 这类服务端响应,则更关注用户输入是否已经参与 HTML 字符串构造。
所以服务端响应场景更典型的写法是:
const q = req.query.q;
const html = `<h1>${q}</h1>`;
res.send(html);
res.write(html);
res.end(html);
reply.send(html);
这次还补了一些前端里比较常见的 jQuery 风格写法:
const html = `<span>${q}</span>`;
$("#target").html(html);
$(".box").append(html);
$(".box").prepend(html);
$(".box").before(html);
$(".box").after(html);
React 里比较明确的 HTML 注入点也会识别:
const html = document.cookie;
const node = <div dangerouslySetInnerHTML={{ __html: html }} />;
如果经过转义或净化,会尽量放行:
const safe = escapeHtml(q);
element.innerHTML = safe;
const clean = DOMPurify.sanitize(q);
element.innerHTML = clean;
目前会识别的安全写法包括:
escapeHtml
DOMPurify.sanitize
sanitizeHtml
_.escape
这部分和 Python XSS 的原则一致:尽量确认用户输入真的进入了 HTML 输出位置,而不是只看某个函数名。
六、JavaScript 硬编码密钥
硬编码密钥这类风险本身不太依赖复杂数据流,所以 JS 侧和 Python 侧的思路比较接近:
可疑变量名 / 字段名 + 字符串字面量
例如变量赋值:
const apiKey = "sk-live-123";
const password = "123456";
const token = "prod-token-123";
对象字段也支持:
const config = {
clientSecret: "prod-secret-123"
};
下标赋值也支持:
config["token"] = "prod-token-123";
JavaScript 里经常会出现 camelCase 命名,所以类似 apiKey、clientSecret 这类字段名也会被识别。
同时也做了一些误报控制。
比如下面这些更像字段说明、环境变量名或占位符:
const apiKeyName = "OPENAI_API_KEY";
const tokenType = "Bearer";
const token = "your_token";
const secret = "dummy";
当前不会直接当成真实密钥报。
这部分重点还是控制误报:不能只要变量名里有 key、token、secret 就报,还要看它是不是明显的占位符或元数据字段。
七、JavaScript 危险函数
危险函数和 SQL 注入、XSS 不太一样。它更关注危险 API 本身,以及参数是否可能来自不可信输入。
当前支持动态执行代码:
eval(userCode);
new Function(userCode);
字符串形式的定时器也会识别:
setTimeout("alert(1)", 1000);
setInterval("doSomething()", 1000);
Node 命令执行也做了覆盖:
const cp = require("child_process");
cp.exec(cmd);
cp.execSync(cmd);
cp.spawn(cmd, [], { shell: true });
也支持一些真实项目里常见的别名写法:
const { exec } = require("child_process");
exec(cmd);
import { exec as run } from "child_process";
run(cmd);
这里加了一个轻量 alias state,用来记录 child_process 的模块别名和函数别名。
这样可以识别:
const cp = require("child_process");
cp.exec(cmd);
也可以识别:
import { exec as run } from "child_process";
run(cmd);
但它主要覆盖常见 require / import 写法,不做复杂作用域分析,也不做跨文件 import 追踪。
八、统一输出结果
虽然 JavaScript 内部实现比 Python 轻,但最终还是会转换成统一的 RiskCandidate。
比如一个 JS SQL 注入结果大概会是这样:
{
"risk_type_candidate": "SQLInjection",
"severity": "high",
"start_line": 3,
"end_line": 3,
"summary": "不可信输入进入动态 SQL 并传入数据库执行函数,存在 SQL 注入风险。",
"needs_more_context": false,
"evidence_chain": {
"source_line": 1,
"sql_build_line": 2,
"sink_line": 3
}
}
这样做的好处是,不管风险来自 Python 还是 JavaScript,后面都能用同一套结构处理。
插件展示层、Problems 面板、代码高亮和后续解释层,都不需要为了 JavaScript 重新设计一套结果格式。
九、当前边界
当前 JS 侧定位就是轻量多语言验证,所以边界也比较明确。
暂不支持:
完整 JavaScript AST
跨文件 import 解析
复杂函数调用传播
深度对象属性追踪
复杂框架生命周期分析
这些能力属于更重的 JavaScript 静态分析范围,不是当前项目的重点。
当前更重要的是:JS 四类核心风险能进入统一 detector 链路,结果格式和 Python 保持一致,插件和后续解释层可以统一消费。
十、测试情况
这轮测试主要确认三件事。
第一,JS 四类风险都能进入统一 detector 链路。
第二,常见危险写法可以被识别,同时参数化 SQL、escapeHtml、DOMPurify.sanitize 这类安全写法不会乱报。
第三,新增 JavaScript detector 后,原来的 result_converter 和插件编译没有被破坏。
后端测试:
cd backend
python -m pytest tests -q
插件侧也重新编译了一遍:
cd vscode-extension
npm run compile
结果都已通过。
十一、总结与下一步
这一轮完成的是 JavaScript 四类核心风险的轻量闭环。
它的重点不是把 JS 做成完整成熟的深度分析器,而是验证当前可插拔 detector 架构可以扩展到第二种语言,并让 JS 侧四类风险都有可运行、可测试的版本。
到这里,项目里的分工就比较清楚了:
Python:主线,承担更完整的数据流和上下文分析
JavaScript:轻量版,覆盖常见写法,验证多语言扩展能力
后续重点会放在联调、测试矩阵、文档整理和答辩材料上,而不是继续把 JavaScript 扩展成另一条重型分析主线。
更多推荐

所有评论(0)