Skip to content

feat(sharing): enforce sharing config as server-side security#9578

Open
nojaf wants to merge 8 commits into
marimo-team:mainfrom
nojaf:secure-shareable-links
Open

feat(sharing): enforce sharing config as server-side security#9578
nojaf wants to merge 8 commits into
marimo-team:mainfrom
nojaf:secure-shareable-links

Conversation

@nojaf
Copy link
Copy Markdown
Contributor

@nojaf nojaf commented May 18, 2026

📝 Summary

Closes #6318

This PR makes the existing sharing.wasm and sharing.html configuration flags control what the UI surfaces, so users cannot accidentally share notebook source code through molab or WebAssembly links. Previously sharing.wasm only 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 notebook was not gated at all. It now respects sharing.wasm = false and is hidden alongside the "Create WebAssembly link" item.

Exported HTML files (static and WASM)

The sharing config is now baked into the user_config JSON embedded in exported HTML. The StaticBanner component reads sharing.wasm from 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.io at 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.toml or user config:

# pyproject.toml  — project-level, applies to all contributors
[tool.marimo.sharing]
wasm = false
html = false
# ~/.config/marimo/marimo.toml  — user-level, applies to all notebooks
[sharing]
wasm = false
html = false

Compliance

The risk

Several marimo features encode the full notebook source code and make it trivially accessible outside the organisation:

  • "Create WebAssembly link" and "Create molab notebook" compress the source with lz-string and 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.
  • Hosted WASM notebooks contain the source code and, without this PR, include an "Open in molab" button that sends the code to an external service with one click.

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, and static.marimo.app at 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 = false to pyproject.toml in repositories that contain proprietary notebooks. Pair with network egress filtering on the external marimo domains for defence-in-depth.

📋 Pre-Review Checklist

  • For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on Discord, or the community discussions (Please provide a link if applicable).
  • Any AI generated code has been reviewed line-by-line by the human PR author, who stands by it.
  • Video or media evidence is provided for any visual changes (optional).

✅ Merge Checklist

  • I have read the contributor guidelines.
  • Documentation has been updated where applicable, including docstrings for API changes.
  • Tests have been added for the changes made.

nojaf added 2 commits May 18, 2026 15:04
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.
Copilot AI review requested due to automatic review settings May 18, 2026 13:24
@vercel
Copy link
Copy Markdown

vercel Bot commented May 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment May 21, 2026 7:39am

Request Review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_code now returns 403 when sharing.wasm = false.
  • POST /api/export/html now returns 403 when sharing.html = false.
  • The "Create molab notebook" Share dropdown item is hidden when sharing.wasm is 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.

Comment on lines +578 to +580
# handle_error converts all 403s → 401 to prompt browser re-auth
assert response.status_code == 401
assert "Authorization header required" in response.json()["detail"]
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Loading

Re-trigger cubic

mscolnick
mscolnick previously approved these changes May 18, 2026
@mscolnick mscolnick added the enhancement New feature or request label May 18, 2026
@mscolnick
Copy link
Copy Markdown
Contributor

thanks @nojaf, the PR description might be outdated as it references the CLI. i do think we should not change that codepath anyways though

@nojaf
Copy link
Copy Markdown
Contributor Author

nojaf commented May 18, 2026

@mscolnick ah sorry, I did not push my second commit it seems.
I understand the reluctance in changing the CLI behaviour.
It was just the simplest thing I could think of that our organization could enforce.
Happy to explore other suggestions.

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.
@mscolnick
Copy link
Copy Markdown
Contributor

exporting != sharing, so we can't merge this as is. is this for a local marimo server or hosted somewhre? hiding the molab sharing makes sense, but downloading HTML or exporting with the CLI is a different thing.

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.
@github-actions github-actions Bot added the bash-focus Area to focus on during release bug bash label May 19, 2026
@nojaf
Copy link
Copy Markdown
Contributor Author

nojaf commented May 20, 2026

@mscolnick I removed the env var so that can be added later.
Updated the description.
This PR is ready for another review.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Bundle Report

Changes will increase total bundle size by 98.18kB (0.39%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
marimo-esm 25.26MB 98.18kB (0.39%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: marimo-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/cells-*.js 3.16kB 709.93kB 0.45%
assets/JsonOutput-*.js 195.77kB 556.64kB 54.25% ⚠️
assets/index-*.js -176.22kB 431.34kB -29.01%
assets/index-*.css 899 bytes 366.91kB 0.25%
assets/dist-*.js -13 bytes 164 bytes -7.34%
assets/dist-*.js -198 bytes 137 bytes -59.1%
assets/dist-*.js -82 bytes 177 bytes -31.66%
assets/dist-*.js 79 bytes 183 bytes 75.96% ⚠️
assets/dist-*.js -67 bytes 102 bytes -39.64%
assets/dist-*.js 73 bytes 256 bytes 39.89% ⚠️
assets/dist-*.js 250 bytes 387 bytes 182.48% ⚠️
assets/dist-*.js -60 bytes 104 bytes -36.59%
assets/dist-*.js 243 bytes 403 bytes 151.88% ⚠️
assets/dist-*.js 172 bytes 276 bytes 165.38% ⚠️
assets/dist-*.js 59 bytes 335 bytes 21.38% ⚠️
assets/dist-*.js -46 bytes 137 bytes -25.14%
assets/dist-*.js -299 bytes 104 bytes -74.19%
assets/dist-*.js 90 bytes 259 bytes 53.25% ⚠️
assets/dist-*.js -283 bytes 104 bytes -73.13%
assets/dist-*.js 65 bytes 169 bytes 62.5% ⚠️
assets/dist-*.js 67 bytes 169 bytes 65.69% ⚠️
assets/dist-*.js 39 bytes 176 bytes 28.47% ⚠️
assets/dist-*.js -16 bytes 160 bytes -9.09%
assets/dist-*.js -73 bytes 183 bytes -28.52%
assets/edit-*.js 202 bytes 325.12kB 0.06%
assets/reveal-*.js 72.43kB 249.47kB 40.91% ⚠️
assets/add-*.js -100 bytes 201.99kB -0.05%
assets/layout-*.js 182 bytes 198.42kB 0.09%
assets/dependency-*.js 341 bytes 156.71kB 0.22%
assets/file-*.js 195 bytes 47.03kB 0.42%
assets/input-*.js 46 bytes 60.48kB 0.08%
assets/panels-*.js 711 bytes 47.89kB 1.51%
assets/useNotebookActions-*.js 2.64kB 29.99kB 9.66% ⚠️
assets/react-*.browser.esm-Ce2ksurd.js (New) 25.64kB 25.64kB 100.0% 🚀
assets/vega-*.browser-xq8miGHn.js (New) 24.8kB 24.8kB 100.0% 🚀
assets/state-*.js -13 bytes 24.79kB -0.05%
assets/home-*.js 147 bytes 24.17kB 0.61%
assets/MarimoErrorOutput-*.js -265 bytes 23.74kB -1.1%
assets/run-*.js -104 bytes 9.73kB -1.06%
assets/react-*.esm--O4lBTlZ.js (New) 8.37kB 8.37kB 100.0% 🚀
assets/config-*.js 315 bytes 6.41kB 5.17% ⚠️
assets/ttcn-*.js 7 bytes 64 bytes 12.28% ⚠️
assets/ttcn-*.js -7 bytes 57 bytes -10.94%
assets/emotion-*.esm-Dangy3Bv.js (New) 4.37kB 4.37kB 100.0% 🚀
assets/useBoolean-*.js -2.75kB 3.02kB -47.66%
assets/mermaid-*.core-867JpRcd.js (New) 2.38kB 2.38kB 100.0% 🚀
assets/slides-*.css 224 bytes 529 bytes 73.44% ⚠️
assets/package-*.js (New) 372 bytes 372 bytes 100.0% 🚀
assets/__vite-*.js 5 bytes 98 bytes 5.38% ⚠️
assets/__vite-*.js -5 bytes 93 bytes -5.1%
assets/react-*.browser.esm-BdtIs0E-.js (Deleted) -25.64kB 0 bytes -100.0% 🗑️
assets/vega-*.browser-C8wT63Va.js (Deleted) -24.8kB 0 bytes -100.0% 🗑️
assets/react-*.esm-BNzu6e7h.js (Deleted) -8.37kB 0 bytes -100.0% 🗑️
assets/emotion-*.esm-C59xfSYt.js (Deleted) -4.37kB 0 bytes -100.0% 🗑️
assets/mermaid-*.core-CMygPhv_.js (Deleted) -2.38kB 0 bytes -100.0% 🗑️

Files in assets/useNotebookActions-*.js:

  • ./src/components/editor/actions/useNotebookActions.tsx → Total Size: 21.48kB

Files in assets/run-*.js:

  • ./src/components/static-html/static-banner.tsx → Total Size: 7.65kB

@mscolnick
Copy link
Copy Markdown
Contributor

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.
@nojaf
Copy link
Copy Markdown
Contributor Author

nojaf commented May 21, 2026

@mscolnick, right, I removed those checks in the api endpoints.
I was overcomplicating that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bash-focus Area to focus on during release bug bash enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove "try in browser with WebAssembly" link in static notebooks

3 participants