DEV Community

jackymenCZ (jackymenCZ)
jackymenCZ (jackymenCZ)

Posted on Edited 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 上安装、分析并验证。


Independent Engineering Projects

Built as independent engineering experiments across AI, software analysis, security, and autonomous systems.

Evaluated by reproducible evidence, not by the number of layers.

Explore the projects:

  • Sentinel Ecosystem: AutoDoc-Sentinel, Sentinel-IR, Sentinel-IR-MCP and related tools.
  • Agentix Lite: Independent security and system-observation project.

Find the projects: GitLab repositories | NPM packages

Support the work: ⭐ Star the repositories on GitLab.

Connect: Follow me here on DEV Community or explore the Playground.

Each project is distributed under its own license. Check the corresponding repository or NPM package for licensing terms.

Copyright © 2026 jackymenCZ. All rights reserved. Written for the community.

Top comments (0)