front door: drop google fonts CDN @import + add security headers#149
Open
Antawari wants to merge 1 commit into
Open
front door: drop google fonts CDN @import + add security headers#149Antawari wants to merge 1 commit into
Antawari wants to merge 1 commit into
Conversation
Two-defect convergence on the host-only Front Door: 1. ui.html imported a stylesheet from fonts.googleapis.com, sending the user's browser fingerprint + IP + Referer to Google on every scan. The Front Door is bound to 127.0.0.1 precisely because the page exposes scraped local state; the third-party font fetch contradicted that posture. 2. _process_request returned only Content-Type on the HTML response — no Content-Security-Policy, no X-Frame-Options, no Referrer-Policy, no X-Content-Type-Options. Drive-by visit to http://127.0.0.1:<port> while a scan was running could render the page in an iframe inside an attacker tab, and any third-party subresource fetch would leak the referrer. Shipping both as one PR — CSP and the @import removal are the two halves of "the page makes no third-party network requests." Add the @import back later and CSP refuses to load it; remove it without CSP and the next contributor can re-introduce it silently. ## Changes src/bonfire/onboard/ui.html - Removed @import url('https://fonts.googleapis.com/...') from the <style> block. The existing font-family fallback chain ('JetBrains Mono', 'Fira Code', 'Courier New', monospace) is in place — most developer workstations have JetBrains Mono installed locally already; remaining users get a graceful fallback. src/bonfire/onboard/server.py - Extended _process_request's GET / response Headers with: * Content-Security-Policy with default-src 'self'; connect-src 'self' (covers same-origin WebSocket to /ws); img-src 'self' data: (allows inline data URLs); style-src 'self' 'unsafe-inline' (the page uses inline <style>); script-src 'self' 'unsafe-inline' (the page uses inline <script>). * X-Frame-Options: DENY * Referrer-Policy: no-referrer * X-Content-Type-Options: nosniff tests/unit/test_onboard_server_security_hardening.py (new · Knight RED) - Six tests pinning the contract: * ui.html contains zero https:// references (post-fix grep). * GET / response carries CSP with default-src 'self'. * GET / response carries X-Frame-Options: DENY. * GET / response carries Referrer-Policy: no-referrer. * GET / response carries X-Content-Type-Options: nosniff. * Content-Type header preserved (regression guard). ## Out of scope (filed for follow-up PR) WebSocket _ws_handler currently does json.loads(raw) without calling parse_client_message from onboard/protocol.py. The typed-discriminator validation is decorative for the wire path. Wiring parse_client_message into _ws_handler is a separate change with its own test surface (touches server.py, flow.py, and adds malformed-frame rejection). ## Verification pytest tests/unit/test_onboard_server_security_hardening.py 6 passed in 51.06s (RED→GREEN cycle verified) pytest tests/unit/test_onboard_server.py (regression) 19 passed in 12.09s ruff check + format on changed files: clean Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Two-defect convergence on the host-only Front Door, shipped together because the CSP and the
@importremoval are the two halves of "the page makes no third-party network requests."1. Fingerprint exfiltration on every onboarding
ui.htmlimported a stylesheet fromfonts.googleapis.com, sending the user's browser fingerprint + IP +Refererto Google on everybonfire scaninvocation. The Front Door is bound to127.0.0.1precisely because the page exposes scraped local state; the third-party font fetch contradicted that posture.2. Missing security headers
_process_requestreturned onlyContent-Typeon the HTML response — noContent-Security-Policy, noX-Frame-Options, noReferrer-Policy, noX-Content-Type-Options. A drive-by visit tohttp://127.0.0.1:<port>/while an operator had a scan running could render the page in an iframe inside an attacker tab, and any third-party subresource fetch would leak the referrer.Changes
src/bonfire/onboard/ui.html— removed the@import url('https://fonts.googleapis.com/...')line. The existing font-family fallback chain ('JetBrains Mono', 'Fira Code', 'Courier New', monospace) is in place — most developer workstations have JetBrains Mono installed locally already; remaining users get a graceful fallback to system monospace.src/bonfire/onboard/server.py— extended theGET /responseHeaderswith:Content-Security-Policy: default-src 'self'; connect-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'X-Frame-Options: DENYReferrer-Policy: no-referrerX-Content-Type-Options: nosniffconnect-src 'self'covers the same-origin WebSocket to/ws.'unsafe-inline'on style/script is required because the current page uses inline<style>and<script>blocks; tightening that requires moving them to sibling resources (out of scope).tests/unit/test_onboard_server_security_hardening.py(new) — 6 Knight RED tests pinning the contract:ui.htmlcontains zerohttps://references.GET /response carries CSP withdefault-src 'self'.GET /response carriesX-Frame-Options: DENY.GET /response carriesReferrer-Policy: no-referrer.GET /response carriesX-Content-Type-Options: nosniff.Content-Typeheader preserved (regression guard).TDD verification
tests/unit/test_onboard_server.py: 19/19 pass (zero regressions).ruff check+ruff format --checkon changed files: clean.Out of scope (filed for follow-up PR)
The WebSocket
_ws_handlercurrently doesjson.loads(raw)without callingparse_client_messagefromonboard/protocol.py. The typed-discriminator validation is decorative for the wire path. Wiringparse_client_messageinto_ws_handleris a separate change with its own test surface (touchesserver.py,flow.py, and adds malformed-frame rejection at the wire boundary).Test plan
pytest tests/unit/test_onboard_server_security_hardening.py— confirm 6/6 green.pytest tests/unit/test_onboard_server.py— confirm 19/19 still green (no regression).bonfire scan --no-browserthencurl -i http://127.0.0.1:<port>/and verify all four security headers are present.fonts.googleapis.com.🤖 Generated with Claude Code