harden(deploy): add CSP + security headers, drop external Google Fonts#1
Merged
Merged
Conversation
Two defense-in-depth fixes from the security review: 1. Self-contained fonts. Remove the Google Fonts <link>s from build/template.html; the --ui/--mono CSS stacks already fall back to system fonts. The served page now makes zero third-party requests (privacy + air-gap + truly self-contained). 2. Security headers. deploy/http_handlers.xml now sends a strict CSP (default-src 'none'; connect-src 'self' <issuer-origins>; frame-ancestors 'none'; base-uri 'none'; img-src data:; script/style 'unsafe-inline' since the bundle is inlined), plus nosniff and Referrer-Policy: no-referrer. connect-src is the real win — it bounds where the sessionStorage tokens can be sent if an XSS ever lands. install.sh resolves the issuer's OIDC discovery and rewrites connect-src to the real issuer + token-endpoint origins (fail-soft to the Google default if discovery is unreachable), writing the rendered file to dist/http_handlers.xml. New --dry-run flag renders config.json + http_handlers.xml and prints them with no ClickHouse contact. README + DEPLOYMENT docs updated. No src/ changes; 319 tests still pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef
…pkce transients Security-review follow-ups (#4/#6/#7): - sqlString (core/format.js): escape `\` -> `\\` before doubling `'`. CH honors backslash escapes in string literals, so a value ending in `\` escaped the closing quote and could break out (second-order via loadColumns' system.tables names). Now closed. - bootstrap (main.js): handle an IdP error redirect (?error=access_denied&…) instead of dropping silently to the login screen — surfaces error_description||error, and the URL cleanup now strips the error params too. - setTokens (ui/app.js): clear the one-shot oauth_verifier + oauth_state from sessionStorage once tokens are held (refresh path is a harmless no-op). Tests added in the same change; 323 pass, per-file coverage gate holds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef
The editor overlays a <textarea> on a highlight <pre>. With a fractional line box (line-height 1.7 * 13px = 22.1px) the textarea's internal text layout and the block <pre> rounded lines differently, so the native selection drifted upward from the painted glyphs, growing with line number (visible by ~line 37). Use an integer px line-height (22px) on .sql-editor so both lay out lines identically before `zoom: 1.2` scales them together, and match .sql-gutter > div height so the line numbers stay row-aligned. Verified in-browser: selection on a deep line now brackets the glyphs exactly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef
In TSV/JSON output mode the result is a plain <div>, so ⌘A fell through to the browser's whole-page select-all. Make the raw (.raw-text-view) and JSON (.json-view) panes focusable (tabindex=0) and, when ⌘/Ctrl+A fires with focus inside one, select that node's contents via Selection.selectAllChildren so it can be copied. Focus elsewhere (e.g. the editor textarea) still falls through to the native select-all, so ⌘A in the SQL area keeps selecting the whole query. Verified in-browser: ⌘A in a focused TSV pane selects exactly the TSV text; ⌘A in the editor is not intercepted. 325 tests pass; shortcuts/results at 100%. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef
Each data-column header gets a right-edge resize handle. Dragging it freezes every column at its current width, switches the table to table-layout:fixed, and tracks the cursor to set that column's width (content ellipsizes when narrowed). Widths are stored on the per-query result object so they survive the frequent re-renders (sort, streaming chunks, view switches) and reset on a new query. Details: - zoom-safe: the client-px delta is divided by a per-element scale (getBoundingClientRect().width / offsetWidth) so the edge tracks the cursor under the global `zoom: 1.2` (verified in-browser: 70 CSS px → 84 device px). - the handle swallows its own click so resizing/clicking it never triggers the column sort; the row-number column is not resizable. Verified in-browser: dragging narrows a column with ellipsis clipping while others keep their widths. 329 tests pass; results.js at 100%. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef
History had no in-app way to clear it (only the saved panel had per-item delete). Add a "Clear history" button in the History panel header (wires the existing clearHistory) and a per-row delete × (new deleteHistory state op, mirroring deleteSaved). Both persist via localStorage like the rest of the panel. The row-level delete stops propagation so it doesn't also load the query. Verified in-browser: Clear history empties asb:history + the list; per-row delete removes one entry. 332 tests pass; state.js + saved-history.js at 100%. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef
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.
Two defense-in-depth fixes from the security review. No
src/logic changes — the 100% coverage gate is untouched (319 tests pass).1. Truly self-contained — drop external Google Fonts
build/template.htmlno longer pulls Inter / JetBrains Mono fromfonts.googleapis.com/gstatic.com. The--ui/--monostacks instyles.cssalready fall back to system fonts, so the UI is unchanged in spirit and the served page now makes zero third-party requests (privacy, air-gap, and the "self-contained single file" claim all become literally true). The favicon stays an inlinedata:URI.2. Strict CSP + hardening headers
The
/sqlresponse (deploy/http_handlers.xml) now sends:Content-Security-Policy:default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src 'self'; connect-src 'self' <issuer-origins>; base-uri 'none'; frame-ancestors 'none''unsafe-inline'is required because the JS+CSS are inlined into the single HTML file; the real protection isconnect-src, which bounds where thesessionStorageid_token/refresh_token can be sent if an XSS ever lands.X-Content-Type-Options: nosniffandReferrer-Policy: no-referrer(on both the SPA and config responses).connect-src is templated, not guessed
http_handlers.xmlis normally a static drop-in, but the OIDC origins are deployment-specific. Soinstall.shnow:connect-srcinto a rendereddist/http_handlers.xml;--dry-runflag that rendersconfig.json+http_handlers.xmland prints them with no ClickHouse contact.Non-Google manual installs: edit one
connect-srcline (documented in README +deploy/http_handlers.xmlcomment).Verification
npm test→ 319 pass;npm run build→grep googleapis|gstatic dist/sql.html= 0 matches.xmllintclean on committed + rendered XML.sedrewrite verified against Google- and Auth0-shaped discovery docs (Google → 2 origins, Auth0 → 1;'self'and trailing;preserved).🤖 Generated with Claude Code
https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef