DYN-10642 Enforce no-network startup behavior for WebView2 surfaces#17212
DYN-10642 Enforce no-network startup behavior for WebView2 surfaces#17212edwin-vasquez-ucaldas wants to merge 25 commits into
Conversation
…ork traffic during startup even when launched with --NoNetworkMode.
…o-network_startup_for_WebView2
… Acceptance criteria
…o-network_startup_for_WebView2
There was a problem hiding this comment.
See the ticket for this pull request: https://jira.autodesk.com/browse/DYN-10642
There was a problem hiding this comment.
Pull request overview
This PR hardens Dynamo’s --NoNetworkMode startup contract by centralizing and applying a WebView2/Edge “no background networking” policy across startup WebView2 surfaces (including SplashScreen), and by gating additional network-adjacent features (connectivity probe allocation, crash report upload, ML pipeline auth wiring) when no-network mode is enabled.
Changes:
- Adds a centralized WebView2 no-network policy (
WebView2Utilities) and applies it across SplashScreen, HomePage, Library, Documentation Browser, and Package Manager wizard WebView2 instances. - Threads
NoNetworkModeinto SplashScreen construction early enough to affect the first WebView2 surface shown at startup; guards connectivity probing allocation and crash-report upload in no-network mode. - Adds unit tests for the centralized WebView2 policy and an explicit system-level network-egress audit test.
Reviewed changes
Copilot reviewed 17 out of 18 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test/System/IntegrationTests/DynamoApplicationTests.cs | Adds an explicit network-egress audit test that inspects TCP connections for the DynamoSandbox process tree. |
| test/DynamoCoreWpfTests/WebView2NoNetworkPolicyTests.cs | New unit tests validating the centralized WebView2 no-network policy behavior and diagnostics. |
| src/LibraryViewExtensionWebView2/LibraryViewController.cs | Applies centralized WebView2 no-network policy when the model is in no-network mode. |
| src/DynamoSandbox/DynamoCoreSetup.cs | Passes no-network mode into SplashScreen so the first WebView2 surface is hardened early. |
| src/DynamoMLDataPipeline/DynamoMLDataPipelineExtension.cs | Skips wiring ML pipeline auth providers when no-network mode is enabled. |
| src/DynamoCoreWpf/Views/SplashScreen/SplashScreen.xaml.cs | Adds a constructor overload to carry no-network state and applies the WebView2 policy before initialization. |
| src/DynamoCoreWpf/Views/PackageManager/Components/PackageManagerWizard/PackageManagerWizard.xaml.cs | Applies no-network policy defensively to the wizard WebView2 surface. |
| src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs | Applies no-network policy to the HomePage WebView2 surface and logs diagnostics via the model logger. |
| src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs | Guards connectivity-check HttpClient allocation and stop-call behind !NoNetworkMode. |
| src/DynamoCoreWpf/Utilities/WebView2Utilities.cs | Introduces the centralized no-network policy API + constant Edge switches string. |
| src/DynamoCoreWpf/Utilities/CrashReportTool.cs | Skips CER crash report upload in no-network mode and logs a warning. |
| src/DynamoCoreWpf/PublicAPI.Unshipped.txt | Registers new public APIs (SplashScreen ctor overload + WebView2Utilities policy APIs/constants). |
| src/DocumentationBrowserViewExtension/DocumentationBrowserViewExtension.cs | Propagates no-network state into the documentation browser view. |
| src/DocumentationBrowserViewExtension/DocumentationBrowserView.xaml.cs | Applies the centralized no-network policy when NoNetworkMode is set. |
| doc/distrib/xml/en-US/ProtoGeometry.XML | Fixes a spelling error (“cetered” → “centered”). |
| doc/distrib/no-network-mode.md | Adds documentation for the --NoNetworkMode startup contract and WebView2 hardening details. |
| AGENTS.md | Links the new no-network mode contract doc for agent guidance. |
| .claude/rules/dynamo-core-rules.md | References the no-network startup contract doc in shared constraints. |
Files not reviewed (1)
- doc/distrib/xml/en-US/ProtoGeometry.XML: Generated file
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…ject. Remove the dependency and restored the previous Win32-based parent-PID
…ne' may silently drop its earlier arguments' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…ttps://github.com/DynamoDS/Dynamo into DYN-10642_Enforce_no-network_startup_for_WebView2
…o-network_startup_for_WebView2
…ng direct this.startPage dereferences from the flagged execution paths in UserControl_Loaded
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…null' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
|
jasonstratton
left a comment
There was a problem hiding this comment.
I have made a few comments indicating that they are low probability. If you feel it is unnecessary to address, you can reply with a comment stating so, but the others require code changes.
|
|
||
| // This test is Explicit because it launches the full DynamoSandbox process tree (including | ||
| // WebView2/Edge child processes) and inspects live OS TCP tables. | ||
| [Test, Explicit("Network-egress audit; run on a clean, isolated machine. See comment above.")] |
There was a problem hiding this comment.
Given this is the only test that actually verifies the no-network contract at the OS/TCP level (the others just check flag propagation), what's the intended cadence for running it — manual pre-release check, or should it be wired into a scheduled/release job?
The explanation for being an explicit test makes sense, but I think it is the only explicit test in Dynamo.
Take a look at the long running tests job, which is not run on every PR check. Or maybe discuss the AGT tests with QA as they launch full sandbox for testing.
| public IntPtr InheritedFromUniqueProcessId; | ||
| } | ||
|
|
||
| private static int GetParentProcessId(int pid) |
There was a problem hiding this comment.
GetParentProcessId returns 0 ("no parent") whenever NtQueryInformationProcess fails for any reason — not only when the process genuinely has no parent.
A protected/access-restricted child (e.g. a WebView2 process running at a different integrity level) would hit this and get misread as parentless.
This would result in terminating walking the process tree and could result in a passing test if one of the missed processes does have a network connection.
| var rows = new List<TcpRow>(); | ||
| int size = 0; | ||
|
|
||
| var result = GetExtendedTcpTable(IntPtr.Zero, ref size, true, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0); |
There was a problem hiding this comment.
I think there is a race condition introduced here. If the system-wide TCP table grows in between the GetExtendedTcpTable() call here and the one below, the second call will return ERROR_INSUFFICIENT_BUFFER again — but the code only checks for result != 0 and throws, instead of reallocating and retrying with the updated size.
The result can be intermittently flaky test failures.
See this article for details:
https://textslashplain.com/2018/08/29/error_insufficient_buffer-and-concurrency/
| var rows = new List<TcpRow>(); | ||
| int size = 0; | ||
|
|
||
| var result = GetExtendedTcpTable(IntPtr.Zero, ref size, true, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0); |
There was a problem hiding this comment.
Similar to the above comment, I think there is a race condition introduced here. If the system-wide TCP table grows in between the GetExtendedTcpTable() call here and the one below, the second call will return ERROR_INSUFFICIENT_BUFFER again — but the code only checks for result != 0 and throws, instead of reallocating and retrying with the updated size.
The result can be intermittently flaky test failures.
See this article for details:
https://textslashplain.com/2018/08/29/error_insufficient_buffer-and-concurrency/
| public IntPtr InheritedFromUniqueProcessId; | ||
| } | ||
|
|
||
| private static int GetParentProcessId(int pid) |
There was a problem hiding this comment.
The only client of this method is GetProcessTreePids(), which already has an open Process. If the argument is changed to take a Process object or the Process.Handle instead of the process id, it would eliminate the redundant GetProcessById() below.
| { | ||
| return 0; | ||
| } | ||
| return pbi.InheritedFromUniqueProcessId.ToInt32(); |
There was a problem hiding this comment.
This is a low probability of resulting in a false positive test result, but certainly plausible and should be addressed.
GetParentProcessId returns pbi.InheritedFromUniqueProcessId as-is, but Windows records that field once at process creation and never invalidates it — it's not verified against the actual current parent. If the original parent process has since exited and its PID gets reused by an unrelated process (PID reuse is common on long-running machines), therefore GetProcessTreePids could graft that unrelated process into the Dynamo tree.
This is a mitigation strategy for this exact PID-recycling problem:
int candidateParentPid = pbi.InheritedFromUniqueProcessId.ToInt32();
try
{
using var parent = Process.GetProcessById(candidateParentPid);
if (parent.StartTime > process.StartTime)
{
return 0; // PID reused by an unrelated, later-started process
}
}
catch (ArgumentException)
{
return 0; // parent PID no longer refers to a running process
}
return candidateParentPid;
| dynamoSandbox.Kill(entireProcessTree: true); | ||
| } | ||
| } | ||
| catch (InvalidOperationException) |
There was a problem hiding this comment.
This situation is low probability of ocurring, but is plausable.
Process.Kill() can throw Win32Exception in addition to InvalidOperationException.
Since the Win32Exception is uncaught inside a finally clause, .NET will replace the in-flight Exception with that new Win32Exception and the test will report a generic process-termination error instead of the list of offending connections, hiding the real failure detail.
The solution is to catch both InvalidOperationException and Win32Exception.



Purpose
Declarations
Check these if you believe they are true
Release Notes
Added one place for Centralized policy that defines and applies the policy, so surfaces can't drift:
public const string NoNetworkAdditionalBrowserArguments— the Edge command-line switches.GetNoNetworkBrowserArguments(bool)— returns the switches or null.ApplyNoNetworkPolicy(CoreWebView2CreationProperties props, bool noNetworkMode, Action<string> logFn = null)— sets props.AdditionalBrowserArguments only when the flag is on, and emits a diagnostic log line (the telemetry-safe diagnostics the ticket asked for). No-op when the flag is off.SplashScreen(bool enableSignInButton, bool noNetworkMode)(the existing SplashScreen(bool enableSignInButton = true) now delegates to it, so the public API is preserved).OnContentRenderedright after building CreationProperties, before Initialize().NetworkUtilities.InitInternetCheck()(which allocates an HttpClient) was called unconditionally. Now guarded behind if (!Model.NoNetworkMode). CheckOnlineAccess() already short-circuits, so behavior is unchanged when the flag is off.Reviewers
@jasonstratton @RobertGlobant20
FYIs
@jnealb