Commit Graph
134 Commits
Author SHA1 Message Date
Jeremy 38361c33f1 fix(installer): stop the fallback on a doubly-registered copy; check the launcher restore in the execute sequence
- 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.
2026-10-27 21:34:48 +00:00
Jeremy ed34ecc898 fix(installer): keep updating when the Windows Installer lookup cannot run, and elevate custom protected folders
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.
2026-10-27 21:13:30 +00:00
Jeremy ab4e227536 fix(installer): find the exe in the uninstall record's own scope
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.
2026-10-27 12:25:17 +00:00
Jeremy a83a505bc7 fix(installer): detect apps in the ArtCraft suite folder and describe the per-user default install location 2026-10-27 12:09:01 +00:00
Jeremy f6413ce476 fix(installer): read InstallDir from the uninstall record's registry view; align fixture WiX warnings and CI checkout 2026-10-27 11:45:43 +00:00
Jeremy 4084181e5a feat(installer): preserve app update context and install path 2026-10-26 22:18:45 +00:00
Jeremy d73819619d native(install): keep MSI updates in the registered installation context
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.
2026-10-26 15:04:26 +00:00
Dylan M. Taylor 7aafad661a native(install): split the archive unpack and the flat-layout move into helpers (S3776) (#903) 2026-10-25 21:14:39 -04:00
Dylan M. Taylor e2b1b73a08 refactor: delegate self-update functionality to craft-update (#871)
* 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)
2026-10-25 13:40:46 -04:00
skylar taylor-barrick 4ab652afd8 Migrate Craft Launcher to egui 0.37 (#873)
* 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
2026-10-25 10:49:00 -04:00
Dylan M. Taylor 2d122457d1 Merge pull request #869 from dylanmtaylor/security/single-instance-challenge-response
fix(security): implement mutual challenge-response for single-instance IPC
2026-10-24 22:37:26 -04:00
Dylan M. Taylor 44cbaeedce Merge pull request #868 from dylanmtaylor/security/proxy-loopback-credential-logging
security: bypass proxy for loopback and suppress HTTP wire credential logging (F-003, F-005)
2026-10-24 22:37:23 -04:00
Dylan M. Taylor 5f8cfd3c9a Merge pull request #867 from dylanmtaylor/security/artifact-identity-binding
security: bind artifact URL identity in install and self-update (F-010, F-011, F-015)
2026-10-24 22:37:18 -04:00
Dylan M. Taylor ca67541cbd Merge pull request #865 from dylanmtaylor/security/decoded-payload-limits
security: bound decoded HTTP responses and screenshot decoding (F-006, F-008, F-009)
2026-10-24 22:37:15 -04:00
Dylan M. Taylor 248db064eb security(install): skip records under symlinked directories in remove_files 2026-10-24 22:36:35 -04:00
Dylan M. Taylor 6bd7efa57c install(freebsd): record non-traversed symlink entries in tree manifest 2026-10-24 22:25:12 -04:00
Dylan M. Taylor ad40581d64 fix: implement constant-time equality in pure Rust without modifying Cargo.lock 2026-10-24 22:23:33 -04:00
Dylan M. Taylor a9ac09060a fix(install): parse release tag and filename from release download URLs
- Validates tag and artifact filename from /releases/download/ URLs without breaking mock/loopback URLs.
2026-10-24 22:21:43 -04:00
Dylan M. Taylor f8c691e55c fix(clippy): remove redundant let binding in rogue-server test 2026-10-24 22:18:58 -04:00
Dylan M. Taylor 72b9418c05 fix(security): address review comments on proxy and logging
- 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().
2026-10-24 22:18:27 -04:00
Dylan M. Taylor df0b2e4d96 fix(install): enforce URL filename and tag identity checks when release.version is absent
- Retains download URL filename and release tag checks in bind_artifact_version even when semantic version parsing is unavailable.
2026-10-24 22:15:21 -04:00
Dylan M. Taylor d31ee123b5 fix(install): defer directory unpacking, use effective entry size, and fix FreeBSD paths
- 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.
2026-10-24 22:14:22 -04:00
Dylan M. Taylor 6f2c7e7b44 fix(http): restrict oversized check to resuming partial downloads
Allows a full 200 response to replace an existing oversized partial file instead of erroneously rejecting it.
2026-10-24 22:12:07 -04:00
Dylan M. Taylor 886fa562d3 fix(security): implement mutual challenge-response for single-instance IPC
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.
2026-10-24 22:07:59 -04:00
Dylan M. Taylor 004fe617af security: bypass proxy for loopback and suppress HTTP wire credential logging (F-003, F-005) 2026-10-24 21:58:20 -04:00
Dylan M. Taylor 0594ec7f03 security: bind artifact URL identity in install and self-update (F-010, F-011, F-015) 2026-10-24 21:52:05 -04:00
Dylan M. Taylor 5ec7596b61 security: harden archive extraction, layout normalization, and file enumeration (F-001, F-002, F-012, F-013, F-014) 2026-10-24 21:49:55 -04:00
Dylan M. Taylor ab5e83ffd1 security: bound decoded HTTP responses and screenshot decoding (F-006, F-008, F-009) 2026-10-24 21:47:13 -04:00
Brandon ThomasandClaude Opus 5.5 fdcd6fb631 Tests: retry the version probe of a script a test just wrote (ETXTBSY) (#848)
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>
2026-10-24 02:17:58 -04:00
803d5f57a4 fix(windows): launch system helpers by absolute path (#815)
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-24 01:44:10 -04:00
CapriandGPT-6 Luna 75a1ed5167 Backend: Clear busy state when a worker panics (#780) (#845)
Co-authored-by: GPT-6 Luna <noreply@openai.com>
2026-10-24 01:43:57 -04:00
smittie2000andClaude Opus 5.5 b5333a6247 refactor(core): one table of bundled app icons (#789)
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>
2026-10-24 01:43:51 -04:00
XianZheYangandYangXZ2008 3905a8981d Tests: retry hdiutil create in the macOS checks, and say what it said (#442)
`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>
2026-10-24 01:42:30 -04:00
smittie2000andClaude Opus 5.5 e6c4f24e9a portable: a network-share install matches its canonical path (#821)
* 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>
2026-10-24 01:42:24 -04:00
Justin JohnandCodex f195d59997 fix(native): gate remote feed overrides to dev builds (#817)
Co-authored-by: Codex <codex@openai.com>
2026-10-24 01:42:07 -04:00
smittie2000andClaude Opus 5.5 8a4b875640 refactor(native): host() with split_once, and a test (#785)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 01:41:56 -04:00
Ian Holloway f1fe597eca Resolve Unix restart test helpers through PATH (#424)
Assisted-by: OpenAI Codex (GPT-6)

Signed-off-by: IanHollow <72767437+IanHollow@users.noreply.github.com>
2026-10-24 01:41:50 -04:00
Justin JohnandCodex 07f4f2959c test: avoid comparing different Windows catalog signers (#814)
Co-authored-by: Codex <codex@openai.com>
2026-10-24 01:41:44 -04:00
Dylan M. TaylorandCodebuff 674b893f0e Refactor the complex fns, expand wildcard imports, tidy closures and lifetimes (SonarCloud) (#836)
* 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>
2026-10-23 17:14:44 -04:00
aiongg 2d3992cf0f Linux and FreeBSD install: run pkexec and package tools from the system directories (#689)
* 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
2026-10-23 13:03:01 -04:00
Dylan M. Taylor a010a50d73 fix: put NOSONAR markers on the flagged lines (SonarCloud)
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>
2026-10-23 09:44:36 -04:00
Dylan M. Taylor d861df547a fix: spell out why installs use 0755 and keep recovery backups (SonarCloud)
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>
2026-10-23 09:29:39 -04:00
skylartaylor 9419d8977b Merge pull request #756: install wrapped portable ZIP releases
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.
2026-10-23 02:40:45 +02:00
skylar taylor-barrick 49a8ba5dd9 Merge pull request #377: refresh past a lagging release feed
Check the latest GitHub release redirect during manual refresh before trusting older feed metadata.
2026-10-22 18:39:02 -06:00
Dylan M. Taylor c190a590bc Merge pull request #750 from dylanmtaylor/security/ipc-local-service-hardening
security: authenticate single-instance replace and bound the local mirror server
2026-10-22 19:41:10 -04:00
Dylan M. Taylor 7cf50d7a10 security: address CodeRabbit review on IPC token CSPRNG, retry, and mirror timeouts 2026-10-22 19:28:40 -04:00
Dylan M. Taylor 329e9c2fb7 Merge pull request #749 from dylanmtaylor/security/install-filesystem-safety
security: harden local install and packaging against filesystem attacks
2026-10-22 19:28:01 -04:00
Dylan M. Taylor d75fd3b2d1 security: authenticate single-instance replace and bound the local mirror server
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.
2026-10-22 19:22:00 -04:00
Dylan M. Taylor ceffdd6d5d style: rustfmt 2026-10-22 19:16:10 -04:00
Dylan M. Taylor 398dd79332 Merge pull request #748 from dylanmtaylor/security/resource-limits
security: bound decoded feed size, screenshot dimensions, and locale tag matching
2026-10-22 17:54:15 -04:00