DEV Community

jackymenCZ (jackymenCZ)
jackymenCZ (jackymenCZ)

Posted on

Sentinel-IR 0.1.2:在 Android Termux 上安装、分析 JavaScript 并验证 AST 检测结果

无需电脑、无需 Root、无需 API Key:直接用 Android 手机运行 JavaScript 结构分析与回归测试。

Sentinel-IR 是一个用于 JavaScript 代码结构分析的开源工具。它可以从源代码中提取路由、模块导入、环境变量、文件系统操作、网络相关操作以及潜在风险信号,并将这些信息整理成紧凑的结构化报告。

对于 AI Agent 来说,这意味着不必在每一次分析中都直接传入整个源文件。系统可以先获得一份结构化的事实摘要,再根据任务决定是否需要查看原始代码。

本文将两个实践指南合并为一套完整流程:

  • 从零开始在 Android Termux 中安装 Sentinel-IR。
  • 初始化随 npm 包提供的 WebAssembly 解析器。
  • 分析一个可复现的 JavaScript 示例。
  • 检查 JSON 报告中的结构化事实。
  • 使用 Sentinel-IR 0.1.2 验证动态代码执行、变量作用域和 "child_process" 检测。
  • 运行针对特定回归场景的测试。
  • 检查发布包,并在需要时运行完整开发测试套件。
  • 了解分析器的限制,以及如何安全地分享测试结果。

项目链接


一、开始之前:需要准备什么?

你需要:

  • 一部能够运行 Termux 的 Android 手机。
  • 可以访问软件仓库和 npm registry 的网络连接。
  • 一些可用存储空间。
  • 大约 20 至 30 分钟,具体时间取决于下载速度和设备环境。

不需要 Root 权限、桌面电脑、OpenAI 账户或 AI API Key。

安装 Termux

建议从官方渠道获取 Termux:

不要随意安装聊天群或不明下载网站提供的 APK。

如果手机上已经安装了 Termux,不要为了本教程直接卸载。来自不同发行渠道的版本可能使用不同的签名密钥。

本文的验证原则

本文区分三种不同的检查:

  1. 安装检查:确认安装的是指定版本的 npm 包。
  2. 功能检查:确认解析器能处理给定的 JavaScript 示例,并输出预期事实。
  3. 回归测试:确认特定语法和作用域场景得到预期分类。

通过其中一种检查,不等于证明整个程序安全,也不等于证明分析器覆盖了所有 JavaScript 语法。

二、打开 Termux 并更新软件包

打开 Termux,等待终端提示符出现。

一次复制一个命令块,执行后等待命令结束,再继续下一步。

不要复制文档中的提示符 "$",只复制代码块里的命令。

第 1 步:更新 Termux

运行:

pkg update && pkg upgrade -y

这会更新软件包列表并升级已安装的软件包。

如果遇到仓库或镜像错误,先解决软件源问题,不要直接跳过。

第 2 步:安装 Node.js 和 npm

Sentinel-IR 要求 Node.js 20 或更新版本。

pkg install nodejs -y

检查版本:

node --version
npm --version

进一步检查 Node.js 主版本:

node -e "const major = Number(process.versions.node.split('.')[0]); console.log('Node.js:', process.version); if (major < 20) { console.error('Sentinel-IR requires Node.js 20 or newer'); process.exit(1); } console.log('PASS: Node.js version is supported');"

预期结果:

PASS: Node.js version is supported

如果 Node.js 版本低于 20,请先更新 Termux 软件包并检查可用版本。

三、创建独立测试目录

为了避免修改其他项目,我们先创建一个专门的测试目录。

mkdir -p "$HOME/sentinel-ir-termux-test"
cd "$HOME/sentinel-ir-termux-test"
pwd

输出路径通常以以下内容开头:

/data/data/com.termux/files/home/

后续测试文件都放在这个目录中。

以后可以使用以下命令返回:

cd "$HOME/sentinel-ir-termux-test"

四、安装指定版本 Sentinel-IR 0.1.2

本教程以 0.1.2 为主要验证版本。

首先创建一个最小 npm 项目:

npm init -y

安装指定版本:

npm install --save-exact sentinel-ir@0.1.2

检查实际安装的版本:

npm ls sentinel-ir

预期依赖树中包含:

sentinel-ir@0.1.2

然后检查公共 npm registry:

npm view sentinel-ir@0.1.2 version
npm view sentinel-ir dist-tags --json

版本查询应返回:

0.1.2

发行标签中应包含:

{
  "latest": "0.1.2"
}

这里有一个重要区别:本地 "package.json" 中写着某个版本,并不能证明该版本已经在公共 npm registry 中发布。registry 查询可以独立验证这一点。

如果 npm 无法访问 registry,请先检查网络连接:

npm ping

不要把网络超时直接当作分析器故障。

五、检查公开 API

运行以下命令,确认需要的函数已经导出:

node - <<'NODE'
const sentinel = require('sentinel-ir');

for (const name of ['analyzeAsync', 'compress', 'initializeWasm']) {
  console.log(${name}: ${typeof sentinel[name]});

  if (typeof sentinel[name] !== 'function') {
    console.error(FAIL: missing function ${name});
    process.exitCode = 1;
  }
}

if (!process.exitCode) {
  console.log('PASS: expected Sentinel-IR functions are exported');
}
NODE

预期结果应包含:

analyzeAsync: function
compress: function
initializeWasm: function
PASS: expected Sentinel-IR functions are exported

如果出现 "Cannot find module",请检查当前目录和安装状态:

cd "$HOME/sentinel-ir-termux-test"
npm ls sentinel-ir

如果安装的不是 0.1.2,请返回安装步骤。

六、初始化 WebAssembly 解析器

Sentinel-IR 包含 WebAssembly 解析路径。在 Android 等环境中,如果可选的原生 Tree-sitter 依赖无法正常构建,可以尝试使用该路径。

运行:

node - <<'NODE'
const { initializeWasm } = require('sentinel-ir');

initializeWasm()
  .then(() => {
    console.log('PASS: Sentinel-IR WebAssembly parser initialized');
  })
  .catch((error) => {
    console.error('FAIL: WebAssembly initialization failed');
    console.error(error);
    process.exitCode = 1;
  });
NODE

预期结果:

PASS: Sentinel-IR WebAssembly parser initialized

如果初始化失败,先不要继续假设解析器已经可以工作。

检查安装版本及相关依赖:

npm ls sentinel-ir web-tree-sitter
ls -lh node_modules/sentinel-ir/wasm/

检查包中是否包含 JavaScript 对应的 WebAssembly 语法文件:

tree-sitter-javascript.wasm

文件缺失、安装不完整或依赖初始化失败,都需要单独调查。

七、创建一个可复现的 JavaScript 示例

接下来创建一个简单的 Express 风格服务器代码。

注意:这里仅分析源代码,不会运行服务器,也不需要安装 Express。

创建文件:

cat > sample.js <<'JS'
const express = require("express");
const fs = require("fs");

const app = express();

app.post("/upload", (req, res) => {
  fs.writeFileSync("/tmp/upload", req.body);
  res.end();
});

app.listen(process.env.PORT);
JS

检查文件内容:

cat sample.js

检查语法:

node --check sample.js

如果语法正确,Node.js 通常不会输出任何内容,并会返回终端提示符。

这个示例刻意包含几个容易识别的结构:

  • "POST /upload" 路由。
  • "express" 和 "fs" 模块导入。
  • "process.env.PORT" 环境变量。
  • 文件系统写入操作。

这些结构可以用来检查分析器是否提取了预期事实。

八、创建分析脚本

下面的脚本读取指定 JavaScript 文件,将源代码传给 "analyzeAsync()",再通过 "compress()" 生成紧凑报告。

它不会执行输入文件,也不会调用 LLM 或上传源代码。

创建脚本:

cat > analyze.js <<'JS'
const fs = require('node:fs/promises');
const path = require('node:path');
const { analyzeAsync, compress } = require('sentinel-ir');

async function main() {
  const inputPath = process.argv[2];

  if (!inputPath) {
    console.error('Usage: node analyze.js ');
    process.exitCode = 1;
    return;
  }

  const source = await fs.readFile(inputPath, 'utf8');

  const ir = await analyzeAsync(source, {
    target: path.basename(inputPath),
  });

  const report = compress(ir);

  console.log(JSON.stringify(report, null, 2));

  if (report.analysisStatus !== 'complete') {
    console.error(
      'WARNING: analysisStatus is not complete. Do not treat this result as a complete structural analysis.'
    );
    process.exitCode = 2;
  }
}

main().catch((error) => {
  console.error('ERROR: analysis could not finish.');
  console.error(error && error.stack ? error.stack : error);
  process.exitCode = 1;
});
JS

检查脚本自身的语法:

node --check analyze.js

这一步只验证包装脚本的语法,不验证 Sentinel-IR 的检测准确性。

九、运行分析并保存 JSON 报告

执行:

node analyze.js sample.js > report.json
rc=$?
printf 'Analyzer exit code: %s\n' "$rc"

查看报告:

cat report.json

退出码含义:

  • "0":脚本生成了 "analysisStatus" 为 "complete" 的报告。
  • "2":生成了报告,但分析状态不完整。
  • "1":脚本无法完成分析,例如无法读取文件或初始化解析器失败。

对于本文提供的有效 JavaScript 示例,预期可以检查以下内容:

  • "analysisStatus" 为 "complete"。
  • "facts.ast" 为 "true"。
  • "facts.routes" 包含 "POST /upload"。
  • "facts.imports" 包含 "express" 和 "fs"。
  • "facts.env" 包含 "PORT"。
  • 报告中包含与路由和文件写入相关的操作或风险信号。

实际报告中的其他字段及其格式可能随输入和实现而变化。

还可以单独查看主要字段:

node - <<'NODE'
const r = require('./report.json');

console.log('analysisStatus:', r.analysisStatus);
console.log('routes:', r.facts.routes);
console.log('imports:', r.facts.imports);
console.log('environment variables:', r.facts.env);
console.log('operations:', r.facts.operations);
console.log('risk signals:', r.facts.riskSignals);
NODE

十、自动验证预期结果

仅仅看到一份 JSON 并不意味着分析正确。

我们可以编写一个小检查程序,对示例中的已知结构进行断言。

node - <<'NODE'
const report = require('./report.json');
const failures = [];

if (report.analysisStatus !== 'complete') {
  failures.push(analysisStatus expected complete, got ${report.analysisStatus});
}

if (report.facts?.ast !== true) {
  failures.push('facts.ast expected true');
}

if (!Array.isArray(report.facts?.routes) ||
    !report.facts.routes.includes('POST /upload')) {
  failures.push('expected route POST /upload was not found');
}

if (!Array.isArray(report.facts?.imports) ||
    !report.facts.imports.includes('express')) {
  failures.push('expected import express was not found');
}

if (!Array.isArray(report.facts?.imports) ||
    !report.facts.imports.includes('fs')) {
  failures.push('expected import fs was not found');
}

if (!Array.isArray(report.facts?.env) ||
    !report.facts.env.includes('PORT')) {
  failures.push('expected environment variable PORT was not found');
}

if (failures.length) {
  console.error('FAIL: sample analysis did not match expectations');

  for (const failure of failures) {
    console.error(- ${failure});
  }

  process.exitCode = 1;
} else {
  console.log('PASS: the sample produced the expected structural facts');
}
NODE

预期结果:

PASS: the sample produced the expected structural facts

这是一个可复现的 smoke test。它证明的是:在当前安装环境中,给定示例产生了检查程序所要求的结构化事实。

它不是完整的安全审计,不是全面的准确率基准测试,也不证明所有 JavaScript 模式都能被正确识别。

十一、尝试分析自己的 JavaScript 文件

完成已知示例的测试后,可以尝试分析自己拥有或有权检查的 JavaScript 文件。

选项 A:文件已经位于 Termux 中

将文件复制到测试目录,或者直接使用其路径。

node analyze.js ./your-file.js > your-file-report.json
rc=$?
printf 'Analyzer exit code: %s\n' "$rc"

将 "your-file.js" 替换成实际文件名。

请为报告使用不同的文件名,避免覆盖之前的 "report.json"。

选项 B:文件位于 Android Downloads

如果需要访问 Android 的共享存储,可以执行:

termux-setup-storage

按照 Android 显示的权限提示进行授权。

列出下载目录:

ls "$HOME/storage/downloads"

复制一个 JavaScript 文件:

cp "$HOME/storage/downloads/example.js" "$HOME/sentinel-ir-termux-test/your-file.js"
cd "$HOME/sentinel-ir-termux-test"
node analyze.js your-file.js > your-file-report.json
rc=$?
printf 'Analyzer exit code: %s\n' "$rc"

将 "example.js" 替换成实际文件名。

如果文件名包含空格,请保留命令中的引号。

重要限制

Sentinel-IR 当前面向 JavaScript,包括 CommonJS 和 ESM。

它不声称能够可靠地分析所有 TypeScript、复杂 JSX 或其他编程语言。

此外,分析器一次处理一个 JavaScript 文件,不会自动完成跨文件调用图或数据流分析。

十二、理解 analysisStatus

"analysisStatus" 是理解报告时最重要的字段之一。

complete

表示 AST 提取成功,源代码解析没有出现导致分析不完整的语法错误。

但这不代表已经发现所有安全问题,也不代表程序一定安全。

incomplete

表示 AST 提取不可用,或者源代码未能被完整解析。

此时输出可能依赖基于文本的回退启发式规则,结果可能出现误报或漏报。

如果报告不完整,不要声称 Sentinel-IR 已经确认该文件安全。

建议检查:

node --check sample.js

确认输入是受支持的 JavaScript,并尝试缩小问题示例。

即使状态为 "complete",仍然存在明确的分析边界:

  • 这是基于语法结构的分析,不是完整语义分析。
  • 一次只分析一个文件。
  • 不一定能解析别名、动态计算的属性名或跨模块传递的行为。
  • 不证明应用程序安全。
  • 不能消除 Prompt Injection。
  • 不会运行应用程序,也不会替 AI Agent 决定应该采取什么行动。

像 "world.confidence" 这样的字段,也不应被误解为经过统计校准的正确率概率。


第二部分:Sentinel-IR 0.1.2 的 AST 检测改进

十三、为什么 0.1.2 值得单独测试?

0.1.2 的重点不是重新设计整个 API,而是改善若干具体 JavaScript 语法场景中的结构化事实提取。

本次发布重点包括:

  1. 识别更多动态代码执行形式。
  2. 更好地区分全局 "eval"、"Function" 与同名局部变量。
  3. 识别部分使用字符串字面量的计算属性方法调用。
  4. 将静态 "child_process" 导入记录为一种潜在能力。
  5. 通过回归测试检查正确识别和正确不识别的情况。

在发布验证记录中,开发测试套件通过了 69 项测试,GitLab CI 的三个任务全部成功,独立消费者环境中的定向测试通过了 8 项。

这些结果支持特定实现和测试场景,不代表完整的 JavaScript 安全覆盖。

十四、变量名称相同,不代表行为相同

考虑以下代码:

eval(userInput);

以及:

parser.eval(userInput);

两者都包含 "eval",但不一定执行相同的操作。

第一种形式通常调用全局 "eval",除非该名称被局部绑定遮蔽。

第二种形式调用 "parser" 对象的方法。它可能只是解析表达式或验证字符串。

如果检测器仅仅搜索字符串 "eval",就容易产生误报。

类似地:

function evaluate(value) {
  return value * 2;
}

module.exports = { evaluate };

这里的 "evaluate" 只是一个普通函数名称,并没有执行动态代码。

分析器应该识别实际语法结构,而不是把名称相似当作能力相同。

十五、识别 globalThis 的不同写法

JavaScript 可以通过 "globalThis" 访问全局对象。

以下代码表达了相关的全局操作:

eval(userInput);

globalThis.eval(userInput);

globalThis"eval";

"Function" 构造器也有类似形式:

new Function("return 1");

new globalThis.Function("return 1");

new globalThis"Function";

这些写法可能对应不同的 AST 节点。

0.1.2 扩展了相应的识别逻辑和测试,覆盖更多显式写出的动态代码执行形式。

它也考虑了 "globalThis" 自身被局部变量遮蔽的情况。如果源文件声明了一个名为 "globalThis" 的局部变量,分析器不能仅凭名称就认定它是真正的全局对象。

十六、区分局部变量与全局 Function

例如:

const Function = value => value;

Function("ordinary text");

这里的 "Function" 是局部变量,并不是全局 "Function" 构造器。

类似情况也可能发生在函数参数中:

function run(Function) {
  return Function("ordinary text");
}

或者导入绑定中:

import Function from "safe-module";

Function("ordinary text");

如果检测器把所有名为 "Function" 的调用都标记为动态代码执行,就会把这些情况错误地分类。

0.1.2 在 "src/ast-facts.js" 中扩展了保守的 AST 作用域检查,用于识别局部变量、参数和导入绑定对全局名称的遮蔽。

相关辅助函数包括:

  • "bindingPatternHasName()"
  • "lexicalDeclarationHasName()"
  • "importStatementHasBinding()"
  • "lexicalScopeHasBinding()"
  • "varScopeHasBinding()"
  • "functionScopeHasBinding()"
  • "isNameShadowed()"
  • "isUnshadowedGlobalThis()"

这不是完整的 JavaScript 名称解析器,但它比简单搜索关键词更精确。

十七、识别计算属性调用

JavaScript 支持点号调用:

cp.execSync("example");

也支持方括号形式:

cp"execSync";

对于 AST 来说,这两种写法可能对应不同节点类型。

0.1.2 扩展了 "callTarget()" 对可识别字符串字面量属性的处理,使分析器可以在特定条件下将调用归一化为:

object: cp
property: execSync

如果 "cp" 已经与 "child_process" 模块关联,这种信息就能帮助分析器识别潜在的进程创建操作。

但以下形式更复杂:

cpmethodName;

如果 "methodName" 的值需要运行时才能确定,分析器不能自动假设它就是 "execSync"。

0.1.2 的改进针对可静态识别的属性,而不是承诺解析所有动态属性。

十八、导入 child_process 不等于执行系统命令

考虑:

import { execSync } from "node:child_process";

这段代码导入了一个能够启动系统命令的 API。

但是,导入本身并不能证明已经执行了命令。

真正的调用可能是:

execSync("some-command");

因此,需要区分两个事实:

  1. 文件导入了具有特定能力的模块。
  2. 文件中检测到了实际的进程创建调用。

0.1.2 将静态 "child_process" 导入作为能力信号记录,并将它与 "processSpawn" 分开。

例如,仅有导入的测试场景预期包含:

childProcess: true
processSpawn: false

这里有一个容易误解的地方:当前风险策略仍然可能因为存在 "child_process" 能力而将整体风险设为 "critical"。

这是一种保守的风险分类策略。

"critical" 并不证明文件已经执行了命令,也不证明代码具有恶意。

下游系统应该同时检查能力、实际操作及相关证据,而不是把整体风险标签当作完整的运行时行为描述。

十九、0.1.2 的主要变化

检测领域| 改进内容
动态代码执行| 覆盖更多全局 "eval" 和 "Function" 形式
计算属性| 识别部分字符串字面量方法调用
局部变量| 区分局部 "eval"、"Function" 与全局函数
函数参数| 增加参数遮蔽相关测试
globalThis| 检查全局对象名称被局部变量遮蔽的情况
对象与类方法| 避免把方法名称错误当作词法绑定
ES Module 导入| 为静态 "child_process" 导入添加能力信号
风险证据| 增加针对操作标志、风险等级和风险信号的回归测试
发布验证| 69/69 开发测试、通过的 CI,以及 8/8 定向消费者测试

这些改进的目标是让结构化事实更加准确,而不是让分析器声称自己了解所有程序行为。


第三部分:在 Termux 中复现 0.1.2 的回归测试

二十、创建独立的版本测试目录

前面的基础示例验证了路由、导入和环境变量。

现在,我们测试 0.1.2 特别关注的场景。

mkdir -p "$HOME/sentinel-ir-v012-check"
cd "$HOME/sentinel-ir-v012-check"

npm init -y
npm install --save-exact sentinel-ir@0.1.2
npm ls sentinel-ir

确认安装的是 "sentinel-ir@0.1.2"。

二十一、创建六个定向测试

以下测试会把源代码字符串交给分析器,而不是执行这些源代码。

创建文件:

cat > check-v012.js <<'NODE'
const assert = require("node:assert/strict");
const { analyzeAsync } = require("sentinel-ir");

const cases = [
  {
    name: "local Function is not the global constructor",
    source: 'const Function = value => value; Function("ordinary text");',
    risk: "low",
    operations: { dynamicEval: false }
  },
  {
    name: "global Function is dynamic code",
    source: 'Function("return 1");',
    risk: "critical",
    operations: { dynamicEval: true }
  },
  {
    name: "computed globalThis eval is detected",
    source: 'globalThis"eval";',
    risk: "critical",
    operations: { dynamicEval: true }
  },
  {
    name: "computed child_process method is detected",
    source: 'const cp = require("child_process"); cp"execSync";',
    risk: "critical",
    operations: {
      childProcess: true,
      processSpawn: true
    }
  },
  {
    name: "ordinary object eval method is not global eval",
    source: 'const parser = { eval(input) { return input; } }; parser.eval("text");',
    risk: "low",
    operations: { dynamicEval: false }
  },
  {
    name: "static child_process import records capability, not a spawn",
    source: 'import { execSync } from "node:child_process";',
    risk: "critical",
    operations: {
      childProcess: true,
      processSpawn: false
    }
  }
];

async function main() {
  for (const item of cases) {
    const result = await analyzeAsync(item.source, {
      target: "smoke-test.js"
    });

    assert.equal(
      result.analysisStatus,
      "complete",
      ${item.name}: parsing should complete
    );

    assert.equal(
      result.world.risk,
      item.risk,
      ${item.name}: unexpected risk
    );

    for (const [operation, expected] of Object.entries(item.operations)) {
      assert.equal(
        result.facts.operations[operation],
        expected,
        ${item.name}: operations.${operation}
      );
    }

    console.log(PASS: ${item.name} (${result.world.risk}));
  }

  console.log(\n${cases.length}/${cases.length} smoke tests passed.);
}

main().catch(error => {
  console.error(error);
  process.exitCode = 1;
});
NODE

运行测试:

node check-v012.js

如果所有断言都通过,最后一行应为:

6/6 smoke tests passed.

这些测试检查:

  • 名为 "Function" 的局部变量不会自动被认定为全局构造器。
  • 真正的全局 "Function()" 会被识别为动态代码执行。
  • "globalThis"eval"" 会被识别。
  • 与 "child_process" 关联的 "cp"execSync"" 会被识别为进程创建操作。
  • 普通的 "parser.eval()" 不会仅因名称而被认定为全局 "eval"。
  • 静态导入 "child_process" 会记录能力,但不会凭空证明进程已经启动。

最后一项尤其重要:测试预期 "processSpawn" 为 "false",但整体风险仍可能为 "critical"。这是当前风险策略的预期行为,而不是测试矛盾。

如果测试失败,不要直接修改预期值来让它通过。先记录错误、Node.js 版本和安装的包版本。

二十二、检查完整结构化报告

如果你不仅想检查风险等级,还想查看完整的紧凑 IR,可以创建另一个文件:

cat > inspect-v012.js <<'NODE'
const { analyzeAsync, compress } = require("sentinel-ir");

const code =
const cp = require("child_process");
cp["execSync"]("example");
;

async function main() {
  const result = await analyzeAsync(code, {
    target: "example.js"
  });

  console.log(JSON.stringify(compress(result), null, 2));
}

main().catch(error => {
  console.error(error);
  process.exitCode = 1;
});
NODE

运行:

node inspect-v012.js

检查报告中的:

  • "facts"
  • "operations"
  • "riskSignals"
  • 与识别结果相关的证据

对于这个示例,预期可以看到与 "child_process" 相关的能力,以及被识别的进程创建操作。

具体行号和其他字段可能随输入变化。

重点是:检测结果应当来自解析后的代码结构,而不只是注释或字符串中出现了某个关键词。

二十三、在需要时运行完整开发测试套件

npm 发布包主要包含运行时文件和随包提供的 WebAssembly 语法文件,并不包含完整的开发测试套件。

如果你想检查源代码中的完整测试,需要单独克隆仓库。

cd "$HOME"

git clone https://gitlab.com/jakubrojicek11/sentinel-ir.git
cd sentinel-ir

git checkout adf875a1502e3bcf7242bff85a32dcf62f023c07

npm ci
npm test

这里固定了对应 0.1.2 发布的提交,以避免把其他版本的代码误当作本次发布内容。

该发布版本的开发测试结果为:

69 tests passed
0 tests failed

需要注意,Android 上的可选原生 Tree-sitter 依赖可能与桌面 Linux 环境不同。

如果原生解析器无法初始化,前面针对已发布 npm 包的测试使用的是 "analyzeAsync()" 和 WebAssembly 路径。

不要把原生依赖安装失败与所有分析功能都不可用混为一谈,也不要在没有验证的情况下宣称所有 Android 设备都能顺利运行。

二十四、不同验证结果分别证明什么?

本次发布的验证记录包括以下几类检查:

  1. 本地开发测试
  • 69 项测试通过。
  1. GitLab CI
  1. 独立消费者测试

发布包 "sentinel-ir-0.1.2.tgz" 被安装到独立消费者目录中。

8 项定向测试通过,涉及变量遮蔽、导入绑定、方法名称、全局动态代码执行及 "globalThis" 访问。

  1. 公共 npm registry

通过以下命令检查实际发布状态:

npm view sentinel-ir@0.1.2 version
npm view sentinel-ir dist-tags --json

这些检查回答的是不同的问题,不能相互替代。

本地测试通过,不代表公共包一定正确发布;CI 通过,也不代表每一种 Android 设备环境都已经被验证。


第四部分:常见问题与故障排查

二十五、Termux 软件仓库错误

如果执行 "pkg update" 时出现仓库不可用或镜像错误,可以运行:

termux-change-repo

在界面中选择可用的官方镜像,然后再次运行:

pkg update && pkg upgrade -y

请优先使用 Termux 官方文档中的仓库和镜像说明,不要因为某个镜像超时就复制来源不明的安装命令。

二十六、出现 node: command not found

重新检查软件包安装:

pkg update
pkg install nodejs -y

node --version
npm --version

如果软件包管理器本身无法正常工作,请先解决仓库问题。

二十七、Node.js 版本低于 20

运行:

pkg update && pkg upgrade -y
pkg install nodejs -y
node --version

如果版本仍然低于 20,请检查当前 Termux 仓库提供的版本。

不要盲目安装多个互相冲突的 Node.js 软件包。

二十八、npm install 出现原生 Tree-sitter 编译错误

Sentinel-IR 的原生 Tree-sitter 依赖是可选的。

如果原生依赖无法构建,可以在专门的测试目录中尝试跳过可选依赖:

cd "$HOME/sentinel-ir-termux-test"

rm -rf node_modules package-lock.json

npm install --save-exact --omit=optional sentinel-ir@0.1.2

这会跳过可选的原生解析器依赖,但不会跳过 WebAssembly 路径所需的常规 "web-tree-sitter" 依赖。

安装后重新检查:

npm ls sentinel-ir web-tree-sitter

然后重复 API 检查和 WebAssembly 初始化步骤。

注意:只在本教程创建的专用测试目录中执行清理命令。 不要在需要保留依赖的其他项目中随意删除 "node_modules"。

二十九、Cannot find module 'sentinel-ir'

检查当前目录和依赖:

cd "$HOME/sentinel-ir-termux-test"

npm ls sentinel-ir

如果没有安装,再运行:

npm install --save-exact sentinel-ir@0.1.2

如果仍然失败,请保留完整错误信息。

三十、WebAssembly 初始化失败

检查安装的版本和文件:

cd "$HOME/sentinel-ir-termux-test"

npm ls sentinel-ir web-tree-sitter
ls -lh node_modules/sentinel-ir/wasm/

确认包中包含 "tree-sitter-javascript.wasm"。

如果文件缺失或安装不完整,可以在专用测试目录中重新安装指定版本。

不要从不明网站下载其他 WebAssembly 二进制文件来替换它。

如果问题仍然存在,请记录 Node.js 版本、包版本和完整错误信息。

三十一、analysisStatus 为 incomplete

首先检查输入文件语法:

node --check sample.js

确认输入是受支持的 JavaScript,而不是超出当前支持范围的 TypeScript 或复杂 JSX。

不要将基于回退启发式规则生成的结果当作完整 AST 分析的等价结果。

三十二、预期路由或导入没有被识别

先确认分析的确实是教程中的示例:

cat sample.js
node analyze.js sample.js > report.json

然后检查:

cat report.json

如果示例与教程完全一致、分析状态为 "complete",但预期事实仍然缺失,请保留经过清理的最小示例及报告。

记录包版本、Node.js 版本、实际输出和预期输出。这些信息可以构成一个可复现的问题报告。

不要为了让测试通过而随意修改预期结果。

三十三、npm 无法访问 registry

先检查网络:

npm ping
npm view sentinel-ir@0.1.2 version

如果 registry 暂时不可用,可以稍后重试,或者切换 Wi-Fi 和移动网络。

在 registry 请求本身无法成功的情况下,不应直接将安装超时归因于分析器代码。


第五部分:安全地分享测试结果

三十四、不要直接公开完整项目报告

Sentinel-IR 的 JSON 报告可能包含:

  • 导入的模块名称。
  • 路由路径。
  • 环境变量名称。
  • 操作类型。
  • 风险信号。
  • 目标文件名。

这些信息可能暴露项目的内部结构。

即使报告没有主动提取秘密值,也不能假设报告天然适合公开。

在发布结果之前:

  1. 检查报告中的每个字段。
  2. 删除或泛化内部路由、文件名、客户信息和专有架构信息。
  3. 不要未经许可发布他人的源代码或报告。
  4. 优先使用本文提供的合成示例进行演示。
  5. 明确说明测试的是合成示例还是自己有权检查的项目。
  6. 记录准确的版本、Node.js 版本、命令、预期结果和实际结果。
  7. 删除访问令牌、个人文件路径和其他敏感信息。

三十五、可复现测试报告模板

你可以将下面的模板用于 DEV Community、GitLab 讨论区或其他技术社区。

Title: Sentinel-IR 0.1.2 Termux smoke test

Device/OS: [Android version and device, if you want to share]
Termux source: [F-Droid or official GitHub]
Node.js: [output of node --version]
Sentinel-IR: 0.1.2

Input: [synthetic sample / my own authorized JavaScript file]

Command: node analyze.js sample.js

Expected:
analysisStatus=complete
route POST /upload
imports express and fs
environment variable PORT

Actual: [what really happened]

Result: [PASS / FAIL / INCOMPLETE]

Notes: [reproducible error or unexpected field, without secrets]

使用你自己的 DEV Community 或 GitLab 账户发布测试结果即可,不需要访问维护者账户。

一份有价值的问题报告应包含最小可公开示例、准确版本、预期行为、实际行为,以及分析状态是否完整。


最终检查清单

在结束之前,请确认以下事项。

  • [ ] "node --version" 显示 Node.js 20 或更新版本。
  • [ ] "npm ls sentinel-ir" 显示 "sentinel-ir@0.1.2"。
  • [ ] "analyzeAsync"、"compress" 和 "initializeWasm" 已正确导出。
  • [ ] WebAssembly 初始化成功。
  • [ ] 示例文件通过语法检查。
  • [ ] "node analyze.js sample.js" 成功生成报告。
  • [ ] 报告的 "analysisStatus" 为 "complete"。
  • [ ] 路由、导入和环境变量检查通过。
  • [ ] 六项 0.1.2 定向 smoke tests 全部通过。
  • [ ] 了解 "critical" 风险等级不等于已证明恶意或已执行命令。
  • [ ] 了解 smoke test 不是完整安全认证。

如果某一步失败,不要隐藏失败结果。

记录错误、版本和环境信息。一个其他开发者可以复现的失败结果,通常比一句没有细节的“工具无法运行”更有价值。


最后的邀请

Sentinel-IR 的目标不是声称分析器可以理解所有代码,而是在分析开始之前,为下一步决策提供更紧凑、更容易检查的结构化事实。

如果你使用 Android 和 Termux 完成了测试,欢迎分享:

  • 哪些结构被正确识别?
  • 哪些事实缺失?
  • 是否出现误报?
  • 不同 Node.js 版本或 Android 环境是否产生了不同结果?

请尽量提供可复现的最小示例,而不是只发布截图。

让代码分析的结论可以被检查,让工具的限制也保持可见。

项目地址:

Sentinel-IR 0.1.2:在 Android Termux 上安装、分析并验证。

Top comments (0)