Skip to content

Add BSOD AI: ask questions about a finished scan (Turnstile + layered abuse protection) - #386

Merged
faratech merged 2 commits into
mainfrom
bsod-ai-chat
Oct 31, 2026
Merged

faratech merged 2 commits into
mainfrom
bsod-ai-chat

Conversation

@faratech

Copy link
Copy Markdown
Owner

Summary

  • After a scan, a wand button in the summary strip opens a chat about the visitor's own crash, answered by gpt-6-luna. Off by default: set BSOD_AI_CHAT_ENABLED=true to turn it on. When off, /api/bsod-ai/pass and /ask answer 503 with no limiter in front.
  • Turnstile: a per-visit pass (signed, session-bound, 15 min, own HKDF purpose), minted only for a Turnstile token of action bsod-ai on an allowed hostname; the existing single-use replay guard applies.
  • Abuse protection: same-origin check; strict body validation; input screening for prompt injection and off-topic use (also applied to the history/report text sent back by the browser); strikes and lockouts; per-address and per-session limiters; one question in flight per session; global concurrency cap; per-tier hourly quota on the session and the address; a global daily ceiling.
  • Prompt/output safety: case file and history are redacted, capped and fenced as data; the model answers in JSON; the answer is scrubbed (no links, bare domains, emails, phones, HTML, markdown, destructive or download-and-run commands) and rendered as text.
  • Cost: free providers only (Experiential, then the OpenAI free route), never the billed legs; with neither the chat rests and the question is refunded. The chat yields to scans on both legs (it may use BSOD_AI_CHAT_BUDGET_FRACTION, default 0.4, of each budget).
  • Chat turns are not stored; /privacy and CLAUDE.md document what is sent.
  • An independent adversarial review found a budget-cap gap on the OpenAI leg, false-positive lockouts and some screening bypasses; all are fixed here and covered by tests.

Test plan

  • npm run check (903 tests, typecheck, build, hydration)
  • Real server in dev mode against a fake Experiential provider: answer, injection refused without a provider call, provider down -> 503 resting, disabled default -> 503
  • Chat component in Chrome (dark/light, phone) with a scripted network
  • After enabling in production: one real question end to end (Turnstile pass, answer, quota)

Enabling

gcloud run services update bsod-analyzer --region us-east1 --update-env-vars BSOD_AI_CHAT_ENABLED=true (limits and budget share are env vars, see CLAUDE.md).

🤖 Generated with Claude Code

faratech and others added 2 commits October 31, 2026 00:20
…d layered abuse protection

After a scan the summary strip can show a wand button that opens a chat about the
visitor's own crash, answered by gpt-6-luna. Dormant unless BSOD_AI_CHAT_ENABLED is
set (a typo keeps it off); when off, the pass and ask routes answer 503 with no
limiter in front of them.

Protection, in the order a request meets it:
- same-origin check, lockout check, then a Turnstile-backed pass: a signed,
  session-bound, 15-minute cookie with its own HKDF purpose, minted only for a
  token of the action "bsod-ai" on an allowed hostname (single-use through the
  existing replay guard)
- strict body validation, input screening for prompt injection (look-alike
  letters folded, spacing-insensitive) and off-topic use, applied to the question
  and to the history and report text sent back by the browser
- strikes and lockouts (a session at 4, an address at 12), per-address and
  per-session limiters, one question in flight per session, a global concurrency
  cap, a per-tier hourly quota on the session and the address, and a global daily
  ceiling, all reserved before the evidence fetch
- the case file (the owned job's WinDBG evidence plus the report) and the history
  are redacted, capped and fenced as untrusted data; the model answers in JSON
  and the answer is scrubbed (no links, bare domains, emails, phone numbers, HTML,
  markdown, or destructive / download-and-run commands) before the browser
  renders it as text

Cost: free providers only (Experiential, then the OpenAI free route), never the
billed legs; with neither the chat rests and the question is refunded. The chat
yields to scans on both legs: it is admitted only while total usage is under
BSOD_AI_CHAT_BUDGET_FRACTION of the budget (OpenAI's budget gains a `fraction`).
Chat turns are not stored; /privacy and CLAUDE.md say so.

The mascot moves into components/Mascot.tsx so the chat avatar can reuse it.
Tests cover the pure rules, the routes over real HTTP with fakes, the component in
jsdom, the env switch and the budget share.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The provider adapters read the upstream SSE (retries and error mapping stay
shared); the model replies in text with a topic line, and createAnswerStream
releases the answer a sentence at a time, each one scrubbed first. Anything
before the first sentence (pass, screening, quota, off-topic, provider
failure) stays plain JSON and refunded as before; after text went out a
failure is an error event with no refund and no provider fall-through.
Off-topic stops the upstream call after the first line, and a closed
connection aborts it. The non-streaming JSON path is unchanged. The chat
shows the growing text aria-hidden and swaps in the finished message, so a
screen reader announces it once; the typewriter is gone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@faratech
faratech merged commit 2b9313f into main Oct 31, 2026
3 checks passed
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