Skip to content

Fix shell-in behind host-rewriting proxy + tab-overflow alignment#7

Merged
falkoro merged 1 commit into
masterfrom
fix/shell-in-origin-and-tab-overflow
May 30, 2026
Merged

Fix shell-in behind host-rewriting proxy + tab-overflow alignment#7
falkoro merged 1 commit into
masterfrom
fix/shell-in-origin-and-tab-overflow

Conversation

@falkoro
Copy link
Copy Markdown
Owner

@falkoro falkoro commented May 30, 2026

Shell-in regression: the new term_ws Origin check broke the browser terminal behind Cloudflare, because cloudflared rewrites the Host header (httpHostHeader: 127.0.0.1) so the browser Origin never matched. blocked_origin() now also checks X-Forwarded-Host and fails open when the public origin is undeterminable (loopback Host + no DASHBOARD_ALLOWED_ORIGINS); set DASHBOARD_ALLOWED_ORIGINS to enforce strictly.

Tab overflow: the flex-wrap tabs are narrower, and the work-title brief spilled out (overflow:visible + max-content track) and overlapped neighbours. Now contained with overflow:hidden + a 2-line clamp, plus a smaller per-tab word count.

Verified: WS handshake (rewritten Host + allowlisted Origin) → 101; evil Origin → 403. Live headless screenshot confirms tabs fill the bar without overlap.

🤖 Generated with Claude Code

Shell-in (regression from the term_ws Origin check): cloudflared rewrites the Host header
(`httpHostHeader: 127.0.0.1`), so the browser's Origin (code.falkinator.org) never matched
the Host the app saw → the WebSocket was rejected and the terminal showed "disconnected".
- blocked_origin() now also accepts X-Forwarded-Host, and FAILS OPEN when it genuinely can't
  determine the public origin (Host is loopback AND no DASHBOARD_ALLOWED_ORIGINS set) instead
  of breaking the terminal. Setting DASHBOARD_ALLOWED_ORIGINS to the public hostname restores
  strict cross-site-WS-hijacking protection.

Tab alignment: with the flex-wrap bar the tabs are narrower, and the work-title brief
(`.session-tab-summary` with overflow:visible + a max-content marquee track) spilled out and
overlapped neighbouring tabs. Contain it: overflow:hidden + a 2-line clamp on the static
(desktop) variant (display:contents on the inner track), and reduce the per-tab word count so
the brief reads cleanly. The full title still shows on the card and in the tab tooltip.

Verified: WS handshake with rewritten Host=127.0.0.1 + Origin=code.falkinator.org (allowlisted)
-> 101; Origin=evil.com -> 403. Live headless screenshot confirms the tabs fill the bar
without overlap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 30, 2026 15:35
@falkoro falkoro merged commit b524d78 into master May 30, 2026
@falkoro falkoro deleted the fix/shell-in-origin-and-tab-overflow branch May 30, 2026 15:35
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Loosens the WebSocket Origin check to support reverse proxies that rewrite the Host header (e.g. cloudflared with httpHostHeader: 127.0.0.1), and tightens shell tab summary rendering so briefs stay within their tab.

Changes:

  • Accept X-Forwarded-Host as a valid Origin match and fail-open when Host is loopback and no allowlist is configured.
  • Clamp tab summary to 2 lines via CSS and reduce per-tab word counts.
  • Update .env.example documentation for DASHBOARD_ALLOWED_ORIGINS.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/term.rs Adds X-Forwarded-Host matching, loopback detection, and fail-open behavior when public origin is undeterminable.
public/render.js Reduces word counts in tab summary breakpoints.
frontend/render.ts Mirrors render.js changes in TypeScript source.
public/app.css Clamps static tab summary to 2 lines via line-clamp.
.env.example Documents proxy/loopback behavior for DASHBOARD_ALLOWED_ORIGINS.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/term.rs
Comment on lines +117 to +123
let host_is_public = !host.is_empty() && !is_loopback_host(&host);
let has_basis = host_is_public || !forwarded_host.is_empty() || !config.allowed_origins.is_empty();
if has_basis {
Some("Cross-origin WebSocket blocked".to_string())
} else {
None
}
Comment thread src/term.rs
Comment on lines +126 to 132
fn is_loopback_host(host: &str) -> bool {
let bare = host
.strip_prefix('[')
.and_then(|h| h.split(']').next())
.unwrap_or_else(|| host.split(':').next().unwrap_or(host));
bare.eq_ignore_ascii_case("localhost") || bare == "::1" || bare.starts_with("127.")
}
Comment thread src/term.rs
Comment on lines +99 to 103
for candidate in [&host, &forwarded_host] {
if !candidate.is_empty() && origin_host.eq_ignore_ascii_case(candidate) {
return None;
}
}
Comment thread public/app.css
.session-tab-summary .marquee-track{display:inline-flex;gap:18px;min-width:max-content}
/* Static (desktop) tabs: clamp the brief to 2 lines INSIDE the tab so it can't spill into
neighbours when tabs are narrow. The marquee variant (mobile) keeps its max-content track. */
.session-tab-summary:not(.moving){display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
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.

2 participants