- The uninstall-record fallback read the deduplicated install list, so a copy
registered per-user and per-machine reported only the machine record and the
update went ahead. It now reads every record of the product code and gives no
scope when they disagree, so the update stops as the probe path does.
- The launcher policy check returned before looking at InstallExecuteSequence.
It now requires CraftRestoreLauncherFolder after AppSearch and before
CostInitialize, so silent updates cannot lose the folder.
- Launcher self-detection also looks in ArtCraft\Launcher (the folder the MSI
uses) and reads its folder from the publisher-named registry key.
- README notes that a custom folder the account cannot write installs for
everyone, with an administrator prompt.
Two review findings on the MSI update path.
- The update context came from a PowerShell and Windows Installer COM probe,
and any failure to run it blocked the update with the real error dropped
(Constrained Language Mode, AppLocker or WDAC, a slow first start). The error
is now logged. When the probe cannot run at all, the context comes from the
uninstall record the copy was found by (HKLM is per-machine, the account's
own HKCU is per-user). A probe that ran and gave no answer, or a copy
registered for both contexts, still stops the update.
- A fresh install into a custom folder under Program Files passed no scope, so a
package that defaults to per-user could not write there. A fresh install into
a folder this account cannot write now asks for the per-machine context, and
Windows prompts for permission.
Look for the executable in the recorded folder first, then in App Paths and the usual folders of the
record's own hive and registry view, so a per-machine entry never stands in for a per-user record.
Craft app MSIs are becoming dual-scope packages that default to a per-user
install. An update must land in the same Windows Installer context as the
copy it replaces: a per-machine copy needs elevation and writes HKLM
registrations, and a per-user one must not silently duplicate itself
per-machine (or the other way round). The context is read from the product
code's registration and passed to msiexec (ALLUSERS=1 for per-machine, the
documented ALLUSERS=2 + MSIINSTALLPERUSER=1 for per-user). A fresh install
passes nothing and takes the package's own default, so the current
per-machine-only packages keep behaving exactly as before.
Detection now also reads the per-user places a dual-scope package records
itself: the HKCU InstallDir, App Paths under HKCU, and the user's own
Programs folder.
* refactor: delegate self_update to craft-update
* ci: point craft-update patch to dylanmtaylor/craft-libs branch
* nix: set allowBuiltinFetchGit for git dependencies
* chore(deps): update craft-update commit
* build: pin craft-update to storytold/craft-libs fe61243 and drop the temporary patch
* fix(update): share craft_update::Stop so pause and cancel reach the self-update
* build: pin craft-update to the dirs-free, zip 9 build
Satisfies cargo-deny: no MPL-2.0 option-ext, no zip 8 advisories, and a versioned git dependency instead of a wildcard.
* build: pin craft-update to craft-libs main (b9ba47f)
* Migrate Launcher to egui 0.37 and preserve current-main behavior
* egui037: record final published pins and local qualification
* fix(ui): gate CJK language drawing until fonts are installed
* Document migrated launcher UI contracts and regression invariants
- Redact Bearer tokens with multiple spaces and add regression test.
- Explicitly disable proxies on direct and direct_download ureq agents.
- Add direct_download agent with download timeout profile for loopback downloads.
- Preserve proxy username, password, and NO_PROXY exclusions in proxy::pick().
- Uses entry.size() rather than header.size() to account for PAX size headers in cumulative unpack limits.
- Defers directory extraction until child entries are written to avoid restrictive directory modes preventing child file creation.
- Returns error when tree file enumeration exceeds limits or fails to read directory.
- Supports both /usr/local and ~/.local install prefixes based on needs_root.
Resolves F-016 (CWE-522) and F-017 (CWE-287).
- Replaces cleartext transmission of persistent bearer token with ephemeral mutual challenge-response.
- Client initiates handshake with ephemeral client nonce without disclosing token.
- Server proves possession of per-user token via SHA-256 HMAC-style proof before client discloses client proof.
- Uses constant-time equality check to prevent timing leaks.
- Replayed or stale auth proofs across handshakes are rejected via fresh server nonces.
reads_binary_version_when_the_cli_uses_the_display_name_or_a_former_id
failed now and then: another test thread that forks while the script is
still open for writing holds that descriptor until it execs, and running
the script in that window fails with ETXTBSY. Both binary_version tests
now retry a miss for up to a second.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
native's Linux menu integration and ui-egui's icons each kept the id -> PNG table;
adding FigCraft had to edit both. core::catalog::app_icon_png is the one table now,
with a test that every app in SUITE has a PNG.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
`the_bundle_must_be_the_releases_version` failed now and then with
nothing more than "hdiutil create failed": the disk-image helper turns
a create away while it is busy with anything else on the machine, as
`attach` in the launcher already knows and retries for (#135). The
tests' own hdiutil calls were serialized, but that isn't enough.
The test helper tries `hdiutil create` up to five times with a short,
growing pause, and keeps hdiutil's stderr for the failure message, so a
real failure says why. Ten consecutive runs of the macOS checks passed
with it, where before 1 run in 3 failed.
Co-authored-by: YangXZ2008 <141027290+YangXZ2008@users.noreply.github.com>
* portable: a network-share install matches its canonical path
same_windows_path stripped only `\?\`, so the `\?\UNC\server\share\...`
that canonicalize returns for a folder on a network share became
`UNC\server\share\...` and never matched the recorded
`\server\share\...`. Updating or removing a portable app installed on a
share then failed with "resolves through a changed alias".
Map `\?\UNC\` back to `\` before comparing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* portable: read the verbatim prefix in any case
Windows accepts `\\?\unc\` as well as `\\?\UNC\`, and a recorded path is
compared as it was stored, so a lower-case prefix fell through to the
`\\?\` branch and became `unc\server\share\...` again. Lower-case the
path before stripping the prefix (the comparison was case-insensitive
already), and document same_windows_path.
From CodeRabbit's review of this PR.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* 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
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.
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.