Skip to content

Harden the clipboard, start-up and legacy login migration (2.5.6) - #87

Merged
ESJavadex merged 2 commits into
ESJavadex:mainfrom
nsleigh:fix/harden-clipboard-startup
Nov 12, 2026
Merged

ESJavadex merged 2 commits into
ESJavadex:mainfrom
nsleigh:fix/harden-clipboard-startup

Conversation

@nsleigh

@nsleigh nsleigh commented Nov 11, 2026

Copy link
Copy Markdown

Security hardening, adapted from owine/claude-terminal-home-assistant's
2026-11-06 audit. Numbered 2.5.6 after #85 and #86; if this should go out
first, I'm happy to renumber it (or the others).

Each fix closes a way for something that can write to /config (another
add-on, a Samba user) or a program in the terminal to reach further than it
should.

1. Clipboard text loses its control bytes (owine #542)

Text on the clipboard is pasted back into the terminal, which acts on control
bytes. An ESC starting the bracketed-paste end marker, followed by a CR,
ends the paste early and runs what follows. Two sources can put such text on
the clipboard:

  • OSC 52: any program in the terminal can set the clipboard (this is how
    Claude's /copy works);
  • Copy path in the file preview copies a raw /config file name, and a
    file name can contain control bytes.

sanitizeForClipboard (terminal-clipboard.js) removes every C0 byte except
TAB and LF, plus DEL and C1. It's applied in makeCopyText, the one writer
behind OSC 52, selections and the touch controls, and in the page's
copyToClipboard (Copy path, links, voice). Text that is only control bytes
copies nothing.

2. tmux no longer answers clipboard reads (owine #533)

set-clipboard on leaves get-clipboard at its default, buffer, so tmux
answers a program's OSC 52 read itself, with its top paste buffer: whatever
was last copied in tmux. The browser-side handler already refuses reads, but
tmux replies before they get there. set -g get-clipboard off stops that;
writes still work. (The image's tmux is 3.7c, which has the option.)

3. Start-up runs from / (owine #531)

The image's WORKDIR is /config, so run.sh ran there as root. Python puts
the current directory first on its import path, so a planted /config/venv.py
would run when the persist-install venv is rebuilt after a Python upgrade
(migrate_python_venv).

  • main() now starts with cd /. Steps that want /config already name it
    (init_commands, the tmux session's -c).
  • migrate_python_venv and persist-install run python3 -I. The terminal
    opens in /config, so an interactive persist-install --python had the
    same exposure.

4. The legacy login migration runs once (owine #543)

migrate_legacy_auth_files copied /config/claude-config (and
/tmp/claude-config) into ANTHROPIC_CONFIG_DIR on every boot, with
cp -r, replacing existing files. Claude Code reads Anthropic profiles from
that folder: configs/<profile>.json, with credentials and an API base_url
(checked in the 2.1.319 binary). So anything written to the legacy folder
later could be picked up.

  • The migration now records a marker (/data/.legacy-auth-migrated) after
    its first pass and skips on later boots. A failed copy is retried.
  • It copies only entries the target doesn't have, never replacing one. (Not
    cp -n, whose exit status differs between coreutils versions.)
  • LEGACY_AUTH_SOURCES and LEGACY_AUTH_MARKER are test seams.

Tests

  • test-terminal-clipboard.js: stripping (TAB/LF kept, ESC/CR/DEL/C1 gone,
    UTF-8 intact), an OSC 52 write carrying an injection, and control-only text.
  • test-production-run.sh:
    • get-clipboard off is in the generated tmux.conf, and a real tmux that
      supports the option loads it;
    • the fake Python used by the venv test now rejects any call without -I;
    • main() starts with cd /, and persist-install has no python3 -m
      without -I;
    • the migration copies what's missing, never replaces a file, leaves its
      marker, and ignores a configs/default.json planted afterwards.

Each new check fails with its fix removed. The full suite passes, including
on Alpine 3.24 with tmux 3.7c. ShellCheck only reports two existing SC2155
warnings in persist-install lines this doesn't touch.

Tested

On Home Assistant OS, as a dev add-on (together with #85 and #86): the
terminal works as usual. The log shows the migration ran once on the first
start ("No existing authentication files found to migrate") and not at all
after a restart.

🤖 Generated with Claude Code

https://claude.ai/code/session_018rxd5pKHfU3wFULG6yLDE6

Four fixes adapted from owine/claude-terminal-home-assistant's security
audit (#542, #533, #531, #543):

- Strip control bytes (all C0 but TAB and LF, DEL, C1) from every
  clipboard write: OSC 52, selections and Copy path. An ESC and CR pasted
  back into the terminal could end a bracketed paste and run a command.
- set -g get-clipboard off: tmux answered OSC 52 reads with its top
  paste buffer.
- Start main() with cd /, and run python3 -I for the venv rebuild and in
  persist-install: Python imports from the current directory first, and
  /config is writable by other add-ons and Samba users.
- Make the legacy auth migration one-time (a marker in /data) and never
  overwrite: it copied /config/claude-config into ANTHROPIC_CONFIG_DIR on
  every boot, and Claude Code reads profiles (credentials, base_url)
  from there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018rxd5pKHfU3wFULG6yLDE6
@nsleigh nsleigh closed this Nov 11, 2026
@nsleigh nsleigh reopened this Nov 11, 2026
@nsleigh

nsleigh commented Nov 11, 2026

Copy link
Copy Markdown
Author

The two red checks are a CI network problem, not this change: the browser
job timed out both times in "Install browser test dependencies", because the
runner couldn't resolve the Ubuntu mirrors (Temporary failure resolving 'archive.ubuntu.com'). docker-build then failed only because browser
didn't succeed. checks, workflow-lint and both architecture builds
passed. The same browser job passed on #86 earlier today, so a re-run of the
failed jobs should go green once the runners recover. (I can't re-run it from
my side.)

@ESJavadex
ESJavadex merged commit d0ffb2c into ESJavadex:main Nov 12, 2026
6 checks passed
@ESJavadex

ESJavadex commented Nov 12, 2026 •

Copy link
Copy Markdown
Owner

Thanks, @nsleigh, for this PR. I reviewed the clipboard hardening, isolated startup changes, and legacy authentication migration. Local tests and the full CI suite passed, including browser tests, the npm audit gate, and amd64/aarch64 builds. Merged as 2.5.6.

@nsleigh
nsleigh deleted the fix/harden-clipboard-startup branch November 13, 2026 08:35
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