Skip to content

Add Fingerprint page to investigate model substitution - #516

Merged
yetone merged 2 commits into
magpie-community:mainfrom
KaranocaVe:feat/fingerprint-page
Oct 30, 2026
Merged

yetone merged 2 commits into
magpie-community:mainfrom
KaranocaVe:feat/fingerprint-page

Conversation

@KaranocaVe

@KaranocaVe KaranocaVe commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

API model labels alone do not help users investigate suspected model substitution. This adds Fingerprint, a page that samples models through Magpie’s existing providers and compares numeric response fingerprints with lm-detector’s reference bank. Users can compare channels with three shared challenges, inspect candidate rankings and actual routes, and retain raw evidence in local history or export it as JSON. Each result summary labels rankings Evidence, not proof.

  • Add @magpie-community/page-fingerprint under packages/fingerprint, with a provider-menu action and English/Chinese registry entry.
  • Include complete source, offline tests, and rebuilt UI/service. Magpie’s theme/language bridge and app-style menus work in desktop and magpie web; English is the fallback, Simplified/Traditional Chinese are distinct, and the boot page also supports Japanese/German.
  • Vendor the MIT lmfpd-sdk@0.2.0 runtime/declarations with provenance and third-party notices. Installed bundles and offline tests need no dependency installation. Exact development versions and bun.lock use registry.npmjs.org; bun run rebuild performs a frozen install and checks in an isolated temporary copy.
  • Opening/refreshing the page never downloads reference data. An explicit Download reference data action fetches a snapshot; starting a valid run downloads only when no usable snapshot is cached. No background refresh. The default sources are https://lm.ikale.io/data/ and fallback https://cdn.jsdmirror.com/npm/lmfpd-data@latest/, named in the UI, registry and READMEs. These requests reveal the client IP but carry no credentials, model ids, prompts or replies.
  • Public health configuration digests use HMAC-SHA256 with an owner-only random per-install key, atomically published across concurrent starts; changing a gateway API key still prevents reuse of an incompatible service.

Starting a run sends three sampling requests per selected model and uses provider quota. Remote reference data is not release-pinned or verified against a locally pinned hash; each job retains its loaded snapshot and records builtAt. Rankings provide evidence for investigation, not proof of model identity. New network hosts and the minified bundles remain subject to maintainer review.

Validation at 531ca11:

  • Typecheck; 30 offline tests / 197 assertions; production build; bun scripts/check.mjs fingerprint; git diff --check.
  • Two independent bun run rebuild runs with Bun 1.4.4 and the committed frozen lockfile produced byte-for-byte identical dist/ (six files).
  • npm pack --ignore-scripts, then extracted-bundle demo startup without node_modules: bootstrap, four demo models and both UI assets verified.
  • Chromium QA through a sandboxed host frame: German-host English fallback, Traditional Chinese, explicit download/source disclosure, keyboard menu selection and Tab dismissal, result-summary caveats, and narrow layout.
  • Tests cover no bootstrap/refresh download, stale-cache reuse without background requests, explicit download success/failure, no credential leakage to the data source, localized boot text, and concurrent service-identity creation.

Independent re-review at 531ca11 (review) confirmed all six requested changes and byte-identical rebuilt assets. On this head merged with main at 848497c, the reviewer reported 3,838 repository tests passed, 0 failed (3,846 tests across 312 files), and bun scripts/check.mjs exited 0. Their non-loopback-denying sandbox also passed all 30 Fingerprint tests, and deliberate regressions in bootstrap loading, cache TTL and job-validation order each made the relevant tests fail. These are reviewer-run results; GitHub Actions still requires approval. They supersede the earlier local full-suite timeout notes.

Maintainer decisions remain: accepting the two disclosed, opt-in reference hosts and the unpinned @latest fallback. The new package is not yet on npm; Magpie can install it from the repository’s dist branch until its first npm publication, as the reviewer noted.

Standalone project and release: https://github.com/KaranocaVe/magpie-fingerprint/releases/tag/v0.2.0
Demo screenshot: https://github.com/KaranocaVe/magpie-fingerprint/blob/main/docs/preview.png

@yetone

yetone commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Thanks for this. I reviewed it at e5b5825 and read all of src/, web/, page/, tests/ and the vendored SDK. The code is careful. The service is loopback-only and checks Host, Origin and a bearer token. The sample tickets are one-shot, bound to a job and sent over a second loopback listener. Model traffic goes only to the local magpie gateway, and I found no eval or telemetry.

What I checked:

  • Dist: I rebuilt dist/ on macOS with Bun 1.4.2 and the versions in your upstream repo's bun.lock (react 19.3.1, vite 7.3.7, @vitejs/plugin-react 5.2.0, @phosphor-icons/react 2.1.10, typescript 5.9.3). diff -r against the committed dist/ showed no differences.
  • Vendored SDK: vendor/lmfpd-sdk matches the npm lmfpd-sdk@0.2.0 tarball (its sha512 matches npm's integrity). The only difference is the extra index.d.ts shim.
  • Tests: bun test in packages/fingerprint passed 22 of 22 under a sandbox that denies every non-loopback connection. tsc --noEmit is clean, and bun scripts/check.mjs fingerprint passes.
  • Quota: only POST /api/jobs sends model requests, 3 per model. Opening the page reads /v1/models and /v1/magpie/quotas?kept=1, which spend nothing.

Under the repo's contributor rules, two things need the maintainer before this can merge: the new network hosts, and the minified bundles. Here is what to change.

  1. Third-party hosts are contacted as soon as the page opens. GET /api/bootstrap (src/server.ts:444-445) calls ensureDetector(). That downloads the reference bank from https://lm.ikale.io/data/ and falls back to https://cdn.jsdmirror.com/npm/lmfpd-data@latest/. A refresh then runs every day in the background. The requests carry no prompts, keys or model ids, but they do reveal the user's IP and that they opened the page. Please:
    • Download the bank only when the user starts a run, or when they press an explicit "Download reference data" button.
    • Name both hosts in the page itself, in the registry summary and in the README.
  2. The reference data isn't pinned. The README says "a pinned reference version" (README.md:62). In practice the fallback is lmfpd-data@latest on a third-party mirror, and only builtAt is recorded. Either pin a version, with a hash checked before the data is used, or change the wording.
  3. The dependencies aren't pinned either. "Build-tool versions are pinned in package.json" (README.md:39) isn't true: every entry uses a caret range, and the package has no lockfile. Please commit the bun.lock that produced dist/, so anyone can rebuild it byte for byte. Please also point it at registry.npmjs.org rather than npmmirror.
  4. Language:
    • Any host language that isn't English falls back to Simplified Chinese (web/main.tsx:142,145), and zh-TW also gets Simplified.
    • The boot page is Chinese only (page/index.html: lang="zh-CN", "正在连接模型指纹服务…").
    • The manifest titles give only zh. Other page packages (chat, for example) also give zh-TW, ja and de.
    • Please make English the default and follow the host language for the boot text.
  5. Wording of the results. The summary row puts the top candidate's name (prediction_name) as the headline. The only caveat, "Probabilities cover reference-bank candidates only…", appears when the row is expanded. Please put "evidence, not proof" (as the README says) next to the result itself, so a ranking doesn't read as a verdict on a provider.
  6. Minor:
    • The dropdowns use native <select> elements (web/main.tsx:557,638,647,687). magpie's own UI uses its app menu instead.
    • The unauthenticated /_fingerprint/health returns a sha256 over a JSON that includes the gateway apiKey (src/service-identity.ts). A random salt per install, or leaving the key out, would avoid publishing a hash of a secret.

Two notes on the repo itself: under the same offline sandbox, the root bun test has 20 failures (cowork, devin images/usage, zcode) and check.mjs fails for cloudflare-workers-ai. Main at 57f3f90 has exactly the same failures, so this PR didn't cause them.

@KaranocaVe

Copy link
Copy Markdown
Contributor Author

Addressed the review in 531ca11:

  1. Bootstrap no longer calls the detector loader; refresh reads only the gateway/local state. Added an explicit Download reference data action. A valid run loads/downloads only as needed; cached data uses ttl: Infinity, so the SDK cannot schedule background refreshes. Both default hosts and the IP/request disclosure are visible in the page, registry and both READMEs.
  2. Removed the reference-version pinning claim. The docs now state that the fallback is @latest, remote data has no locally pinned hash, and a job retains its loaded snapshot with builtAt.
  3. Added the complete bun.lock on registry.npmjs.org, exact direct versions and Bun 1.4.4. bun run rebuild uses a temporary standalone copy to honor this package’s frozen lockfile inside the monorepo. Two clean isolated rebuilds produced byte-identical dist/ (all six files).
  4. English now defaults for unsupported languages; Simplified/Traditional Chinese are separate. The boot page follows the host (including ja/de), and manifest page/action titles include zh-TW/ja/de.
  5. Evidence, not proof is next to every result headline and remains visible when collapsed.
  6. Replaced all four native selects with Magpie-style listboxes, including keyboard navigation, focus, Escape/Tab and outside dismissal. The public health digest now uses HMAC-SHA256 with a private random per-install key. Concurrent first starts publish that key atomically; changing the gateway key still rejects service sharing.

Validation: typecheck, 30 tests / 197 assertions, production build, package check, no-install extracted-tarball smoke test, and Chromium QA in a sandboxed host frame (English fallback, Traditional Chinese, menus and narrow layout). Regression tests cover the download boundaries and concurrent salt creation. The existing unrelated root-suite failures are retained in the PR notes.

The requested source/build changes are ready for another review. Approval of the new hosts and minified bundles remains with the maintainers.

@yetone yetone left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at 531ca11. Thanks, every point from my review is addressed, and I checked each one against the code. The code side is ready. What's left is the maintainer's call on the two new hosts, as you said yourself, so I'm not merging yet.

The six points:

  1. No download when the page opens.
    • GET /api/bootstrap now calls only catalog() (src/server.ts:471), and reports bankStatus from a local access() check.
    • Downloads happen in two places only. One is POST /api/bank/download, behind the new Download reference data button. The other is a valid POST /api/jobs when nothing is cached. A malformed job is now rejected before any download (createJob moved above ensureDetector, :509).
    • ttl: Infinity really stops the SDK's background refresh: loadData refreshes only when Date.now() - checkedAt >= ttl * 1000 (vendor/lmfpd-sdk/index.js:1921).
    • Both hosts and the IP disclosure are named on the page (web/main.tsx:675-682, always shown under the button, en/zh-Hans/zh-Hant), in registry.json (en and zh) and in both READMEs and integration.md.
  2. Pinning claim: removed from README, README.zh-CN and integration.md, which now say @latest, that there is no locally pinned hash, and builtAt per job.
  3. Lockfile: bun.lock is committed, every resolved tarball is on registry.npmjs.org, and the direct devDependencies are exact.
  4. Language: a non-Chinese host language falls back to English (web/locale.ts) and zh-TW gets Traditional. The boot page starts in English and follows the host, and the manifest titles now cover zh-TW, ja and de.
  5. Wording: "Evidence, not proof" sits on the collapsed result row, next to the prediction (web/main.tsx:1179-1181).
  6. Minor points:
    • The four native <select>s are gone; none is left in web/.
    • The health digest is now an HMAC-SHA256 keyed with a random 32-byte salt per install. The salt is published with link(), so it is written once and never replaced, and the file is 0600.

What I ran (Mac mini, Bun 1.4.2, through mote-run):

  • Dist: bun run rebuild from your bun.lock (frozen, --ignore-scripts, registry.npmjs.org). It ran typecheck, 30/30 tests and the build. diff -r against the committed dist/ showed no differences in any of the six files, so the minified bundles are exactly what the source builds.
  • Repo suite, as CI runs it, on this head merged with main at 848497c (clean merge):
    • HOME=$(mktemp -d) bun test: 3838 pass, 0 fail (3846 tests, 312 files).
    • bun scripts/check.mjs: exit 0.
    • CI hasn't run on this PR (no checks reported), so this run is the record.
  • Offline run: bun test in packages/fingerprint, with a preload that refuses every non-loopback fetch and socket, passed 30 of 30. Nothing was refused.
  • Broken by hand in tests/bank.test.ts, each going red:
    1. Restoring ensureDetector() in bootstrap made 3 of 3 fail.
    2. Dropping ttl: Infinity failed "an old cached snapshot is reused without background network refresh".
    3. Validating the job after the download failed "opening, refreshing and invalid jobs never download reference data".
  • Hosts and telemetry: I grepped src/, web/, page/, scripts/, dist/ and vendor/ for URLs. The only remote hosts in code are still lm.ikale.io and cdn.jsdmirror.com. The others are loopback, plus the w3.org and react.dev strings React carries. There is nothing new since e5b5825, and there is no beacon, WebSocket, eval or new Function. vendor/ is unchanged.

Left for the maintainer:

  • the two third-party hosts, now opt-in and disclosed;
  • the fallback, which is @latest on a jsDelivr mirror with no pinned hash. The page uses the data only to rank candidates and sends it nowhere, but a bad snapshot would rank wrongly.

Also for when it's merged: @magpie-community/page-fingerprint isn't on npm (npm view → E404), so trusted publishing can't create it. magpie will install it from the dist branch until an owner publishes the first version.

@KaranocaVe

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough re-review and the independent rebuild, offline/mutation tests, and full repository checks. I updated the PR description with your validation results and their exact head/main revisions, replacing the older local timeout notes.

The current head remains 531ca11; all requested code changes are addressed. The remaining decision is whether the two disclosed opt-in reference hosts and unpinned fallback are acceptable under this repository’s policy.

I also recorded the first-publication limitation: the package is not on npm yet, and Magpie can use the dist branch until the first npm release. No npm publication is claimed here.

@yetone
yetone merged commit 00f4d84 into magpie-community:main Oct 30, 2026
@yetone

yetone commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Merged as 00f4d84. My call on the two hosts: they're acceptable as they are now. They're contacted only on an explicit Download or a valid job, they're named on the page, in registry.json and the READMEs, and the data is used locally to rank candidates and sent nowhere. The @latest fallback without a pinned hash is a known limit, and the page states it.

@magpie-community/page-fingerprint isn't on npm yet, so trusted publishing can't create it. magpie installs it from the dist branch until an owner publishes the first version. Thanks for the careful rework.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants