feat(security): wire ModelHost lifecycle + pf-analyze IPC bridge#293
Merged
Conversation
Collaborator
Author
This was referenced May 22, 2026
4c54f65 to
950fd74
Compare
PR 5c of the Privacy Filter ML stack. ModelHost.analyze now does a
real correlation-ID round-trip to the hidden inference window, and
flipping the pfEnabled toggle in Settings actually spawns / tears
down that window. The inference handler still stubs out to no
matches — wiring the ONNX model into the renderer ships later.
- inference/types.ts: PfAnalyzeRequest / PfAnalyzeResponse / PfAnalyzeError
+ ANALYZE_REQUEST / ANALYZE_RESULT channels
- pf-inference.ts: stub handler returns []; preserves the
detectRuntime → pf:ready handshake from PR 5a
- preload/inference.ts: onAnalyzeRequest + sendAnalyzeResult bridges
- model-host.ts: analyze does Map<reqId, {resolve,reject,timer}>
correlation; per-call timeout (default 30 s, generous for WASM cold);
rejects every pending request on scope close; ignores results from
the wrong sender. 5 new analyze test cases (not-ready, happy path,
wrong sender, timeout, inference error)
- pf-runtime.ts (NEW): main-side singleton owning the ModelHost Scope.
start() / stop() idempotent; analyze() returns [] when inactive so
callers don't have to gate; pfModelInstalled() helper. 5 unit tests
- main/index.ts: holds the pfRuntime singleton; syncPfRuntime() flips
it based on pfEnabled + model install status; clean shutdown
- ipc/security.ts: onPfEnabledChanged callback on SecurityIpcDeps;
SET_PREFS handler fires it on pfEnabled flip only
- PfDownloadCard.tsx: replaces "Ready" badge in installed phase with
a real Toggle wired to setPrefs({pfEnabled})
What's deferred to PR 5d:
- The scan worker doesn't yet consult pfProvider — findings stay
regex-only even with the toggle on. PR 5d adds the worker → main →
inference window bridge + the regex@4,pf@1.5b-q4 profile bump that
rescans on activation.
Full app suite 270/270. Typecheck clean.
PR 5d of the Privacy Filter ML stack. The scan worker now actually consults the Privacy Filter when pfEnabled is on and the inference window is up; the profile string flips to regex@4,pf@1.5b-q4, which forces a backfill of every session that was scanned regex-only. - scan-worker-thread.ts: builds a pfProvider whose available() reads a sync pfOnline flag + the user's pfEnabled pref; analyze posts a pf-analyze-req over parentPort and awaits the matching pf-analyze-res. After the round-trip, runs a cheap regex pass over the same text so class-mapping suppression rules (url/secret boost, DOB gating) have the context they need. currentProfile callback now folds pfEnabled + PF_PROFILE_VERSION into the canonical profile string - scan-worker-thread.ts protocol: added pf-online / pf-offline / pf-analyze-res (ToWorker) and pf-analyze-req (FromWorker) - scan-worker-proxy.ts: accepts an optional pfBridge; routes incoming pf-analyze-req to pfBridge.analyze, posts pf-analyze-res back (ok or ok:false with message); exposes notifyPfOnline / notifyPfOffline so main can flip the worker's flag on pfRuntime transitions - main/index.ts: pfBridge wired to pfRuntime.analyze; syncPfRuntime now also notifies the scan worker AND kicks worker.backfill() so sessions whose stored scan_profile no longer matches get re-enqueued Profile transitions: pfEnabled off → 'regex@4' pfEnabled on → 'regex@4,pf@1.5b-q4' Backfill picks up the drift automatically; users see the existing progress bar in the Security page banner. Full app suite 270/270. Typecheck clean. New protocol surfaces get e2e coverage in PR 5e.
…phan PR 5e of the Privacy Filter ML stack — polish + coverage. - en/zh-CN/zh-TW: 8 new keys for PfDownloadCard (Download / Cancel / Retry / Ready / WebGPU / WASM / progress template / error default). ja/ko/de/fr get English placeholders so the locales-parity test stays green; native pass deferred (same pattern as backups, PR #277) - Drop the `detector_coming_soon` key everywhere — no code references it after PR 5c replaced the inert toggle - New e2e: security-pf-download-card.spec.ts. Verifies the card lands in not-installed phase on a fresh app, no toggle is reachable pre-install, and clicking Download advances the state machine Note for follow-up: the inference window's pf:analyze handler is still a stub returning []. Real ONNX inference (load transformers.js + pinned model from disk) lives outside this stack — see the project_security_scan_feature memory's GA-gating rule. PR 5e leaves VITE_FEATURE_SECURITY OFF in release.yml; no release-notes copy. Full app suite 270/270.
950fd74 to
7992338
Compare
The PR 2 e2e (security-pf-download-card.spec.ts) clicks the Download button and expects the card to leave the not-installed phase. But pfCoordinator was null at PR 2 because the makePfCoordinator() call + pass-through to registerSecurityIpc were originally landing in PR 3 (0df364f). So the IPC handler returned `unavailable` and the card stayed put, failing the test. Move the wiring forward to where its consumer lives. The constructor + ipc plumbing already exist as of PR 1's PfCoordinator landing; only the boot-time instantiation was missing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

PR 2/7 — PF engine wiring: ModelHost lifecycle + pf-analyze IPC bridge + scan worker participation
PR 1 set up the scaffold; this one wires it to the scan engine. After this PR, flipping
pfEnabledactually spawns the inference window, the scan worker consults apfProvider, and the profile string drifts toregex@N,pf@1.5b-q4so the backfill loop will re-scan stale sessions. Real ONNX inference still stubs out to[]— that lands in PR 3.IPC flow
sequenceDiagram participant W as scan-worker (worker_thread) participant M as main process participant H as Inference window participant S as Settings UI S->>M: setPrefs({pfEnabled: true}) M->>M: onPfEnabledChanged(true) → syncPfRuntime M->>H: spawn BrowserWindow H-->>M: pf:ready (runtime, adapterLabel) M->>W: notifyPfOnline() M->>W: backfill() ← profile drift triggers rescan W-->>W: pfProvider.available() returns true W->>M: pf-analyze-req {reqId, text} M->>H: PF_ANALYZE_REQUEST {reqId, text} H-->>M: PF_ANALYZE_RESULT {reqId, matches[]} M-->>W: pf-analyze-res {reqId, matches[]} W-->>W: mapPfMatches → mergeMatches → DB insertThe round-trip is correlation-ID based: each
pf-analyze-reqcarries a uniquereqIdfrom the worker; main routes the response to the samereqId. ModelHost holds aMap<reqId, {resolve,reject,timer}>and ignores results from the wrong sender.What's in this PR
inference/types.ts— wire-protocol surfacesPfAnalyzeRequest,PfAnalyzeResponse,PfAnalyzeErrorANALYZE_REQUEST/ANALYZE_RESULTchannel constantspf-inference.ts(renderer)[]for now (real ONNX in PR 3)detectRuntime → pf:readyhandshake from PR 1model-host.ts— analyze pipeanalyze(text): postsPF_ANALYZE_REQUESTto the renderer, registers a(resolve, reject, timer)slot, awaits the responsepf-runtime.ts(NEW)start()/stop()idempotentanalyze()returns[]when inactive so callers don't gatepfModelInstalled()helpermain/index.ts— toggle wiringsyncPfRuntime(pfEnabled): brings the runtime up/down to match the user's preference, then callsscanWorker.backfill()so existing sessions rescanscan-worker-thread.ts— pfProvideravailable()reads a syncpfOnlineflag + the user'spfEnabledprefanalyze()posts apf-analyze-reqoverparentPortand awaits the matchingpf-analyze-rescurrentProfile()foldspfEnabled + PF_PROFILE_VERSIONinto the canonical profile stringi18n + e2e + cleanup
PfDownloadCard(Download / Cancel / Retry / Ready / WebGPU / WASM / progress / error)detector_coming_soonorphan key dropped (PR 1 replaced the inert toggle)security-pf-download-card.spec.ts— e2e for the card lifecycleTest plan
Builds on PR 1 (foundation). Followed by PR 3 (real ONNX inference + runtime info chip).
🤖 Generated with Claude Code