[fix] Reject network paths and null bytes in st.Page - #16146
Conversation
Validate page paths before filesystem access so Windows cannot initiate an SMB connection when resolving UNC/network paths (SSRF and NTLM hash disclosure). The lexical UNC check normalizes mixed separators (/\, \/) that Windows treats as a UNC root, and null-byte paths are rejected on all platforms.
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
✅ PR preview is ready!
|
Added Agent Docs
|
|
| Filename | Overview |
|---|---|
| lib/streamlit/navigation/page.py | Validates null bytes and Windows network paths before resolving or inspecting page files. |
| lib/streamlit/path_security.py | Adds a shared lexical helper for detecting Windows UNC and device-namespace path prefixes. |
| lib/tests/streamlit/navigation/page_test.py | Covers early rejection of unsafe paths and continued acceptance of supported local paths. |
| lib/tests/streamlit/path_security_test.py | Covers UNC detection across slash variants and local-path exclusions. |
Reviews (4): Last reviewed commit: "Address AI review nits in st.Page path t..." | Re-trigger Greptile
There was a problem hiding this comment.
Summary
This PR hardens st.Page() path handling by adding pre-filesystem validation that rejects Windows UNC/network paths (e.g. \\server\share, //server/share, and mixed-separator variants) and null-byte paths on all platforms. On Windows, resolving a UNC path can trigger an SMB connection to an attacker-controlled host, enabling SSRF and NTLM hash disclosure. A new is_windows_unc_path() helper is factored into path_security.py and shared with the existing is_unsafe_path_pattern() function for DRY reuse.
Reviewer agreement: Both reviewers (claude-4.6-opus-high-thinking and gpt-5.3-codex-high) approved this PR unanimously with no merge-blocking issues identified.
Code Quality
Both reviewers agree the implementation is clean, focused, and well-structured:
- Correct placement: Validation runs after the URL check but before any
Path.resolve()/Path.is_file()call — exactly where it must be to prevent the attack vector. - Minimal surface: The
is_windows_unc_path()helper is a concise lexical check that normalizes forward slashes before checking, covering all four mixed-separator combinations (\\,//,\/,/\). - Platform-scoped: The UNC check is correctly gated behind
env_util.IS_WINDOWS. On POSIX,//server/shareis a valid (if unusual) path with no SMB auto-connect behavior. - DRY refactor:
is_unsafe_path_pattern()now delegates tois_windows_unc_path()instead of duplicating the prefix check, and gains coverage of previously-missed mixed-separator variants. - Import style: Follows project conventions for utility module imports.
No code quality issues identified by either reviewer.
Test Coverage
Both reviewers confirm strong, comprehensive test coverage:
path_security_test.py: Parametrized positive tests for all UNC variants (backslash, forward slash, mixed, extended\\?\UNC\..., device namespace\\.\device\...) and negative tests for local paths (POSIX absolute, Windows drive, rooted, relative).page_test.py: End-to-end unit tests that verify UNC paths are rejected withresolve.assert_not_called()(confirming no filesystem access), null bytes are rejected before filesystem access, non-Windows platforms still accept//paths, and absolute local paths still work (backwards-compat regression guard).- Edge cases: Path objects (not just strings), extended UNC, and device namespace paths are all covered.
Both reviewers agree the absence of E2E tests is appropriate — the behavior is Windows-specific SMB interaction that cannot be meaningfully exercised by the Linux-based e2e harness.
Backwards Compatibility
Both reviewers agree there are no breaking changes:
- UNC paths on Windows: Previously would have attempted (and likely failed or hung) an SMB connection. Now fails fast with a clear
StreamlitAPIException. No legitimate use case is lost. - Null bytes: Previously caused a raw
ValueErrorfrom the OS; now raises a friendlierStreamlitAPIException. No legitimate user code is affected. - Absolute/drive-local paths: Explicitly still allowed, tested by
test_allows_absolute_local_paths. - POSIX behavior: Completely unchanged; the UNC check is gated behind
IS_WINDOWS.
Security & Risk
Both reviewers agree this is a well-scoped security hardening PR that closes a real attack vector (SNOW-3715977):
- Threat model: An attacker supplies a UNC path to force the server process to connect to an attacker-controlled SMB share, leaking NTLM credentials.
- Fix correctness: The lexical check runs before any
Pathmethod that triggers filesystem resolution — the correct approach. - Defense in depth: The null-byte check prevents path truncation attacks on any platform.
- No new dependencies, external requests, or attack surface introduced.
- Residual risk: Low. The rejected inputs were never valid page paths.
External test recommendation
- Recommend external_test: No
- Triggered categories: None
- Evidence:
lib/streamlit/navigation/page.py: Pure Python input validation; no route, websocket, auth, cookie, storage, or embedding boundary changes.lib/streamlit/path_security.py: Utility-level lexical path classification; no runtime networking or cross-origin behavior changes.- Test files: Unit-only coverage additions matching backend validation scope.
- Suggested external_test focus areas: None required. Optional follow-up: manual Windows validation of UNC rejection in a real host environment.
- Confidence: High (both reviewers agree)
- Assumptions and gaps: Assessment is diff-based and does not include runtime execution in a Windows environment.
Accessibility
No frontend/UI changes — not applicable. Both reviewers agree.
Readability
Both reviewers found no readability issues across all changed files:
lib/streamlit/path_security.py: Theis_windows_unc_pathdocstring is clear, explains intent (lexical check before filesystem ops), and documents the mixed-separator rationale concisely.lib/streamlit/navigation/page.py: Inline comments explain why the check exists (SMB credential disclosure) and why absolute paths are still allowed (public API contract). No rewrite needed.- Test files: Test names are descriptive and self-documenting (
test_rejects_windows_network_paths_before_resolving,test_rejects_null_byte_paths_on_all_platforms, etc.). Docstrings add appropriate context. - PR title: Clear, specific, format-compliant (
[fix] Reject network paths and null bytes in st.Page). - PR description: Well-structured with security rationale, scope documentation, and testing plan.
One optional polish suggestion (from gpt-5.3-codex-high): Expand security acronyms (SMB, SSRF, NTLM) on first use in the PR description for broader reviewer readability.
Recommendations
No merge-blocking issues. Two optional, non-blocking observations:
- (Optional) Consider adding a
st.Pagetest case for a\\.\device\...input path at the API entrypoint level, mirroring the helper-level device namespace coverage inpath_security_test.py. (gpt-5.3-codex-high) - (Optional) The
test_allows_absolute_local_pathstest usesPath.cwd(), coupling test behavior to the runner's working directory. This is fine in practice since class-level mocking ensuresis_file()returns True regardless, but worth noting. (claude-4.6-opus-high-thinking)
Verdict
APPROVED: Well-crafted security fix with correct placement of validation, comprehensive test coverage, no backwards-compatibility risk, and clean code structure. Both reviewers unanimously approved with no merge-blocking issues.
| Model | Status | Verdict |
|---|---|---|
claude-4.6-opus-high-thinking |
Completed | APPROVED |
gpt-5.3-codex-high |
Completed | APPROVED |
This is a consolidated AI review by claude-4.6-opus-high-thinking, synthesizing reviews from 2 models. Please verify the feedback and use your judgment.
Patch streamlit.env_util.IS_WINDOWS directly instead of via streamlit.navigation.page.env_util. On Python 3.10, unittest.mock's dotted-path importer resolves streamlit.navigation to the st.navigation function rather than the subpackage module, raising AttributeError.
Cover the \\.\device\ namespace spelling at the st.Page API entrypoint, mirroring the helper-level coverage in path_security_test.py, per AI review suggestion.
|
Addressed review feedback:
|
There was a problem hiding this comment.
Summary
This PR hardens st.Page() path handling to prevent two classes of attack when Streamlit runs on Windows:
- UNC/network path rejection: Paths like
\\server\share\page.py(and all mixed-separator variants) are rejected beforePath.resolve()orPath.is_file()can initiate an SMB connection, preventing SSRF and NTLM credential disclosure. - Null byte rejection: Paths containing
\x00are rejected on all platforms with a clearStreamlitAPIExceptioninstead of a rawValueError.
The UNC detection is factored into a shared is_windows_unc_path() helper in path_security.py, which is also used by the existing is_unsafe_path_pattern() function (replacing an inline startswith check).
Reviewer consensus: Both reviewers (claude-4.6-opus-high-thinking and gpt-5.3-codex-high) approved the PR unanimously. All expected models completed their reviews successfully.
Code Quality
Both reviewers agree the implementation is clean, well-structured, and follows existing codebase patterns:
- Good factoring: The
is_windows_unc_path()helper is a pure lexical check that normalizes/to\before checking for\\prefix, correctly catching all four mixed-separator spellings. - Correct placement: Validation runs before any filesystem access (
Path.resolve(),Path.is_file()), which is the critical security invariant. - Defense in depth: The UNC check is gated on
env_util.IS_WINDOWSinpage.py(only Windows auto-initiates SMB), whileis_unsafe_path_pattern()applies it unconditionally (appropriate for its stricter use in component file serving). - Preserves public API contract: Absolute local paths (e.g.,
C:\Users\...) remain allowed, as documented in thest.PageAPI. - Behavior-preserving refactor: The refactoring of
is_unsafe_path_pattern()to useis_windows_unc_path()is behavior-preserving — the mixed-separator patterns newly caught by the helper were already caught by the subsequentpath.startswith(("\\", "/"))check.
No merge-blocking code quality issues found.
Test Coverage
Both reviewers agree test coverage is thorough and well-designed:
path_security_test.py: NewTestIsWindowsUncPathclass with parametrized positive cases (6 UNC variants including mixed separators and device namespaces) and negative cases (5 local path types).page_test.py: Tests for UNC rejection (7 variants includingPathobjects), null byte rejection (string andPath), an explicit regression test for allowing network-style paths on non-Windows, and a regression test for allowing absolute local paths.- Anti-regression: Tests assert
resolve.assert_not_called()to verify the security invariant that no filesystem access occurs before validation. - No E2E tests: Both reviewers agree this is the correct decision — the behavior is Windows-SMB-specific and cannot be meaningfully exercised by the Linux-based E2E harness; unit tests with
IS_WINDOWSpatched are the correct layer.
Noted gap (non-blocking): Behavior is simulated via patched env_util.IS_WINDOWS in Linux CI, not exercised on a real Windows runtime. Both reviewers acknowledge this as an acceptable residual limitation.
Backwards Compatibility
Both reviewers agree there are no breaking changes:
- Non-Windows platforms: The UNC check only fires when
env_util.IS_WINDOWSisTrue. POSIX paths like//server/share/page.pyremain valid on Linux/macOS (confirmed bytest_allows_network_style_paths_on_non_windows). - Windows absolute local paths:
C:\Users\...\page.pyand similar drive-local paths are still allowed (confirmed bytest_allows_absolute_local_paths). - Null bytes: Were never valid in practice (the OS would reject them), but now produce a clearer
StreamlitAPIExceptioninstead of a rawValueError.
Security & Risk
Both reviewers agree the security fix is sound and addresses a real vulnerability:
- Threat model: On Windows,
Path.resolve()on a UNC path triggers an SMB connection, which can leak the server process's NTLM hash to an attacker-controlled host. - Fix correctness: The lexical check runs before any filesystem operation, preventing the SMB connection entirely.
- Mixed-separator handling: The slash normalization (
path.replace("/", "\\")) correctly catches all four mixed-separator spellings that Windows resolves as UNC roots. - No new dependencies or external requests: The fix is pure string validation.
- Residual risk: Low; main uncertainty is Windows-runtime validation depth in CI.
External test recommendation
- Recommend external_test: No
- Triggered categories: None
- Evidence:
lib/streamlit/navigation/page.py: Pure input validation added before filesystem access; no routing, auth, WebSocket, embedding, asset serving, cross-origin, or header changes.lib/streamlit/path_security.py: Internal helper refactoring with no network or runtime impact.- Test files are test-only updates validating backend input handling.
- Suggested external_test focus areas: None — the change is a pre-I/O validation gate with no effect on externally observable behavior.
- Confidence: High
- Assumptions and gaps: None — the change is strictly additive input validation in the Python backend.
Both reviewers independently reached the same conclusion on external test risk.
Accessibility
No frontend changes; accessibility is not affected. Both reviewers agree.
Readability
lib/streamlit/path_security.py
is_windows_unc_path docstring (line 36): Clear and well-structured. Correctly explains the normalization strategy, the variants caught, and the security rationale. No changes needed. (Both reviewers agree.)
Inline comment at line 87: Still accurate after the refactoring. No changes needed.
lib/streamlit/navigation/page.py
Comment at lines 339–342: One reviewer (GPT) noted the wording says "Reject UNC paths" while the guard also rejects device-namespace patterns, and suggested rewording to "Reject Windows network/device-namespace paths." The other reviewer (Claude) found the comment clear and sufficient. Resolution: The current wording is acceptable — UNC paths encompass device namespaces (\\?\, \\.\) in Windows terminology, and the comment's focus on the security rationale (SMB credential disclosure) is more important than enumerating every sub-category. This is a non-blocking style preference.
lib/tests/streamlit/navigation/page_test.py
Test names and docstrings are descriptive and self-explanatory across both test files. No changes needed.
PR title and description
Both reviewers agree the PR title and description are clear, concise, and follow expected conventions. No rewrite needed.
Recommendations
-
Consider removing the
@patch("streamlit.env_util.IS_WINDOWS", False)decorator ontest_rejects_null_byte_paths_on_all_platforms(line 119 ofpage_test.py). The null byte check runs before the UNC check and doesn't depend onIS_WINDOWS, so this patch is unnecessary. Removing it would make the "all platforms" claim in the test name more accurate. (Raised by Claude; non-blocking.) -
Consider adding an explicit test for device-namespace paths like
\\?\C:\...to document whether they are expected to be allowed or rejected, locking in intent for future maintainers. (Raised by GPT; non-blocking.) -
Consider improving the assertion in
test_allows_network_style_paths_on_non_windows(line 137 ofpage_test.py). The currentassert st.Page(...) is not Noneis trivially true — the real assertion is that no exception is raised. Either drop the assertion or assert a meaningful property. (Raised by Claude; non-blocking nitpick.)
Verdict
APPROVED: A well-executed security fix that correctly prevents UNC-based SSRF/NTLM credential disclosure and null-byte path attacks in st.Page(), with thorough test coverage, no breaking changes, and clean code quality. Both reviewers approved unanimously with only minor, non-blocking suggestions.
This is a consolidated AI review by claude-4.6-opus-high-thinking, synthesizing reviews from claude-4.6-opus-high-thinking and gpt-5.3-codex-high.
This review also includes 2 inline comment(s) on specific code lines.
- Drop the unnecessary IS_WINDOWS patch on the null-byte test so the "all platforms" claim is genuine (the null-byte check runs before the Windows-gated UNC check). - Assert a meaningful property in the non-Windows network-path test instead of the trivially-true `is not None`. - Add an explicit extended-length local path (\\?\C:\...) case to the is_windows_unc_path test to lock in the fail-closed intent.
There was a problem hiding this comment.
Summary
This PR hardens st.Page() path handling by adding two pre-filesystem-access validation checks:
- UNC/network path rejection (Windows only): Before
Path.resolve()orPath.is_file()can trigger an SMB connection to an attacker-controlled host, paths that look like Windows UNC paths (e.g.\\server\share,//server/share, and mixed-separator variants) are rejected with a clearStreamlitAPIException. The check is gated onenv_util.IS_WINDOWS. - Null-byte rejection (all platforms): Paths containing
\x00are rejected with a descriptive error instead of letting Python raise a rawValueError.
The UNC detection is factored into a shared is_windows_unc_path() helper in path_security.py, also reused by the existing is_unsafe_path_pattern() function.
Reviewer consensus: Both reviewers (claude-4.6-opus-high-thinking and gpt-5.3-codex-high) approved this PR unanimously with no critical or blocking issues identified.
Code Quality
Both reviewers agreed the implementation is clean, focused, and follows existing patterns:
- The
is_windows_unc_path()helper is a pure lexical check — easy to test and reason about. - It is correctly reused in both
st.Page.__init__andis_unsafe_path_pattern, eliminating duplication. - Validation is placed at exactly the right point — after the external-URL early return, before any filesystem access.
- Import style follows codebase conventions.
- Error messages are specific and user-facing.
No code quality issues were raised by either reviewer.
Test Coverage
Both reviewers found unit test coverage strong and comprehensive:
path_security_test.py: 7 positive UNC cases (backslash, forward-slash, mixed, extended-length, device-namespace) and 5 negative cases. Usespytest.mark.parametrizecorrectly.page_test.py: 7 parameterized UNC-rejection cases withIS_WINDOWSpatched, 2 null-byte cases, 1 non-Windows allowance test, and 1 absolute-local-path backwards-compatibility assertion. Includes anti-regression assertions verifyingPath.resolve()is never called.- No E2E tests — both reviewers agreed this is correctly justified since the behavior is Windows-SMB-specific and the Linux-based e2e harness cannot meaningfully exercise it.
Backwards Compatibility
Both reviewers agreed there are no breaking changes for legitimate use cases. The PR only rejects inputs that were previously either:
- Dangerous (UNC paths triggering SMB on Windows — security vulnerability).
- Invalid (null-byte paths would crash with a raw
ValueError).
Absolute local paths (e.g. C:\Users\... or /home/user/...) remain allowed, as tested.
Both reviewers noted the fail-closed treatment of extended-length Windows device paths (\\?\C:\...) — these are also rejected. Both agreed this is an acceptable and intentional security posture. One reviewer (gpt-5.3-codex-high) suggested documenting this explicitly; the other (claude-4.6-opus-high-thinking) noted it is already documented in the implementation.
Security & Risk
Both reviewers confirmed the security posture is improved:
- Prevents unintended SMB/network access and NTLM credential disclosure during path resolution on Windows.
- The helper normalizes
/to\before checking, catching all 4 mixed-separator combinations. - Fail-closed design: any
\\-prefixed input is treated as a network path. - No new attack surface, no new dependencies, no endpoint/auth/session changes.
- Low regression risk — changes are narrowly scoped and purely additive.
External test recommendation
- Recommend external_test: No
- Triggered categories: None
- Evidence:
lib/streamlit/navigation/page.py: Adds pre-filesystem lexical validation — no routing, auth, WebSocket, embedding, CORS, or asset-serving changes.lib/streamlit/path_security.py: Pure lexical helper — no network, server, or browser interaction.
- Suggested external_test focus areas: N/A
- Confidence: High
- Assumptions and gaps: Changes are purely backend input validation with no impact on externally-hosted behavior, iframe embedding, or cross-origin resource loading.
Accessibility
No frontend changes — not applicable. Both reviewers agreed.
Readability
Both reviewers found the code well-documented with clear naming and appropriate comments explaining security rationale.
One minor finding (agreed by both reviewers):
lib/tests/streamlit/path_security_test.pyline 45: The phrase "are also rejected fail-closed" uses "fail-closed" awkwardly as an adverb. Suggested rewrite: "are also rejected as a fail-closed policy: any\\-prefixed input is treated as a network path."
PR title and description: Both reviewers found the title concise and specific, and the description well-structured with clear security rationale and testing plan. One reviewer noted the internal ticket reference (SNOW-3715977) is acceptable for a security fix.
Recommendations
- (Non-blocking) In
path_security_test.pyline 45, consider rewording "fail-closed" for grammatical clarity (see Readability section). - (Non-blocking) Consider adding a brief note in the PR description that the internal ticket reference is for security tracking purposes, to set expectations for external readers.
Verdict
APPROVED: This is a well-scoped, well-tested security hardening fix with unanimous approval from both reviewers. The implementation is clean, test coverage is comprehensive, and there are no backwards-compatibility concerns. The minor suggestions above are non-blocking and can be addressed in follow-up.
| Model | Status | Verdict |
|---|---|---|
| claude-4.6-opus-high-thinking | Completed | APPROVED |
| gpt-5.3-codex-high | Completed | APPROVED |
This is a consolidated AI review by claude-4.6-opus-high-thinking. Please verify the feedback and use your judgment.
This review also includes 1 inline comment(s) on specific code lines.
Describe your changes
Hardens
st.Page()path handling to run validation before any filesystemaccess (
Path.resolve()/Path.is_file()):\\server\share,//server/share,and the mixed-separator spellings
/\...and\/...that Windows alsoresolves as a UNC root). On Windows, resolving such a path would initiate an
SMB connection to an attacker-controlled host, enabling SSRF and NTLM hash
disclosure. Absolute/drive-local paths remain allowed (part of the public
st.Pagecontract).StreamlitAPIExceptioninstead of a rawValueError.The UNC check is factored into a shared
is_windows_unc_path()helper inpath_security.py(reused byis_unsafe_path_pattern).GitHub Issue Link (if applicable)
Internal security ticket: SNOW-3715977
Testing Plan
lib/tests/streamlit/path_security_test.py—is_windows_unc_path(), incl. mixed-separator spellings.lib/tests/streamlit/navigation/page_test.py—st.Pagerejects network paths (assertingresolve()is not called) and null bytes, while still allowing absolute local paths and non-Windows network-style paths.env_util.IS_WINDOWSpatched are the correct layer.Made with Cursor