CVE-2026-15719
Overview
Background
- `nsDocShellLoadState`
- the object that packages up all parameters of a navigation (target
mURI,mTriggeringPrincipal,mTriggeringRemoteType, flags) and is passed across processes to drive a docshell load. - `javascript:` scheme
- a URI scheme whose “load” executes script in the context of the target document rather than fetching a resource.
- `mTriggeringRemoteType`
- the identifier of the process that initiated the load (
NOT_REMOTE_TYPEmeaning the trusted parent process, any other value meaning a specific content child process). - IPC deserialization constructor
- the
nsDocShellLoadStateconstructor variant that takes anaActorand rebuilds the load state from data sent by a child process, so its fields are attacker-influenced.
Root Cause Analysis
The vulnerable path is the nsDocShellLoadState::nsDocShellLoadState constructor that reconstructs a load request from IPC data supplied by a content process via aActor.
Before the fix, this constructor validated the triggering principal but never checked the combination of a javascript: target URI with a triggering remote type belonging to a content process, so a javascript: load could be attributed to and execute across a process boundary. The violated invariant is that a javascript: “navigation” must remain intra-process — it must run only in the same content process that legitimately triggered it — because it executes script directly against the target document.
The fix adds an explicit guard: if mURI->SchemeIs("javascript") and mTriggeringRemoteType != NOT_REMOTE_TYPE, the parent treats the message as malformed, calls aActor->FatalError, and aborts. This works because a genuine same-process javascript: load never needs to cross a process boundary, so rejecting cross-process javascript: loads outright closes the injection channel without breaking legitimate flows.
javascript: execution across processes without verifying that such a scheme should never carry a foreign mTriggeringRemoteType. The fix rejects any javascript: load whose triggering remote type is not NOT_REMOTE_TYPE, restoring the intra-process invariant for script-executing schemes.Attack Path
- Compromise a content process The attacker gains code execution in a sandboxed content process (for example via a separate renderer bug).
- Forge a load request
From that process, craft an IPC message that constructs an
nsDocShellLoadStatewith ajavascript:mURIand amTriggeringRemoteTypenaming a different process/context. - Bypass the missing check The parent’s deserialization constructor accepts the request because it only validated the principal, not the scheme-versus-remote-type combination.
- Cross-process script injection
The
javascript:load is executed in a target document belonging to a different origin or process, running attacker-controlled script there. - Escalate The injected script operates with the target document’s privileges, defeating same-origin and process isolation boundaries.
Impact Assessment
javascript: in another origin’s or process’s document context, a universal cross-site-scripting / process-isolation bypass driven from the privileged parent’s load logic. The critical precondition is prior code execution or message-forging capability in a content process; the flaw itself lives in the trusted parent process that arbitrates cross-process loads. This turns a single compromised renderer into cross-origin script injection, which is why the impact is rated critical and public exploit code exists.Files Changed
docshell/base/nsDocShellLoadState.cpp
Audit Directions
- Scheme-sensitive IPC validationAudit every parent-side deserialization constructor and IPC handler that consumes a child-supplied URI for schemes with side effects (
javascript:,data:,blob:) and confirm the scheme is checked against the triggering process, not just the principal. - Remote-type invariantsSearch for uses of
mTriggeringRemoteTypeandGetEffectiveTriggeringRemoteTypeand verify thatNOT_REMOTE_TYPEversus content-process values is enforced wherever a load can execute script rather than fetch a resource. - Cross-process load state trustReview all fields of
nsDocShellLoadStatereconstructed from IPC for combinations that are only valid intra-process, ensuring the parent rejects rather than silently honors attacker-influenced cross-process attributions.
Patch
diff --git a/docshell/base/nsDocShellLoadState.cpp b/docshell/base/nsDocShellLoadState.cpp
index 957c4db74f1..ea31776de66 100644
--- a/docshell/base/nsDocShellLoadState.cpp
+++ b/docshell/base/nsDocShellLoadState.cpp
@@ -162,6 +162,12 @@ nsDocShellLoadState::nsDocShellLoadState(
return;
}
+ if (mURI->SchemeIs("javascript") &&
+ mTriggeringRemoteType != NOT_REMOTE_TYPE) {
+ aActor->FatalError("Illegal cross-process javascript: load attempt");
+ return;
+ }
+
if (!ValidatePrincipalCouldPotentiallyBeLoadedBy(
mTriggeringPrincipal, GetEffectiveTriggeringRemoteType(),
{ValidatePrincipalOptions::AllowExpanded,