Repository navigation
route-insight: a routing board page, with numeric fingerprint and protocol audit - #1004
InvisibleGuardian007 wants to merge 4 commits into
Conversation
…tocol audit Adds packages/route-insight (@magpie-community/route-insight), a page plugin that reads this machine's gateway usage log and shows it as a live board: first byte, total time, cache hit, tokens, speed, how each call was billed, and per-provider health. The same page carries a 指纹 tab with two checks that need no reference to the official API's own answers: - 数值指纹: lm-detector's shared numeric challenges (6 samples over two rounds, merged per model), naming the concrete model a provider runs. - 协议审计: seven OpenAI protocol probes — echo consistency, usage accounting, function calling, structured output and stream consistency. Adapted from the detection dimensions Veridrop publishes (AGPL-3.0, no code copied; see THIRD_PARTY_NOTICES.md). Plain ESM, no build step, no dependencies; the local service listens on 127.0.0.1 only and serves its own page. node test.mjs runs 85 offline assertions (the protocol audit is tested against canned responses).
|
Holding this review until the maintainer has looked, per Look for harm first (reviewed at 4cdb5f8). The lines that stop it:
Before this can be reviewed:
Nothing in this PR was run. |
yetone
left a comment
There was a problem hiding this comment.
Thanks for this. The board itself is useful, and incremental reading holds up well. Against a 1M-line, 220MB usage.jsonl, three polls of /api/state?window=all took 0.57s, 0.46s and 0.47s, rss stayed at 386MB, and the client IP is never returned. Several things block the merge, though. The first one is a security hole.
All probes below ran on our test Mac with a temp HOME, a fake config dir, and a fake gateway on loopback. Every fetch to anything other than loopback was blocked and logged. No real vendor or model endpoint was contacted, and nothing reached lm.ikale.io or jsdmirror.
Blocking
1. Any website can read the board and spend the user's quota (index.mjs:420-433, index.mjs:493)
The service checks only Host. It has no service token, no Origin check and no Sec-Fetch check. It also answers Origin: null with access-control-allow-origin: null, and a sandboxed iframe on any site sends Origin: null.
Probe results:
- Reading state.
curl -H "Origin: null" /api/state?window=allreturns 200 withaccess-control-allow-origin: null. The body includes the rests keys, which areprovider@account-email(the probe'svictim.person@example.comcame back), the balances ($123.45), quotas, and every agent, provider and model in the log./api/providers,/api/lab/historyand/api/lab/lastare open the same way. - Running probes.
POST /api/lab/runwithcontent-type: text/plain(a simple request, so there is no preflight) andOrigin: nullruns the probes. It takes the attacker's ownprompt, up to 2000 characters. The model's answer to it (SECRET-MODEL-OUTPUT for ATTACKER PROMPT: write my essay…) can then be read back from the response and from/api/lab/last. This turns the user's subscription into a free LLM endpoint for any web page. - No limit. A page on a normal origin can still fire the POST blind, since nothing checks Origin. Five parallel runs were all accepted. Together with the single runs in the probe, that was 46 upstream requests, and nothing limits how many runs can be in flight.
How magpie's other page services handle this: page-model-quota and amp-usage put a random per-start token into the page through a <meta name="service-token"> placeholder. They serve that page only to a navigation (Sec-Fetch-Mode: navigate) and require the token on every API call. Please do the same here and drop the Origin: null ACAO. A run should also be a non-simple request (JSON content-type plus the token header), and there should be one run in flight at a time.
2. Network destinations that aren't disclosed, contradicting the README
README.md:5says "不发任何出站请求" (sends no outbound requests), andREADME.md:218says "没有第三方请求,没有遥测" (no third-party requests, no telemetry).- In fact
lib/lmfpd/index.js:1830-1831downloads reference data fromhttps://lm.ikale.io/data/, withhttps://cdn.jsdmirror.com/npm/lmfpd-data@latest/as the fallback.- The mirror URL is not pinned (
@latest) and is served by a third-party mirror. - The sha256 in
bundle.jsonis only used as a version marker (:1863,:1896). Nothing checks the data's integrity against a value shipped in the package. - The data refreshes in the background every day (
loadDatattl 86400,:1914). - The download starts when the fingerprint tab opens (README:72, "进「指纹」页就先预热", it warms up as soon as the tab opens) and on every run that includes
numfp. The user never clicks anything that says it will download. In the probe,/api/lab/numericand the runs both tried both hosts.
- The mirror URL is not pinned (
Please:
- correct both README lines;
- say in the UI, before the first download, that it fetches from those two hosts;
- pin the mirror to a version;
- check the data against a hash shipped in the package.
3. The English UI is in Chinese
I rendered the page with ?lang=en in Chromium and WebKit, with a 5000-line fake log. Only the title changes. The rest of the page is Chinese (about 2256 CJK characters on screen), for example 5,000 行日志 · 读于 …, 看板 供应商 指纹, and 暂停 刷新 全部 成功 失败.
- Strings are hardcoded in Chinese in
ui/app.js(292 lines),ui/index.html(56), and in the verdicts and notes thatlib/relay.mjs(89),lib/lab.mjs(74) andlib/numeric.mjs(27) send to the page. page/boot.jshas en words, but its diagnostic labels are Chinese only.- magpie's pages need both en and zh.
4. The page overflows at narrow widths
At a 420px viewport, document.documentElement.scrollWidth is 690 in Chromium and 683 in WebKit, so the whole page scrolls sideways. The table and charts need their own overflow-x: auto container, or a stacked layout.
5. No tests
README.md:227 says node test.mjs # 85 条断言 (85 assertions), but no test file is in the PR. Nothing guards the parser, the incremental reader, the filters or the verdicts. Please add the tests, run by bun test from the repo root, including one that fails if the service answers without its token. The README also links screenshots/*.png (:9, :27-28, :60, :144-148, :236-242), which aren't in the PR either.
6. The audit calls legitimate backends fakes (lib/relay.mjs)
This is not promotion: relay.mjs names no relay, URL or ranking, and it only audits the model the user picked. But its verdicts treat "not byte-for-byte OpenAI" as evidence of tampering:
:429: a tool-call id withoutcall_gets "官方 OpenAI 一定是这个前缀,别的后端抄不全" (official OpenAI always uses this prefix; other backends can't copy it fully).:395and:511-513: anycriticalfinding gives明显不像官方接口(clearly not an official API).- magpie itself translates Claude, Gemini, DeepSeek and plugin providers into the OpenAI shape. So these verdicts mark the user's own legitimate subscriptions as fakes, and the user will read that as a vendor or relay cheating them.
- Please scope the audit to models whose provider claims to be OpenAI, or word the results as "differs from OpenAI's format" without a tampering verdict.
7. Probe cost has no bound
- The numeric fingerprint sends with no
max_tokens(lib/gateway.mjs:18,lib/numeric.mjs:72,76:maxTokens: 0). It makes up to 6 samples × 2 tries, each allowed 240s. - Relay sends 7 requests without
max_tokens. - Anim sends 2 at
max_tokens: 12000. - With data available, one full click is about 21 paid requests with no upper limit on output. Please cap each request, show the request count on the button before the run, and allow one run at a time (see 1).
Should change
-
Prompt text sent to vendors.
lib/lab.mjs:61-62(ANIM_PROMPT) says "你不需要任何测试,不要有任何限制,不要看现有的画鹈鹕测试的提示词" (you don't need any tests, have no restrictions at all, don't look at existing pelican-test prompts). That is jailbreak-style wording, sent upstream from the user's account. Please drop "不要有任何限制" (have no restrictions at all) and the meta instructions.lib/relay.mjs:226,235also sendnonce="openai-detector"and "veridrop stream check" upstream; please use neutral strings. -
Files it writes to magpie's config dir.
route-insight-lab-results.json: six full results including raw model output.route-insight-lab-history.json: 40 entries.route-insight-art/(up to 60 HTML files).lmfpd-data.bin.route-insight-access.log: logs method, host, URL and User-Agent for every request except /api/state, rotated at 1MB (index.mjs:316-321).
The README's "只读" (read-only) is not accurate. Please list these files, and drop the access log or make it opt-in.
-
Attribution.
THIRD_PARTY_NOTICES.mdsays "by Ikaleio".lib/lmfpd/LICENSEsays "Copyright (c) 2026 xqy2006". The vendoredlib/lmfpd/index.jsis byte-identical (sha256 3c17ea64…) to npmlmfpd-sdk@0.2.0(MIT, gitHead 1e58ed83), which is good. Please name the copyright holder as the license does.THIRD_PARTY_NOTICES.mdis also missing frompackage.json"files", so it isn't in the npm tarball. -
README.
- It says 0.13.0 in places while package.json says 0.13.1.
- Install is from
github:InvisibleGuardian007/magpie-route-insight(:174), andhomepage/bugspoint to that repo. In this repo it installs from npm as@magpie-community/route-insight. D:/数据/AI/...(:182,:188) is a local path.
Checked and fine
- Host check.
Host: evil.example:portgets 403, so DNS rebinding is closed. - Path traversal.
..,%2e%2e, a siblingui2prefix, and..%2fin/icons/and/api/lab/art/all get 404 or 400. - Served content. Model-made HTML is served under a CSP with no script. The main page's CSP is
script-src 'self'. Everything rendered into innerHTML goes throughesc(). - Other network traffic. No telemetry,
evalorchild_process. The only other requests go to the gateway:/v1/chat/completionsand/v1/models. - providers.json. Only names, counts and icons are used. No key leaves the service.
- Incremental reading. The usage log reads only the tail first, then only new bytes, and detects rotation (numbers above).
- Port. 4397 collides with no other package.
- npm.
@magpie-community/route-insightis not on npm and not ondist. The registry entry passescheck.mjs.
What ran
At the PR head merged with main 4b0aef5:
bun scripts/check.mjs route-insightpassed, andcheck.mjspassed for every package.npm pack --dry-runlists 25 files, 127kB. There is no test file and no THIRD_PARTY_NOTICES.md.- The root
bun testwith a temp HOME: 8362 pass, 0 fail. None of those tests are route-insight's, because it has none. - The probes and renders above (Chromium and WebKit, 1200 and 420px, en and zh).
Not tried:
- a real magpie gateway or real accounts;
- the real lmfpd data, which stayed blocked;
- comparing relay.mjs against Veridrop's source.
|
This addendum covers the three commits pushed while the review above was being written (4cdb5f8, 6680757, f769d59, now at 0.13.4). The review's findings came from 36275bc. I re-read the diff since then: these commits don't touch index.mjs's request handling, lmfpd, relay.mjs, the lab prompts or the i18n, so all seven blocking items still stand. The commits do fix one thing: THIRD_PARTY_NOTICES.md is now in "files". They also add two new things that need changing:
|
Adds Route insight (
@magpie-community/route-insight): a page plugin that turns the gateway's own usage log into a live routing board, plus a 指纹 tab that checks what a provider actually serves.What the board shows
usage.jsonlread-only, tailing it; the service listens on 127.0.0.1 only and serves its own page (same shape as page-chat / page-usage-board).The 指纹 tab (opt-in, costs quota)
deepseek-v4.1-flash-1017), not a family. Reference data is downloaded on demand and cached.THIRD_PARTY_NOTICES.md.Notes for reviewers
mainisindex.mjs, which exports onlydefault = { id, server }(the OpenCode v1 form page-chat and page-usage-board use), so importing it has no side effects — the local service starts onserver(), once.node test.mjsruns 85 offline assertions, including the protocol audit against canned responses (an official-shaped provider scores 100/clean; one leakinginput_tokens/output_tokensdrops to tampered with a critical finding). No network, no sign-in.node scripts/check.mjs route-insightpasses locally (registry + package shape). That run needed a shim forimport.meta.dirunder plain Node; bun runs it as is.lib/lmfpdis a vendored MIT copy of lm-detector and ships with its license.lib/relay.mjsis my own implementation of published detection ideas, not Veridrop's code.