Skip to content

Fix CSP blocking eval in TTYD and add debug logging - #7

Merged
axisrow merged 3 commits into
mainfrom
fix/csp-eval-debug-logging
Mar 23, 2026
Merged

Fix CSP blocking eval in TTYD and add debug logging#7
axisrow merged 3 commits into
mainfrom
fix/csp-eval-debug-logging

Conversation

@axisrow

@axisrow axisrow commented Mar 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add 'unsafe-eval' to script-src CSP directive in all 3 locations to fix TTYD JavaScript that requires eval()
  • Add [ttyd] prefixed console.log debug output for terminal create/list/delete operations

Test plan

  • python -m pytest tests/ — 129 tests pass
  • Deploy and verify CSP eval error no longer appears in browser console
  • Open browser console, click "+ New terminal" — see [ttyd] debug logs showing each step
  • Verify TTYD terminal loads correctly in iframe

🤖 Generated with Claude Code

- Add 'unsafe-eval' to script-src CSP directive in all 3 locations
  (server.py, ttyd_proxy.py login page, ttyd_proxy.py dashboard)
  to fix TTYD JavaScript that requires eval()
- Add [ttyd] prefixed console.log debug output for terminal
  create/list/delete operations to diagnose issues in browser

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@axisrow

axisrow commented Mar 23, 2026

Copy link
Copy Markdown
Owner Author

@claude review. Focus on critical issues: bugs, security vulnerabilities, logical errors, data loss risks, performance problems. Do NOT nitpick style, naming conventions, minor formatting, or subjective preferences — only flag issues that could break functionality or cause real harm in production.

@claude

claude Bot commented Mar 23, 2026

Copy link
Copy Markdown

Claude finished @axisrow's task in 2m 43s —— View job


Code Review

  • Gather context
  • Read changed files
  • Analyze for critical issues
  • Post review

1. 'unsafe-eval' applied to wrong pages — fix may not work, and weakens login page CSP

Severity: High

The three CSP locations modified by this PR are:

  • server.py:44 — generic send_html() fallback (error pages, etc.)
  • ttyd_proxy.py:611login page (handle_login_page)
  • ttyd_proxy.py:879dashboard/menu page (handle_menu)

None of these serve TTYD's JavaScript. TTYD terminal content is proxied via proxy_ttyd_http (line 1266–1284 in ttyd_proxy.py), which forwards TTYD's own response headers unchanged, including any Content-Security-Policy TTYD sets itself. The CSP on the wrapper/dashboard pages does not govern scripts running inside the TTYD <iframe>.

This means:

  • If the eval error originates inside the TTYD iframe, this fix does nothing to resolve it — the actual fix needs to go in proxy_ttyd_http, either by overriding or stripping TTYD's CSP header and re-emitting one with 'unsafe-eval'.
  • If the eval error is in a script on the dashboard or wrapper page (e.g. the virtual keyboard JS), then the fix location is correct, but that should be confirmed.

Additionally, adding 'unsafe-eval' to the login page (handle_login_page) is a concrete security regression. The login page has no inline scripts that use eval. 'unsafe-eval' on the login page is the highest-risk place for XSS to cause credential theft — this should not have it.

Fix this →


2. Permanent debug console.log statements leak terminal data in production

Severity: Medium

The index.html changes add 10+ console.log calls that emit:

  • All terminal IDs and their full JSON data (JSON.stringify(data.terminals))
  • Individual terminal paths opened (/ttyd + data.id)
  • HTTP status codes and raw API response bodies

These are unconditional and permanent. In a multi-user environment, any user who opens DevTools sees session-level terminal data. These should either be removed before merging or gated behind a debug flag (e.g. const DEBUG = false checked before each log).

Fix this →


Summary

Issue File Severity
'unsafe-eval' added to login page CSP ttyd_proxy.py:611 High
Fix may target wrong CSP (TTYD runs in iframe with its own headers) ttyd_proxy.py:1266 High
Permanent debug logging leaks terminal data app/index.html Medium

@axisrow

axisrow commented Mar 23, 2026

Copy link
Copy Markdown
Owner Author

Triage

Comment 1 — unsafe-eval on login page

Verdict: FIX. Correct — login page doesn't need eval. Will remove.

Comment 1b — CSP fix targets wrong location

Verdict: FIX. Correct — TTYD content is served through proxy_ttyd_http which passes TTYD's own headers. Need to override/add CSP with unsafe-eval in proxy_ttyd_http for HTML responses.

Comment 2 — Permanent debug logging

Verdict: SKIP. User explicitly requested debug logging in browser console to diagnose terminal creation bug. Will gate behind a DEBUG flag as suggested.

- Remove 'unsafe-eval' from login page CSP (security regression)
- Add CSP override in proxy_ttyd_http for HTML responses from TTYD
  (strips upstream CSP, adds one with 'unsafe-eval' for TTYD's JS)
- Gate debug logging behind DEBUG flag

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@axisrow

axisrow commented Mar 23, 2026

Copy link
Copy Markdown
Owner Author

@claude review. Focus on critical issues: bugs, security vulnerabilities, logical errors, data loss risks, performance problems. Do NOT nitpick style, naming conventions, minor formatting, or subjective preferences — only flag issues that could break functionality or cause real harm in production.

@claude

claude Bot commented Mar 23, 2026

Copy link
Copy Markdown

Claude finished @axisrow's task in 1m 59s —— View job


Code Review

  • Gather context
  • Read changed files
  • Analyze for critical issues
  • Post review

1. DEBUG = true hardcoded — debug logging is always on

Severity: Medium

index.html adds a DEBUG flag but sets it to true:

var DEBUG = true;
function log() { if (DEBUG) console.log.apply(console, arguments); }

The triage said "gate behind a DEBUG flag" but the current implementation leaves debug logging permanently enabled. Every terminal create/list/delete call emits terminal IDs, paths, and raw API response bodies to the browser console. This should ship as false.

Fix this →


2. TTYD proxy CSP missing frame-ancestors — clickjacking risk

Severity: Medium

The new CSP injected by proxy_ttyd_http (around line 1292) omits frame-ancestors:

"default-src 'self'; "
"base-uri 'none'; "
"object-src 'none'; "
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
"style-src 'self' 'unsafe-inline'",

Without frame-ancestors, any external site can embed the TTYD terminal page in an <iframe>. Since the terminal is authenticated, a malicious page could trick a logged-in user into interacting with their terminal while framed (clickjacking). Since TTYD runs inside the dashboard's iframe, frame-ancestors 'self' is the right constraint here (allows the same-origin dashboard to embed it, blocks everyone else).

Fix this →


3. unsafe-eval added to pages that don't need it

Severity: Low

unsafe-eval was added to both server.py:44 (error pages) and ttyd_proxy.py:879 (dashboard/menu). Neither the dashboard JS (index.html) nor error pages use eval(). The correct location — the TTYD proxy response at line 1296 — is properly handled in this PR. The other two additions widen the attack surface unnecessarily. They don't break anything, but they go beyond what's needed.


Summary

Issue File Severity
DEBUG = true hardcoded — terminal data always logged app/index.html Medium
TTYD proxy CSP missing frame-ancestors 'self' ttyd_proxy.py:1292 Medium
unsafe-eval added to pages that don't need it server.py:44, ttyd_proxy.py:879 Low

The CSP fix for TTYD (stripping upstream CSP and injecting a custom one in proxy_ttyd_http) is the right approach and correctly placed.

- Set DEBUG=false (was true)
- Remove unsafe-eval from server.py and dashboard CSP (not needed)
- Add frame-ancestors 'self' to TTYD proxy CSP (anti-clickjacking)
- unsafe-eval now only in proxy_ttyd_http for TTYD HTML responses

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@axisrow

axisrow commented Mar 23, 2026

Copy link
Copy Markdown
Owner Author

@claude review. Focus on critical issues: bugs, security vulnerabilities, logical errors, data loss risks, performance problems. Do NOT nitpick style, naming conventions, minor formatting, or subjective preferences — only flag issues that could break functionality or cause real harm in production.

@claude

claude Bot commented Mar 23, 2026

Copy link
Copy Markdown

Claude finished @axisrow's task in 1m 22s —— View job


Code Review

  • Gather context
  • Read changed files
  • Analyze for critical issues
  • Post review

All critical issues identified in previous review cycles have been resolved. Current state looks clean:

Previous Issue Status
'unsafe-eval' on login page CSP Fixed — login page CSP unchanged at 'unsafe-inline' only
CSP fix targeting wrong location Fixed — override now correctly placed in proxy_ttyd_http
DEBUG = true hardcoded Fixed — var DEBUG = false
Missing frame-ancestors in TTYD proxy CSP Fixed — frame-ancestors 'self' present at line 1295
unsafe-eval on pages that don't need it Fixed — server.py:44 and ttyd_proxy.py:879 have no unsafe-eval

No new critical issues found. The implementation is correct:

  • TTYD HTML responses strip upstream CSP and inject a controlled one with unsafe-eval scoped only to that content path
  • frame-ancestors 'self' correctly permits the same-origin dashboard iframe while blocking external framing
  • Debug logging is gated behind DEBUG = false — off by default, can be enabled locally when needed

Approved — no blocking issues.

@axisrow
axisrow merged commit 933b171 into main Mar 23, 2026
1 check passed
@axisrow
axisrow deleted the fix/csp-eval-debug-logging branch March 23, 2026 09:54
axisrow added a commit that referenced this pull request Jul 8, 2026
…ries (P2)

Six P2 refactoring-debt fixes from the #101 code review, plus NOTE comments for
the deferred TOCTOU finding (#8).

processes were only reaped lazily inside list_terminals()/get_terminal(), so
with no list/get/health traffic a defunct zombie pinned its PID slot forever.
The reaper polls periodically (start/stop wired into main + signal handler).

of the string-interpolating run_as_hapi (`sh -c "... ${command}"`) — a latent
root-context command-injection surface. `| tee` is replaced with a redirect
(argv can't express a pipe, matching the tunnel helpers) and inline env vars go
through `env NAME=VAL`. The unused string run_as_hapi is removed.

ttydproxy.views.build_hapi_url_from_runtime the dashboard uses, replacing a
drifted bash reimplementation (bash took the FIRST relay URL + left the token
un-encoded; views takes the LAST + quote()s both).

skip-set, and read resp.getheaders() once (was called twice per response). A
keep-alive connection pool was intentionally NOT added — a shared HTTPConnection
across ThreadingHTTPServer worker threads would introduce a concurrency bug.

tab_fix_script and delegate to them from the parent page + virtual keyboard,
removing byte-identical containsFiles copies and three hard-coded socket-
discovery + '0' ttyd-prefix sites (protocol change now lives in one place).

ao git-fetch + Hermes git-clone/pip network steps in the CLAUDE.md retry loop
(`for i in 1 2 3 4 5; ... && break || sleep 10`), previously applied only to npm.

(narrow window, not a security boundary) and point at a per-terminal lease as the
proper fix, tracked separately.

No new env vars. No port or volume changes.

Closes #101 findings #7, #9, #12, #13, #14, #15 (documents deferred #8).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KgvJhzd8gEhXVE4MVu18nn
axisrow added a commit that referenced this pull request Jul 9, 2026
…ries (#101 P2) (#104)

* refactor: ttyd reaper, argv daemon launch, url dedup, keep-alive, retries (P2)

Six P2 refactoring-debt fixes from the #101 code review, plus NOTE comments for
the deferred TOCTOU finding (#8).

processes were only reaped lazily inside list_terminals()/get_terminal(), so
with no list/get/health traffic a defunct zombie pinned its PID slot forever.
The reaper polls periodically (start/stop wired into main + signal handler).

of the string-interpolating run_as_hapi (`sh -c "... ${command}"`) — a latent
root-context command-injection surface. `| tee` is replaced with a redirect
(argv can't express a pipe, matching the tunnel helpers) and inline env vars go
through `env NAME=VAL`. The unused string run_as_hapi is removed.

ttydproxy.views.build_hapi_url_from_runtime the dashboard uses, replacing a
drifted bash reimplementation (bash took the FIRST relay URL + left the token
un-encoded; views takes the LAST + quote()s both).

skip-set, and read resp.getheaders() once (was called twice per response). A
keep-alive connection pool was intentionally NOT added — a shared HTTPConnection
across ThreadingHTTPServer worker threads would introduce a concurrency bug.

tab_fix_script and delegate to them from the parent page + virtual keyboard,
removing byte-identical containsFiles copies and three hard-coded socket-
discovery + '0' ttyd-prefix sites (protocol change now lives in one place).

ao git-fetch + Hermes git-clone/pip network steps in the CLAUDE.md retry loop
(`for i in 1 2 3 4 5; ... && break || sleep 10`), previously applied only to npm.

(narrow window, not a security boundary) and point at a per-terminal lease as the
proper fix, tracked separately.

No new env vars. No port or volume changes.

Closes #101 findings #7, #9, #12, #13, #14, #15 (documents deferred #8).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KgvJhzd8gEhXVE4MVu18nn

* fix: truncate daemon logs at startup instead of touch (review cleanup)

Non-blocking finding from the local review of #104: the argv refactor changed
the daemon launches from `| tee "$LOG"` (which truncates the log on open) to
`>>"$LOG"` (append). Without a matching truncate, server.log / droid /ao logs
on the persistent /home/hapi volume would keep stale content across restarts —
for server.log that means a brief window where the /home/hapi/url fallback pairs
a fresh token with an old relay URL (the live dashboard builder self-heals, but
the fallback file can go stale) — and grow unbounded.

Replace `touch "$LOG"` with `: > "$LOG"` (truncate-or-create) for the three
daemon logs, restoring the old tee truncate-at-startup semantics. Update the
server-log pre-create test accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KgvJhzd8gEhXVE4MVu18nn

---------

Co-authored-by: axisrow <axisrow@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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