feat(sharing): enforce sharing config as server-side security#9578
feat(sharing): enforce sharing config as server-side security#9578nojaf wants to merge 8 commits into
Conversation
mode Gate the read_code and export_as_html endpoints behind the existing sharing.wasm and sharing.html config flags respectively. Previously these flags only hid UI buttons, leaving the underlying APIs callable directly. Now the server returns 403 (surfaced as 401 by the error handler) when the relevant flag is false. Also gates the "Create molab notebook" menu item behind sharingWasmEnabled, which was the only sharing action with no visibility control.
Block `marimo export html-wasm` entirely when sharing.wasm = false. Default `--no-include-code` for `marimo export html` when either sharing.wasm or sharing.html is false, so code is absent from the output without requiring per-invocation flags.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Enforces the existing sharing.wasm and sharing.html configuration flags as real server-side controls (not just UI hints) on the marimo edit server endpoints, and hides the corresponding "Create molab notebook" action in the editor when sharing.wasm is disabled.
Changes:
POST /api/kernel/read_codenow returns 403 whensharing.wasm = false.POST /api/export/htmlnow returns 403 whensharing.html = false.- The "Create molab notebook" Share dropdown item is hidden when
sharing.wasmis disabled, matching the existing WebAssembly-link gating.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| marimo/_server/api/endpoints/files.py | Adds a server-side sharing.wasm check in read_code and updates the OpenAPI 403 description. |
| marimo/_server/api/endpoints/export.py | Adds a server-side sharing.html check in export_as_html and updates the OpenAPI 403 description. |
| frontend/src/components/editor/actions/useNotebookActions.tsx | Hides the "Create molab notebook" action when sharingWasmEnabled is false. |
| tests/_server/api/endpoints/test_files.py | Adds a test that read_code is blocked when sharing.wasm = false. |
| # handle_error converts all 403s → 401 to prompt browser re-auth | ||
| assert response.status_code == 401 | ||
| assert "Authorization header required" in response.json()["detail"] |
There was a problem hiding this comment.
No issues found across 4 files
Architecture diagram
sequenceDiagram
participant User as Browser User
participant UI as Editor UI
participant API as Server API
participant Config as Config Manager
participant FS as File System
Note over User,FS: Server-Side Sharing Enforcements
User->>UI: Click "Create molab notebook"
UI->>UI: Check sharingWasmEnabled flag
alt sharing.wasm = false
UI->>UI: Hide action button (no request sent)
else sharing.wasm = true (or unset)
UI->>API: POST /api/kernel/read_code
API->>Config: get_config()
Config-->>API: {sharing: {wasm: true/false, html: true/false}}
alt sharing.wasm = false
API->>API: Return 403 Forbidden
API-->>UI: Error response
else sharing.wasm != false
API->>FS: Read notebook source
FS-->>API: Source code
API-->>UI: Source code
end
end
User->>UI: Click "Export HTML"
UI->>API: POST /api/export/html
API->>Config: get_config()
Config-->>API: {sharing: {html: true/false}}
alt sharing.html = false
API->>API: Return 403 Forbidden
API-->>UI: Error response
else sharing.html != false
API->>FS: Generate HTML export
FS-->>API: HTML content
API-->>UI: Downloaded file
end
Note over User,FS: CLI Export Paths
User->>FS: Run marimo export html-wasm
FS->>Config: Resolve sharing config
alt sharing.wasm = false
FS->>FS: Raise UsageError, abort
else sharing.wasm != false
FS->>FS: Generate WASM export
end
User->>FS: Run marimo export html
FS->>Config: Resolve sharing config
alt sharing.html = false OR sharing.wasm = false
FS->>FS: Auto-apply --no-include-code
else sharing controls enabled
FS->>FS: Use user-provided --include-code flag
end
FS->>FS: Generate HTML with/without source code
Note over User,FS: Config Resolution Chain
User->>Config: Provide pyproject.toml
User->>Config: Provide user config (~/.config/marimo/marimo.toml)
User->>Config: Provide env vars
Config->>Config: Merge configs (project > user > env)
Config-->>API: Resolved sharing settings
Config-->>FS: Resolved sharing settings
|
thanks @nojaf, the PR description might be outdated as it references the CLI. i do think we should not change that codepath anyways though |
|
@mscolnick ah sorry, I did not push my second commit it seems. |
enforcement Injects sharing.wasm = false and sharing.html = false into the config resolution chain via EnvConfigManager when set, taking precedence over all per-project and per-user config. Intended for devpod/container environments where infra sets the variable and all notebooks inherit it without requiring per-project configuration.
|
|
button Rather than blocking CLI export commands (which are local operations), thread the resolved sharing config through all export paths so it is embedded in the generated HTML. The static banner reads sharing.wasm from the embedded config and conditionally hides the "Open in molab" button. This prevents a hosted WASM or static HTML file from offering an easy one-click path to send source code to an external service. Covers all export paths: CLI (export_as_wasm, run_app_then_export_as_wasm, run_app_then_export_as_html), server endpoint, and picks up MARIMO_RESTRICT_SHARING automatically via EnvConfigManager.
|
@mscolnick I removed the env var so that can be added later. |
Bundle ReportChanges will increase total bundle size by 98.18kB (0.39%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: marimo-esmAssets Changed:
Files in
Files in
|
|
This breaks the "Download as Python" flow, which is not exactly sharing. This is not hidden in the UI and would throw a server error. |
The read_code endpoint is used by several local operations (Download Python code, Copy code to clipboard) that have nothing to do with external sharing. Blocking it via sharing.wasm broke those flows with an unexpected server error while the buttons remained visible in the UI. Only the HTML export endpoint warrants a server-side sharing gate, since it is a server-driven operation. The wasm/molab sharing flow constructs URLs entirely client-side and is already gated at the UI layer.
Exporting != sharing. Blocking POST /api/export/html when sharing.html is false prevented legitimate local downloads (Download as HTML) with no security benefit, since the user already has the source in the editor. The sharing config is still baked into exported HTML so the static banner's molab button respects the flag. Enforcement stays at the UI layer, which is what the maintainer intended.
|
@mscolnick, right, I removed those checks in the api endpoints. |
📝 Summary
Closes #6318
This PR makes the existing
sharing.wasmandsharing.htmlconfiguration flags control what the UI surfaces, so users cannot accidentally share notebook source code through molab or WebAssembly links. Previouslysharing.wasmonly hid "Create WebAssembly link" — the "Create molab notebook" button was left fully ungated, and exported HTML files showed the "Open in molab" button regardless of config.What changed
marimo edit(live server)Share → Create molab notebookwas not gated at all. It now respectssharing.wasm = falseand is hidden alongside the "Create WebAssembly link" item.Exported HTML files (static and WASM)
The sharing config is now baked into the
user_configJSON embedded in exported HTML. TheStaticBannercomponent readssharing.wasmfrom this embedded config and conditionally hides the "Open in molab" button. This means a hosted WASM notebook or a shared static HTML file will not offer an easy one-click path to send source code to an external service when the flag is set.Important limitation: hiding the molab button is a convenience measure, not a security control. A WASM HTML file always embeds the full source code (Pyodide requires it to run the notebook). Anyone who receives the file can read the source directly from the HTML, and can trivially reconstruct a molab URL themselves — the lz-string compression used in those URLs is a well-known open-source scheme, reversible with any standard lz-string library. Blocking
molab.marimo.ioat the network level prevents the link from being opened in molab but does not prevent the source from being extracted from the file and shared by other means.Configuration
Per-project or per-user — committed
pyproject.tomlor user config:Compliance
The risk
Several marimo features encode the full notebook source code and make it trivially accessible outside the organisation:
lz-stringand embed it in a URL hash fragment (https://marimo.app/#code/...). The encoding is not encryption — it is a well-known open-source compression scheme that any developer can decode in seconds with freely available tools.For organisations with proprietary notebooks, model code, or data pipelines, these paths can result in unintended IP disclosure — including accidental sharing by well-meaning users who don't realise what the link or file contains.
Honest threat model
These controls reliably prevent accidental disclosure and enforce policy for compliant users. They are not designed to stop a determined insider who has shell access to their own environment. Exporting to HTML or downloading Python code remains unrestricted — exporting is not sharing.
For defence-in-depth, pair this config with network egress filtering: block outbound traffic to
marimo.app,molab.marimo.io, andstatic.marimo.appat the infrastructure level. That control is independent of marimo and cannot be bypassed by editing a config file.Deployment recommendation
Commit
[tool.marimo.sharing] wasm = falsetopyproject.tomlin repositories that contain proprietary notebooks. Pair with network egress filtering on the external marimo domains for defence-in-depth.📋 Pre-Review Checklist
✅ Merge Checklist