Skip to content

fix: allow local-ab deep-link param for loopback realms, deriving the assets base from the realm - #9519

Merged
anicalbano merged 3 commits into
mainfrom
hotfix/local-ab-allowlist
Jul 30, 2026
Merged

fix: allow local-ab deep-link param for loopback realms, deriving the assets base from the realm#9519
anicalbano merged 3 commits into
mainfrom
hotfix/local-ab-allowlist

Conversation

@dalkia

@dalkia dalkia commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Pull Request Description

What does this PR change?

Creator Hub / sdk-commands preview deep links need to enable local asset bundles, but local-ab was not yet allowlisted and the full-URL override (optimized-assets-url) is deliberately never-permitted for deep links: it rewrites the AssetBundlesCDN, LodGeneratorCDN, and AssetBundleRegistry endpoints globally for the whole session, and the loopback-realm gate only checks the realm param — which an attacker also controls — so allowlisting it would let a crafted link point asset loading at arbitrary remote infrastructure.

This PR implements the explorer side of decentraland/js-sdk-toolchain#1504: the preview server proxies its abgen sidecar at {realm}/optimized-assets, so the client derives the assets base from the single origin it already has — no URL, port, or any other attacker-controllable value travels in the link.

  • Adds local-ab to DeepLinkAllowlist.LOOPBACK_REALM_PERMITTED_KEYS (with the per-key security rationale the set requires). It is a pure boolean: the optimized-assets base becomes {realm}/optimized-assets (RealmLaunchSettings.LocalAssetBundlesBaseUrl), the same realm the loopback gate already validated, so the flag can only point asset loading at the realm the link already targets.
  • The Editor InitialRealm.Localhost path derives from IRealmNavigator.LOCALHOST the same way, so the deeplink-less Editor dev loop keeps working.
  • An explicit --optimized-assets-url CLI arg still takes precedence (CLI args are trusted and not allowlist-filtered); the param stays never-permitted for deep links, now pinned by a test.
  • Retires the http://127.0.0.1:5147 default-port convention — with the proxy, the sidecar's port is private to sdk-commands (it moves back to getPort(0)), so there is nothing to guess.
  • Path-prefix audit (the base now carries /optimized-assets): all consumers compose verbatim — URLBuilder.AppendDomain (manifest loads), URLDomain.Append/URLBuilder.Combine (bundle URLs), and ComposeRegistryUrl string interpolation (registry endpoints). The path-stripping AppendDomainWithReplacedPath is only used with the Lambdas URL, never with the optimized-assets endpoints.

Note: per DeepLinkAllowlist's class doc, changes to the permitted sets are a SEC-019/020 product decision — flagging for that sign-off in review.

⚠️ Dependency: the deep-link local-ab flow requires decentraland/js-sdk-toolchain#1504 (the /optimized-assets proxy route) to be released.

Test Instructions

Easiest way to run this build: swap it into the launcher's latest folder

The SDK/Creator Hub deeplink flow always launches whatever build sits in the launcher's latest folder, so the most straightforward way to test end-to-end is to put this PR's build there:

  • macOS: ~/Library/Application Support/DecentralandLauncherLight/latest/Decentraland.app
  • Windows: %APPDATA%\DecentralandLauncherLight\latest\
  1. Rename the original — do not delete it (e.g. Decentraland.appDecentraland.original.app) so the released build is not lost.
  2. Copy this PR's build in under the original name (Decentraland.app) — the deeplink flow now opens it transparently, no App Parameters needed.
  3. When you finish testing, rename things back. Otherwise every preview keeps launching the PR build until the launcher's next self-update replaces latest.

Prerequisites

  • An SDK7 scene using js-sdk-toolchain#1498's branch build — install the two tarballs from the "Test this pull request" bot comment on that PR (URLs change per push; current example):
cd path/to/your-scene
npm install "https://sdk-team-cdn.decentraland.org/@dcl/js-sdk-toolchain/branch/feat/abgen-proxied-assets/dcl-sdk-commands-7.25.1-30488513888.commit-1d09bd1.tgz"
  • The scene running with the asset-bundles sidecar:
npm run start -- --asset-bundles

No abgen installation or ABGEN_* configuration is needed — the SDK downloads a pinned, sha256-verified abgen binary (v0.11.7) on first run and wires it automatically. E

Test Steps

  1. Launch the explorer: swap this build into the launcher's latest folder (see above) and just run the scene — the deeplink flow opens it with all parameters set.
  2. Confirm you are on the right build: the Authentication screen reports the branch on the bottom left — it should read feat/lsd-local-ab-allowlist.
  3. Confirm bundles are actually being built: the scene's terminal shows the sidecar converting (manifest GET, per-asset build lines, then one bundle GET per asset).
  4. Compare memory: open the debug panel → Memory tab. Loading the same scene with --asset-bundles should sit noticeably lower than without it (Genesis Plaza used as the reference scene). Please report any inconsistency between flag on and off.
  5. Degrade path: run the scene without --asset-bundles (or kill the sidecar and reload) → the scene still renders via raw GLTFs, no exception spam.
  6. Hot reload: with the sidecar running, overwrite a .glb in the scene → the scene reloads, only that asset reconverts, and the updated model appears.
  7. Wearable caching: avatars/wearables render normally (streamed from the production ab-cdn through the sidecar) and stay disk-cached — exit and enter again: wearables/emotes load near-instantly with no re-downloads in the sidecar log, while the scene's own bundles are re-requested fresh.

Additional Testing Notes

  • First load of a large scene may time out before every asset converts (Genesis Plaza reproduces this) — re-run the scene and it completes; conversions are cached in .dcl-optimized-assets/, so any subsequent run is fine. Known issue, still to be addressed.

Quality Checklist

  • Changes have been tested locally
  • Documentation has been updated (if required)
  • Performance impact has been considered
  • For SDK features: Test scene is included

🤖 Generated with Claude Code

…r loopback realms

Adds local-ab and a new local-ab-port app arg to the loopback-realm
deep-link allowlist so Creator Hub / sdk-commands preview links can
enable local asset bundles. The port variant keeps the host pinned to
127.0.0.1 (int-parsed, 1024-65535, falling back to abgen's default
5147), so unlike optimized-assets-url — which stays never-permitted —
a crafted link cannot point the AB/LOD/registry endpoints at remote
infrastructure. local-ab-port implies local-ab, mirroring mcp-port.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dalkia
dalkia requested review from a team as code owners July 29, 2026 18:05
@github-actions
github-actions Bot requested a review from anicalbano July 29, 2026 18:05
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@decentraland-bot
decentraland-bot self-requested a review July 29, 2026 18:05

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

PR Review: fix: allow local-ab and host-pinned local-ab-port deep-link params for loopback realms


STEP 2 — Root-cause check ✅

Creator Hub / sdk-commands preview deep links need to enable local asset bundles, but local-ab was not allowlisted and the full-URL override (optimized-assets-url) is deliberately never-permitted — it rewrites AssetBundlesCDN, LodGeneratorCDN, and AssetBundleRegistry endpoints globally for the whole session, letting a crafted link point asset loading at arbitrary remote infrastructure.

The PR takes the correct safe variant instead: a host-pinned port param (local-ab-port) whose URL construction hardcodes 127.0.0.1 and only allows a validated integer port. This addresses the actual need (local-dev AB loading) without introducing the attack surface of optimized-assets-url.

Verdict: PASS — the fix addresses the root cause, not a symptom.


STEP 3 — Design & integration ✅

No new long-lived units introduced. This PR adds:

  • A const string in AppArgsFlags (leaf declaration)
  • Two entries in the DeepLinkAllowlist set (configuration)
  • A static helper ResolveLocalAssetBundlesUrl on RealmLaunchSettings
  • Implication logic in ParseRealmAppParameter

Owner search:

  • Local AB configuration is owned by RealmLaunchSettings — it holds useLocalAssetBundles, DEFAULT_LOCAL_ASSET_BUNDLES_URL, and the ParseRealmAppParameter logic that interprets LOCAL_AB. The new ResolveLocalAssetBundlesUrl static method and the LOCAL_AB_PORT implication are both placed in this class. ✅
  • The deep-link allowlist is owned by DeepLinkAllowlist — the two new entries follow the existing tier-2 pattern (per-key security rationale comment). ✅
  • The consumer (MainSceneLoader.InitializeFlowAsync) already called RealmLaunchSettings.DEFAULT_LOCAL_ASSET_BUNDLES_URL at this point — the change to ResolveLocalAssetBundlesUrl is a drop-in replacement at initialization time (not per-frame). ✅

Pattern consistency:

  • The local-ab / local-ab-port pair mirrors the existing mcp / mcp-port pattern faithfully:
    • MCP port resolution: McpServerPlugin.cs:79-83TryGetValue + int.TryParse + range clamp
    • Local AB port resolution: RealmLaunchSettings.cs:108-113 — identical shape
    • MCP implication: FeaturesRegistry.cs:75HasFlag(MCP) || HasFlag(MCP_PORT)
    • Local AB implication: RealmLaunchSettings.cs:140HasFlag(LOCAL_AB) || HasFlag(LOCAL_AB_PORT)

Verdict: PASS — no design issues. Code is in the right place.


STEP 4 — Member audit ✅

Member Consumers Assessment
AppArgsFlags.LOCAL_AB_PORT DeepLinkAllowlist, RealmLaunchSettings (×2: implication + resolve), tests Multi-use constant, appropriate
ResolveLocalAssetBundlesUrl(IAppArgs) MainSceneLoader.cs:236, RealmLaunchSettingsShould tests 2 production+test consumers; encapsulates port validation + URL construction; static with no hidden state
MIN_LOCAL_AB_PORT / MAX_LOCAL_AB_PORT ResolveLocalAssetBundlesUrl Private constants at top of type per convention

No single-use extraction, no absent≠false conflation, no redundant guards.


STEP 5 — Line-level review

Security audit — no issues found:

  • Port validation (int.TryParse + 1024-65535 range) is strict — non-numeric values, path suffixes (5147/path), and out-of-range ports all fall back to the default. The parsed int is interpolated into a hardcoded http://127.0.0.1:{port} template, so no host/path injection is possible.
  • optimized-assets-url is correctly kept in the never-permitted tier and now has an explicit test assertion for loopback realms (AppArgsTests.cs:130).
  • The loopback gate (Uri.IsLoopback) is sound — DNS rebinding and userinfo tricks don't bypass it in .NET's Uri implementation.
  • Defense in depth: loopback gate → hardcoded host → strict int parsing → range clamp.

One P2 finding — see inline comment.


STEP 6 — Complexity

SIMPLE. Changes are a straightforward allowlist addition + port validator + tests. No ECS systems, components, queries, async/UniTask, plugin registration, or assembly definition changes.


STEP 7 — QA assessment

QA_REQUIRED: YES. The PR modifies runtime code (RealmLaunchSettings.cs, MainSceneLoader.cs) that affects asset bundle loading behavior during local scene development. Manual verification with a local scene server and abgen is needed to confirm the deep-link flow works end-to-end.


STEP 8 — Non-blocking warnings

None. Main.unity is not modified.


REVIEW_RESULT: PASS ✅
COMPLEXITY: SIMPLE
COMPLEXITY_REASON: Straightforward allowlist entries, a static port-validation helper, and implication logic in the existing RealmLaunchSettings — no ECS, async, or system-level changes.
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by decentraland-bot via GitHub

@github-actions github-actions 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.

Auto-approved based on Jarvis review — simple fix/chore with no blocking issues. QA approval is still required.

Reworks the approach per decentraland/js-sdk-toolchain#1504: the
preview server now proxies its abgen sidecar at {realm}/optimized-assets,
so the client derives the assets base from the single origin it already
has. local-ab becomes a pure boolean deep-link param (the base derives
from the realm the loopback gate already validated), local-ab-port is
removed before ever shipping, and the 5147 default-port convention
retires along with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

badge

Warnings not reduced: 14075 => 14087 — remove at least one warning to merge.

Warnings/errors in files changed by this PR (30)
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:250  ArrangeRedundantParentheses  Redundant parentheses
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:214  AssignNullToNotNullAttribute  Possible 'null' assignment to non-nullable entity
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:445  CSharpWarnings::CS0162  Code is unreachable
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:828  CSharpWarnings::CS0618  CS0618: Enum 'UnityEngine.FindObjectsSortMode' is obsolete: 'FindObjectsSortMode has been deprecated. Use the FindObjectsByType overloads that do not take a FindObjectsSortMode parameter.'
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:828  CSharpWarnings::CS0618  CS0618: Method 'UnityEngine.Object.FindObjectsByType<T>(FindObjectsInactive, FindObjectsSortMode)' is obsolete: 'FindObjectsByType with FindObjectsSortMode parameter has been deprecated. Use FindObjectsByType<T>() or FindObjectsByType<T>(FindObjectsInactive) instead. InstanceID will be replaced in the future with EntityId and previous sort order cannot be maintained.'
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:213  CSharpWarnings::CS8600  Converting null literal or possible null value into non-nullable type
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:902  CSharpWarnings::CS8600  Converting null literal or possible null value into non-nullable type
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:487  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:892  DuplicatedStatements  Duplicated statements
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:172  EmptyGeneralCatchClause  Empty general catch clause suppresses any errors
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:445  HeuristicUnreachableCode  Code is heuristically unreachable
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:445  HeuristicUnreachableCode  Code is heuristically unreachable
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:448  HeuristicUnreachableCode  Code is heuristically unreachable
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:449  HeuristicUnreachableCode  Code is heuristically unreachable
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:456  HeuristicUnreachableCode  Code is heuristically unreachable
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:456  HeuristicUnreachableCode  Code is heuristically unreachable
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:457  HeuristicUnreachableCode  Code is heuristically unreachable
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:457  HeuristicUnreachableCode  Code is heuristically unreachable
Assets/DCL/Infrastructure/Global/Dynamic/RealmLaunchSettings.cs:39  InconsistentNaming  Name 'EditorSceneStartPosition' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'editorSceneStartPosition'.
Assets/DCL/Infrastructure/Global/Tests/EditMode/RealmLaunchSettingsShould.cs:229  InconsistentNaming  Name 'IgnoreMacOSRealmInvalidation' does not match rule 'members_should_be_pascal_case'. Suggested name is 'IgnoreMacOsRealmInvalidation'.
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:846  InconsistentNaming  Name 'baseUrl' does not match rule 'members_should_be_pascal_case'. Suggested name is 'BaseUrl'.
Assets/DCL/Infrastructure/Global/Dynamic/RealmLaunchSettings.cs:33  InconsistentNaming  Name 'enabled' does not match rule 'members_should_be_pascal_case'. Suggested name is 'Enabled'.
Assets/DCL/Infrastructure/Global/Dynamic/RealmLaunchSettings.cs:34  InconsistentNaming  Name 'parcels' does not match rule 'members_should_be_pascal_case'. Suggested name is 'Parcels'.
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:598  ParameterHidesMember  Parameter 'splashScreen' hides field 'Global.Dynamic.MainSceneLoader.splashScreen'
Assets/DCL/Infrastructure/Global/Dynamic/RealmLaunchSettings.cs:53  UnusedMember.Local  Field 'portableExperiencesEnsToLoadAtGameStart' is never used
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:97  UnusedMember.Local  Method 'Awake' is never used
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:168  UnusedMember.Local  Method 'OnApplicationQuit' is never used
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:102  UnusedMember.Local  Method 'OnDestroy' is never used
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:401  VariableHidesOuterVariable  Parameter 'ct' hides outer parameter with the same name
Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs:413  VariableHidesOuterVariable  Parameter 'ct' hides outer parameter with the same name

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Tests: 24382 passed, 0 failed

@dalkia dalkia changed the title fix: allow local-ab and host-pinned local-ab-port deep-link params for loopback realms fix: allow local-ab deep-link param for loopback realms, deriving the assets base from the realm Jul 29, 2026

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

PR Review — #9519 fix: allow local-ab deep-link param for loopback realms, deriving the assets base from the realm

STEP 2 — Root-cause check: ✅ PASS

The problem: Creator Hub / sdk-commands preview deep links need to enable local asset bundles, but local-ab was not allowlisted for deep links, and the full-URL override (optimized-assets-url) is deliberately never-permitted because it rewrites AB/LOD/registry endpoints globally with an attacker-controllable URL. The previous fallback also hardcoded port 5147, coupling the client to abgen's default port.

The fix addresses the root cause correctly:

  • Adds the boolean local-ab to the loopback-only tier (no URL data flows through it).
  • Derives the optimized-assets base from the realm itself ({realm}/optimized-assets), so the preview server proxies its abgen sidecar and the client needs no second origin.
  • Keeps optimized-assets-url in the never-permitted tier, now pinned by an explicit test.

This is a cause fix, not a symptom workaround.

STEP 3 — Design & integration: ✅ PASS

No new long-lived units introduced. The only new member is LocalAssetBundlesBaseUrl() — a pure derived method on RealmLaunchSettings that reads existing fields (customRealm, isLocalSceneDevelopmentRealm, initialRealm). No persistent state, no lifecycle management.

Owner search:

  • customRealm is owned by RealmLaunchSettings.SetLocalSceneDevelopmentRealm() / SetCustomRealm() — the same class.
  • useLocalAssetBundles is set in ParseRealmAppParameter() — the same class.
  • The URL derivation logically belongs here: RealmLaunchSettings owns both the realm and the local-AB flag, so deriving the URL from them is cohesive.
  • MainSceneLoader.InitializeFlowAsync() is the existing wiring point where cliOptimizedAssetsUrl is resolved and passed to GatewayUrlsSource — the one-line change (line 236) correctly calls the settings object rather than inlining the derivation.

Precedence chain: CLI --optimized-assets-url > LocalAssetBundlesBaseUrl() > null (falls through to feature flags / default CDN). Verified correct — matches the established cliGatekeeperUrl pattern on the adjacent line.

Allowlist tier placement: local-ab fits the loopback-only tier cleanly. It's a pure boolean whose effect (enable AB loading from {realm}/optimized-assets) is gated on the same loopback validation the tier already enforces. The dangerous full-URL variant stays never-permitted.

OPTIMIZED_ASSETS_PATH constant: Placed on RealmLaunchSettings where its sole consumer (LocalAssetBundlesBaseUrl()) lives — correct. If other subsystems needed it, extraction would be warranted, but currently unnecessary.

Teardown / consumption trace: No subscriptions, callbacks, connections, or disposables are added. N/A.

STEP 4 — Member audit: ✅ PASS

LocalAssetBundlesBaseUrl() (RealmLaunchSettings.cs:107) — 2 consumers: MainSceneLoader.InitializeFlowAsync() (runtime) and RealmLaunchSettingsShould tests (2 test methods). Not a single-use merge candidate — it encapsulates URL derivation from data owned by RealmLaunchSettings; the caller (MainSceneLoader) should not contain this logic. Returns string? — null for the non-applicable case (not local-scene-dev), which the caller handles correctly (falls through to downstream defaults). No absent ≠ false/null concern.

OPTIMIZED_ASSETS_PATH (RealmLaunchSettings.cs:28) — Used in LocalAssetBundlesBaseUrl() and in the serialized [Tooltip]. Appropriate as a named constant replacing the previous magic port.

STEP 5 — Line-level review: ✅ No issues found

Pass A (blocking issues): None found.

  • No CLAUDE.md violations, no bugs, no security vulnerabilities, no performance issues, no missing error handling, no resource leaks, no detached async, no nullability violations, no false-intent conditions.
  • TrimEnd('/') in LocalAssetBundlesBaseUrl() correctly prevents double-slash; tested with trailing-slash case.
  • IRealmNavigator.LOCALHOST ("http://127.0.0.1:8000") has no trailing slash — no issue.
  • Path-prefix audit confirmed: downstream consumers (URLBuilder.AppendDomain, URLDomain.Append, ComposeRegistryUrl string interpolation) all compose paths verbatim onto the base. AppendDomainWithReplacedPath is only used with the Lambdas URL, never with optimized-assets endpoints.

Pass B (design/encapsulation smells): None found.

  • No construction smell, no naming issues, no over-explained comments (the extensive security rationale is justified for an allowlist boundary), no magic values, no resource lifecycle concerns.

Security Review: ✅ No security issues found

  • Loopback gate: Uri.TryCreate + Uri.IsLoopback covers 127.0.0.1, localhost, [::1]. Remote-realm deep links correctly drop local-ab.
  • Boolean-only consumption: local-ab is consumed via HasFlag() — a presence check, not a value read. No path to inject a URL.
  • URL derivation: LocalAssetBundlesBaseUrl() constructs customRealm.TrimEnd('/') + "/optimized-assets" — the only input is the realm that the loopback gate already validated. No attacker-controllable data.
  • @ URI bypass check: .NET Uri parses http://127.0.0.1:8000@evil.example with evil.example as host (IsLoopback = false) — not a bypass vector.
  • optimized-assets-url stays never-permitted: Confirmed absent from both PERMITTED_KEYS and LOOPBACK_REALM_PERMITTED_KEYS; pinned by DeepLinkDropsExecAndInfraParamsEvenForLoopbackRealm test.
  • No hardcoded secrets, credentials, or injection vectors.
  • Known residual (documented): The override outlives realm changes in-session — pre-existing behavior, and strictly narrower exposure than the previously-accepted mcp-port since it can only point at the loopback realm the link targeted.

STEP 6 — Complexity

SIMPLE — The changes are straightforward: one boolean added to an existing allowlist set, one URL derivation method replacing a hardcoded constant, and corresponding tests. No ECS systems, async flows, plugin wiring, or cross-assembly interface changes.

STEP 7 — QA assessment

QA_REQUIRED: YES — Changes affect how the client derives optimized-assets URLs during local scene development (runtime code under Explorer/).

STEP 8 — Non-blocking warnings

⚠️ Branch targets main instead of dev. The standard branch model for unity-explorer targets dev. The hotfix/ prefix suggests this is intentional — verify with the team.

Test Coverage

Comprehensive:

  • DeepLinkKeepsLocalAbForLoopbackRealm — boolean survives for loopback realm
  • DeepLinkDropsLocalAbForRemoteRealm — boolean dropped for remote realm
  • DeepLinkDropsExecAndInfraParamsEvenForLoopbackRealmoptimized-assets-url pinned as never-permitted
  • DeriveLocalAssetBundlesBaseUrlFromRealm — parametrized with trailing-slash and different loopback forms
  • NotDeriveLocalAssetBundlesBaseUrlOutsideLocalSceneDevelopment — null for non-local-dev mode

REVIEW_RESULT: PASS ✅
COMPLEXITY: SIMPLE
COMPLEXITY_REASON: Adds a boolean to an existing deep-link allowlist set and replaces a hardcoded port constant with a realm-derived URL method — straightforward config/infrastructure change.
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by Juan Ignacio Molteni (<@U03JSUQ5Z7U>) via Slack

@dalkia dalkia self-assigned this Jul 30, 2026
…ronment

dclenv sat in the loopback-realm tier of the deep-link allowlist. The login
callback and jump-in links that carry it have no realm at all, so that condition
could never pass and the param was always dropped — a client launched from such a
link silently fell back to the default environment (org) even when the flow
started on zone, taking its auth-api, signature web app and identity storage with
it.

Move it to the always-permitted tier. It is safe on its own: a closed
Decentraland-owned enum, parsed with Enum.TryParse where it is consumed and
ignored otherwise, never a URL, so it cannot point the client at attacker
infrastructure. Its worst case, a session in a Decentraland-owned test
environment, is strictly weaker than realm, which is already always-permitted.
The local-development params (local-scene, hub, skip-auth-screen,
landscape-terrain-enabled, multi-instance, mcp, mcp-port, local-ab) stay
loopback-gated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@DafGreco DafGreco 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.

✔️ PR reviewed and approved by QA on both platforms following instructions playing both happy and un-happy path

Regressions for this ticket had been performed in order to verify that the normal flow is working as expected:

  • [ ✔️] Backpack and wearables in world
  • [✔️ ] Emotes in world and in backpack
  • [✔️ ] Teleport with map/coordinates/Jump In
  • [✔️ ] Chat and multiplayer
  • [✔️ ] Profile card

Evidence with --asset-bundles and without the flag

Image Image Image Image

more evidence in the following thread

@Ludmilafantaniella Ludmilafantaniella 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.

✅ Approved. QA'd on Windows and Mac.

Test Steps completed:

  1. ✅ Build launched via deep-link flow, branch confirmed (local-ab-allowlist shown in-game / correct branch on both platforms).
  2. ✅ Bundles conversion confirmed (sidecar logs showing manifest + per-asset conversion + bundle serving).
  3. ✅ Memory comparison in Genesis Plaza:
    • Mac: 7026 MB without --asset-bundles → 6931 MB with (lower, as expected)
    • Windows: 3340 MB without --asset-bundles → 2357 MB with (lower, as expected)
  4. ✅ Degrade path: killed abgen.exe mid-session (Task Manager) — scene kept rendering fine visually, reload (button + chat command) worked without issues.
  5. ✅ Hot reload: verified the sidecar picks up .glb changes and reconverts only the modified asset.
  6. ✅ Wearable caching: wearables/emotes loaded correctly and stayed cached across scene re-entry.

Notes / minor findings (non-blocking):

  • When abgen was killed mid-session, the server terminal logged repeated ECONNREFUSED errors to port 8001 for as long as the sidecar stayed down. Visual degrade path works fine, but wanted to flag the log spam in case it's worth a follow-up.
  • On one Mac run with --asset-bundles active, the avatar's wearables/hair didn't load (bare body) — didn't reproduce consistently, flagging in case it's relevant.

Windows With Asset Bundles:

Image

Windows Without Asset Bundles:

Image

Mac With Asset Bundles:

Image

Mac Without Asset Bundles:

Image

@anicalbano
anicalbano added this pull request to the merge queue Jul 30, 2026
@balducciv

Copy link
Copy Markdown

✅ PR reviewed and approved by QA following the PR test instructions.

Build: v0.161.0-alpha-hotfix/local-ab-allowlist-b83971b (PR #9519, HEAD commit b83971b)
OS: macOS — Apple M3 Pro

Test results:

  • Explorer launched via deep link with local-ab=true set — deep link params confirmed present and correctly parsed
  • Build version in Player.log matches PR HEAD commit across all test runs
  • Bundles confirmed being built — sidecar log shows scene conversion (1009–1013 bundles depending on run) and serving from http://127.0.0.1:8000/optimized-assets
  • Memory comparison — --asset-bundles on sits noticeably lower than off, as expected
  • Degrade path (no --asset-bundles / sidecar killed) — not yet tested by me
  • Hot reload (overwrite a .glb, only that asset reconverts) — attempted multiple times; visual update in-game confirmed, but inconclusive as documented (see notes below)
  • Wearable caching (exit/re-enter, no re-downloads) — not yet tested by me
  • Log into zone works

Unrelated errors noted (do not affect verdict):

  • Recurring CreateGltfAssetFromAssetBundleSystem / AvatarWearablePoses 404 when local-ab is on — reproduces consistently across sessions, only with local-ab=true, and appears tied to a wearable-equip failure observed in-game. Flagged to @dalkia in Slack, awaiting feedback.
  • Hot reload step: visual update happens in-game on GLB swap, but no corresponding per-asset reconversion line appears in the sidecar terminal (unlike the scoped rebuild lines seen during initial cold conversion). Unclear whether this goes through abgen or a raw-GLTF fallback path. Flagged to @dalkia in Slack, awaiting feedback.

Verdict: PASS ✅

Remaining test steps (degrade path, wearable caching) verified by the rest of the QA team — overall team consensus is PASS. The two items above are tracked separately and do not block this PR.

Player asset bundle args 9519.log
Player no asset-bundle args.log
Player replace GLB.log
replace GLB.rtf
scene console asset bundle args test .rtf
scene console no asset bundle.rtf

replace.GLB.mp4

Merged via the queue into main with commit 25aa095 Jul 30, 2026
25 of 26 checks passed
@anicalbano
anicalbano deleted the hotfix/local-ab-allowlist branch July 30, 2026 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants