Skip to content

fix: allow explicitly opted-in local HTTP comms adapters - #9876

Open
pentreathm wants to merge 2 commits into
mainfrom
fix/local-http-comms-adapter
Open

fix: allow explicitly opted-in local HTTP comms adapters#9876
pentreathm wants to merge 2 commits into
mainfrom
fix/local-http-comms-adapter

Conversation

@pentreathm

Copy link
Copy Markdown
Member

What does this PR change?

Allows the Archipelago global-room selector to use the fixed comms room when a local fixture returns a signed adapter over HTTP, for example fixed-adapter:signed-login:http://127.0.0.1:8080/....

The fallback is fail-closed:

  • It requires the direct command-line flag --accept-untrusted-realm.
  • The adapter URL must resolve to a loopback host (localhost, 127.0.0.1, or ::1).
  • Remote HTTP adapters remain rejected.
  • The flag is not accepted from decentraland:// deep links.
  • HTTPS and WSS behavior is unchanged.

Test Instructions

Automated tests

The new protocol-selection tests cover loopback allow/deny cases, remote lookalikes, HTTPS, and the explicit opt-in requirement. The existing deep-link allowlist tests also verify that accept-untrusted-realm is dropped from deep links.

Run the Unity test suite in CI, or locally with the project Unity version:

make test-editmode TEST_FILTER=ArchipelagoProtocolSelectionShould
make test-editmode TEST_FILTER=AppArgsTest

Manual fixture test

Start the local fixture with the Unity Explorer helper from explorer-e2e-infra:

./scripts/fixture-local.sh up
./scripts/fixture-open-unity.sh --world seed

Expected result: the local signed HTTP comms adapter is selected as a fixed room instead of failing with Cannot determine the protocol from the about url.

Quality Checklist

  • Added coverage for the loopback and opt-in security boundary.
  • Updated application argument documentation.
  • Preserved the existing HTTPS/WSS paths.
  • Full Unity tests require Unity 6000.4.0f1 and run in CI.

@pentreathm
pentreathm requested review from a team as code owners August 26, 2026 18:32
@decentraland-bot decentraland-bot added the ext-contribution Identifies a contribution which was not initiated by a Unity Developer label Aug 26, 2026
@github-actions
github-actions Bot requested a review from DafGreco August 26, 2026 18:32
@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

🚦 CI Status

Build

Windows and Mac build successful in Unity Cloud! You can find a link to the downloadable artifact below.

Name Link
Commit 144cd5b
Logs https://github.com/decentraland/unity-explorer/actions/runs/33014361864
Download Windows https://github.com/decentraland/unity-explorer/suites/89441043836/artifacts/9624964445
Download Windows S3 https://explorer-artifacts.decentraland.org/@dcl/unity-explorer/branch/fix/local-http-comms-adapter/pr-25553-144cd5b/Decentraland_windows64.zip
Download Mac https://github.com/decentraland/unity-explorer/suites/89441043836/artifacts/9625131454
Download Mac S3 https://explorer-artifacts.decentraland.org/@dcl/unity-explorer/branch/fix/local-http-comms-adapter/pr-25553-144cd5b/Decentraland_macos.zip
Built on 2026-08-26T21:57:02Z

Lint

Warnings not reduced: 12566 => 12618 — remove at least 53 warnings to merge.

No warnings in files changed by this PR — showing general ones you can remove to unblock (50 of 12618)
Assets/DCL/AvatarRendering/AvatarShape/Tests/PerformanceTests/AvatarOutlineFrustumHoistPerformanceTest.cs:126  ArrangeRedundantParentheses  Redundant parentheses
Assets/DCL/AvatarRendering/AvatarShape/Tests/PlayMode/BoneMatrixCalculationJobPerformanceTest.cs:58  ArrangeRedundantParentheses  Redundant parentheses
Assets/DCL/Rendering/GPUInstanceBatcher/ComputeShaders/DrawArgsInstanceCountTransfer.compute:1  CppUnusedIncludeDirective  Possibly unused #include directive
Assets/DCL/Infrastructure/CrdtEcsBridge/JsModulesImplementation/ClientWebSocketApiImplementation.cs:275  RedundantArgumentDefaultValue  The parameter 'initialCount' has the same default value
Assets/DCL/VoiceChat/Microphone/MicrophoneTrackPublisher.cs:43  RedundantArgumentDefaultValue  The parameter 'initialCount' has the same default value
Assets/DCL/Web3/Authenticators/Implementations/ThirdWeb/ThirdWebLoginService.cs:29  RedundantArgumentDefaultValue  The parameter 'initialCount' has the same default value
Assets/DCL/Infrastructure/CrdtEcsBridge/JsModulesImplementation/ClientWebSocketApiImplementation.cs:275  RedundantArgumentDefaultValue  The parameter 'maxCount' has the same default value
Assets/DCL/VoiceChat/Microphone/MicrophoneTrackPublisher.cs:43  RedundantArgumentDefaultValue  The parameter 'maxCount' has the same default value
Assets/DCL/Web3/Authenticators/Implementations/ThirdWeb/ThirdWebLoginService.cs:29  RedundantArgumentDefaultValue  The parameter 'maxCount' has the same default value
Assets/DCL/PluginSystem/Global/DefaultTexturesContainer.cs:75  RedundantArgumentDefaultValue  The parameter 'optionValue' has the same default value
Assets/DCL/PluginSystem/Global/DefaultTexturesContainer.cs:76  RedundantArgumentDefaultValue  The parameter 'optionValue' has the same default value
Assets/DCL/Communities/CommunitiesCard/Events/EventListController.cs:156  RedundantArgumentDefaultValue  The parameter 'parcelToTeleport' has the same default value
Assets/DCL/Events/EventCardActionsController.cs:93  RedundantArgumentDefaultValue  The parameter 'parcelToTeleport' has the same default value
Assets/DCL/Friends/UI/FriendPanel/Sections/Friends/FriendListSectionUtilities.cs:60  RedundantArgumentDefaultValue  The parameter 'parcelToTeleport' has the same default value
Assets/DCL/Places/PlacesCardSocialActionsController.cs:183  RedundantArgumentDefaultValue  The parameter 'parcelToTeleport' has the same default value
Assets/DCL/Tests/PlayMode/PerformanceTests/EventsStateServiceLookupPerformanceTest.cs:140  RedundantArgumentDefaultValue  The parameter 'unit' has the same default value
Assets/DCL/Tests/PlayMode/PerformanceTests/EventsStateServiceLookupPerformanceTest.cs:141  RedundantArgumentDefaultValue  The parameter 'unit' has the same default value
Assets/DCL/Tests/PlayMode/PerformanceTests/PlacesStateServiceLookupPerformanceTest.cs:161  RedundantArgumentDefaultValue  The parameter 'unit' has the same default value
Assets/DCL/Tests/PlayMode/PerformanceTests/PlacesStateServiceLookupPerformanceTest.cs:162  RedundantArgumentDefaultValue  The parameter 'unit' has the same default value
Assets/DCL/Chat/_Refactor/ChatReactions/Tests/LocalPlayerWorldReactorShould.cs:141  RedundantArgumentDefaultValue  The parameter 'walletId' has the same default value
Assets/DCL/Chat/_Refactor/ChatReactions/Tests/LocalPlayerWorldReactorShould.cs:156  RedundantArgumentDefaultValue  The parameter 'walletId' has the same default value
Assets/DCL/Chat/_Refactor/ChatReactions/Tests/LocalPlayerWorldReactorShould.cs:171  RedundantArgumentDefaultValue  The parameter 'walletId' has the same default value
Assets/DCL/MapRenderer/MapLayers/HomeMarker/HomeMarkerController.cs:135  RedundantArgumentDefaultValue  The parameter 'worldName' has the same default value
Assets/DCL/Infrastructure/ECS/Unity/Materials/Tests/CreateBasicMaterialSystemShould.cs:76  RedundantAssignment  The value passed to the method is never used because it is overwritten in the method body before being read
Assets/DCL/Infrastructure/ECS/Unity/Materials/Tests/CreatePBRMaterialSystemShould.cs:85  RedundantAssignment  The value passed to the method is never used because it is overwritten in the method body before being read
Assets/DCL/RealmNavigation/RetrieveSceneFromFixedRealm.cs:46  RedundantAssignment  The value passed to the method is never used because it is overwritten in the method body before being read
Assets/DCL/AvatarRendering/Emotes/Editor/EmbeddedEmotesEditor.cs:37  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/AvatarRendering/Emotes/Editor/EmbeddedEmotesEditor.cs:38  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Backpack/AvatarSection/Outfits/OutfitsPresenter.cs:193  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Chat/History/ChatHistoryEncryptor.cs:25  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/ECS/Unity/Materials/Systems/StartMaterialsLoadingSystem.cs:167  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/ECS/Unity/StreamableLoading/GLTF/DownloadProvider/GltFastDownloadProviderBase.cs:67  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Global/Editor/DebugSettingsDrawer.cs:73  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Global/Editor/RealmLaunchSettingsDrawer.cs:248  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Utility/Primitives/BoxFactory.cs:64  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Utility/Primitives/BoxFactory.cs:100  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Utility/Primitives/BoxFactory.cs:136  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Utility/Primitives/BoxFactory.cs:172  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Utility/Primitives/BoxFactory.cs:219  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Utility/Primitives/CylinderVariantsFactory.cs:137  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Landscape/Jobs/NoiseJob.cs:68  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/PerformanceAndDiagnostics/Diagnostics/ReportsHandling/Sentry/DclAnrIntegration.cs:411  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/PluginSystem/Global/VoiceChatDebugContainer.cs:164  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/SDKComponents/AudioAnalysis/AudioAnalysisSystem.cs:78  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/SDKComponents/AvatarAttach/Systems/AvatarAttachHandlerSystem.cs:87  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Translation/Processors/AngleBracketSegmentationRule.cs:29  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Translation/Processors/AngleBracketSegmentationRule.cs:38  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Multiplayer/Connections/Archipelago/Rooms/ArchipelagoIslandRoom.cs:52  RedundantBaseConstructorCall  Redundant base constructor call
Assets/DCL/AvatarRendering/AvatarShape/ComputeShader/TransformGatherJobs.cs:38  RedundantCast  Type cast is redundant
Assets/DCL/AvatarRendering/Wearables/Helpers/WearablePolymorphicBehaviour.cs:139  RedundantCast  Type cast is redundant

Tests

All Unity tests passed ✅

TESTS SUITE Result Passed Failed Skipped
EditMode ✅ Passed 25483 0 13
PlayMode ✅ Passed 243 0 37

@github-actions

Copy link
Copy Markdown
Contributor

Slack notification sent to #explorer-ext-contributions for external review.
To re-send, delete this comment and re-add the ext-contribution label.

@decentraland-bot decentraland-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.

STEP 2 — Root-cause check

Problem: ForkGlobalRealmRoom.ChooseRoom() throws InvalidOperationException when a local fixture realm returns a signed comms adapter over HTTP (fixed-adapter:signed-login:http://127.0.0.1:8080/...), because only WSS and HTTPS schemes were recognized.

Does the diff fix the cause? Yes. The diff adds a new HTTP-loopback branch to ChooseRoom() that routes the adapter to the existing FixedConnectiveRoom. The branch requires both an explicit CLI opt-in (--accept-untrusted-realm) and a validated loopback host. This is a proper feature addition for a previously unsupported protocol path, not a symptom workaround.

PASS

STEP 3 — Design & integration

Owner search: ForkGlobalRealmRoom already owns the protocol-selection logic in ChooseRoom(). No new long-lived unit is introduced — the diff adds a branch to the existing method and a static helper. The composition root (CommsContainer.Create) already constructs the ForkGlobalRealmRoom via IArchipelagoIslandRoom.NewDefault; the flag is threaded from IAppArgsbool at that boundary. No lifecycle duplication, no reconciliation, no persistent state outside ECS.

Assembly boundary: The diff adds using Global.AppArgs; to IArchipelagoIslandRoom.cs (in the Multiplayer.Connections assembly). This is used only in the factory method NewDefault to read IAppArgs. Since CommsContainer.cs (which calls NewDefault) already depends on Global.AppArgs, and the factory is the composition seam where the flag is resolved, this is a justified boundary extension.

Rename accuracy: httpsRoomFactoryfixedRoomFactory is correct: the factory now produces FixedConnectiveRoom instances for both HTTPS and HTTP-loopback adapters, so the old name was too narrow.

Teardown/consumption trace: No new subscriptions, events, connections, or disposables are introduced. The bool allowInsecureLocalHttp is a constructor-injected immutable value. No teardown needed.

PASS

STEP 4 — Member audit

Member Consumers Assessment
IsLoopbackHttpAdapter(string, bool) ChooseRoom() + tests internal static — appropriate for testability. Encapsulates URL parsing and security validation; not a single-use predicate that should be inlined.
allowInsecureLocalHttp field IsLoopbackHttpAdapter call in ChooseRoom() Constructor-injected config value, not a derived predicate. Legitimate.
fixedRoomFactory field (renamed) ChooseRoom() (two branches: HTTPS and HTTP-loopback) Correctly renamed to reflect broader usage.
IAppArgs? appArgs parameter on NewDefault CommsContainer.Create Nullable optional — appropriate since NewDefault is an interface default method and other callers may not have IAppArgs. The == true pattern handles null correctly.

No issues found.

STEP 5 — Line-level review

A. Blocking-issue categories

No P0 or P1 issues found across all categories (bugs, security, performance, error handling, resource leaks, nullability, detached async, false-intent conditions).

B. Design, encapsulation & resource smells

No issues found. The change is well-scoped within the existing owner, naming is accurate, no magic values, no leaked encapsulation.

P2 findings are attached as inline comments.

Security review

  • Deep-link denial: accept-untrusted-realm is not in PERMITTED_KEYS or WHITELISTED_REALM_PERMITTED_KEYS in DeepLinkAllowlist.cs, so it is automatically denied. The test DeepLinkDropsInternalFlags verifies this.
  • Default closed: allowInsecureLocalHttp defaults to false; IsLoopbackHttpAdapter returns false immediately when disabled.
  • Loopback validation: Uses Uri.TryCreate + Uri.IsLoopback — checks the parsed hostname, not a string match, so userinfo and subdomain attacks are rejected.
  • Scheme validation: Double-checks uri.Scheme == Uri.UriSchemeHttp after the Contains pre-filter.
  • No SSRF risk: Traffic is restricted to loopback addresses.
  • No DNS rebinding risk: Uri.IsLoopback checks the literal hostname in the URL, not a DNS-resolved address.
  • No secrets or credentials committed.

STEP 6 — Complexity

COMPLEX — Modifies the comms protocol selection path (ForkGlobalRealmRoom.ChooseRoom) and extends a shared interface (IArchipelagoIslandRoom.NewDefault). Introduces a security-sensitive feature gating HTTP acceptance behind a CLI flag with loopback validation.

STEP 7 — QA assessment

QA_REQUIRED: YES — Changes runtime comms connection selection code in Explorer/. The new code path affects which room type is instantiated for local fixture development.

STEP 8 — Non-blocking warnings

⚠️ Branch target: This PR targets main rather than dev. Unity Explorer PRs typically target dev — verify this is intentional (e.g., a hotfix needed directly on main).

STEP 9 — Verdict

REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Modifies comms protocol selection (ForkGlobalRealmRoom.ChooseRoom) and shared interface (IArchipelagoIslandRoom.NewDefault) with security-sensitive loopback validation
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested via Slack

@decentraland-bot

Copy link
Copy Markdown
Contributor

PR #9876, run #33017651983

Overall: ✅ no significant changes

Builds: Windows change, Windows baseline, macOS change, macOS baseline

How to read this table
  • Each build is measured 3 times, interleaved with the other build (change, baseline, change, baseline, ...) in the same session, so both see the same world content and machine state. The values are the median, and (min–max) is the lowest and highest of those runs.
  • Δ is Change minus Baseline (a negative Δ means Change is faster).
  • 🟢 faster / 🔴 slower — a difference that passed every check: the runs are fully separated (every run of one build faster than every run of the other), and the median difference is at least 3% and at least 0.5 ms.
  • ⚪ within noise — the builds' runs overlap, or the difference is tiny; it cannot be told apart from random variation. Treat it as no change.
  • — informational — the 0.1% worst metrics average only the few worst frames of a run, so a single OS hiccup swings them by a lot; they are shown for context and never earn a verdict.
  • ⚠️ no verdict — the two builds' sessions were not comparable (very different sample counts, or too few usable runs), so no conclusion is drawn from them.
  • Exceptions per run — the average number of exceptions in a run's log, not counting teardown ones logged while the app quits. Flagged only on a difference of at least 2 per run and 1.5× the other build; exception kinds the baseline never threw are called out under the table. The Exception breakdown groups all of them by the explorer's report category and exception type (as totals across the runs).
  • A run that logged unusually many exceptions (at least 10 and 5× the median of its build's runs — e.g. a service was down during it) is excluded from all numbers and called out under the table.
  • The Overall line at the top only reacts to a metric that moved on two or more machines, or by 10% or more on one — a single modest 🟢/🔴 cell can still be a statistical fluke.

Intel Core i5

Metric Baseline Change Δ Result
Samples 2380 (×3) 2339 (×3)
CPU average 37.5 ms (37.3–38.3) 38.1 ms (37.6–38.4) 0.6 ms ⚪ within noise
CPU 1% worst 329.9 ms (298.3–342.6) 317.5 ms (285.7–345.8) -12.5 ms ⚪ within noise
CPU 0.1% worst 347.7 ms (320.9–355.7) 327.3 ms (313.6–389.1) -20.4 ms — informational
GPU average 24.0 ms (23.3–24.3) 23.7 ms (23.6–24.0) -0.2 ms ⚪ within noise
GPU 1% worst 335.7 ms (304.5–348.2) 323.3 ms (289.8–352.2) -12.4 ms ⚪ within noise
GPU 0.1% worst 354.6 ms (325.4–358.9) 334.7 ms (304.1–392.2) -19.9 ms — informational
Exceptions per run 0 0 0 ⚪ no significant change

Apple M1

Metric Baseline Change Δ Result
Samples 2744 (×3) 2761 (×3)
CPU average 32.7 ms (31.8–32.8) 32.4 ms (31.9–32.6) -0.3 ms ⚪ within noise
CPU 1% worst 228.6 ms (224.6–229.0) 224.4 ms (220.8–225.0) -4.2 ms ⚪ within noise
CPU 0.1% worst 230.8 ms (226.5–234.4) 232.9 ms (223.7–241.1) 2.1 ms — informational
GPU average 22.3 ms (21.3–22.6) 21.5 ms (21.2–21.8) -0.9 ms ⚪ within noise
GPU 1% worst 44.5 ms (44.5–44.9) 45.4 ms (45.3–45.6) 0.9 ms ⚪ within noise
GPU 0.1% worst 45.8 ms (45.6–48.1) 46.7 ms (46.6–47.5) 0.9 ms — informational
Exceptions per run 0 0 0 ⚪ no significant change

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

Labels

ext-contribution Identifies a contribution which was not initiated by a Unity Developer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants