Fix/update metadata#52
Conversation
Signed-off-by: fufesou <linlong1266@gmail.com>
Signed-off-by: fufesou <linlong1266@gmail.com>
Signed-off-by: fufesou <linlong1266@gmail.com>
WalkthroughThis PR implements a comprehensive verified software update system with cryptographic signing and integrity checks. It adds Python tooling for update metadata generation, refactors the Rust updater module for SHA-256 verification, introduces HTTP timeouts and strict TLS enforcement, implements platform-specific secure update flows for Windows and macOS, and updates the Flutter UI for async download coordination. ChangesVerified Update Infrastructure and Implementation
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes This is a substantial, intricate, high-density logic PR with multi-pattern changes across cryptographic tooling, platform-specific update flows, HTTP networking, Flutter UI, and CI/CD orchestration. It introduces new verification pipelines, safe temp-file handling, and security-critical features (EdDSA signing, SHA-256 validation, reparse-point checks) requiring careful review across multiple subsystems with interdependencies.
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Comment |
There was a problem hiding this comment.
Actionable comments posted: 31
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/flutter-build.yml:
- Around line 854-887: The macOS metadata fragment and upload steps ("Generate
update metadata fragment", "Upload macOS DMG fragment", "Upload signed macOS
DMG") are currently gated only by UPDATE_METADATA_SIGNING_ENABLED and can run
when the DMG wasn't actually codesigned; update each step's if-expression to
also require the same macOS signing prerequisite used by the codesign/notarize
step (the same env var or condition that the codesign step checks, e.g. the
MACOS_SIGNING_* or MACOS_CODESIGNING_ENABLED variable) so unsigned DMGs cannot
enter the signed-metadata path, and add an early validation step that fails fast
when PUBLISH_UPDATE_METADATA==true && UPDATE_METADATA_SIGNING_ENABLED==true but
the macOS signing material/flag is missing.
- Around line 1024-1029: The GitHub Action usage for publishing signed updates
currently pins softprops/action-gh-release@v1 which uses an older Node runtime;
update the step that declares "Publish signed update release" by changing the
action reference in the step using softprops/action-gh-release (the line
beginning with uses: softprops/action-gh-release@v1) to
softprops/action-gh-release@v3, keep the existing inputs (prerelease, tag_name,
files) as-is unless v3 introduces breaking input changes, and run the workflow
to verify the publish job still succeeds with the new runtime.
In `@flutter/lib/desktop/widgets/update_progress.dart`:
- Around line 24-31: The initial verification path handles backend errors from
bind.mainGetCommon('verified-download-url-$releasePageUrl') by passing raw error
text to _showUpdateError, but it doesn't normalize GitHub rate-limit messages
like _onError does; update the code after the downloadUrl.startsWith('error:')
check to detect the same throttling/rate-limit pattern used in _onError and
replace or map that backend error to the friendly throttling message before
calling _showUpdateError (i.e., normalize the error string for the
'verified-download-url-*' branch to the same user-facing message _onError uses).
In `@res/generate_update_metadata.py`:
- Around line 42-46: The current display_version_from_release_id function only
accepts plain semver and must be extended to accept workflow-supported tags like
"v1.4.6-1" or "1.4.6-1"; update the regex used in
display_version_from_release_id (where re.fullmatch is called) to allow an
optional hyphenated build number suffix (e.g. /v?(\d+\.\d+\.\d+)(?:-\d+)?$/) and
continue returning only the captured X.Y.Z group so check-version correctly
recognizes and strips the -N suffix.
In `@res/test_generate_update_metadata.py`:
- Around line 219-225: The test
test_check_version_accepts_parseable_tags_rejects_mismatch_and_skips_nightly
misses coverage for build-suffixed tags like vX.Y.Z-N; update it to also assert
that run_script("check-version", "--release-id", "v1.4.6-1", "--version",
"1.4.6-1") and run_script("check-version", "--release-id", "1.4.6-1",
"--version", "1.4.6-1") succeed (returncode 0 / no error), and add a negative
case asserting that run_script("check-version", "--release-id", "v1.4.7-1",
"--version", "1.4.6-1", check=False) has a nonzero returncode; keep the existing
nightly assertion. Use the same test function name and the existing
run_script/check-version calls to locate where to add these assertions.
In `@src/common.rs`:
- Around line 953-959: Tighten the GitHub URL validation around the existing url
and update_url checks by rejecting URLs with explicit ports or embedded
credentials: after parsing into url (Url::parse(update_url)? and the existing
scheme/host checks), add checks that url.port().is_none(),
url.username().is_empty(), and url.password().is_none(); if any fail, bail! with
a clear message including update_url. Also add regression tests that exercise a
URL with an explicit port (e.g., https://github.com:8443/...) and a URL with
credentials (e.g., https://user@github.com/...) to ensure resp.url cannot be
used to inject non‑canonical release URLs.
In `@src/core_main.rs`:
- Around line 26-35: parse_macos_update_args currently treats any
present-but-invalid second argument as MacosUpdateArgs::App, which hides bad
invocations and bypasses the DMG update path called by platform::update_me();
change the logic in parse_macos_update_args so that: if args.get(1) is None ->
return Ok(MacosUpdateArgs::App); if args.get(1) is Some and ends_with(".dmg") ->
return Ok(MacosUpdateArgs::Dmg(dmg_path.clone())); otherwise return Err(...)
indicating a malformed `--update <path>` (e.g., invalid file extension) so bad
`--update foo.pkg` or wrong-cased names fail fast instead of falling through to
App.
In `@src/flutter_ffi.rs`:
- Around line 2981-2989: The code re-fetches and re-verifies artifact metadata
by calling crate::updater::verified_update_artifact_for_download_url(&_value)
during install; instead, propagate the already-verified artifact metadata
(sha256 and size) from the earlier download step into this install code path and
use that data to construct the artifact instead of calling
verified_update_artifact_for_download_url again. Change the caller to pass the
verified fields (e.g., sha256 and size or an Artifact struct) into the
function/block that currently binds let artifact = ..., remove the network call
and the Err path that logs and calls push_update_me_error, and use the passed-in
verified values to verify the on-disk file before installing. Ensure references
to the symbols artifact, _value, verified_update_artifact_for_download_url, and
push_update_me_error are updated accordingly so no second network fetch occurs.
In `@src/hbbs_http/downloader.rs`:
- Around line 181-189: The client warm-up currently awaits
tokio::time::timeout(create_http_client_async_with_url(...)) without observing
rx_cancel.recv(), so cancel(id) is ignored during warm-up; change this to use
tokio::select! to race create_http_client_async_with_url (with
DOWNLOAD_REQUEST_TIMEOUT) against rx_cancel.recv() and the timeout error,
returning early (bail or propagate a cancel error) if rx_cancel.recv() signals
cancellation, otherwise proceed to assign the client on success; refer to the
create_http_client_async_with_url call, DOWNLOAD_REQUEST_TIMEOUT, and
rx_cancel.recv() when updating the warm-up block so it matches the cancel
handling used for HEAD/GET/chunk stages.
In `@src/lang/ca.rs`:
- Around line 748-749: The two Catalan localization entries for the keys
"Verifying update" and "Please wait while {} verifies the update." are empty;
update the tuple values in src/lang/ca.rs to proper Catalan strings (replace the
empty "" values for those keys). For example, set "Verifying update" ->
"Verificant l'actualització" and "Please wait while {} verifies the update." ->
"Espereu mentre {} verifica l'actualització." ensuring the {} placeholder is
preserved exactly.
In `@src/lang/cs.rs`:
- Around line 748-749: In src/lang/cs.rs fill in Czech translations for the two
new localization keys "Verifying update" and "Please wait while {} verifies the
update." (the tuple entries currently have empty strings); update the second
string to include the "{}" placeholder preserved in the Czech sentence so
runtime interpolation still works (e.g., translate the messages into natural
Czech while keeping the placeholder token intact).
In `@src/lang/da.rs`:
- Around line 748-749: Add Danish translations for the two new localization keys
currently left empty: replace the empty string paired with "Verifying update"
and the empty string paired with "Please wait while {} verifies the update." in
src/lang/da.rs with appropriate Danish text (e.g., "Bekræfter opdatering" and
"Vent venligst mens {} bekræfter opdateringen."). Keep the placeholders intact
(the {} token) and ensure punctuation and capitalization follow the surrounding
translations.
In `@src/lang/de.rs`:
- Around line 748-749: The two new localization entries in src/lang/de.rs — the
tuple keys ("Verifying update", "") and ("Please wait while {} verifies the
update.", "") — are missing German translations; replace the empty strings with
appropriate German text (e.g., "Überprüfung des Updates" for "Verifying update"
and "Bitte warten, während {} das Update prüft." for the second) so the update
verification dialog shows localized strings instead of blanks.
In `@src/lang/el.rs`:
- Around line 748-749: Fill the two empty Greek translation entries in
src/lang/el.rs for the update-verification copy: replace the tuple for
"Verifying update" and the tuple for "Please wait while {} verifies the update."
with appropriate Greek strings (e.g., "Επαλήθευση ενημέρωσης" for "Verifying
update" and "Παρακαλώ περιμένετε ενώ το {} επαληθεύει την ενημέρωση." for the
templated sentence), keeping the original keys unchanged and preserving the
placeholder {} in the second string.
In `@src/lang/ptbr.rs`:
- Around line 748-749: The two new translation entries ("Verifying update" and
"Please wait while {} verifies the update.") in src/lang/ptbr.rs currently have
empty values; replace the empty strings with proper Portuguese translations
(e.g., "Verificando atualização" for "Verifying update" and "Aguarde enquanto {}
verifica a atualização." for the formatted string) so the update dialog shows
text; ensure the second entry preserves the {} placeholder exactly as in the
key.
In `@src/lang/ro.rs`:
- Around line 748-749: The two localization entries ("Verifying update", "") and
("Please wait while {} verifies the update.", "") must not have empty values —
replace the empty RHS with appropriate Romanian translations (e.g. "Se verifică
actualizarea" for "Verifying update" and "Vă rugăm să așteptați în timp ce {}
verifică actualizarea." for the templated string) so users see meaningful
messages; update these entries in src/lang/ro.rs (the tuple keys "Verifying
update" and "Please wait while {} verifies the update.") ensuring the
placeholder {} is preserved exactly in the translated string.
In `@src/lang/ru.rs`:
- Around line 748-749: The two Russian translation entries for the keys
"Verifying update" and "Please wait while {} verifies the update." are empty
causing blank status text; open src/lang/ru.rs, find the tuple entries matching
those exact English strings and replace the empty strings with appropriate
Russian translations (or temporary fallback text) preserving the {} placeholder
in the second string so formatting still works (e.g., supply translations like
"Проверка обновления" and "Пожалуйста, подождите, пока {} проверяет
обновление.").
In `@src/lang/sc.rs`:
- Around line 748-749: Two localization entries ("Verifying update", "") and
("Please wait while {} verifies the update.", "") were added with empty
translations; update those tuples to provide non-empty localized strings. Locate
the tuple list where these keys are defined (the localization entries for the
update verification messages) and replace the empty second elements with
appropriate translated text (preserving the {} placeholder in "Please wait while
{} verifies the update."). Ensure the translated strings match the original
meaning and keep the placeholder syntax intact.
In `@src/lang/sk.rs`:
- Around line 748-749: The two new Slovak translation entries for the
verification UI ("Verifying update" and "Please wait while {} verifies the
update.") are currently empty; populate them in src/lang/sk.rs with proper
Slovak strings (e.g., "Overovanie aktualizácie" and "Počkajte, kým {} overí
aktualizáciu.") replacing the empty values so the UI shows text during update
verification and preserve the placeholder {} in the second string.
In `@src/lang/sl.rs`:
- Around line 748-749: The two localization entries for the keys "Verifying
update" and "Please wait while {} verifies the update." are empty; replace the
empty strings with appropriate Slovene translations (non-empty text) so the
update UI shows labels/messages — update the values for those keys in
src/lang/sl.rs (the tuple entries with "Verifying update" and "Please wait while
{} verifies the update.") to proper Slovene strings.
In `@src/lang/sq.rs`:
- Around line 748-749: Two new Albanian translation entries are left as empty
strings for the keys "Verifying update" and "Please wait while {} verifies the
update." Update the translation tuples in src/lang/sq.rs where these entries
appear (the 2-tuple pairs containing "Verifying update" and "Please wait while
{} verifies the update.") with appropriate Albanian strings so the UI shows
non-empty text during update verification (e.g., provide translations for the
short label and the formatted waiting message, preserving the {} placeholder).
In `@src/lang/sr.rs`:
- Around line 748-749: Missing Serbian translations for the new
update-verification keys will show blank text; add Serbian strings for the keys
"Verifying update" and "Please wait while {} verifies the update." in the
translations list in sr.rs (the tuple entries currently set to ""), replacing
the empty second elements with appropriate Serbian translations that include the
placeholder {} in the second string for the verifier name/agent.
In `@src/lang/sv.rs`:
- Around line 748-749: These two translation entries currently have empty
Swedish values; update the tuple entries for "Verifying update" and "Please wait
while {} verifies the update." in src/lang/sv.rs to include proper Swedish text
(for example, "Verifierar uppdatering" for "Verifying update" and "Vänligen
vänta medan {} verifierar uppdateringen." for the templated message), replacing
the empty strings so the updater UI shows a Swedish label and message.
In `@src/lang/ta.rs`:
- Around line 748-749: Two translation strings are empty for the keys "Verifying
update" and "Please wait while {} verifies the update."; add appropriate Tamil
translations in place of the empty strings so the progress/status text appears
during update verification. Locate the tuple entries where the source strings
are "Verifying update" and "Please wait while {} verifies the update." in the
translations array and replace the second element of each tuple with the correct
Tamil text (e.g., "புதுப்பிப்பை உறுதி செய்கிறோம்" and "புதுப்பிப்பை {} உறுதி
செய்துகொள்ளும் வரை தயவுசெய்து காத்திருக்கவும்." or equivalent), preserving the
placeholder "{}" exactly. Ensure punctuation and string quoting follow the
surrounding Rust translation entries.
In `@src/lang/th.rs`:
- Around line 748-749: Populate the two untranslated entries in the Thai
translations map: replace the empty string for the key "Verifying update" with a
Thai translation such as "กำลังตรวจสอบการอัปเดต" and replace the empty string
for "Please wait while {} verifies the update." with a Thai sentence like
"กรุณารอสักครู่ ขณะที่ {} กำลังตรวจสอบการอัปเดต." Update these values in the
th.rs translation tuples so the keys "Verifying update" and "Please wait while
{} verifies the update." return proper Thai text.
In `@src/lang/tr.rs`:
- Around line 748-749: Add Turkish translations for the two empty localization
entries whose keys are "Verifying update" and "Please wait while {} verifies the
update." in the tr language map; replace the empty strings with appropriate
Turkish phrases (e.g., "Güncelleme doğrulanıyor" and "Lütfen {} güncellemeyi
doğrularken bekleyin." or similar) so the verification dialog shows proper
Turkish text.
In `@src/lang/tw.rs`:
- Around line 748-749: Add Traditional Chinese translations for the two new keys
in src/lang/tw.rs: replace the empty strings for the keys "Verifying update" and
"Please wait while {} verifies the update." with appropriate Traditional Chinese
text (e.g., "正在驗證更新" and "請稍候,{} 正在驗證更新。") so status/progress messages are not
blank during update verification; ensure the translations preserve the
placeholder {} in the second string.
In `@src/lang/uk.rs`:
- Around line 748-749: Replace the two empty translation strings for the
verification UI: change the tuple ("Verifying update", "") to include the
Ukrainian "Перевірка оновлення" and change ("Please wait while {} verifies the
update.", "") to "Зачекайте, будь ласка, поки {} перевіряє оновлення." so the
keys "Verifying update" and "Please wait while {} verifies the update." have
proper Ukrainian translations.
In `@src/lang/vi.rs`:
- Around line 748-749: The two localization entries ("Verifying update", "") and
("Please wait while {} verifies the update.", "") in src/lang/vi.rs are missing
Vietnamese translations and therefore show up blank; replace the empty strings
with appropriate Vietnamese translations (e.g., "Đang kiểm tra bản cập nhật" for
"Verifying update" and "Vui lòng chờ trong khi {} đang kiểm tra bản cập nhật."
for "Please wait while {} verifies the update.") ensuring the placeholder {} is
preserved exactly in the second string and that the translations are added as
the second element of the tuple for the keys "Verifying update" and "Please wait
while {} verifies the update.".
In `@src/platform/macos.rs`:
- Around line 1070-1073: The code currently calls
copy_dmg_to_update_temp_file(dmg_path) before verifying artifact.size, which can
duplicate a large/malicious DMG into temp; instead, perform the size/hash check
against the original file first using the signed metadata. Reorder to call
verify_dmg_file_size_and_sha256 (or add/overload a helper that accepts a file
path) on dmg_path using artifact.size and artifact.sha256 immediately after
verify_dmg_sidecar_metadata, and only if that passes call
copy_dmg_to_update_temp_file(dmg_path) to create the temp copy; update function
signatures as needed (e.g., make verify_dmg_file_size_and_sha256 accept &Path or
&str rather than &mut File) and adjust callers verify_dmg_sidecar_metadata,
copy_dmg_to_update_temp_file, and the surrounding logic to reflect the new
order.
In `@src/updater.rs`:
- Around line 489-496: The download request uses
client.get(download_url).send()? with no timeout, so a stalled transfer can
hang; update the GET call in ensure_verified_update_artifact (the code that
creates response from client.get(download_url)) to apply the same
UPDATE_HTTP_REQUEST_TIMEOUT used elsewhere by calling
.timeout(Duration::from_secs(UPDATE_HTTP_REQUEST_TIMEOUT)) (or the existing
constant) before .send(), so the request will error on timeout and the rest of
the flow (write_verified_update_artifact, error handling) remains unchanged;
reference the variables/client: client, download_url, and the response handling
that calls write_verified_update_artifact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: fe003b9b-c1ea-4b30-981c-6771a8107a60
📒 Files selected for processing (69)
.github/workflows/flutter-build.yml.github/workflows/flutter-nightly.yml.github/workflows/flutter-tag.ymlflutter/lib/desktop/pages/desktop_home_page.dartflutter/lib/desktop/widgets/update_progress.dartlibs/hbb_commonres/generate_update_metadata.pyres/test_generate_update_metadata.pysrc/common.rssrc/core_main.rssrc/flutter_ffi.rssrc/hbbs_http/account.rssrc/hbbs_http/downloader.rssrc/hbbs_http/http_client.rssrc/hbbs_http/record_upload.rssrc/lang/ar.rssrc/lang/be.rssrc/lang/bg.rssrc/lang/ca.rssrc/lang/cn.rssrc/lang/cs.rssrc/lang/da.rssrc/lang/de.rssrc/lang/el.rssrc/lang/eo.rssrc/lang/es.rssrc/lang/et.rssrc/lang/eu.rssrc/lang/fa.rssrc/lang/fi.rssrc/lang/fr.rssrc/lang/ge.rssrc/lang/gu.rssrc/lang/he.rssrc/lang/hi.rssrc/lang/hr.rssrc/lang/hu.rssrc/lang/id.rssrc/lang/it.rssrc/lang/ja.rssrc/lang/ko.rssrc/lang/kz.rssrc/lang/lt.rssrc/lang/lv.rssrc/lang/ml.rssrc/lang/nb.rssrc/lang/nl.rssrc/lang/pl.rssrc/lang/pt_PT.rssrc/lang/ptbr.rssrc/lang/ro.rssrc/lang/ru.rssrc/lang/sc.rssrc/lang/sk.rssrc/lang/sl.rssrc/lang/sq.rssrc/lang/sr.rssrc/lang/sv.rssrc/lang/ta.rssrc/lang/template.rssrc/lang/th.rssrc/lang/tr.rssrc/lang/tw.rssrc/lang/uk.rssrc/lang/vi.rssrc/platform/macos.rssrc/platform/privileges_scripts/update.scptsrc/platform/windows.rssrc/updater.rs
| - name: Install Python | ||
| if: env.UPLOAD_ARTIFACT == 'true' && env.PUBLISH_UPDATE_METADATA == 'true' && env.UPDATE_METADATA_SIGNING_ENABLED == 'true' | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: "3.x" | ||
|
|
||
| - name: Generate update metadata fragment | ||
| if: env.UPLOAD_ARTIFACT == 'true' && env.PUBLISH_UPDATE_METADATA == 'true' && env.UPDATE_METADATA_SIGNING_ENABLED == 'true' | ||
| shell: bash | ||
| env: | ||
| TAG_NAME: ${{ env.TAG_NAME }} | ||
| VERSION: ${{ env.VERSION }} | ||
| ARCH: ${{ matrix.job.arch }} | ||
| run: | | ||
| python3 -m pip install cryptography | ||
| python3 res/generate_update_metadata.py fragment --artifact "./rustdesk-${VERSION}-${ARCH}.dmg" --artifact-url "https://github.com/rustdesk/rustdesk/releases/download/${TAG_NAME}/rustdesk-${VERSION}-${ARCH}.dmg" --platform macos --arch "$ARCH" --format dmg --fragment-out "./fragments/macos-${ARCH}-dmg.json" | ||
|
|
||
| - name: Upload macOS DMG fragment | ||
| if: env.UPLOAD_ARTIFACT == 'true' && env.PUBLISH_UPDATE_METADATA == 'true' && env.UPDATE_METADATA_SIGNING_ENABLED == 'true' | ||
| uses: actions/upload-artifact@master | ||
| with: | ||
| name: macos-${{ matrix.job.arch }}-dmg.json | ||
| path: ./fragments/macos-${{ matrix.job.arch }}-dmg.json | ||
|
|
||
| - name: Upload signed macOS DMG | ||
| if: env.UPLOAD_ARTIFACT == 'true' && env.PUBLISH_UPDATE_METADATA == 'true' && env.UPDATE_METADATA_SIGNING_ENABLED == 'true' | ||
| uses: actions/upload-artifact@master | ||
| with: | ||
| name: macos-${{ matrix.job.arch }}-dmg | ||
| path: ./rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.dmg | ||
|
|
||
| - name: Publish DMG package | ||
| if: env.UPLOAD_ARTIFACT == 'true' | ||
| if: env.UPLOAD_ARTIFACT == 'true' && (env.PUBLISH_UPDATE_METADATA != 'true' || env.UPDATE_METADATA_SIGNING_ENABLED != 'true') | ||
| uses: softprops/action-gh-release@v1 |
There was a problem hiding this comment.
Don’t feed unsigned macOS DMGs into the signed-metadata path.
These steps run even when the macOS codesign/notarize step was skipped, so the workflow can publish signed metadata that points at an unsigned macOS update artifact. The client-side macOS updater is expected to reject that artifact, which turns this into a release-time breakage rather than a fallback path.
Gate the macOS fragment/upload path on the same signing prerequisite as the codesign step, or fail early when metadata publishing is requested without macOS signing material. Based on learnings, the macOS updater intentionally requires SHA256 verification plus a valid codesign --verify --deep --strict bundle signature, so unsigned DMGs should not enter the signed-metadata path.
🧰 Tools
🪛 actionlint (1.7.12)
[error] 887-887: the runner of "softprops/action-gh-release@v1" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🪛 zizmor (1.25.2)
[error] 856-856: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 873-873: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 880-880: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 887-887: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[info] 887-887: action functionality is already included by the runner (superfluous-actions): use gh release in a script step
(superfluous-actions)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/flutter-build.yml around lines 854 - 887, The macOS
metadata fragment and upload steps ("Generate update metadata fragment", "Upload
macOS DMG fragment", "Upload signed macOS DMG") are currently gated only by
UPDATE_METADATA_SIGNING_ENABLED and can run when the DMG wasn't actually
codesigned; update each step's if-expression to also require the same macOS
signing prerequisite used by the codesign/notarize step (the same env var or
condition that the codesign step checks, e.g. the MACOS_SIGNING_* or
MACOS_CODESIGNING_ENABLED variable) so unsigned DMGs cannot enter the
signed-metadata path, and add an early validation step that fails fast when
PUBLISH_UPDATE_METADATA==true && UPDATE_METADATA_SIGNING_ENABLED==true but the
macOS signing material/flag is missing.
| - name: Publish signed update release | ||
| uses: softprops/action-gh-release@v1 | ||
| with: | ||
| prerelease: true | ||
| tag_name: ${{ env.TAG_NAME }} | ||
| files: | |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/flutter-build.yml"
echo "== Snippet around requested lines =="
sed -n '1000,1060p' "$FILE" | nl -ba | sed -n '1,80p'
echo
echo "== All occurrences of softprops/action-gh-release in repo =="
rg -n "softprops/action-gh-release@v" -S .github/workflows || true
echo
echo "== Snippet around publish signed update release step (wider context) =="
# show from 980 to 1120 to include dependencies/steps
sed -n '980,1120p' "$FILE" | nl -ba | sed -n '1,160p'Repository: fufesou/rustdesk
Length of output: 158
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/flutter-build.yml"
echo "== Snippet around requested lines (best-effort) =="
sed -n '1000,1060p' "$FILE" | cat -n
echo
echo "== All occurrences of softprops/action-gh-release in repo =="
rg -n "softprops/action-gh-release@v" -S .github/workflows || true
echo
echo "== Wider context around publish signed update release step =="
sed -n '960,1120p' "$FILE" | cat -nRepository: fufesou/rustdesk
Length of output: 13136
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/flutter-build.yml"
rg -n "publish_signed:|Sign update metadata|Verify update metadata|Publish signed update release|softprops/action-gh-release@v1" "$FILE"
echo
echo "== Context: job block around publish_signed =="
# Print a wider slice around the signed job
# (use grep to locate the job header first)
START="$(rg -n "publish_signed:" "$FILE" | head -n1 | cut -d: -f1)"
# if not found, fallback
if [[ -z "${START:-}" ]]; then START=900; fi
sed -n "$((START-40)), $((START+180))p" "$FILE" | cat -nRepository: fufesou/rustdesk
Length of output: 1037
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/flutter-build.yml"
# Find the exact line number for the publish step
LINE="$(rg -n "Publish signed update release" "$FILE" | head -n1 | cut -d: -f1)"
echo "Publish signed update release step line: $LINE"
echo
echo "== Containing job header + needs (scan upward) =="
# Scan upward until we find a top-level job key (no leading spaces, ends with colon)
# Print a window of context around that job header.
awk -v L="$LINE" '
NR<=L {
if ($0 ~ /^[A-Za-z0-9_.-]+:[[:space:]]*$/) last_job=NR
}
END{
if (last_job=="") last_job=1;
start=last_job-10; if (start<1) start=1;
end=L+40;
for (i=start; i<=end; i++) {
# We cannot directly print by line number in awk without storing;
# so re-run with sed for the selected range.
}
}
' "$FILE" >/dev/null
# Use sed directly for readability (awk was used only to find job header position)
JOB_HEADER="$(awk -v L="$LINE" '
NR<=L {
if ($0 ~ /^[A-Za-z0-9_.-]+:[[:space:]]*$/) last_job=NR
}
END{ print last_job }
' "$FILE")"
echo "Job header line: $JOB_HEADER"
echo
sed -n "$((JOB_HEADER-5)),$((JOB_HEADER+60))p" "$FILE" | cat -nRepository: fufesou/rustdesk
Length of output: 3207
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/flutter-build.yml"
LINE="$(rg -n "Publish signed update release" "$FILE" | head -n1 | cut -d: -f1)"
echo "Publish step line: $LINE"
# Find nearest job key above with exactly 2 leading spaces and ending colon
JOB_LINE="$(awk -v L="$LINE" '
NR<=L {
if ($0 ~ /^ [A-Za-z0-9_.-]+:[[:space:]]*$/) cand=NR
}
END { print cand }
' "$FILE")"
echo "Containing job header line: $JOB_LINE"
echo
sed -n "$((JOB_LINE-10)),$((JOB_LINE+80))p" "$FILE" | cat -nRepository: fufesou/rustdesk
Length of output: 3886
🌐 Web query:
softprops/action-gh-release v1 node runtime version actionlint outdated Actions runtime
💡 Result:
To address the GitHub Actions runtime deprecations and the corresponding softprops/action-gh-release updates, you should upgrade to version 3.0.0. Major Update for softprops/action-gh-release - softprops/action-gh-release version 3.0.0 was released on April 12, 2026, to migrate the action's runtime from Node 20 to Node 24 [1][2]. - This change is required because GitHub Actions has deprecated Node 20 runtimes [3]. If you are still using v2 (specifically versions prior to 2.6.2), your workflows may encounter deprecation warnings or compatibility issues as GitHub completes its transition to Node 24 [1][3]. - If your environment specifically requires a Node 20-compatible version, the last supported version is v2.6.2 [4][1]. actionlint and Runtime Version Checks - actionlint is a static analysis tool that detects outdated or deprecated GitHub Actions runtimes [5]. - Following the GitHub Actions migration to Node 24, actionlint was updated to support and recognize the node24 runtime [6]. - If you encounter actionlint errors regarding "outdated" runtimes or "invalid runner" warnings after upgrading your action, ensure that you are using a version of actionlint that supports Node 24 (generally v1.7.11 or newer) [6]. In some cases, if you are developing your own actions, you may need to update your action.yml to specify runs: using: 'node24' [7][8]. Recommendation - Update your workflow to use softprops/action-gh-release@v3 [4][2]. - Ensure your actionlint version is up to date to avoid false positives or failure to detect necessary runtime upgrades [6].
Citations:
- 1: https://github.com/softprops/action-gh-release/releases/tag/v3.0.0
- 2: https://github.com/softprops/action-gh-release
- 3: Node.js 20 actions are deprecated softprops/action-gh-release#742
- 4: https://github.com/softprops/action-gh-release/blob/master/README.md
- 5: https://github.com/rhysd/actionlint/releases/tag/v1.7.0
- 6: Allow node 24 for actions rhysd/actionlint#561
- 7: softprops/action-gh-release@b430933
- 8: Add actionlint configuration actions/publish-immutable-action#228
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/flutter-build.yml"
LINE="$(rg -n "Publish signed update release" "$FILE" | head -n1 | cut -d: -f1)"
echo "Publish step line: $LINE"
# Find nearest job key above with exactly 2 leading spaces and ending colon
JOB_LINE="$(awk -v L="$LINE" '
NR<=L {
if ($0 ~ /^ [A-Za-z0-9_.-]+:[[:space:]]*$/) cand=NR
}
END { print cand }
' "$FILE")"
echo "Containing job header line: $JOB_LINE"
echo
sed -n "$((JOB_LINE-10)),$((JOB_LINE+110))p" "$FILE" | cat -nRepository: fufesou/rustdesk
Length of output: 5396
Upgrade softprops/action-gh-release@v1 for the signed publish job to avoid Node runtime deprecation failures.
The publish-signed-update-metadata job signs + verifies update metadata and then publishes it using uses: softprops/action-gh-release@v1; if GitHub blocks the action’s older runtime, the job can fail at publish after the prior work completed. softprops/action-gh-release moved its runtime to Node 24 in v3, so use softprops/action-gh-release@v3 instead.
Original snippet
- name: Publish signed update release
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: ${{ env.TAG_NAME }}
files: |🧰 Tools
🪛 actionlint (1.7.12)
[error] 1025-1025: the runner of "softprops/action-gh-release@v1" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🪛 zizmor (1.25.2)
[error] 1025-1025: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[info] 1025-1025: action functionality is already included by the runner (superfluous-actions): use gh release in a script step
(superfluous-actions)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/flutter-build.yml around lines 1024 - 1029, The GitHub
Action usage for publishing signed updates currently pins
softprops/action-gh-release@v1 which uses an older Node runtime; update the step
that declares "Publish signed update release" by changing the action reference
in the step using softprops/action-gh-release (the line beginning with uses:
softprops/action-gh-release@v1) to softprops/action-gh-release@v3, keep the
existing inputs (prerelease, tag_name, files) as-is unless v3 introduces
breaking input changes, and run the workflow to verify the publish job still
succeeds with the new runtime.
| final downloadUrl = | ||
| await bind.mainGetCommon(key: 'verified-download-url-$releasePageUrl'); | ||
| if (canceled.value) { | ||
| return; | ||
| } | ||
| if (downloadUrl.startsWith('error:')) { | ||
| final error = downloadUrl.replaceFirst('error:', ''); | ||
| _showUpdateError(releasePageUrl, error); |
There was a problem hiding this comment.
Normalize rate-limit errors on the initial verification path too.
The GitHub throttling message is only mapped in _onError(). If the first verified-download-url-* lookup fails for the same reason, the dialog falls back to the raw backend error instead of the friendly user message.
Suggested fix
if (downloadUrl.startsWith('error:')) {
final error = downloadUrl.replaceFirst('error:', '');
- _showUpdateError(releasePageUrl, error);
+ if (error.contains(_githubRateLimitErrorMarker)) {
+ _showUpdateError(releasePageUrl, error,
+ userMessage: _githubRateLimitUserMessage);
+ } else {
+ _showUpdateError(releasePageUrl, error);
+ }
return;
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@flutter/lib/desktop/widgets/update_progress.dart` around lines 24 - 31, The
initial verification path handles backend errors from
bind.mainGetCommon('verified-download-url-$releasePageUrl') by passing raw error
text to _showUpdateError, but it doesn't normalize GitHub rate-limit messages
like _onError does; update the code after the downloadUrl.startsWith('error:')
check to detect the same throttling/rate-limit pattern used in _onError and
replace or map that backend error to the friendly throttling message before
calling _showUpdateError (i.e., normalize the error string for the
'verified-download-url-*' branch to the same user-facing message _onError uses).
| def display_version_from_release_id(release_id): | ||
| match = re.fullmatch(r"v?(\d+\.\d+\.\d+)", release_id) | ||
| if not match: | ||
| return None | ||
| return match.group(1) |
There was a problem hiding this comment.
Parse the workflow-supported vX.Y.Z-N release tags here.
flutter-tag.yml explicitly publishes metadata for tags like v1.4.6-1 and 1.4.6-1, but this parser only accepts plain semver. In that case check-version silently skips the guard, so a mismatched VERSION can still flow into signed metadata.
Suggested fix
def display_version_from_release_id(release_id):
- match = re.fullmatch(r"v?(\d+\.\d+\.\d+)", release_id)
+ match = re.fullmatch(r"v?(\d+\.\d+\.\d+)(?:-\d+)?", release_id)
if not match:
return None
return match.group(1)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@res/generate_update_metadata.py` around lines 42 - 46, The current
display_version_from_release_id function only accepts plain semver and must be
extended to accept workflow-supported tags like "v1.4.6-1" or "1.4.6-1"; update
the regex used in display_version_from_release_id (where re.fullmatch is called)
to allow an optional hyphenated build number suffix (e.g.
/v?(\d+\.\d+\.\d+)(?:-\d+)?$/) and continue returning only the captured X.Y.Z
group so check-version correctly recognizes and strips the -N suffix.
| def test_check_version_accepts_parseable_tags_rejects_mismatch_and_skips_nightly(self): | ||
| self.run_script("check-version", "--release-id", "v1.4.6", "--version", "1.4.6") | ||
| self.run_script("check-version", "--release-id", "1.4.6", "--version", "1.4.6") | ||
| mismatch = self.run_script("check-version", "--release-id", "v1.4.7", "--version", "1.4.6", check=False) | ||
| self.assertNotEqual(mismatch.returncode, 0) | ||
| nightly = self.run_script("check-version", "--release-id", "nightly", "--version", "1.4.6") | ||
| self.assertIn("not an official client update source", nightly.stdout) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | ⚡ Quick win
Cover the vX.Y.Z-N tags that the release workflow accepts.
This test only exercises plain semver and nightly, so it would not catch the current gap for v1.4.6-1 / 1.4.6-1.
Suggested test additions
def test_check_version_accepts_parseable_tags_rejects_mismatch_and_skips_nightly(self):
self.run_script("check-version", "--release-id", "v1.4.6", "--version", "1.4.6")
self.run_script("check-version", "--release-id", "1.4.6", "--version", "1.4.6")
+ self.run_script("check-version", "--release-id", "v1.4.6-1", "--version", "1.4.6")
+ self.run_script("check-version", "--release-id", "1.4.6-1", "--version", "1.4.6")
mismatch = self.run_script("check-version", "--release-id", "v1.4.7", "--version", "1.4.6", check=False)
self.assertNotEqual(mismatch.returncode, 0)
nightly = self.run_script("check-version", "--release-id", "nightly", "--version", "1.4.6")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@res/test_generate_update_metadata.py` around lines 219 - 225, The test
test_check_version_accepts_parseable_tags_rejects_mismatch_and_skips_nightly
misses coverage for build-suffixed tags like vX.Y.Z-N; update it to also assert
that run_script("check-version", "--release-id", "v1.4.6-1", "--version",
"1.4.6-1") and run_script("check-version", "--release-id", "1.4.6-1",
"--version", "1.4.6-1") succeed (returncode 0 / no error), and add a negative
case asserting that run_script("check-version", "--release-id", "v1.4.7-1",
"--version", "1.4.6-1", check=False) has a nonzero returncode; keep the existing
nightly assertion. Use the same test function name and the existing
run_script/check-version calls to locate where to add these assertions.
| ("Verifying update", ""), | ||
| ("Please wait while {} verifies the update.", ""), |
There was a problem hiding this comment.
Please add Traditional Chinese translations for the new verification keys.
Empty values here can cause blank status/progress text during update verification.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lang/tw.rs` around lines 748 - 749, Add Traditional Chinese translations
for the two new keys in src/lang/tw.rs: replace the empty strings for the keys
"Verifying update" and "Please wait while {} verifies the update." with
appropriate Traditional Chinese text (e.g., "正在驗證更新" and "請稍候,{} 正在驗證更新。") so
status/progress messages are not blank during update verification; ensure the
translations preserve the placeholder {} in the second string.
| ("Verifying update", ""), | ||
| ("Please wait while {} verifies the update.", ""), |
There was a problem hiding this comment.
Provide non-empty Ukrainian translations for new verification strings.
Both new keys are mapped to "", so update verification UI text will be blank in Ukrainian locale.
💬 Suggested fix
- ("Verifying update", ""),
- ("Please wait while {} verifies the update.", ""),
+ ("Verifying update", "Перевірка оновлення"),
+ ("Please wait while {} verifies the update.", "Зачекайте, поки {} перевіряє оновлення."),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ("Verifying update", ""), | |
| ("Please wait while {} verifies the update.", ""), | |
| ("Verifying update", "Перевірка оновлення"), | |
| ("Please wait while {} verifies the update.", "Зачекайте, поки {} перевіряє оновлення."), |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lang/uk.rs` around lines 748 - 749, Replace the two empty translation
strings for the verification UI: change the tuple ("Verifying update", "") to
include the Ukrainian "Перевірка оновлення" and change ("Please wait while {}
verifies the update.", "") to "Зачекайте, будь ласка, поки {} перевіряє
оновлення." so the keys "Verifying update" and "Please wait while {} verifies
the update." have proper Ukrainian translations.
| ("Verifying update", ""), | ||
| ("Please wait while {} verifies the update.", ""), |
There was a problem hiding this comment.
Fill Vietnamese translations for the newly added update-verification keys.
These entries currently resolve to empty strings, which can hide verification status text in the UI.
💬 Suggested fix
- ("Verifying update", ""),
- ("Please wait while {} verifies the update.", ""),
+ ("Verifying update", "Đang xác minh bản cập nhật"),
+ ("Please wait while {} verifies the update.", "Vui lòng chờ trong khi {} xác minh bản cập nhật."),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lang/vi.rs` around lines 748 - 749, The two localization entries
("Verifying update", "") and ("Please wait while {} verifies the update.", "")
in src/lang/vi.rs are missing Vietnamese translations and therefore show up
blank; replace the empty strings with appropriate Vietnamese translations (e.g.,
"Đang kiểm tra bản cập nhật" for "Verifying update" and "Vui lòng chờ trong khi
{} đang kiểm tra bản cập nhật." for "Please wait while {} verifies the update.")
ensuring the placeholder {} is preserved exactly in the second string and that
the translations are added as the second element of the tuple for the keys
"Verifying update" and "Please wait while {} verifies the update.".
| let artifact = verify_dmg_sidecar_metadata(dmg_path, metadata_bytes, signature_bytes)?; | ||
| let (mut dmg_file, temp_dmg_path) = copy_dmg_to_update_temp_file(dmg_path)?; | ||
| verify_dmg_file_size_and_sha256(&mut dmg_file, artifact.size, &artifact.sha256, dmg_path)?; | ||
| let temp_dmg_path = temp_dmg_path.to_string_lossy(); |
There was a problem hiding this comment.
Use the signed artifact size to fail fast before duplicating the DMG.
This path already has artifact.size, but it copies the whole image into /tmp before checking it. A wrong or maliciously oversized file can therefore consume temp disk space twice before the validation fails.
Suggested fix
fn update_from_verified_dmg_sidecar_(
dmg_path: &str,
metadata_bytes: &[u8],
signature_bytes: &[u8],
) -> ResultType<()> {
let artifact = verify_dmg_sidecar_metadata(dmg_path, metadata_bytes, signature_bytes)?;
+ let source_size = std::fs::metadata(dmg_path)?.len();
+ if source_size != artifact.size {
+ bail!(
+ "DMG size mismatch for {}: expected {}, got {}",
+ dmg_path,
+ artifact.size,
+ source_size
+ );
+ }
let (mut dmg_file, temp_dmg_path) = copy_dmg_to_update_temp_file(dmg_path)?;
verify_dmg_file_size_and_sha256(&mut dmg_file, artifact.size, &artifact.sha256, dmg_path)?;
let temp_dmg_path = temp_dmg_path.to_string_lossy();
update_from_mounted_dmg_with_options(&temp_dmg_path, true, None)
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/platform/macos.rs` around lines 1070 - 1073, The code currently calls
copy_dmg_to_update_temp_file(dmg_path) before verifying artifact.size, which can
duplicate a large/malicious DMG into temp; instead, perform the size/hash check
against the original file first using the signed metadata. Reorder to call
verify_dmg_file_size_and_sha256 (or add/overload a helper that accepts a file
path) on dmg_path using artifact.size and artifact.sha256 immediately after
verify_dmg_sidecar_metadata, and only if that passes call
copy_dmg_to_update_temp_file(dmg_path) to create the temp copy; update function
signatures as needed (e.g., make verify_dmg_file_size_and_sha256 accept &Path or
&str rather than &mut File) and adjust callers verify_dmg_sidecar_metadata,
copy_dmg_to_update_temp_file, and the surrounding logic to reflect the new
order.
| let mut response = client.get(download_url).send()?; | ||
| if !response.status().is_success() { | ||
| bail!( | ||
| "Failed to download the new version file: {}", | ||
| response.status() | ||
| ); | ||
| } | ||
| write_verified_update_artifact(file_path, &mut response, expected_size, expected_sha256)?; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate file and surrounding code
ls -la src/updater.rs || true
rg -n "UPDATE_HTTP_REQUEST_TIMEOUT" src/updater.rs
rg -n "timeout\\(" src/updater.rs
rg -n "download_url" src/updater.rs
rg -n "write_verified_update_artifact" src/updater.rs
# Show relevant sections around the cited lines
sed -n '440,540p' src/updater.rs | nl -ba | sed -n '1,120p'
sed -n '540,700p' src/updater.rs | nl -ba | sed -n '1,200p'
# Extract implementation of write_verified_update_artifact if present
# Use rg to find its definition line numbers.
line=$(rg -n "fn write_verified_update_artifact" src/updater.rs | head -n1 | cut -d: -f1 || true)
if [ -n "${line:-}" ]; then
start=$((line-30))
end=$((line+220))
sed -n "${start},${end}p" src/updater.rs | nl -ba | sed -n "1,260p"
else
echo "write_verified_update_artifact not defined in src/updater.rs (or different signature)."
# Search for it in repository
rg -n "write_verified_update_artifact\\b" -S .
fiRepository: fufesou/rustdesk
Length of output: 2539
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate file and surrounding code
ls -la src/updater.rs || true
rg -n "UPDATE_HTTP_REQUEST_TIMEOUT" src/updater.rs
rg -n "timeout\\(" src/updater.rs
rg -n "download_url" src/updater.rs
rg -n "write_verified_update_artifact" src/updater.rs
# Show relevant sections around the cited lines
sed -n '440,540p' src/updater.rs | nl -ba | sed -n '1,120p'
sed -n '540,700p' src/updater.rs | nl -ba | sed -n '1,200p'
# Extract implementation of write_verified_update_artifact if present
line=$(rg -n "fn write_verified_update_artifact" src/updater.rs | head -n1 | cut -d: -f1 || true)
if [ -n "${line:-}" ]; then
start=$((line-30))
end=$((line+260))
sed -n "${start},${end}p" src/updater.rs | nl -ba | sed -n "1,300p"
else
echo "write_verified_update_artifact not defined in src/updater.rs (or different signature)."
rg -n "write_verified_update_artifact\\b" -S .
fiRepository: fufesou/rustdesk
Length of output: 2539
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ls -la src/updater.rs || true
rg -n "UPDATE_HTTP_REQUEST_TIMEOUT" src/updater.rs
rg -n "timeout\\(" src/updater.rs
rg -n "download_url" src/updater.rs
rg -n "write_verified_update_artifact" src/updater.rs
echo "----- snippet 440-540 -----"
sed -n '440,540p' src/updater.rs | nl -ba | sed -n '1,120p'
echo "----- snippet 540-700 -----"
sed -n '540,700p' src/updater.rs | nl -ba | sed -n '1,200p'
line=$(rg -n "fn write_verified_update_artifact" src/updater.rs | head -n1 | cut -d: -f1 || true)
if [ -n "${line:-}" ]; then
start=$((line-30))
end=$((line+260))
echo "----- write_verified_update_artifact around line $line -----"
sed -n "${start},${end}p" src/updater.rs | nl -ba | sed -n "1,300p"
else
echo "write_verified_update_artifact not defined in src/updater.rs; searching entire repo..."
rg -n "write_verified_update_artifact\\b" -S .
fiRepository: fufesou/rustdesk
Length of output: 2567
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ls -la src/updater.rs || true
rg -n "UPDATE_HTTP_REQUEST_TIMEOUT" src/updater.rs
rg -n "timeout\\(" src/updater.rs
rg -n "download_url" src/updater.rs
rg -n "write_verified_update_artifact" src/updater.rs
echo "----- snippet 440-540 -----"
sed -n '440,540p' src/updater.rs | nl -ba | sed -n '1,120p'
echo "----- snippet 540-700 -----"
sed -n '540,700p' src/updater.rs | nl -ba | sed -n '1,200p'
line=$(rg -n "fn write_verified_update_artifact" src/updater.rs | head -n1 | cut -d: -f1 || true)
if [ -n "${line:-}" ]; then
start=$((line-30))
end=$((line+260))
echo "----- write_verified_update_artifact around line $line -----"
sed -n "${start},${end}p" src/updater.rs | nl -ba | sed -n "1,300p"
else
echo "write_verified_update_artifact not defined in src/updater.rs; searching entire repo..."
rg -n "write_verified_update_artifact\\b" -S .
fiRepository: fufesou/rustdesk
Length of output: 2567
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "UPDATE_HTTP_REQUEST_TIMEOUT" src/updater.rs
rg -n "timeout\\(" src/updater.rs
rg -n "client\\.get\\(download_url\\)" -n src/updater.rs || true
rg -n "write_verified_update_artifact" src/updater.rs
sed -n '470,520p' src/updater.rs | nl -ba
# show definition of write_verified_update_artifact (first ~250 lines around it)
line=$(rg -n "fn write_verified_update_artifact" src/updater.rs | head -n1 | cut -d: -f1 || true)
if [ -n "${line:-}" ]; then
start=$((line-20)); end=$((line+250))
sed -n "${start},${end}p" src/updater.rs | nl -ba | sed -n '1,280p'
fiRepository: fufesou/rustdesk
Length of output: 767
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find create_http_client_with_url definition and inspect its timeout config
line=$(rg -n "fn create_http_client_with_url\\b" src/updater.rs | head -n1 | cut -d: -f1 || true)
echo "create_http_client_with_url at line: ${line:-<not found>}"
if [ -n "${line:-}" ]; then
start=$((line-30)); end=$((line+160))
sed -n "${start},${end}p" src/updater.rs | cat -n
fi
# Inspect the exact callsite block around 461-500
echo "----- callsite around line 461-510 -----"
sed -n '440,520p' src/updater.rs | cat -n
# Inspect write_verified_update_artifact to see if it adds any timeout
line2=$(rg -n "fn write_verified_update_artifact\\b" src/updater.rs | head -n1 | cut -d: -f1 || true)
echo "write_verified_update_artifact at line: ${line2:-<not found>}"
if [ -n "${line2:-}" ]; then
start=$((line2-20)); end=$((line2+140))
sed -n "${start},${end}p" src/updater.rs | cat -n
fiRepository: fufesou/rustdesk
Length of output: 9784
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find where create_http_client_with_url is defined
rg -n "fn create_http_client_with_url\\b|create_http_client_with_url\\b" -S . > /tmp/create_http_client_hits.txt || true
head -n 50 /tmp/create_http_client_hits.txt
echo "---- total hits ----"
wc -l /tmp/create_http_client_hits.txt
# Print the definition(s)
rg -n "fn create_http_client_with_url\\b" -S . || true
# If found, print surrounding code for each definition
defs=$(rg -n "fn create_http_client_with_url\\b" -S . | cut -d: -f1 | sort -u)
for f in $defs; do
echo "===== $f ====="
l=$(rg -n "fn create_http_client_with_url\\b" "$f" | head -n1 | cut -d: -f1)
start=$((l-30)); end=$((l+120))
sed -n "${start},${end}p" "$f" | cat -n
done
# Also check if UPDATE_HTTP_REQUEST_TIMEOUT is used in any client builder elsewhere
rg -n "UPDATE_HTTP_REQUEST_TIMEOUT" -S . || trueRepository: fufesou/rustdesk
Length of output: 7721
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find create_http_client (sync) implementation and the configure_http_client macro usage
rg -n "fn create_http_client\\b" -S src/hbbs_http/http_client.rs src/hbbs_http -S || true
rg -n "configure_http_client!" -S src/hbbs_http/http_client.rs src/hbbs_http -S || true
# Print around create_http_client definition
file="src/hbbs_http/http_client.rs"
line=$(rg -n "fn create_http_client\\b" "$file" | head -n1 | cut -d: -f1 || true)
echo "create_http_client line: ${line:-<not found>}"
if [ -n "${line:-}" ]; then
start=$((line-60)); end=$((line+180))
sed -n "${start},${end}p" "$file" | cat -n
fi
# Locate macro definition of configure_http_client
macro_file=$(rg -n "macro_rules!\\s*configure_http_client" -S src | head -n1 | cut -d: -f1 || true)
echo "macro_rules! configure_http_client in: ${macro_file:-<not found>}"
if [ -n "${macro_file:-}" ]; then
line2=$(rg -n "macro_rules!\\s*configure_http_client" "$macro_file" | head -n1 | cut -d: -f1)
start=$((line2-20)); end=$((line2+260))
sed -n "${start},${end}p" "$macro_file" | cat -n
fi
# If macro is in another crate, search workspace root
macro_file2=$(rg -n "configure_http_client" -S src | head -n20 || true)
echo "Top configure_http_client occurrences:"
echo "$macro_file2"Repository: fufesou/rustdesk
Length of output: 12907
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="src/hbbs_http/http_client.rs"
echo "--- rg timeout* in $file ---"
rg -n "timeout\\b|connect_timeout\\b|read_timeout\\b|write_timeout\\b" "$file" || true
macro_line=$(rg -n "macro_rules!\\s*configure_http_client\\b" "$file" | head -n1 | cut -d: -f1 || true)
echo "macro_line=$macro_line"
if [ -n "${macro_line:-}" ]; then
start=$((macro_line-30)); if [ "$start" -lt 1 ]; then start=1; fi
end=$((macro_line+220))
echo "--- configure_http_client macro around lines $start..$end ---"
sed -n "${start},${end}p" "$file" | cat -n
fiRepository: fufesou/rustdesk
Length of output: 10521
Add request timeout to the artifact download request
ensure_verified_update_artifact() downloads the main update file with client.get(download_url).send()? but only the sidecar path applies UPDATE_HTTP_REQUEST_TIMEOUT (via .timeout(...)). The shared HTTP client builder configures TLS/proxy only and does not set a default request timeout, so a stalled artifact transfer can hang the updater thread.
Suggested fix
- let mut response = client.get(download_url).send()?;
+ let mut response = client
+ .get(download_url)
+ .timeout(UPDATE_HTTP_REQUEST_TIMEOUT)
+ .send()?;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let mut response = client.get(download_url).send()?; | |
| if !response.status().is_success() { | |
| bail!( | |
| "Failed to download the new version file: {}", | |
| response.status() | |
| ); | |
| } | |
| write_verified_update_artifact(file_path, &mut response, expected_size, expected_sha256)?; | |
| let mut response = client | |
| .get(download_url) | |
| .timeout(UPDATE_HTTP_REQUEST_TIMEOUT) | |
| .send()?; | |
| if !response.status().is_success() { | |
| bail!( | |
| "Failed to download the new version file: {}", | |
| response.status() | |
| ); | |
| } | |
| write_verified_update_artifact(file_path, &mut response, expected_size, expected_sha256)?; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/updater.rs` around lines 489 - 496, The download request uses
client.get(download_url).send()? with no timeout, so a stalled transfer can
hang; update the GET call in ensure_verified_update_artifact (the code that
creates response from client.get(download_url)) to apply the same
UPDATE_HTTP_REQUEST_TIMEOUT used elsewhere by calling
.timeout(Duration::from_secs(UPDATE_HTTP_REQUEST_TIMEOUT)) (or the existing
constant) before .send(), so the request will error on timeout and the rest of
the flow (write_verified_update_artifact, error handling) remains unchanged;
reference the variables/client: client, download_url, and the response handling
that calls write_verified_update_artifact.
Summary by CodeRabbit
Release Notes
New Features
Improvements