Repository navigation
Harden install records and downloads (review 2026-10, D1-D7) - #5
Merged
Merged
Conversation
- D1: every manifest path is canonicalised and contained: Files and backup originals must stay inside the game root, owned folders inside the install folder, backups inside the backup folder, no junctions or symlinks on the way, proxy must be a known proxy name. Anything else refuses the whole operation. - D2: entries are rebased onto the current game root; a narrower root that can't reach them refuses instead of misplacing the originals. - D3: an unreadable manifest stops installs/uninstalls instead of starting a fresh record; new backups never overwrite an existing file; manifest and ini writes are flushed to disk before the rename. - D4: MFG Unlock only accepts its exact asset name; GitHub assets without a published SHA-256 digest are refused; a Seaglass launch reinstalls the cached OptiScaler/MFG versions the game already has instead of upgrading them. - D5: restore leaves a DLSS/Streamline file the game updated since in place. - D6: only a 404 means "this DLSS version has no such file"; rate limits and server errors fail instead of skipping files or falling back to main. - D7: the GitHub token is stored DPAPI-encrypted; plain tokens are migrated.
- Install records may point into the game root OR the chosen install folder, so installs into a folder picked outside the game work again and records written by 1.5.1 for such folders still uninstall and restore. Any relative RootRel and ".." entries are accepted once the resolved path lands in one of those two folders; junction, backup-folder and proxy checks are unchanged. - Uninstall saves the record after every file deleted, folder removed and original restored, so a retry after a failure part-way (a locked dll) carries on where it stopped instead of deleting originals already put back.
- Install records now store each entry relative to the folder it belongs to: the game root as before, or the install folder behind an explicit "|install|\" tag. Both round-trip across drive letters, subst drives and UNC shares, where a path relative to the game root can't be expressed. - Backups of install-folder files go under backup\_install instead of being joined onto a full path, which left the displaced original beside itself as "<name>.N" (and on a share could land in the wrong place). - Records written by 0ed78f7 for such folders (full-path entries, full RootRel, "<original>.N" backups) are read, contained and rewritten, so their displaced originals restore through Uninstall and Restore DLSS. - Containment is unchanged: every entry must resolve inside the game or install folder, owned folders inside the install folder, backups inside the backup folder (or the legacy "<original>.N" beside the original), no links on the way, no streams or device paths. - The add-on's reverted-DLSS check resolves entries the same way.
ApolloF
marked this pull request as ready for review
October 4, 2026 17:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the DLSS Updater findings from the 2026-10-04 correctness/security review (
Notes/claude/reviews/syncer-dlssupdater.md, findings D1 to D7). It doesn't overlap #4.Finding → fix → test
InstallManifest.Contain(target, gameRoot)runs before every install, uninstall and restore:•
FilesandBackups.Originalmust resolve inside the game root.•
Dirsmust be strictly inside the install folder, so a recursive delete never leaves it.•
Backups.Backupmust be inside.dlssupdater\backup.• Rooted paths,
:, and..in entries are rejected;RootRelmay only be./..parts.• No junction or symlink between the game root and any entry, and none on the
.dlssupdater\backuppath.•
Proxy(from the manifest or the options) must be a known proxy name.• Any violation throws
UnsafeManifestExceptionand nothing changes.Ctx.Fullalso refuses paths outside the root, as defence in depth.CraftedManifest_UninstallRefuses_AndTouchesNothingOutside(7 cases: dir.., absolute dir, dir outside the install folder,RootRelup, file.., backup original.., backup escaping the backup folder)CraftedManifest_LaunchStyleReinstall_RefusesProxyOutsideTheGameManifestPathThroughJunction_IsRefusedRootRelis updated. If the root is narrower and can't reach existing entries, the install is refused instead of misplacing them.SameFolderFromNarrowerRoot_IsRefused_OriginalsStayRestorableSameFolderFromWiderRoot_RebasesRecord_RestoreReturnsOriginalsInstallManifest.ReadthrowsInvalidDataExceptionwhen the file exists but can't be read; the installer uses it.Loadkeeps returning null for display code. New backups get a free name and never overwrite a file in the backup folder.AtomicWriteTextflushes to disk before the rename.UnreadableManifest_InstallRefuses_BackupsSurviveLostManifest_NewInstallNeverOverwritesSavedOriginals(the flush has no test)
renodx-mfgunlock.addon64asset (no "any.addon64" fallback).• GitHub assets with no published digest are refused. All current OptiScaler, MFG and Streamline releases have digests, checked via the API.
•
beforeLaunchusesUpdateOptions(m).KeepingInstalledVersions(m): it reinstalls the cachedOptiTag/MfgTag, and leaves a component alone if that version is no longer cached. Moving to a new release takes a click in the GUI.Mfg_AnyOtherAddonInTheRelease_IsNotAcceptedMfg_AssetWithoutDigest_IsRefusedLaunchReinstall_KeepsInstalledOptiAndMfgVersionsRestoreskips a file whose hash isn't the recordedInstalledSha(the game owns it now) and drops the stale backup.RestoreDlss_KeepsAFileTheGameUpdatedSinceGitHubClient.ExistsAsyncreturns false only on a 404 and throws on anything else. A rate limit no longer skips files or falls back tomain. Themainfallback is kept for a genuine 404 on the latest tag.Dlss_PinnedVersion_RateLimitedCheckFails_InsteadOfSkippingFilesDlss_Latest_RateLimitedCheck_NeverFallsBackToMainGitHubTokenProtected. A legacy plainGitHubTokenis read once and rewritten encrypted.GitHubToken_IsSavedEncrypted_AndPlainTextIsMigratedTest seam:
GitHubClienthas an internal constructor that takes anHttpMessageHandler, so the store tests use a fake GitHub and nothing leaves the machine.Fails before, passes after
origin/main, with only the new exception types loosened. All 15 fail there.Not done (needs the owner)
nvngx_*, crosire for the ReShade setup). I couldn't check real signed files offline, so a wrong check could block every DLSS install.nvapi64.dllfrom System32 only; I didn't touch NvAPI code.Follow-up fixes (0ed78f7)
..in entries and any relativeRootRelare accepted once the resolved path lands in one of those two folders.Ctx.Fullfollows the same rule. The junction, backup-folder, owned-folder and proxy checks are unchanged. Tests:InstallFolderOutsideGame_InstallUpdateUninstall_RoundTripandInstallFolderOutsideGame_RecordWrittenByEarlierVersion_StillUninstalls.Uninstall_FailsPartWay_RetryKeepsOriginalsAlreadyRestored.dotnet build -warnaserror: 0 warnings, 0 errors.dotnet test: 79 passed.Gate
dotnet build -warnaserror: 0 warnings, 0 errors. Main was already at 0.dotnet testwith integration tests off: 76 passed (56 existing + 20 new).