Skip to content

security: unify the LLM pre-flight guard across all paths (agent, comparison, HTTP + chat) - #19

Merged
Baldri merged 3 commits into
mainfrom
claude/session/fix/mingly-guardrail-all-paths
Aug 22, 2026
Merged

security: unify the LLM pre-flight guard across all paths (agent, comparison, HTTP + chat)#19
Baldri merged 3 commits into
mainfrom
claude/session/fix/mingly-guardrail-all-paths

Conversation

@Baldri

@Baldri Baldri commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Was

Ein einziger geteilter LLM-Guard (request-guard.ts) auf allen Pfaden: Agent, Comparison, HTTP/WS und Chat. Prompt-Injection-Scan, PII-Scan + Cloud-Upload-Consent, Budget, Sensitivity-Routing.

Warum

Die Schutzkette lief bisher nur inline auf dem Chat-IPC-Pfad — genau die Duplikation, die Agent/Comparison/HTTP aus der Deckung driften liess (Audit: rls-gap Medium). Jetzt gibt es eine Implementierung.

Änderungen

  1. Geteiltes Modul src/main/security/request-guard.ts (rein, dependency-arm, unit-testbar ohne die Electron-Modulkette). Zwei Phasen — guardInput (Injection + PII/Consent) und guardDispatch (Budget + Routing) — plus preflightGuard als Komposition. request-guard-deps.ts verdrahtet die echten Singletons.
  2. Ungeschützte Pfade abgesichert (fail-closed): ServiceLayer.sendMessageStreaming (HTTP/WS), AgentExecutor.execute, ComparisonService.runSingleModel. Ein Block verweigert den Cloud-Call; kein Consent-Kanal dort → bewusst fail-closed.
  3. Chat-Pfad dedupliziert (single source): ipc-handlers.ts verliert seine ~180-Zeilen-Inline-Kette und ruft die geteilten Phasen (guardInput vor Commands, guardDispatch danach — gleiche Logik, gleiche Reihenfolge, kein Verhaltenswechsel). Die IPC-Oberfläche ist byte-identisch erhalten: message:error / message:permission-required / message:safety-warning und die injectionBlocked / sensitiveDataBlocked / pendingConsent-Returns; die volle UploadPermissionResponse + Request werden an den Consent-Dialog durchgereicht.

Tests

  • request-guard.test.ts: 18 Tests — preflightGuard (non-interactive) + guardInput/guardDispatch (Chat-Phasen: Consent-Surfacing, Routing-sensitivity/reasons, Budget-Fallback/Block).
  • Bestehende Agent-/Comparison-/Context-Tests: Pass-Through-GuardDeps-Mock (testen Logik, nicht den Guard).
  • Volle Suite: 1310 passed, tsc -p tsconfig.main.json clean.

Review-Punkte

  • Der Chat-Pfad war nicht test-abgedeckt; die IPC-Payloads wurden manuell 1:1 gegen das Original verifiziert (jedes Event/Return + volle Consent-Response). Die neuen guardInput/guardDispatch-Tests decken die Kern-Logik ab.
  • Follow-up: Circuit-Breaker (Kostenschätzung) + Canary-Token (System-Prompt-Injektion + Leak-Check) sind noch chat-only — in den geteilten Guard heben.

Dokumentation

Security-Audit (2026-08-21), Mingly rls-gap Medium. Refactor-Plan §5 aus dem Handoff — inkl. des dort empfohlenen Chat-Pfad-Dedups.

🤖 Generated with Claude Code

Baldri and others added 2 commits August 22, 2026 15:31
…nd HTTP paths

Prompt-injection scanning, sensitive-data + cloud-upload consent, budget and
sensitivity routing were enforced only on the chat IPC path (ipc-handlers.ts).
AgentExecutor.execute, ComparisonService.runComparison and the HTTP/WS pipeline
(ServiceLayer.sendMessageStreaming) call the LLM directly and reached cloud
providers with none of these guards — content that would be blocked or
consent-gated in chat flowed straight to a cloud model.

- New src/main/security/request-guard.ts: pure, dependency-light orchestration
  (preflightGuard + postflightGuard) with an injectable GuardDeps, so it is
  unit-testable without the Electron/model module graph. request-guard-deps.ts
  wires the real singletons (input sanitizer, sensitive-data detector, upload
  permission, budget manager, data classifier, output guardrails).
- Wire preflightGuard into ServiceLayer.sendMessageStreaming, AgentExecutor.execute
  and ComparisonService.runSingleModel. Non-interactive callers fail CLOSED:
  a block (injection / sensitive-denied / consent-required / budget / routing)
  refuses the cloud call. Chat/agent honour a budget/routing provider switch;
  comparison keeps its chosen model and blocks only on a hard deny.

Tests: request-guard.test.ts (red before the module existed) covers each guard.
Existing agent/comparison/context tests get a pass-through GuardDeps mock so
they test service logic, not the guard. Full suite: 1302 passed, tsc clean.

Note: the chat IPC path keeps its existing inline guard (already protected).
Follow-up: collapse that copy onto request-guard too (single source), and lift
the circuit-breaker + canary-token checks into the shared guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…single source)

The chat SEND_MESSAGE handler carried its own ~180-line copy of the injection /
sensitive-data-consent / budget / routing chain — the very duplication that let
the agent/comparison/HTTP paths drift out of guard coverage. This removes the
copy so there is one implementation.

- request-guard.ts is split into guardInput (injection + sensitive-data/upload
  consent) and guardDispatch (budget + routing); preflightGuard now composes
  them, so the non-interactive callers are unchanged. The chat path interleaves
  command handling between the two phases, so it calls guardInput before commands
  and guardDispatch after — same logic, same order, no behaviour change.
- ipc-handlers.ts drops the inline chain (and its now-unused imports/consts) and
  calls the shared phases. The exact IPC surface is preserved: message:error /
  message:permission-required / message:safety-warning events and the
  injectionBlocked / sensitiveDataBlocked / pendingConsent return payloads are
  byte-identical; request-guard-deps forwards the full UploadPermissionResponse
  plus the built request so the consent dialog gets exactly what it did before.
- Tests: guardInput/guardDispatch get direct coverage (consent surfacing, routing
  sensitivity/reasons, budget fallback/block). Full suite: 1310 passed, tsc clean.

Follow-up unchanged: lift the circuit-breaker + canary-token checks into the
shared guard too (still chat-only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Baldri Baldri changed the title security: enforce the LLM pre-flight guard on agent, comparison & HTTP paths security: unify the LLM pre-flight guard across all paths (agent, comparison, HTTP + chat) Aug 22, 2026
…essage

InputSanitizer.sanitize() computes a `sanitized` string (invisible Unicode
removed, Cyrillic/Greek homoglyphs normalised to Latin) but callers used it only
for the safe/riskScore decision and sent the RAW message — so obfuscated content
reached the model.

- request-guard: SanitizeResult carries `sanitized`; guardInput returns it as
  sanitizedText on the ok outcome, and preflightGuard surfaces it, so every path
  can apply the cleaned text.
- Chat path (ipc-handlers): the message that reaches the LLM is now derived from
  the sanitised text (`inputGuard.sanitizedText ?? userMessage`) — the exact
  spot the finding flagged.

Test: guardInput surfaces the cleaned last message. Full suite: 1311 passed,
tsc clean.

Follow-up: the agent/comparison/HTTP paths now have sanitizedText available from
preflightGuard too — apply it at their send sites as well.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Baldri
Baldri merged commit bb832f0 into main Aug 22, 2026
10 checks passed
@Baldri
Baldri deleted the claude/session/fix/mingly-guardrail-all-paths branch August 22, 2026 15:03
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.

1 participant