Skip to content

Fix five security scan findings - #8

Merged
Commanderx-code merged 5 commits into
mainfrom
security/scan-fixes
Sep 25, 2026
Merged

Commanderx-code merged 5 commits into
mainfrom
security/scan-fixes

Conversation

@Commanderx-code

@Commanderx-code Commanderx-code commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Change

Fixes five findings from a security scan of e7163bb. There is one commit per finding, and each commit adds its own regression test.

  • Per-file diffs could run a submodule's own git filters (medium). platform::git added --ignore-submodules=dirty only when args[0] was status or diff. diff_file passes --literal-pathspecs as its first argument, so the guard was skipped. Opening the Unstaged diff of a staged submodule then let git run git status inside that submodule, and a filter defined in the submodule's config could execute a command. The wrapper now treats leading -- options as global options and applies the guards to the real subcommand.
  • Recovery notes read had no size limit (low). A settings path such as /dev/zero, which an imported setup bundle can supply, was read without limit before the 256 KB check ran. The read now refuses anything that is not a regular file and stops at 256,001 bytes.
  • settings.json and its backup were world-readable (low). Both files were written with the umask default, usually 0644. They now go through platform::atomic_write and are created with 0600.
  • REST-server credentials reached restic's argv (low). The embedded-password check parsed rest:https://user:pass@host as an opaque rest URL, so it never saw the password. It now also checks the address after the rest: prefix.
  • Job output cleaning could freeze the renderer (low). In cleanOutput's OSC regex, /\x1b\][^\x07]*(?:\x07|\x1b\\)/g, the body could run past later ESC bytes. A flood of unterminated ESC ] pairs in git sideband output from a hostile remote therefore made it backtrack quadratically. Because the job stays in activity history, the renderer froze on every launch. An indexOf scan replaces the regex. It produces exactly the same result (the first BEL after the introducer, otherwise the last ST) in linear time.

Validation

  • cargo test: 71 passed, including 4 new regression tests. Each new test fails on the unpatched code.
  • cargo clippy --all-targets -- -D warnings: clean.
  • npm test: 78 passed, including 2 new cleanOutput tests. One pins the existing output, the other checks a 2 MB flood.
  • npm run check: passed.
  • cleanOutput was compared against the original regex on 500,000 random inputs built from ESC, ], \, BEL, CSI characters and CR/LF, with no differences. Adversarial 2 MB inputs finish in 1–3 ms.
  • Each Rust patch was also reviewed independently for scope, new attack paths and behaviour changes.

Review notes

  • Behaviour change (F4): a rest: Restic repository with a password in the address is now refused, with the existing error that points to Restic's credential environment. Move those credentials to RESTIC_REST_USERNAME and RESTIC_REST_PASSWORD.
  • A per-file Unstaged diff of a submodule with local edits no longer shows the -dirty marker. This matches the file list and the full diff view, which already had the guard.
  • Recovery notes paths that point at a device, FIFO or directory now return "Recovery notes must be a regular file".
  • Existing 0644 settings files are tightened to 0600 the next time settings are saved, not when the app starts.
  • Job output displays exactly as before. The cleanOutput change affects only speed.

🤖 Generated with Claude Code

Commanderx-code and others added 5 commits September 25, 2026 02:41
platform::git only added --ignore-submodules=dirty when args[0] was
"status" or "diff". diff_file passes --literal-pathspecs first, so the
guard was skipped and viewing a staged submodule's Unstaged diff let git
run `git status` inside it, loading that repository's own clean/process
filters. Leading --options are now passed through as global options and
the guards apply to the real subcommand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
recovery_notes read the settings-supplied path with an unbounded
read_to_string before its 256 KB check, so a path like /dev/zero (for
example from an imported setup bundle) exhausted memory. Non-regular
files are now refused and the read stops at 256,001 bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
save_settings wrote settings.json with the umask default (usually 0644)
and copied the .bak with the same mode, although it can hold repository
addresses, password-file names and custom commands. Both now go through
platform::atomic_write like the app's other private stores.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The embedded-password check parsed rest:https://user:pass@host as an
opaque "rest" URL and never saw the password, so REST-server credentials
reached restic's argv. The check now also inspects the address after the
rest: prefix, with the same error pointing to Restic's credential
environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cleanOutput's /\x1b\][^\x07]*(?:\x07|\x1b\\)/g let the body run past
later ESC bytes, so a flood of unterminated "ESC ]" pairs, for example in
git sideband output from a hostile remote, backtracked quadratically and
froze the renderer on every launch while the job stayed in history. An
indexOf scan now reproduces the regex's result exactly (first BEL after
the introducer, otherwise the last ST) in linear time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Commanderx-code Commanderx-code changed the title Fix four security scan findings Fix five security scan findings Sep 25, 2026
@Commanderx-code
Commanderx-code merged commit 02eeeaf into main Sep 25, 2026
7 checks passed
@Commanderx-code
Commanderx-code deleted the security/scan-fixes branch September 25, 2026 07:01
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