* Refactor the complex fns, expand wildcard imports, tidy closures and lifetimes (SonarCloud)
- S3776: split the twelve flagged functions (portable install, backend check
sweep, app details drawer/hero/identity, toolbar and its banner rows, table
header/rows/cells, action menu and its phases, settings install panels,
widgets cells, narrow-header test) into focused helpers
- S2208: replace the nine `use super::*` in platform test modules with the
exact imports each module needs
- S1612: call clone() on the iterator directly instead of in a closure
- S8833: drop the redundant reference in iterator-based loops
- shellcheck: use [[ ]] division guard in packaging/linux/package.sh
* Lint: pass a fixed printf format to sha256sum, not a variable (shellcheck SC2059)
The shellcheck gate flags printf with a variable format string; inline
'%s %s' at the four sha256 check sites so the format is a literal. No
behavior change.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
* Fix: a Windows build needs CraftApp in the windows module's scope
uninstall_msi references CraftApp but the module's super import omitted
it, breaking the Windows clippy job only (caught by CI, not by the
Linux check the branch was verified with).
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
* Address the CodeRabbit review: no missing-entry assert, and the paused
row's resume button measures against the full row width again
- details: return early when the shown detail has no matching entry
(non-test code never panics); the assert only fired in debug builds
- actions_in: paused_actions gets the full row width, as the branch
subtracted the discard and menu buttons from it itself; passing
main_w sized the resume button one CONTROL_H + gap too narrow
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
---------
Co-authored-by: Codebuff <noreply@codebuff.com>
* Linux install: run package tools by absolute path
* Linux install: run pkexec from the system directories
* FreeBSD install: look for pkexec under /usr/local too
* Packaging: pin the Windows signing client and check its hash
* Packaging: pin appimagetool and check its hash
* Fail clearly when no appimagetool is pinned for the architecture
* Pin the AppImage runtime
* packaging: [[ ]] tests, named positional parameters and explicit returns (SonarCloud S7688, S7679, S7682)
Every packaging script is bash (macOS ships bash 3.2, which has [[ ]]), so the
single-bracket tests become [[ ]]: no word splitting or globbing of unquoted
operands. Every right-hand side of = is quoted or a plain literal, so no test
turns into a pattern match.
Functions that read positional parameters copy them into named locals, and the
env.sh helpers end with a bare return, which keeps the last command's status.
* fix: use POSIX test in generated /bin/sh helper
Sonar applies NOSONAR to the line that carries the marker, so the
explanations above the calls did not suppress anything.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
The AppImage and unpacked binary must be executable, and 0755 is not
group/world-writable; the staging directory is handed to the operator
when a rollback fails, so its Drop must not delete the only copy left.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
Accept validated flat and version-folder-wrapped archives. Adapt the newer self-update replacement caller to the extractor result so the combined Windows source builds.
The 0.6.2 version commit landed on release but never on main, so release
could not fast-forward. Both sides only changed version numbers; main's
0.7.0 wins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two local-service hardening findings:
- Single-instance replacement authority (CWE-862): the listener on the fixed loopback
port 127.0.0.1:47413 granted a Replace (which can make an idle launcher quit) on a
peer's claimed version alone, so a different local OS account could end the victim's
launcher. A per-user token now gates Replace: it lives in the user's own data folder
(created owner-only, 0600 on Unix), the second start echoes it in its hello, and the
primary only honours Replace when the token matches. An unauthenticated peer may
still bring the window forward (Show, harmless), never quit the launcher. The token
is derived without a new RNG crate, hashing OS-seeded RandomState with sha2.
- Local mirror server DoS (xtask local serve, CWE-770): a thread was spawned per
connection with no admission limit, request reads had no deadline (idle clients held
threads forever), and a failed thread spawn could panic the listener. Add a bounded
handler cap (64; excess connections get 503), a 15s request-header read deadline
lifted before streaming the body, and graceful handling of spawn failure.
Addresses findings: single_instance.rs CWE-862, xtask/local.rs CWE-770.
CodeRabbit caught that ureq's body limit sits *below* its transparent gzip
decoder, so limit(max + 1) bounded the compressed bytes on the wire, not the
decoded text: a few hundred gzipped bytes still expanded past the cap into
truncated-but-parseable-looking data (CWE-409). read_text_capped now caps the
fully decoded reader with take(max + 1) and still refuses an oversized answer
outright, before parsing; a new test serves a real gzip bomb and a small gzip
answer to pin both halves.
When the launcher can't find its per-user folders, it keeps its files in
the temporary folder. The update helper finds the folders again from the
installed exe. It then failed to find them, or found a cache other than
the one holding the update, and stopped after the launcher had quit
without starting the launcher again.
Before the launcher quits, the update now also checks that the helper
finds the cache holding the download, the staging folder and the
helper's copy. Otherwise the update stops with an error and the launcher
keeps running.
Feed notice links and release-note hyperlinks are forwarded to the system opener
when clicked. The pinned webbrowser crate's WSL fallback passes the URL to
cmd.exe /c start while escaping only ^ and &, so a pipe (or other metacharacter) in
an otherwise valid https URL becomes a command separator and runs a Windows command
(CWE-78).
Rather than depend on the third-party opener to neutralise the URL, refuse any link
containing a shell/cmd metacharacter at the validation boundary, where a real https
web link never needs one:
- release::safe_web_link (release notes) now rejects shell metacharacters via the
shared release::is_shell_meta, on top of the existing scheme/host/whitespace checks.
- The feed notice link filter routes through the same rule and also rejects control
characters.
Addresses finding: widgets.rs/webbrowser WSL fallback CWE-78.
Read ProductVersion directly from Windows VERSIONINFO, including fallback string tables.
Validated by Windows and Linux tests, WASM checks, Nix evaluation, and SonarCloud.
Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past jiff's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.
Three resource-exhaustion findings in the same untrusted-input handling:
- HTTP feed/API/text (CWE-409): read_to_string(limit(MAX_SMALL)) silently truncates,
and ureq's transparent gzip means a small compressed response can decode far past
the compressed-byte count. Add read_text_capped, which reads one byte past the cap
and rejects an oversized *decoded* body before it is parsed. Used by get_api,
get_feed and get_text.
- Screenshot decode (CWE-789): bytes were accepted on the JPEG signature and a 2 MiB
byte cap only, but a tiny JPEG can declare 65535x65535 and egui_extras/image
allocate width*height*channels from the header before decoding. Add
details::jpeg_dimensions and screenshot_within_budget (8192x8192 budget), enforced
in screenshots::download and saved_copy, and re-checked in Picture::from_result
before bytes reach egui.
- Locale matching (CWE-400): candidates() built every shorter prefix of an unbounded
URL lang value, allocating quadratically. Bound tag length (64) and subtag count (8)
before building candidates.
Addresses findings: http.rs CWE-409, details.rs CWE-789, lang.rs CWE-400.
Download URLs were accepted on a raw starts_with(prefix) check. A value like
.../releases/download/../../../ATTACKER/REPO/releases/download/v99/app shares the
prefix textually but its interpreted path escapes into another repository (CWE-180),
and the Linux AppImage self-update then downloads and executes those bytes.
- Add release::download_url_under / download_url_under_mirror, which require the text
after the prefix to be exactly <tag>/<name>: two non-empty segments, no traversal
(./..), no extra slashes, no query/fragment/backslash, and no percent-encoded
separators (%2f/%5c). release_from_value and FeedRelease::to_release share it.
- Bind the installed artifact to the release that authorized it: before download,
bind_artifact_version rejects an artifact whose filename-encoded version or URL
release tag disagrees with release.version, so a feed cannot advertise a new
version while shipping an older official build and persist a false version.
Addresses findings: release.rs CWE-180, feed.rs CWE-180, platform/install version binding.
`civil_from_unix` (core) and `rfc3339` (xtask) took `i64` Unix seconds, which
the old hand-rolled Hinnant code converted across the whole `i64` range. The
jiff rewrite built the date with `epoch.checked_add(whole_days).unwrap_or(epoch)`,
but jiff's civil calendar only spans years -9999..=9999 — far narrower than
chrono's — so any instant past year 9999 silently became the epoch, changing
existing output. `i64::MAX` used to read `Dec 4, 292277026596` /
`292277026596-12-04T15:30:07Z`, and `i64::MIN` `Jan 27, -292277022657`, not 1970.
Fix: move the instant into jiff's range by whole 400-year Gregorian cycles
(146_097 days) before converting, then restore the real year afterwards. `rest`
is always under one cycle, so the in-range date lands in 1970..=2369 (well inside
jiff) and `rest * 86_400` can't overflow; the year is restored through `i128` so
`cycles * 400` is exact at the `i64` extremes. jiff stays the only calendar logic
(it does the month/day); the cycle count just shifts the year. No epoch fallback
for any value the old code converted. core still builds for wasm32.
Tests now pin the old output instead of the epoch:
- core civil_from_unix(i64::MAX) == (292277026596, 12, 4),
civil_from_unix(i64::MIN) == (-292277022657, 1, 27),
civil_from_unix(253_402_300_800) == (10000, 1, 1)
- xtask rfc3339(i64::MAX) == "292277026596-12-04T15:30:07Z",
rfc3339(i64::MIN) == "-292277022657-01-27T08:29:52Z",
rfc3339(253_402_300_800) == "10000-01-01T00:00:00Z"