OmX 被动 URL 读取器(Passive URL Reader)实战指南:`omx url read` 的安全边界与结构化输出
2026/9/10 2:34:30 网站建设 项目流程

OmX 被动 URL 读取器(Passive URL Reader)实战指南:omx url read的安全边界与结构化输出

【免费下载链接】oh-my-codexOmX - Oh My codeX: Your codex is not alone. Add hooks, agent teams, HUDs, and so much more.项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-codex

omx url read <url> --json是 OmX(Oh My codeX)提供的一项被动式、有界的 URL 读取能力:它只负责把用户提供的 HTTP(S) 地址抓取回来,并以结构化 JSON 交付给自动化流程,全程不依赖浏览器、不注入 Cookie、不解挑战码,也不修改任何全局二进制或PATH。本文以 docs/url-reader.md 为骨架,结合 src/url-reader/index.ts 的真实实现与测试用例,完整讲解命令用法、JSON 输出结构、网络地址封锁策略、DNS 防重绑定机制与重定向校验,并给出可在仓库内直接复现的验证方式。读完你将能安全地把"抓取一个 URL"封装进自己的脚本、Agent 或自动化管线。

一、命令总览:一个命令,四类结果

omx url read的 v0 读取器在设计上刻意保守,它的定位不是"万能抓取器",而是"可审计、可自动化的安全读取器"。CLI 帮助文本(见 src/cli/url.ts)明确列出了它的约束:

  • 不启动浏览器自动化,也不引入浏览器依赖;
  • 不注入 Cookie、不解挑战、不做任何反机器人检测绕过;
  • 不改变全局二进制或PATH的所有权;
  • 仅支持http:https:协议;
  • 在抓取之前就封锁本地、回环、私网、链路本地、唯一本地、组播、保留及内部网络地址;
  • IPv6 特殊用途/保留地址段全部封锁(细节见下文第三节);
  • 主机名在抓取前先解析,任何解析出不安全地址的主机名都会使用与字面 IP 相同的分类器被拦截;
  • 重定向被手动跟踪,且每一个重定向目标在被请求前都会重新校验;
  • 在文本解码之前对响应体做有界读取;
  • 输出结构化verdictokredirectblockederror

命令格式如下:

omx url read https://example.com --json

CLI 层面的参数解析位于 src/cli/url.ts 的parseUrlReadArgs

  • --json:输出结构化 JSON,v0 阶段必填,缺省会直接报Missing required --json flag
  • -h, --help/help:打印帮助;
  • 其余以-开头的 token 一律报Unknown option
  • 恰好接受一个位置参数(URL),多余或缺失都会报错。

CLI 命令在 src/cli/index.ts 中被分发到urlCommand,最终调用核心函数readUrl(src/url-reader/index.ts)。

二、JSON 输出结构:每一个字段的含义

读取结果由 src/url-reader/types.ts 的UrlReadResult定义,字段如下:

字段类型说明
input_urlstring用户最初输入的 URL
final_urlstring | null重定向后的最终 URL;被封锁或出错时为null
verdictok|redirect|blocked|error整体判定结论
statusnumber | nullHTTP 状态码(如 200、302、403)
status_textstring | null状态文本(如OKForbidden
content_typestring | null响应头的Content-Type
redirectedboolean是否发生过重定向
titlestring | null文本类响应的 best-effort 标题(HTML<title>,最长 200 字符)
snippetstring | null文本类响应的摘要(剔除 script/style 与标签后截取 500 字符)
signalsstring[]封锁/挑战信号列表,见下
truncatedboolean响应体是否因超过maxBytes被截断
bytes_readnumber实际读取的字节数
errorUrlReadError | null失败时的安全错误细节{ name, message, code? }

一个成功抓取 HTML 的典型输出大致形如:

{ "input_url": "https://example.com/", "final_url": "https://example.com/", "verdict": "ok", "status": 200, "status_text": "OK", "content_type": "text/html; charset=utf-8", "redirected": false, "title": "Example Domain", "snippet": "Example Domain This domain is for use in illustrative examples ...", "signals": [], "truncated": false, "bytes_read": 1256, "error": null }

verdict 的四态语义

  • ok:成功取得响应,且没有检测到封锁/挑战信号;
  • redirect:请求最终停在一个重定向后的 URL(final_urlinput_url不同),响应本身仍可读;
  • blocked:目标被安全策略拦截——可能是不支持的协议、不安全地址、本地主机名、HTTPS 主机名无法钉扎(pinning)、非法/过多重定向,或响应体命中封锁信号(见signals);
  • error:抓取过程中发生运行时错误(如ECONNREFUSED、DNS 解析失败、fetch 实现不可用)。

signals:封锁与挑战信号

src/url-reader/index.ts 的classifySignals会把两类信息写进signals

  1. 状态码信号401403407423429451503会生成status-<code>(如status-403);
  2. 挑战内容信号:对响应文本做正则匹配(src/url-reader/index.ts 的CHALLENGE_MARKERS),包括:
    • captcha-marker(匹配captcha
    • cloudflare-marker(匹配cloudflare|cf-chl|cf_clearance
    • access-denied-markerjust-a-moment-markerhuman-verification-markerbot-detection-markerblocked-markerchallenge-marker

只要signals非空,verdict就会被置为blocked。测试 src/url-reader/tests/url-reader.test.ts 验证了"403 + Just a moment + Verify you are human"这类反爬页面会被同时标记status-403just-a-moment-markerhuman-verification-marker

三、网络地址封锁策略:字面 IP 与主机名共用同一分类器

这是 v0 读取器最核心的安全设计。无论目标是 IP 字面量还是主机名,最终都汇入同一套地址分类器isSafeIpAddress(src/url-reader/index.ts)。

IPv4 封锁规则(isSafeIpv4,src/url-reader/index.ts)

以下地址段在抓取前一律拦截:

  • 0.0.0.0/8a === 0
  • 10.0.0.0/8私网
  • 127.0.0.0/8回环
  • 224.0.0.0/4及以上(组播/保留)
  • 100.64.0.0/10运营商级 NAT(a === 100 && 64 <= b <= 127
  • 169.254.0.0/16链路本地(含云元数据地址169.254.169.254
  • 172.16.0.0/12私网
  • 192.168.0.0/16私网
  • 192.0.0.0/24(IANA 保留)
  • 192.88.99.0/24(6to4 relay anycast)
  • 198.18.0.0/15198.51.100.0/24(基准测试/文档地址)
  • 203.0.113.0/24(文档地址)

IPv6 封锁规则(isSafeIpv6,src/url-reader/index.ts)

IPv6 判定把地址转成大整数后逐段比较,封锁范围与原文档完全一致:

  • 全零::::1(回环/未指定)
  • 64:ff9b:1::/48本地使用的 IPv4/IPv6 翻译(NAT64)
  • 100::/64仅丢弃(discard-only)、100:0:0:1::/64伪前缀(dummy)
  • 2001::/23IETF 协议分配
  • 2001:db8::/323fff::/20文档/保留
  • 2002::/166to4
  • 5f00::/16Segment Routing SID
  • fc00::/7唯一本地地址(ULA)
  • fe80::/10链路本地
  • ff00::/8组播
  • IPv4 映射地址(::ffff:a.b.c.d)会先解包成 IPv4 再走isSafeIpv4判定,因此::ffff:10.0.0.5::ffff:127.0.0.1::ffff:169.254.169.254全部被拦
  • 公网 IPv6(如2606:4700:4700::1111允许访问

测试 src/url-reader/tests/url-reader.test.ts 用一组不安全 IPv6 字面量逐一断言"先封锁、绝不发起抓取",同时验证公网 IPv6 字面量返回ok

主机名解析与 DNS 防重绑定

对主机名目标,validateAndResolveSafeUrl 会先调用dnsLookup(hostname, { all: true, verbatim: true })解析出全部地址;只要任意一个解析结果不安全,整个读取即被封锁(signal 为unsafe-address)。localhostlocalhost.localdomain以及*.localhost后缀的主机名直接以localhost-name拦截,不会进入解析流程。

更关键的是防 DNS 重绑定/TOCTOU 设计(src/url-reader/index.ts 的prepareSafeFetchTarget):

  • HTTP 主机名:抓取请求不会发给原主机名,而是发给"已校验并钉扎(pin)的 IP"(fetchUrl.hostname = validation.address),同时用Host头携带原始主机名(含端口,见originalHttpHostHeader,src/url-reader/index.ts)。测试 src/url-reader/tests/url-reader.test.ts 证明http://example.test:8080/page实际请求的是http://93.184.216.34:8080/pageHost: example.test:8080;防重绑定测试(src/url-reader/tests/url-reader.test.ts)则证明同一主机名只解析一次,绝不给"第一次返回公网 IP、第二次返回 127.0.0.1"的翻转攻击留机会;
  • HTTPS 主机名:由于当前 v0 运行时而无法在钉扎 TCP 连接的同时安全保留 TLS SNI/证书校验,因此直接封锁(signalhttps-hostname-not-pinned),见测试 src/url-reader/tests/url-reader.test.ts;
  • 公网 IP 字面量的 HTTPS:不涉及主机名解析,直接放行(测试 src/url-reader/tests/url-reader.test.ts)。

四、重定向:手动跟随,逐个校验

v0 读取器不依赖 fetch 内置的自动重定向,而是设置redirect: "manual"手动处理(src/url-reader/index.ts):

  1. 只认301302303307308为可跟随重定向;
  2. 读取Location头,以当前 URL 为基准解析出下一个目标;解析失败直接按unsafe-redirect-url封锁;
  3. 跟随前对下一个目标再次执行完整的prepareSafeFetchTarget校验——包括协议、本地主机名、DNS 解析与地址分类;
  4. 每跳都重复上述过程,超过maxRedirects(默认 10)即按too-many-redirects封锁。

因此,任何"把用户重定向到http://localhost/secret10.0.0.2[::1]或解析到私网地址"的攻击在下一跳发生前就会被拦截,测试 src/url-reader/tests/url-reader.test.ts 覆盖了重定向到 localhost、私网 IPv4、不安全 IPv6 字面量以及解析出不安全 IPv6 的主机名四类场景,均断言只发出了第一跳请求。

五、有界读取:在解码之前截断

响应体通过readBoundedBody(src/url-reader/index.ts)按字节上限流式读取,默认maxBytes = 256 * 1024(256 KiB)。读取逻辑:

  • 逐块累积,达到上限后立即停止并取消底层流(reader.cancel());
  • 恰好等于上限的完整响应不算截断(测试 src/url-reader/tests/url-reader.test.ts);
  • 超过上限的响应标记truncated: truebytes_read为实际读取字节数(测试 src/url-reader/tests/url-reader.test.ts 验证 128 字节上限下bytes_read === 128且流被取消)。

文本解码发生在有界读取之后:优先使用Content-Type中的charset,缺省按 UTF-8(TextDecoder容错模式,src/url-reader/index.ts)。只有"文本类"响应(text/*application/jsonapplication/xmlapplication/xhtml+xmlapplication/rss+xmlapplication/atom+xmlapplication/ld+json,见 src/url-reader/index.ts)才会提取titlesnippetsnippet生成前会先剥离<script>/<style>与所有标签,并解码常见 HTML 实体(src/url-reader/index.ts)。

六、可调参数与可注入依赖

虽然 CLI 的 v0 形态只暴露--json,但核心函数readUrl(url, options)UrlReaderOptions(src/url-reader/types.ts)为嵌入方提供了四个可调项与两个注入点:

选项默认值说明
timeoutMs15_000单次请求超时(毫秒),通过AbortSignal.timeout生效
maxBytes256 * 1024响应体读取上限(字节)
maxRedirects10最大重定向跳数
fetchglobalThis.fetch可注入自定义 fetch 实现(如测试中的桩实现)
resolveHostnamenode:dns/promiseslookup可注入自定义 DNS 解析器(测试用它模拟公网/私网解析结果)

其中timeoutMsmaxBytesmaxRedirects均需为正整数,非法值自动回退到默认值(normalizePositiveInteger,src/url-reader/index.ts)。这正是 src/url-reader/tests/url-reader.test.ts 与 src/cli/tests/url.test.ts 能够在不联网的前提下完整验证全部安全行为的原因。

七、错误处理:只暴露安全细节

任何异常都会被normalizeError(src/url-reader/index.ts)收敛为{ name, message, code? }绝不携带堆栈。测试 src/url-reader/tests/url-reader.test.ts 明确断言:一个带code: "ECONNREFUSED"的异常返回verdict: "error"error.name === "Error"error.code === "ECONNREFUSED",且 JSON 化后的结果不包含 stack。同时读取器会校验运行时是否存在 fetch 实现,缺失时返回FetchUnavailableError(src/url-reader/index.ts)。

八、在仓库中验证与扩展

如果你想在本地复现或深入调试:

  • 运行单元测试(覆盖全部封锁规则、重定向校验、截断与标题/摘要提取):
    node --test src/url-reader/__tests__/url-reader.test.ts node --test src/cli/__tests__/url.test.ts

    其中 src/cli/tests/url.test.ts 还会通过dist/cli/omx.js真实执行omx url --help,断言帮助文本与"被动读取不绕过挑战、不使用浏览器、不注入 Cookie"的承诺出现在顶层及子命令帮助中。

  • CLI 实跑(需要先完成构建得到dist/cli/omx.js):
    node dist/cli/omx.js url read https://example.com --json
  • 阅读实现:入口 src/url-reader/index.ts、类型 src/url-reader/types.ts、CLI 封装 src/cli/url.ts、命令分发 src/cli/index.ts。

总而言之,omx url read --json把"读取一个 URL"封装成了一个默认安全的原语:地址封锁发生在任何网络请求之前,主机名解析被钉扎到已校验 IP,重定向每跳重新校验,响应体有界读取,输出全程结构化 JSON。对于需要把"抓取外部网页"接入 Agent、HUD 或自动化脚本的开发者而言,这是一份开箱即用且可审计的参考实现。

【免费下载链接】oh-my-codexOmX - Oh My codeX: Your codex is not alone. Add hooks, agent teams, HUDs, and so much more.项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-codex

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询