Skip to content

fix: Verify dispatcher provenance before first install#1791

Merged
hatayama merged 13 commits into
security/cli-distribution-hardening-integrationfrom
security/bootstrap-provenance-verification
Jul 15, 2026
Merged

fix: Verify dispatcher provenance before first install#1791
hatayama merged 13 commits into
security/cli-distribution-hardening-integrationfrom
security/bootstrap-provenance-verification

Conversation

@hatayama

@hatayama hatayama commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Summary

  • First Unity installation now authenticates immutable dispatcher Release scripts against package-stamped, Sigstore-verified SHA-256 digests before execution.
  • CLI-only installation documentation now requires an immutable Release tag and gh attestation verify; mutable branch streaming installation is removed.

Root cause

Unity derived a dispatcher tag from its minimum version and trusted a same-origin checksum sidecar for an installer script. That checksum is integrity-only and did not authenticate release provenance.

Changes

  • Added additive dispatcher Release tag and manifest fields to the package pin, a shared dispatcher attestation verifier, and release-automation stamping and guard commands.
  • Made POSIX and PowerShell installers require an authenticated archive manifest before extraction, copy, or version execution.
  • Replaced Unity installer script checksum downloads with pinned script digest verification; missing or malformed bootstrap pins fail closed.
  • Added package release ordering and incident/revocation documentation.

Attack conditions addressed

An attacker must no longer be able to substitute a mutable branch installer, a same-origin checksum, an unmanifested script, or an archive whose digest is absent from the verified pin. Network, bundle, identity, tag, and digest failures fail closed.

Verification

  • scripts/check-go-cli.sh
  • scripts/test-install-archive-manifest.sh
  • scripts/test-install-release-filter.sh
  • scripts/test-stamp-release-inputs.sh
  • scripts/test-verify-dispatcher-release-assets.sh
  • Authenticated check-dispatcher-pin against dispatcher-v3.0.1-beta.6
  • Unity compile and 46 focused pin/setup/installer EditMode tests
  • go test ./internal/dispatcher ./internal/update

Residual risk

  • Existing published package pins continue to authenticate their pinned dispatcher even if it later becomes vulnerable; remediation is a new dispatcher Release, higher minimum version, re-stamp, and package Release.
  • OS-native code signing and notarization remain defense in depth and are intentionally deferred to PR-5.
  • Unity trusts the installed package artifact; an attacker able to replace that package controls its pins.

Review in cubic

hatayama added 10 commits July 15, 2026 15:10
Keep legacy pin readers compatible with additive fields while making
bootstrap-specific tag and manifest inputs fail closed when absent or malformed.
Keep the new additive-pin compatibility test compliant with the Go formatter.
Make runtime verification and release automation share one implementation and
one fixed release identity policy instead of duplicating Sigstore checks.
Verify the immutable dispatcher Release with the shared Sigstore policy before
recording its asset digests, so Unity bootstrap never trusts sibling checksums.
Reject malformed or stale bootstrap pins before Unity installation and verify
published subjects whenever a security-relevant path changes.
Fail before extraction when no verified manifest is supplied, preventing a
same-origin checksum from authenticating a first installation.
Run published subject verification whenever a pull request changes a trust
input, or when no pull request diff exists. The guard now fails closed when
attestation subjects and release assets are not the same complete set.
Use the package-stamped immutable dispatcher tag and manifest instead of a
minimum-derived tag or release checksum sidecar. The installer command verifies
the downloaded script digest before execution and propagates failures through
the setup result contract.
Report installer source drift without blocking the release required to refresh
the package pin, and document the immutable CLI bootstrap and package release
order.
Clarify that digest pinning authenticates a release but cannot revoke an older
published dispatcher, and record the required minimum-version remediation.
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@hatayama, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 27 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 998f53b8-81c7-4422-8742-5d233399fa6b

📥 Commits

Reviewing files that changed from the base of the PR and between 1d01e1c and 53c7bb6.

📒 Files selected for processing (4)
  • .github/workflows/build-and-test.yml
  • README.md
  • README_ja.md
  • scripts/test-bootstrap-manifest-from-bundle.sh
📝 Walkthrough

Walkthrough

Dispatcher releases now use Sigstore verification, immutable release tags, and SHA-256 archive manifests. Unity installation consumes validated bootstrap pins, while release automation stamps and checks pins. Installer commands verify pinned digests, and documentation and CI enforce attested, failure-closed installation flows.

Changes

Dispatcher provenance and installation

Layer / File(s) Summary
Attestation verification foundation
cli/dispatcher/attestation/*, cli/dispatcher/internal/dispatcher/*
Adds bundle and tag verification, trusted-root embedding, release identities, subject extraction, and related tests.
Pin stamping and guard commands
cli/release-automation/cmd/*, cli/release-automation/internal/automation/*, .github/workflows/*, *.json
Adds commands and validation for stamping dispatcher pins, checking manifests, verifying published subjects, detecting script drift, and running CI checks.
Dispatcher bootstrap pin contract
Packages/src/Editor/Application/*, Packages/src/Editor/Infrastructure/CLI/CliPinReaderService.cs, Assets/Tests/Editor/CliPinReaderServiceTests.cs
Adds dispatcher release and manifest fields to pin models and validates bootstrap data fail closed.
Pinned Unity installation flow
Packages/src/Editor/Application/CliSetupApplicationService.cs, Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs, Packages/src/Editor/Presentation/*, Assets/Tests/Editor/*
Passes pinned dispatcher inputs through setup and installer services and reports structured command-loading failures.
Archive-manifest installer verification
Packages/src/Editor/Infrastructure/CLI/NativeCliCommandBuilder.cs, scripts/*, Assets/Tests/Editor/NativeCliInstallerTests.cs
Replaces checksum sidecar downloads with pinned digest verification and requires ULOOP_ARCHIVE_MANIFEST during installation.
Release process and first-install documentation
README*, SECURITY.md, docs/*
Documents attested CLI bootstrap, immutable dispatcher releases, pin stamping, and release sequencing.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UnitySetup
    participant PinReader
    participant NativeInstaller
    participant DispatcherRelease
    participant AttestationVerifier

    User->>UnitySetup: Start CLI installation
    UnitySetup->>PinReader: Load dispatcher bootstrap pin
    PinReader-->>UnitySetup: Release tag and archive manifest
    UnitySetup->>NativeInstaller: Build installation command
    NativeInstaller->>DispatcherRelease: Download installer script
    NativeInstaller->>AttestationVerifier: Verify pinned script digest
    AttestationVerifier-->>NativeInstaller: Verification result
    NativeInstaller->>DispatcherRelease: Execute verified installer with manifest
    DispatcherRelease-->>UnitySetup: Installation result
Loading

Possibly related PRs

Estimated code review effort: 5 (Critical) | ~120 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: verifying dispatcher provenance before first install.
Description check ✅ Passed The description is directly related to the changeset and accurately summarizes the provenance, pinning, installer, and documentation updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch security/bootstrap-provenance-verification

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.

Provide the authenticated archive manifest required by the installer fixture so
release selection tests continue to exercise their intended behavior.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Assets/Tests/Editor/CliSetupApplicationServiceTests.cs (1)

19-32: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Derive the expected dispatcher tag from the stamped package pin.

Both tests hard-code the current beta tag, so the next automated pin stamp will make otherwise-correct release changes fail CI. Load DispatcherReleaseTag through LoadDispatcherBootstrapPin() as the expected value.

Proposed update
- Is.EqualTo("dispatcher-v3.0.1-beta.6"));
+ Is.EqualTo(ExpectedDispatcherReleaseTag()));

- Is.EqualTo("install dispatcher-v3.0.1-beta.6"));
+ Is.EqualTo($"install {ExpectedDispatcherReleaseTag()}"));

+private static string ExpectedDispatcherReleaseTag()
+{
+    DispatcherBootstrapPinLoadResult result =
+        new CliPinReaderService().LoadDispatcherBootstrapPin();
+    Assert.That(result.Success, Is.True, result.ErrorMessage);
+    return result.DispatcherReleaseTag;
+}

Also applies to: 48-64

🤖 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 `@Assets/Tests/Editor/CliSetupApplicationServiceTests.cs` around lines 19 - 32,
Update the tests around InstallGlobalCliAsync_UsesPinnedDispatcherReleaseTag and
the corresponding test at lines 48–64 to derive the expected value from
LoadDispatcherBootstrapPin().DispatcherReleaseTag instead of hard-coding
"dispatcher-v3.0.1-beta.6", then compare InstalledVersion against that loaded
pin value.
🧹 Nitpick comments (2)
cli/release-automation/cmd/check-dispatcher-pin/main_test.go (1)

5-15: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Cover every trust-policy input in the watched-path test.

Add cli/dispatcher/attestation/policy.go and cli/dispatcher/attestation/trusted_root.json. Changes to either alter provenance verification and must trigger the network guard; testing only verifier.go leaves that contract unprotected.

🤖 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 `@cli/release-automation/cmd/check-dispatcher-pin/main_test.go` around lines 5
- 15, Extend
TestDispatcherPinPathNeedsNetworkVerificationRecognizesEveryWatchedPath to
include cli/dispatcher/attestation/policy.go and
cli/dispatcher/attestation/trusted_root.json in the watched paths, preserving
coverage for all existing trust-policy inputs.
Assets/Tests/Editor/CliPinReaderServiceTests.cs (1)

297-328: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Test CRLF and duplicate assets independently.

The input violates both rules, so either validation could regress while this test still passes. Use separate cases for non-LF line endings and duplicate asset names.

🤖 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 `@Assets/Tests/Editor/CliPinReaderServiceTests.cs` around lines 297 - 328,
Split
LoadDispatcherBootstrapPinFromPath_WhenManifestUsesCrLfOrDuplicateAsset_ReturnsFailure
into separate tests: one manifest containing only a CRLF line ending and one
containing only duplicate asset names. Keep each test asserting result.Success
is false so either validation rule is independently covered.
🤖 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/build-and-test.yml:
- Around line 76-82: Update the “Verify published dispatcher pin subjects”
workflow step to pass github.base_ref through the step’s env block, then
reference that environment variable in the run command instead of interpolating
the GitHub expression directly. Preserve the existing origin/ branch argument
and HEAD value.

In `@cli/dispatcher/attestation/verifier.go`:
- Around line 222-233: Update isHexCommitSHA to accept uppercase hexadecimal
characters A through F in addition to digits and lowercase a through f, while
preserving the existing 40-character length requirement and rejecting all
non-hex characters.

In `@cli/release-automation/cmd/stamp-dispatcher-pin/main.go`:
- Around line 35-36: Update the command around StampDispatcherPin to generate
the verified stamp once, then apply it to both
Packages/src/project-runner-pin.json and .uloop/project-runner-pin.json. Ensure
either update failure returns an error, while preserving all existing fields and
making only additive pin changes.

In `@Packages/src/Editor/Infrastructure/CLI/CliPinReaderService.cs`:
- Around line 121-129: Update LoadDispatcherBootstrapPinFromPath to catch
IOException and UnauthorizedAccessException thrown by LoadPinFromPath,
converting only those expected file-access failures into
DispatcherBootstrapPinLoadResult.FromFailure with the exception details. Leave
unexpected exceptions unhandled and preserve the existing pinResult failure and
validation paths.

---

Outside diff comments:
In `@Assets/Tests/Editor/CliSetupApplicationServiceTests.cs`:
- Around line 19-32: Update the tests around
InstallGlobalCliAsync_UsesPinnedDispatcherReleaseTag and the corresponding test
at lines 48–64 to derive the expected value from
LoadDispatcherBootstrapPin().DispatcherReleaseTag instead of hard-coding
"dispatcher-v3.0.1-beta.6", then compare InstalledVersion against that loaded
pin value.

---

Nitpick comments:
In `@Assets/Tests/Editor/CliPinReaderServiceTests.cs`:
- Around line 297-328: Split
LoadDispatcherBootstrapPinFromPath_WhenManifestUsesCrLfOrDuplicateAsset_ReturnsFailure
into separate tests: one manifest containing only a CRLF line ending and one
containing only duplicate asset names. Keep each test asserting result.Success
is false so either validation rule is independently covered.

In `@cli/release-automation/cmd/check-dispatcher-pin/main_test.go`:
- Around line 5-15: Extend
TestDispatcherPinPathNeedsNetworkVerificationRecognizesEveryWatchedPath to
include cli/dispatcher/attestation/policy.go and
cli/dispatcher/attestation/trusted_root.json in the watched paths, preserving
coverage for all existing trust-policy inputs.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 538d510c-dc88-403d-82c2-d63966b1ea4b

📥 Commits

Reviewing files that changed from the base of the PR and between 64c9fc3 and 3b12711.

⛔ Files ignored due to path filters (5)
  • cli/dispatcher/attestation/testdata/happy_asset_digest.txt is excluded by none and included by none
  • cli/dispatcher/attestation/testdata/happy_commit_sha.txt is excluded by none and included by none
  • cli/release-automation/go.mod is excluded by none and included by none
  • cli/release-automation/go.sum is excluded by !**/*.sum and included by none
  • scripts/install.ps1 is excluded by none and included by none
📒 Files selected for processing (45)
  • .github/workflows/build-and-test.yml
  • .uloop/project-runner-pin.json
  • Assets/Tests/Editor/CliPathSetupFlowTests.cs
  • Assets/Tests/Editor/CliPinReaderServiceTests.cs
  • Assets/Tests/Editor/CliSetupApplicationServiceTests.cs
  • Assets/Tests/Editor/NativeCliInstallerTests.cs
  • Packages/src/Editor/Application/CliPinReader.cs
  • Packages/src/Editor/Application/CliSetupApplicationService.cs
  • Packages/src/Editor/Domain/CliConstants.cs
  • Packages/src/Editor/Infrastructure/CLI/CliPinReaderService.cs
  • Packages/src/Editor/Infrastructure/CLI/NativeCliCommandBuilder.cs
  • Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs
  • Packages/src/Editor/Presentation/Setup/SetupWizardCliWorkflowController.cs
  • Packages/src/Editor/Presentation/UnityCliLoopSettingsCliSetupPresenter.cs
  • Packages/src/project-runner-pin.json
  • README.md
  • README_ja.md
  • SECURITY.md
  • cli/dispatcher/attestation/errors.go
  • cli/dispatcher/attestation/fetcher.go
  • cli/dispatcher/attestation/policy.go
  • cli/dispatcher/attestation/policy_test.go
  • cli/dispatcher/attestation/testdata/happy_bundle.json
  • cli/dispatcher/attestation/trusted_root.go
  • cli/dispatcher/attestation/trusted_root.json
  • cli/dispatcher/attestation/trusted_root_test.go
  • cli/dispatcher/attestation/verifier.go
  • cli/dispatcher/attestation/verifier_test.go
  • cli/dispatcher/cmd/refresh-attestation-trusted-root/main.go
  • cli/dispatcher/internal/dispatcher/attestation_release.go
  • cli/dispatcher/internal/dispatcher/attestation_verify.go
  • cli/dispatcher/internal/dispatcher/attestation_verify_test.go
  • cli/dispatcher/internal/dispatcher/dispatcher_test.go
  • cli/dispatcher/shared-inputs-stamp.json
  • cli/release-automation/cmd/check-dispatcher-pin/main.go
  • cli/release-automation/cmd/check-dispatcher-pin/main_test.go
  • cli/release-automation/cmd/stamp-dispatcher-pin/main.go
  • cli/release-automation/internal/architecture/architecture_test.go
  • cli/release-automation/internal/automation/dispatcher_pin_guard.go
  • cli/release-automation/internal/automation/dispatcher_pin_guard_test.go
  • cli/release-automation/internal/automation/dispatcher_pin_stamp.go
  • cli/release-automation/internal/automation/dispatcher_pin_stamp_test.go
  • docs/dispatcher-pin-release-order.md
  • scripts/install.sh
  • scripts/test-install-archive-manifest.sh

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Assets/Tests/Editor/CliSetupApplicationServiceTests.cs (1)

19-32: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Derive the expected dispatcher tag from the stamped package pin.

Both tests hard-code the current beta tag, so the next automated pin stamp will make otherwise-correct release changes fail CI. Load DispatcherReleaseTag through LoadDispatcherBootstrapPin() as the expected value.

Proposed update
- Is.EqualTo("dispatcher-v3.0.1-beta.6"));
+ Is.EqualTo(ExpectedDispatcherReleaseTag()));

- Is.EqualTo("install dispatcher-v3.0.1-beta.6"));
+ Is.EqualTo($"install {ExpectedDispatcherReleaseTag()}"));

+private static string ExpectedDispatcherReleaseTag()
+{
+    DispatcherBootstrapPinLoadResult result =
+        new CliPinReaderService().LoadDispatcherBootstrapPin();
+    Assert.That(result.Success, Is.True, result.ErrorMessage);
+    return result.DispatcherReleaseTag;
+}

Also applies to: 48-64

🤖 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 `@Assets/Tests/Editor/CliSetupApplicationServiceTests.cs` around lines 19 - 32,
Update the tests around InstallGlobalCliAsync_UsesPinnedDispatcherReleaseTag and
the corresponding test at lines 48–64 to derive the expected value from
LoadDispatcherBootstrapPin().DispatcherReleaseTag instead of hard-coding
"dispatcher-v3.0.1-beta.6", then compare InstalledVersion against that loaded
pin value.
🧹 Nitpick comments (2)
cli/release-automation/cmd/check-dispatcher-pin/main_test.go (1)

5-15: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Cover every trust-policy input in the watched-path test.

Add cli/dispatcher/attestation/policy.go and cli/dispatcher/attestation/trusted_root.json. Changes to either alter provenance verification and must trigger the network guard; testing only verifier.go leaves that contract unprotected.

🤖 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 `@cli/release-automation/cmd/check-dispatcher-pin/main_test.go` around lines 5
- 15, Extend
TestDispatcherPinPathNeedsNetworkVerificationRecognizesEveryWatchedPath to
include cli/dispatcher/attestation/policy.go and
cli/dispatcher/attestation/trusted_root.json in the watched paths, preserving
coverage for all existing trust-policy inputs.
Assets/Tests/Editor/CliPinReaderServiceTests.cs (1)

297-328: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Test CRLF and duplicate assets independently.

The input violates both rules, so either validation could regress while this test still passes. Use separate cases for non-LF line endings and duplicate asset names.

🤖 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 `@Assets/Tests/Editor/CliPinReaderServiceTests.cs` around lines 297 - 328,
Split
LoadDispatcherBootstrapPinFromPath_WhenManifestUsesCrLfOrDuplicateAsset_ReturnsFailure
into separate tests: one manifest containing only a CRLF line ending and one
containing only duplicate asset names. Keep each test asserting result.Success
is false so either validation rule is independently covered.
🤖 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/build-and-test.yml:
- Around line 76-82: Update the “Verify published dispatcher pin subjects”
workflow step to pass github.base_ref through the step’s env block, then
reference that environment variable in the run command instead of interpolating
the GitHub expression directly. Preserve the existing origin/ branch argument
and HEAD value.

In `@cli/dispatcher/attestation/verifier.go`:
- Around line 222-233: Update isHexCommitSHA to accept uppercase hexadecimal
characters A through F in addition to digits and lowercase a through f, while
preserving the existing 40-character length requirement and rejecting all
non-hex characters.

In `@cli/release-automation/cmd/stamp-dispatcher-pin/main.go`:
- Around line 35-36: Update the command around StampDispatcherPin to generate
the verified stamp once, then apply it to both
Packages/src/project-runner-pin.json and .uloop/project-runner-pin.json. Ensure
either update failure returns an error, while preserving all existing fields and
making only additive pin changes.

In `@Packages/src/Editor/Infrastructure/CLI/CliPinReaderService.cs`:
- Around line 121-129: Update LoadDispatcherBootstrapPinFromPath to catch
IOException and UnauthorizedAccessException thrown by LoadPinFromPath,
converting only those expected file-access failures into
DispatcherBootstrapPinLoadResult.FromFailure with the exception details. Leave
unexpected exceptions unhandled and preserve the existing pinResult failure and
validation paths.

---

Outside diff comments:
In `@Assets/Tests/Editor/CliSetupApplicationServiceTests.cs`:
- Around line 19-32: Update the tests around
InstallGlobalCliAsync_UsesPinnedDispatcherReleaseTag and the corresponding test
at lines 48–64 to derive the expected value from
LoadDispatcherBootstrapPin().DispatcherReleaseTag instead of hard-coding
"dispatcher-v3.0.1-beta.6", then compare InstalledVersion against that loaded
pin value.

---

Nitpick comments:
In `@Assets/Tests/Editor/CliPinReaderServiceTests.cs`:
- Around line 297-328: Split
LoadDispatcherBootstrapPinFromPath_WhenManifestUsesCrLfOrDuplicateAsset_ReturnsFailure
into separate tests: one manifest containing only a CRLF line ending and one
containing only duplicate asset names. Keep each test asserting result.Success
is false so either validation rule is independently covered.

In `@cli/release-automation/cmd/check-dispatcher-pin/main_test.go`:
- Around line 5-15: Extend
TestDispatcherPinPathNeedsNetworkVerificationRecognizesEveryWatchedPath to
include cli/dispatcher/attestation/policy.go and
cli/dispatcher/attestation/trusted_root.json in the watched paths, preserving
coverage for all existing trust-policy inputs.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 538d510c-dc88-403d-82c2-d63966b1ea4b

📥 Commits

Reviewing files that changed from the base of the PR and between 64c9fc3 and 3b12711.

⛔ Files ignored due to path filters (5)
  • cli/dispatcher/attestation/testdata/happy_asset_digest.txt is excluded by none and included by none
  • cli/dispatcher/attestation/testdata/happy_commit_sha.txt is excluded by none and included by none
  • cli/release-automation/go.mod is excluded by none and included by none
  • cli/release-automation/go.sum is excluded by !**/*.sum and included by none
  • scripts/install.ps1 is excluded by none and included by none
📒 Files selected for processing (45)
  • .github/workflows/build-and-test.yml
  • .uloop/project-runner-pin.json
  • Assets/Tests/Editor/CliPathSetupFlowTests.cs
  • Assets/Tests/Editor/CliPinReaderServiceTests.cs
  • Assets/Tests/Editor/CliSetupApplicationServiceTests.cs
  • Assets/Tests/Editor/NativeCliInstallerTests.cs
  • Packages/src/Editor/Application/CliPinReader.cs
  • Packages/src/Editor/Application/CliSetupApplicationService.cs
  • Packages/src/Editor/Domain/CliConstants.cs
  • Packages/src/Editor/Infrastructure/CLI/CliPinReaderService.cs
  • Packages/src/Editor/Infrastructure/CLI/NativeCliCommandBuilder.cs
  • Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs
  • Packages/src/Editor/Presentation/Setup/SetupWizardCliWorkflowController.cs
  • Packages/src/Editor/Presentation/UnityCliLoopSettingsCliSetupPresenter.cs
  • Packages/src/project-runner-pin.json
  • README.md
  • README_ja.md
  • SECURITY.md
  • cli/dispatcher/attestation/errors.go
  • cli/dispatcher/attestation/fetcher.go
  • cli/dispatcher/attestation/policy.go
  • cli/dispatcher/attestation/policy_test.go
  • cli/dispatcher/attestation/testdata/happy_bundle.json
  • cli/dispatcher/attestation/trusted_root.go
  • cli/dispatcher/attestation/trusted_root.json
  • cli/dispatcher/attestation/trusted_root_test.go
  • cli/dispatcher/attestation/verifier.go
  • cli/dispatcher/attestation/verifier_test.go
  • cli/dispatcher/cmd/refresh-attestation-trusted-root/main.go
  • cli/dispatcher/internal/dispatcher/attestation_release.go
  • cli/dispatcher/internal/dispatcher/attestation_verify.go
  • cli/dispatcher/internal/dispatcher/attestation_verify_test.go
  • cli/dispatcher/internal/dispatcher/dispatcher_test.go
  • cli/dispatcher/shared-inputs-stamp.json
  • cli/release-automation/cmd/check-dispatcher-pin/main.go
  • cli/release-automation/cmd/check-dispatcher-pin/main_test.go
  • cli/release-automation/cmd/stamp-dispatcher-pin/main.go
  • cli/release-automation/internal/architecture/architecture_test.go
  • cli/release-automation/internal/automation/dispatcher_pin_guard.go
  • cli/release-automation/internal/automation/dispatcher_pin_guard_test.go
  • cli/release-automation/internal/automation/dispatcher_pin_stamp.go
  • cli/release-automation/internal/automation/dispatcher_pin_stamp_test.go
  • docs/dispatcher-pin-release-order.md
  • scripts/install.sh
  • scripts/test-install-archive-manifest.sh
🛑 Comments failed to post (4)
.github/workflows/build-and-test.yml (1)

76-82: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Prevent script injection by passing GitHub context via environment variables.

Interpolating GitHub context expressions like ${{ github.base_ref }} directly into a bash script can lead to command injection if a branch name contains malicious characters (such as double quotes or backticks). It is safer to bind the context to an environment variable and reference that variable within the script.

🔒️ Proposed fix
       - name: Verify published dispatcher pin subjects
         if: github.event_name == 'pull_request'
         working-directory: cli/release-automation
         env:
           GITHUB_TOKEN: ${{ github.token }}
+          BASE_REF: ${{ github.base_ref }}
-        run: go run ./cmd/check-dispatcher-pin --base "origin/${{ github.base_ref }}" --head HEAD
+        run: go run ./cmd/check-dispatcher-pin --base "origin/$BASE_REF" --head HEAD
📝 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.

      - name: Verify published dispatcher pin subjects
        if: github.event_name == 'pull_request'
        working-directory: cli/release-automation
        env:
          GITHUB_TOKEN: ${{ github.token }}
          BASE_REF: ${{ github.base_ref }}
        run: go run ./cmd/check-dispatcher-pin --base "origin/$BASE_REF" --head HEAD
🧰 Tools
🪛 zizmor (1.26.1)

[error] 81-81: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 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/build-and-test.yml around lines 76 - 82, Update the
“Verify published dispatcher pin subjects” workflow step to pass github.base_ref
through the step’s env block, then reference that environment variable in the
run command instead of interpolating the GitHub expression directly. Preserve
the existing origin/ branch argument and HEAD value.
cli/dispatcher/attestation/verifier.go (1)

222-233: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Support uppercase characters in hex validation.

The current validation logic strictly requires lowercase hex characters, which causes valid uppercase hex hashes to be rejected. Expanding the check to allow 'A' through 'F' aligns it with standard hex-parsing behavior (like hex.DecodeString, which is used elsewhere in this file) and prevents false negatives if uppercase SHAs are passed.

💡 Proposed fix
 func isHexCommitSHA(s string) bool {
 	if len(s) != 40 {
 		return false
 	}
 	for i := 0; i < len(s); i++ {
 		c := s[i]
-		if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
+		if (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F') {
 			return false
 		}
 	}
 	return true
 }
📝 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.

func isHexCommitSHA(s string) bool {
	if len(s) != 40 {
		return false
	}
	for i := 0; i < len(s); i++ {
		c := s[i]
		if (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F') {
			return false
		}
	}
	return true
}
🤖 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 `@cli/dispatcher/attestation/verifier.go` around lines 222 - 233, Update
isHexCommitSHA to accept uppercase hexadecimal characters A through F in
addition to digits and lowercase a through f, while preserving the existing
40-character length requirement and rejecting all non-hex characters.
cli/release-automation/cmd/stamp-dispatcher-pin/main.go (1)

35-36: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Stamp the mirrored .uloop pin as well.

This command updates only Packages/src/project-runner-pin.json, leaving .uloop/project-runner-pin.json stale. Generate the verified stamp once and write it to both mirrors, failing if either update fails.

As per path instructions, “The project runner pin (Packages/src/project-runner-pin.json, mirrored to .uloop/project-runner-pin.json) must preserve its existing fields and evolve additively only.”

🤖 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 `@cli/release-automation/cmd/stamp-dispatcher-pin/main.go` around lines 35 -
36, Update the command around StampDispatcherPin to generate the verified stamp
once, then apply it to both Packages/src/project-runner-pin.json and
.uloop/project-runner-pin.json. Ensure either update failure returns an error,
while preserving all existing fields and making only additive pin changes.

Source: Path instructions

Packages/src/Editor/Infrastructure/CLI/CliPinReaderService.cs (1)

121-129: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Return a structured failure for expected pin-file I/O errors.

LoadPinFromPath can throw IOException or UnauthorizedAccessException during File.ReadAllText, causing installation to escape the new failure-result contract. Convert only these expected file-access failures into FromFailure.

Based on learnings, catch only expected domain-specific exceptions and let unexpected defects surface.

Proposed handling
- string content = File.ReadAllText(pinPath);
+ string content;
+ try
+ {
+     content = File.ReadAllText(pinPath);
+ }
+ catch (IOException ex)
+ {
+     return CliPinLoadResult.FromFailure(
+         $"Unity CLI Loop pin file at {pinPath} could not be read: {ex.Message}");
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+     return CliPinLoadResult.FromFailure(
+         $"Unity CLI Loop pin file at {pinPath} could not be read: {ex.Message}");
+ }
🤖 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 `@Packages/src/Editor/Infrastructure/CLI/CliPinReaderService.cs` around lines
121 - 129, Update LoadDispatcherBootstrapPinFromPath to catch IOException and
UnauthorizedAccessException thrown by LoadPinFromPath, converting only those
expected file-access failures into DispatcherBootstrapPinLoadResult.FromFailure
with the exception details. Leave unexpected exceptions unhandled and preserve
the existing pinResult failure and validation paths.

Source: Learnings

hatayama added 2 commits July 15, 2026 17:05
Derive the archive manifest from the verified release bundle before executing
the fail-closed installer, and cover that README bootstrap transformation.
@hatayama
hatayama merged commit 7ee46c7 into security/cli-distribution-hardening-integration Jul 15, 2026
5 checks passed
@hatayama
hatayama deleted the security/bootstrap-provenance-verification branch July 15, 2026 08:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant