Skip to content

feat(launching): record and revalidate launch receipts - #341

Open
bobtista wants to merge 7 commits into
developmentfrom
feat/launch-receipt
Open

feat(launching): record and revalidate launch receipts#341
bobtista wants to merge 7 commits into
developmentfrom
feat/launch-receipt

Conversation

@bobtista

@bobtista bobtista commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Record what each successful launch consisted of and cheaply compare subsequent launches against that receipt so configuration and filesystem drift is visible.

Changes

  • Write a versioned JSON receipt into each workspace after a successful launch.
  • Record executable identity, manifest versions, retail archive roots, per-archive size and timestamp fingerprints, the GenHub-built child environment, and resolved variant identity.
  • Hash the executable when recording while using inexpensive existence, size, and timestamp checks during routine revalidation.
  • Compare the previous receipt with both current filesystem state and the upcoming launch configuration.
  • Return drift on the launch result and surface it as a capped informational notification without changing successful-launch presentation.
  • Keep receipt-writing failures non-fatal and avoid recording inherited environment variables that may contain secrets.

Testing

  • dotnet test GenHub/GenHub.sln -c Release — 1,476 tests passed.

Risks and rollback

Routine revalidation deliberately avoids content hashing, so a same-size replacement with a deliberately preserved timestamp is not detected. Receipt persistence is best-effort and does not fail an otherwise successful launch. Reverting this PR removes receipt recording and drift notifications.

Related issues

Fixes #323

Greptile Summary

This PR adds versioned launch receipts and drift reporting.

  • Records executable, manifest, archive-root, environment, and variant fingerprints after successful launches.
  • Revalidates previous receipts against filesystem state and upcoming launch configuration.
  • Surfaces capped informational drift notifications while keeping receipt failures non-fatal.

Confidence Score: 4/5

This PR should not merge until launch receipts stop exposing an offline verification oracle for profile environment secrets.

Profile-defined environment values are transformed into HMACs, but the receipt serializes the HMAC key alongside those hashes, allowing anyone who obtains the file to test likely values and recover low-entropy credentials.

Files Needing Attention: GenHub/GenHub/Features/Launching/LaunchReceiptService.cs; GenHub/GenHub.Core/Models/Launching/LaunchReceipt.cs

Security Review

The revised environment hashing still permits offline recovery of low-entropy secrets because each receipt stores both the keyed hashes and the key required to verify guesses.

Important Files Changed

Filename Overview
GenHub/GenHub/Features/Launching/LaunchReceiptService.cs Implements receipt persistence and drift comparison, but persists environment-value HMACs with the key needed for offline guessing.
GenHub/GenHub/Features/Launching/GameLauncher.cs Integrates pre-launch receipt revalidation, configuration comparison, post-start recording, and drift propagation into the launch result.
GenHub/GenHub.Core/Models/Launching/LaunchReceipt.cs Defines the versioned receipt schema, including environment hashes and their co-located salt.
GenHub/GenHub/Features/GameProfiles/ViewModels/GameProfileLauncherViewModel.cs Adds capped informational notifications for receipt drift without changing successful-launch presentation.
GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/LaunchReceiptServiceTests.cs Adds broad receipt and drift coverage, including plaintext-secret exclusion, but does not address offline guessing with the persisted key.

Sequence Diagram

sequenceDiagram
    participant UI
    participant Launcher as GameLauncher
    participant Receipt as LaunchReceiptService
    participant Workspace
    participant Process
    UI->>Launcher: Launch profile
    Launcher->>Receipt: Revalidate previous receipt
    Receipt-->>Launcher: Filesystem drift + previous receipt
    Launcher->>Workspace: Prepare workspace
    Workspace-->>Launcher: WorkspaceInfo
    Launcher->>Receipt: Compare upcoming configuration
    Receipt-->>Launcher: Configuration drift
    Launcher->>Process: Start game
    Process-->>Launcher: ProcessInfo
    Launcher->>Receipt: Record successful launch
    Launcher-->>UI: LaunchInfo + drift warnings
Loading
Prompt To Fix All With AI
### Issue 1
GenHub/GenHub/Features/Launching/LaunchReceiptService.cs:69-72
**Receipt hashes remain guessable**

When a profile supplies a low-entropy secret as an environment value, the receipt stores its HMAC alongside the key required to calculate candidate hashes, allowing anyone who obtains the receipt to test likely values offline and recover the secret. **How this was verified:** Profile environment values flow into the persisted receipt, which serializes both each value's HMAC and `EnvironmentHashSalt`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (6): Last reviewed commit: "fix(launching): salt receipt environment..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Context used:

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added launch receipts that record executable, workspace, manifest, variant, archive, and environment details.
    • Added lightweight workspace checks to detect configuration changes between launches.
    • Launches now show informational notices when receipt drift is detected.
    • Receipt or validation failures no longer prevent an otherwise successful launch.
  • Bug Fixes

    • Improved handling of missing, corrupt, or incomplete launch receipt data.

Walkthrough

The PR adds launch receipts that capture launch configuration and lightweight workspace fingerprints. It revalidates previous receipts, reports configuration drift, records new receipts after startup, and displays informational drift notices without blocking successful launches.

Changes

Launch receipt tracking

Layer / File(s) Summary
Receipt contracts and models
GenHub/GenHub.Core/Constants/FileTypes.cs, GenHub/GenHub.Core/Interfaces/Launching/*, GenHub/GenHub.Core/Models/Launching/*, GenHub/GenHub.Core/Models/GameProfile/GameLaunchInfo.cs
Defines receipt data, launch context, executable and archive fingerprints, variant identity, drift reports, the receipt file name, and stored drift warnings.
Receipt persistence and drift detection
GenHub/GenHub/Features/Launching/LaunchReceiptService.cs
Writes JSON receipts, fingerprints files, hashes environment values, revalidates workspace state, compares upcoming configuration, and handles malformed or missing data.
Launcher integration and service registration
GenHub/GenHub/Features/Launching/GameLauncher.cs, GenHub/GenHub/Infrastructure/DependencyInjection/GameLaunchingModule.cs
Revalidates and compares receipts before launch, records a receipt after process startup, preserves launch success when receipt operations fail, and registers the service.
Receipt service validation
GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/LaunchReceiptServiceTests.cs
Tests receipt creation, replacement, filesystem drift, configuration drift, environment and variant changes, malformed receipts, and failure handling.
Launch warnings and notification validation
GenHub/GenHub/Features/GameProfiles/ViewModels/GameProfileLauncherViewModel.cs, GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs, GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/ViewModels/GameProfileLauncherViewModelTests.cs
Propagates drift warnings and displays capped informational notices while retaining successful launch status. Tests cover warning, comparison, recording, and notification behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GameLauncher
  participant LaunchReceiptService
  participant Workspace
  participant GameProfileLauncherViewModel

  GameLauncher->>LaunchReceiptService: Revalidate previous receipt
  LaunchReceiptService->>Workspace: Check receipt, executable, and archives
  Workspace-->>LaunchReceiptService: Return current fingerprints
  LaunchReceiptService-->>GameLauncher: Return drift warnings
  GameLauncher->>GameLauncher: Start game process
  GameLauncher->>LaunchReceiptService: Record new launch receipt
  GameLauncher-->>GameProfileLauncherViewModel: Return successful launch with warnings
  GameProfileLauncherViewModel-->>GameProfileLauncherViewModel: Show informational drift notice
Loading

Possibly related PRs

Suggested labels: Enhancement, Testing

Poem

A rabbit records each launch with care,
Fingerprints hop through folders bare.
Drift may whisper, “Things have changed,”
Yet successful starts remain arranged.
A receipt rests where workspaces grow.
“Nibble, test, and onward go!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR meets the receipt, revalidation, drift detection, and surfacing goals, but best-effort writes conflict with the requirement that every launch produces a receipt [#323]. Ensure every launch produces a receipt, or update issue #323 to explicitly allow best-effort persistence when receipt writes fail.
✅ Passed checks (4 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed All production changes support launch receipt recording, revalidation, drift reporting, or required integration, with no unrelated scope identified.
Docstring Coverage ✅ Passed Docstring coverage is 95.83% which is sufficient. The required threshold is 50.00%.
Title check ✅ Passed The title follows the conventional commit format and accurately describes launch receipt recording and revalidation.
Description check ✅ Passed The description clearly explains the launch receipt, drift detection, notification, failure handling, testing, and security considerations.
📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #323

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/launch-receipt

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread GenHub/GenHub/Features/Launching/LaunchReceiptService.cs
Comment thread GenHub/GenHub/Features/Launching/LaunchReceiptService.cs Outdated
@kilo-code-bot

kilo-code-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 1 Issue Found (Already Reported) | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 0
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
GenHub/GenHub/Features/Launching/LaunchReceiptService.cs 68 Environment secrets leak into receipts and notifications (already reported by greptile-apps[bot])
Files Reviewed (15 files)
  • GenHub/GenHub.Core/Constants/FileTypes.cs
  • GenHub/GenHub.Core/Interfaces/Launching/ILaunchReceiptService.cs
  • GenHub/GenHub.Core/Models/GameProfile/GameLaunchInfo.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceipt.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptArchiveEntry.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptArchiveRoot.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptContext.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptDriftReport.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptExecutable.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptVariant.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/ViewModels/GameProfileLauncherViewModelTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/LaunchReceiptServiceTests.cs
  • GenHub/GenHub/Features/GameProfiles/ViewModels/GameProfileLauncherViewModel.cs
  • GenHub/GenHub/Features/Launching/GameLauncher.cs
  • GenHub/GenHub/Features/Launching/LaunchReceiptService.cs
  • GenHub/GenHub/Infrastructure/DependencyInjection/GameLaunchingModule.cs

Notes

  • The CRITICAL security issue regarding environment variable persistence in receipts and drift notifications has already been reported by greptile-apps[bot] on line 68 of LaunchReceiptService.cs
  • No new issues were found beyond those already reported
  • The code is otherwise well-structured with comprehensive test coverage
  • Fix link: Fix these issues in Kilo Cloud

@kilo-code-bot

kilo-code-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Resolved in this revision
  • Previous SUGGESTION (null recorded Executable silently skipped executable drift) is fixed: CompareUpcomingLaunch now compares receipt.Executable?.Path ?? string.Empty against the upcoming path and reports Executable path changed from (none) to {path} when the recorded executable is missing, matching CompareArchiveRootConfiguration's newly configured handling.
  • Environment-value hashes are now keyed with a per-receipt HMAC-SHA256 salt (EnvironmentHashSalt, 128-bit CSPRNG), preventing precomputed-table recovery and cross-receipt correlation while keeping drift comparison exact (it rehashes with the receipt's own salt).
  • Post-spawn receipt recording can no longer fail an already-started launch: RecordLaunchReceiptAsync now passes CancellationToken.None and wraps the call in a try/catch, with LaunchProfileAsync_WhenReceiptRecordingThrows_StillSucceeds covering IOException and OperationCanceledException.
Files Reviewed (5 files)
  • GenHub/GenHub.Core/Models/Launching/LaunchReceipt.cs
  • GenHub/GenHub/Features/Launching/LaunchReceiptService.cs
  • GenHub/GenHub/Features/Launching/GameLauncher.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/LaunchReceiptServiceTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs
Previous Review Summaries (4 snapshots, latest commit 3b7c430)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 3b7c430)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
GenHub/GenHub/Features/Launching/LaunchReceiptService.cs 189 Null recorded Executable silently skips executable drift instead of reporting it (inconsistent with CompareArchiveRootConfiguration)
Resolved in this revision
  • Previous WARNING (null EnvironmentVariableHashes dereference aborting the launch) is now fixed: the comparison path null-coalesces each receipt collection (?? []) and CompareUpcomingLaunch is wrapped in a try/catch that degrades to a drift line, matching the RevalidateAsync guarantee.
Files Reviewed (2 files)
  • GenHub/GenHub/Features/Launching/LaunchReceiptService.cs - 1 issue
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/LaunchReceiptServiceTests.cs

Fix these issues in Kilo Cloud

Previous review (commit ba47c9d)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
GenHub/GenHub/Features/Launching/LaunchReceiptService.cs 327 Null EnvironmentVariableHashes dereference in CompareEnvironment still aborts the launch for corrupt receipts the new RevalidateAsync guard tolerates
Files Reviewed (3 files)
  • GenHub/GenHub.Core/Models/Launching/LaunchReceipt.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/LaunchReceiptServiceTests.cs
  • GenHub/GenHub/Features/Launching/LaunchReceiptService.cs - 1 issue

Fix these issues in Kilo Cloud

Previous review (commit 970d237)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
GenHub/GenHub/Features/Launching/LaunchReceiptService.cs 68 Environment secrets leak into receipts, drift logs, and UI notifications (already reported by greptile-apps[bot])

WARNING

File Line Issue
GenHub/GenHub/Features/Launching/LaunchReceiptService.cs 128 Unguarded receipt-field access after the parse can abort a launch that must never block on receipts
GenHub/GenHub/Features/Launching/LaunchReceiptService.cs 190 PathsEqual ignores Windows path semantics, causing false drift reports (already reported by greptile-apps[bot])
Files Reviewed (17 files)
  • GenHub/GenHub.Core/Constants/FileTypes.cs
  • GenHub/GenHub.Core/Interfaces/Launching/ILaunchReceiptService.cs
  • GenHub/GenHub.Core/Models/GameProfile/GameLaunchInfo.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceipt.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptArchiveEntry.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptArchiveRoot.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptContext.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptDriftReport.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptExecutable.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptVariant.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/ViewModels/GameProfileLauncherViewModelTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/LaunchReceiptServiceTests.cs
  • GenHub/GenHub/Features/GameProfiles/ViewModels/GameProfileLauncherViewModel.cs
  • GenHub/GenHub/Features/Launching/GameLauncher.cs
  • GenHub/GenHub/Features/Launching/LaunchReceiptService.cs - 3 issues
  • GenHub/GenHub/Infrastructure/DependencyInjection/GameLaunchingModule.cs

Fix these issues in Kilo Cloud

Previous review (commit 070d578)

Status: 1 Issue Found (Already Reported) | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 0
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
GenHub/GenHub/Features/Launching/LaunchReceiptService.cs 68 Environment secrets leak into receipts and notifications (already reported by greptile-apps[bot])
Files Reviewed (16 files)
  • GenHub/GenHub.Core/Constants/FileTypes.cs
  • GenHub/GenHub.Core/Interfaces/Launching/ILaunchReceiptService.cs
  • GenHub/GenHub.Core/Models/GameProfile/GameLaunchInfo.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceipt.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptArchiveEntry.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptArchiveRoot.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptContext.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptDriftReport.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptExecutable.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptVariant.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/ViewModels/GameProfileLauncherViewModelTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/LaunchReceiptServiceTests.cs
  • GenHub/GenHub/Features/GameProfiles/ViewModels/GameProfileLauncherViewModel.cs
  • GenHub/GenHub/Features/Launching/GameLauncher.cs
  • GenHub/GenHub/Features/Launching/LaunchReceiptService.cs
  • GenHub/GenHub/Infrastructure/DependencyInjection/GameLaunchingModule.cs

Notes

  • The CRITICAL security issue regarding environment variable persistence in receipts and drift notifications has already been reported by greptile-apps[bot] on line 68 of LaunchReceiptService.cs
  • No new issues were found beyond those already reported
  • The code is otherwise well-structured with comprehensive test coverage
  • Fix link: Fix these issues in Kilo Cloud

Reviewed by glm-5.2 · Input: 53.1K · Output: 12.6K · Cached: 294.7K

@bobtista
bobtista force-pushed the feat/launch-receipt branch from 070d578 to 970d237 Compare August 3, 2026 12:43
@bobtista
bobtista changed the base branch from feat/native-launch to development August 3, 2026 12:44
@bobtista

bobtista commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto development and retargeted from feat/native-launch.

feat/native-launch was squash-merged as #332, so this PR was stacked on a branch that no longer exists in development's history. Retargeting alone would have produced a diff of 63 files, +2427/-3615 — one that deletes the work merged since #331 (#332, #337, #338, #339, #348, #349). That is the squash-orphaning failure #327 describes.

Instead the four commits unique to this branch were replayed onto development with git rebase --onto origin/development d1adeeb. No conflicts. The diff is now 17 files, +1961/-2 — this branch's own work and nothing else.

The rebase applied cleanly but the test suite has not been run against the rebased branch, so CI here is the first verification. #340 received the equivalent rebase and passed 1,553 tests.

No approvals existed, so nothing was dismissed by the force-push.

}

report.Receipt = receipt;
CompareExecutable(receipt.Executable, report);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Unguarded receipt-field access can abort a launch that the documented invariant says must never block on receipts.

The JSON parse immediately above is wrapped defensively, but CompareExecutable(receipt.Executable, report) and the foreach (... in receipt.ArchiveRoots) below it are not. A corrupt or tampered receipt that still parses successfully — e.g. "Executable": null, "ArchiveRoots": null, or a null archive-root entry — produces an unhandled NullReferenceException here; an uncaught SecurityException or transient IO error from FileInfo inside CompareExecutable has the same effect. RevalidateAsync is awaited on the launch path (RevalidateLaunchReceiptAsync -> LaunchProfileAsync), so the exception propagates and fails the launch, contradicting the stated guarantee that receipt and drift issues never block a launch. Wrap this block in the same try/catch used for the parse, or null-guard the receipt fields before use.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 10, 2026
receipt.ManifestVersions[manifestId] = version;
}

foreach (var (variableName, value) in context.EnvironmentVariables)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Deterministic hashes expose secrets

When a profile supplies a low-entropy credential as an environment variable, RecordLaunchAsync stores its unsalted SHA-256 digest in the workspace receipt, allowing anyone who reads the receipt to recover the credential by hashing likely values offline. How this was verified: Profile environment values flow directly into the receipt context and are transformed with unkeyed, unsalted SHA-256 before being written to disk.

Prompt To Fix With AI
This is a comment left during a code review.
Path: GenHub/GenHub/Features/Launching/LaunchReceiptService.cs
Line: 68

Comment:
**Deterministic hashes expose secrets**

When a profile supplies a low-entropy credential as an environment variable, `RecordLaunchAsync` stores its unsalted SHA-256 digest in the workspace receipt, allowing anyone who reads the receipt to recover the credential by hashing likely values offline. **How this was verified:** Profile environment values flow directly into the receipt context and are transformed with unkeyed, unsalted SHA-256 before being written to disk.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

// Names, never values. These lines reach the log and the post-launch notice, both of
// which travel further than the machine that produced them, and a profile-defined
// variable can carry a credential. Which variable changed is the actionable part.
foreach (var (variableName, recordedHash) in receipt.EnvironmentVariableHashes)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Null EnvironmentVariableHashes dereference can still abort the launch.

This foreach dereferences receipt.EnvironmentVariableHashes with no null guard. A receipt that parses but deserializes with "EnvironmentVariableHashes": null (a tampered or corrupt file) reaches CompareEnvironment through CompareUpcomingLaunch on the launch path, where the iteration throws NullReferenceException; LaunchProfileAsync's catch-all then turns that into a launch failure. That is the same corrupt-receipt outcome the new RevalidateAsync guard (lines 136-159) and the RevalidateAsync_WithNullReceiptFields_ReportsDriftWithoutThrowing test were added to prevent, so the tolerance added on the read path is not extended to the comparison path and a null-field receipt still blocks a launch.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@coderabbitai coderabbitai Bot added Enhancement New feature or request Testing Topic related to (unit) tests labels Aug 10, 2026
report.DriftedFields.Add($"Game type changed from {receipt.GameType} to {upcoming.GameType}");
}

if (receipt.Executable is not null &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: A null recorded Executable silently skips executable drift rather than reporting it.

When receipt.Executable is null (a corrupt or tampered receipt that deserializes with "Executable": null) but upcoming.ExecutablePath is set, this if is false and no drift line is added. The sibling CompareArchiveRootConfiguration handles the analogous case by reporting "newly configured" when the recorded root is null but an upcoming root exists (lines 308-312). Note that the surrounding try/catch would otherwise have turned a null dereference into a generic "Receipt could not be compared" drift line, so this guard actually leaves the executable field as the one comparison that reports nothing for a null recorded value. For consistency, consider reporting the executable as newly/differently configured here too (e.g. Executable path changed from (none) to {upcoming.ExecutablePath}).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 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
`@GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/ViewModels/GameProfileLauncherViewModelTests.cs`:
- Around line 311-383: Add a test alongside
LaunchProfileCommand_WithReceiptDrift_ShowsInformationalNotice that supplies
more than MaxReceiptDriftNoticeLines receipt warnings, executes the launch, and
verifies the ShowInfo message contains no more than five drift-warning lines
while retaining the launch-configuration notice. Use the existing
GameLaunchInfo, IProfileLauncherFacade, and notification-service verification
patterns.

In
`@GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs`:
- Around line 1089-1126: Extend
LaunchProfileAsync_WhenReceiptRecordingFails_StillSucceeds with a thrown
receipt-recording failure by configuring RecordLaunchAsync to use ThrowsAsync,
such as an IOException or OperationCanceledException, instead of returning
OperationResult.CreateFailure. Execute the launch and assert the result still
succeeds, covering the exception-handling path in
GameLauncher.RecordLaunchReceiptAsync.

In
`@GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/LaunchReceiptServiceTests.cs`:
- Around line 565-577: Update the Dispose teardown in
GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/LaunchReceiptServiceTests.cs
lines 565-577 to catch Directory.Delete failures matching IOException or
UnauthorizedAccessException, preserving best-effort cleanup. Apply the same
filtered exception handling to the Dispose teardown around
Directory.Delete(_retailRoot, recursive: true) in
GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs
lines 1131-1143.

In `@GenHub/GenHub/Features/Launching/GameLauncher.cs`:
- Line 1397: The post-spawn receipt write must never turn an already-started
launch into a failure. In GenHub/GenHub/Features/Launching/GameLauncher.cs lines
1397-1397, call RecordLaunchReceiptAsync with CancellationToken.None and wrap
its body in a try/catch that logs failures and returns. In
GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs
lines 1089-1126, add coverage configuring RecordLaunchAsync to throw and assert
LaunchProfileAsync still reports success.

In `@GenHub/GenHub/Features/Launching/LaunchReceiptService.cs`:
- Around line 369-376: Update LaunchReceiptService so HashEnvironmentValue
accepts a salt and hashes the salt together with the environment value. Generate
a cryptographically random per-receipt salt in RecordLaunchAsync, store it in
the receipt’s EnvironmentHashSalt property, and pass it to every initial hash
call. Update CompareEnvironment to rehash current values with the recorded salt,
preserving empty-string fallback for legacy receipts, and add the corresponding
property to LaunchReceipt.
🪄 Autofix

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: 9fab1367-fda5-488d-9cf4-6e484663cf01

📥 Commits

Reviewing files that changed from the base of the PR and between b3f5c4a and 3b7c430.

📒 Files selected for processing (17)
  • GenHub/GenHub.Core/Constants/FileTypes.cs
  • GenHub/GenHub.Core/Interfaces/Launching/ILaunchReceiptService.cs
  • GenHub/GenHub.Core/Models/GameProfile/GameLaunchInfo.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceipt.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptArchiveEntry.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptArchiveRoot.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptContext.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptDriftReport.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptExecutable.cs
  • GenHub/GenHub.Core/Models/Launching/LaunchReceiptVariant.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/ViewModels/GameProfileLauncherViewModelTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/LaunchReceiptServiceTests.cs
  • GenHub/GenHub/Features/GameProfiles/ViewModels/GameProfileLauncherViewModel.cs
  • GenHub/GenHub/Features/Launching/GameLauncher.cs
  • GenHub/GenHub/Features/Launching/LaunchReceiptService.cs
  • GenHub/GenHub/Infrastructure/DependencyInjection/GameLaunchingModule.cs

Comment on lines +311 to +383
/// <summary>
/// Verifies that a successful launch carrying receipt drift shows an informational
/// notice naming the drift, while the success presentation stays unchanged.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
[Fact]
public async Task LaunchProfileCommand_WithReceiptDrift_ShowsInformationalNotice()
{
var notificationService = new Mock<INotificationService>();
var launcherFacade = new Mock<IProfileLauncherFacade>();
var launchInfo = new GameLaunchInfo
{
LaunchId = "launch-1",
ProfileId = "profile-1",
WorkspaceId = "profile-1",
ProcessInfo = new GameProcessInfo { ProcessId = 123 },
ReceiptDriftWarnings = ["Executable size changed from 1 to 2 bytes: generalszh"],
};
launcherFacade.Setup(x => x.LaunchProfileAsync("profile-1", It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(ProfileOperationResult<GameLaunchInfo>.CreateSuccess(launchInfo));

var vm = CreateLauncherViewModel(launcherFacade, notificationService);
var profileItem = CreateProfileItem("profile-1", "Test Profile");

await vm.LaunchProfileCommand.ExecuteAsync(profileItem);

Assert.Contains("launched successfully", vm.StatusMessage);
notificationService.Verify(
x => x.ShowSuccess("Game Launched", It.IsAny<string>(), It.IsAny<int?>(), It.IsAny<bool>()),
Times.Once);
notificationService.Verify(
x => x.ShowInfo(
"Launch Configuration Changed",
It.Is<string>(m =>
m.Contains("Launch configuration changed since the last run") &&
m.Contains("Executable size changed from 1 to 2 bytes")),
It.IsAny<int?>(),
It.IsAny<bool>()),
Times.Once);
notificationService.Verify(
x => x.ShowError(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int?>(), It.IsAny<bool>()),
Times.Never);
}

/// <summary>
/// Verifies that a successful launch without receipt drift shows no notice.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
[Fact]
public async Task LaunchProfileCommand_WithoutReceiptDrift_ShowsNoNotice()
{
var notificationService = new Mock<INotificationService>();
var launcherFacade = new Mock<IProfileLauncherFacade>();
var launchInfo = new GameLaunchInfo
{
LaunchId = "launch-1",
ProfileId = "profile-1",
WorkspaceId = "profile-1",
ProcessInfo = new GameProcessInfo { ProcessId = 123 },
};
launcherFacade.Setup(x => x.LaunchProfileAsync("profile-1", It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(ProfileOperationResult<GameLaunchInfo>.CreateSuccess(launchInfo));

var vm = CreateLauncherViewModel(launcherFacade, notificationService);
var profileItem = CreateProfileItem("profile-1", "Test Profile");

await vm.LaunchProfileCommand.ExecuteAsync(profileItem);

Assert.Contains("launched successfully", vm.StatusMessage);
notificationService.Verify(
x => x.ShowInfo(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int?>(), It.IsAny<bool>()),
Times.Never);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for the drift notice cap.

GameProfileLauncherViewModel defines MaxReceiptDriftNoticeLines = 5. The two new tests cover one drift warning and zero drift warnings. Neither test supplies more warnings than the cap.

The cap is a stated requirement of this change. Without a test, a regression that removes it produces an unbounded notification body and no test fails. Add a case with more than five warnings and assert the rendered message includes at most five drift lines.

🤖 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
`@GenHub/GenHub.Tests/GenHub.Tests.Core/Features/GameProfiles/ViewModels/GameProfileLauncherViewModelTests.cs`
around lines 311 - 383, Add a test alongside
LaunchProfileCommand_WithReceiptDrift_ShowsInformationalNotice that supplies
more than MaxReceiptDriftNoticeLines receipt warnings, executes the launch, and
verifies the ShowInfo message contains no more than five drift-warning lines
while retaining the launch-configuration notice. Use the existing
GameLaunchInfo, IProfileLauncherFacade, and notification-service verification
patterns.

Comment on lines +565 to +577
public void Dispose()
{
try
{
Directory.Delete(_root, recursive: true);
}
catch (IOException)
{
// Best effort; a leftover temp directory is not worth failing the run over.
}

GC.SuppressFinalize(this);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Both new test fixtures catch only IOException during teardown. Directory.Delete also throws UnauthorizedAccessException when a file in the temporary directory is read-only or a handle is still open. On Windows that is a realistic outcome for the executable and archive fixtures, and the escaping exception fails a test run that otherwise passed.

  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/LaunchReceiptServiceTests.cs#L565-L577: change the filter around Directory.Delete(_root, recursive: true) to catch (Exception ex) when (ex is IOException or UnauthorizedAccessException).
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs#L1131-L1143: apply the same filter around Directory.Delete(_retailRoot, recursive: true).
📍 Affects 2 files
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/LaunchReceiptServiceTests.cs#L565-L577 (this comment)
  • GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs#L1131-L1143
🤖 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
`@GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/LaunchReceiptServiceTests.cs`
around lines 565 - 577, Update the Dispose teardown in
GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/LaunchReceiptServiceTests.cs
lines 565-577 to catch Directory.Delete failures matching IOException or
UnauthorizedAccessException, preserving best-effort cleanup. Apply the same
filtered exception handling to the Dispose teardown around
Directory.Delete(_retailRoot, recursive: true) in
GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Launching/GameLauncherTests.cs
lines 1131-1143.

Comment thread GenHub/GenHub/Features/Launching/GameLauncher.cs Outdated
Comment thread GenHub/GenHub/Features/Launching/LaunchReceiptService.cs Outdated
Comment on lines +69 to +72
foreach (var (variableName, value) in context.EnvironmentVariables)
{
receipt.EnvironmentVariableHashes[variableName] =
HashEnvironmentValue(value, receipt.EnvironmentHashSalt);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Receipt hashes remain guessable

When a profile supplies a low-entropy secret as an environment value, the receipt stores its HMAC alongside the key required to calculate candidate hashes, allowing anyone who obtains the receipt to test likely values offline and recover the secret. How this was verified: Profile environment values flow into the persisted receipt, which serializes both each value's HMAC and EnvironmentHashSalt.

Prompt To Fix With AI
This is a comment left during a code review.
Path: GenHub/GenHub/Features/Launching/LaunchReceiptService.cs
Line: 69-72

Comment:
**Receipt hashes remain guessable**

When a profile supplies a low-entropy secret as an environment value, the receipt stores its HMAC alongside the key required to calculate candidate hashes, allowing anyone who obtains the receipt to test likely values offline and recover the secret. **How this was verified:** Profile environment values flow into the persisted receipt, which serializes both each value's HMAC and `EnvironmentHashSalt`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request Testing Topic related to (unit) tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant