Skip to content

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

Merged
Light2Dark merged 10 commits into
marimo-team:mainfrom
nojaf:secure-shareable-links
Jun 2, 2026
Merged

feat(sharing): enforce sharing config as server-side security#9578
Light2Dark merged 10 commits into
marimo-team:mainfrom
nojaf:secure-shareable-links

Conversation

@nojaf

@nojaf nojaf commented May 18, 2026

Copy link
Copy Markdown
Contributor

📝 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

vercel Bot commented May 18, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Jun 2, 2026 7:06am

Request Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 thread tests/_server/api/endpoints/test_files.py Outdated

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

Copy link
Copy Markdown
Contributor

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

nojaf commented May 18, 2026

Copy link
Copy Markdown
Contributor Author

@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

nojaf commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

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

@codecov

codecov Bot commented May 20, 2026

Copy link
Copy Markdown

Bundle Report

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

Detailed changes
Bundle name Size Change
marimo-esm 25.29MB 126.6kB (0.5%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: marimo-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/cells-*.js 2.99kB 709.76kB 0.42%
assets/JsonOutput-*.js 196.89kB 557.76kB 54.56% ⚠️
assets/index-*.js -176.28kB 431.28kB -29.01%
assets/index-*.css 1.65kB 367.66kB 0.45%
assets/dist-*.js 46 bytes 183 bytes 33.58% ⚠️
assets/dist-*.js -266 bytes 137 bytes -66.0%
assets/dist-*.js 33 bytes 137 bytes 31.73% ⚠️
assets/dist-*.js -79 bytes 104 bytes -43.17%
assets/dist-*.js -283 bytes 104 bytes -73.13%
assets/dist-*.js 250 bytes 387 bytes 182.48% ⚠️
assets/dist-*.js -16 bytes 160 bytes -9.09%
assets/dist-*.js 73 bytes 177 bytes 70.19% ⚠️
assets/dist-*.js -76 bytes 259 bytes -22.69%
assets/dist-*.js 93 bytes 276 bytes 50.82% ⚠️
assets/dist-*.js 79 bytes 256 bytes 44.63% ⚠️
assets/dist-*.js 16 bytes 176 bytes 10.0% ⚠️
assets/dist-*.js -87 bytes 169 bytes -33.98%
assets/dist-*.js -107 bytes 169 bytes -38.77%
assets/dist-*.js 19 bytes 183 bytes 11.59% ⚠️
assets/dist-*.js 60 bytes 164 bytes 57.69% ⚠️
assets/dist-*.js -65 bytes 104 bytes -38.46%
assets/dist-*.js 166 bytes 335 bytes 98.22% ⚠️
assets/dist-*.js 144 bytes 403 bytes 55.6% ⚠️
assets/edit-*.js 621 bytes 325.54kB 0.19%
assets/ai-*.js 19.71kB 276.99kB 7.66% ⚠️
assets/reveal-*.js 72.55kB 249.59kB 40.98% ⚠️
assets/add-*.js 168 bytes 202.26kB 0.08%
assets/layout-*.js 523 bytes 198.76kB 0.26%
assets/cell-*.js 144 bytes 184.28kB 0.08%
assets/dependency-*.js 378 bytes 156.75kB 0.24%
assets/file-*.js 159 bytes 47.0kB 0.34%
assets/input-*.js 172 bytes 60.6kB 0.28%
assets/panels-*.js 716 bytes 47.9kB 1.52%
assets/dates-*.js 338 bytes 38.67kB 0.88%
assets/chat-*.js 996 bytes 33.62kB 3.05%
assets/chat-*.js 595 bytes 15.26kB 4.06%
assets/utils-*.js 255 bytes 6.24kB 4.26%
assets/MarimoErrorOutput-*.js 2.63kB 26.63kB 10.96% ⚠️
assets/react-*.browser.esm-CV8-hvjx.js (New) 25.64kB 25.64kB 100.0% 🚀
assets/session-*.js 1 bytes 25.04kB 0.0%
assets/state-*.js -13 bytes 24.79kB -0.05%
assets/home-*.js 147 bytes 24.17kB 0.61%
assets/useNotebookActions-*.js -4.49kB 22.86kB -16.42%
assets/command-*.js 3 bytes 18.34kB 0.02%
assets/download-*.js 379 bytes 9.85kB 4.0%
assets/run-*.js -103 bytes 9.73kB -1.05%
assets/useCellActionButton-*.js -35 bytes 9.45kB -0.37%
assets/scratchpad-*.js 15 bytes 8.4kB 0.18%
assets/pair-*.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/config-*.js 315 bytes 6.41kB 5.17% ⚠️
assets/radio-*.js 5 bytes 5.48kB 0.09%
assets/readonly-*.js 1 bytes 4.68kB 0.02%
assets/useDependencyPanelTab-*.js -158 bytes 4.22kB -3.61%
assets/useBoolean-*.js -2.75kB 3.02kB -47.66%
assets/mermaid-*.core-aYU6qXxk.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/arrow-*.js (New) 165 bytes 165 bytes 100.0% 🚀
assets/react-*.browser.esm-BdtIs0E-.js (Deleted) -25.64kB 0 bytes -100.0% 🗑️
assets/mermaid-*.core-CMygPhv_.js (Deleted) -2.38kB 0 bytes -100.0% 🗑️
assets/eye-*.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️

Files in assets/utils-*.js:

  • ./src/core/config/config-schema.ts → Total Size: 7.04kB

Files in assets/useNotebookActions-*.js:

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

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

nojaf commented May 21, 2026

Copy link
Copy Markdown
Contributor Author

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

@nojaf nojaf requested a review from mscolnick May 27, 2026 13:26
@nojaf

nojaf commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

Hi @manzt, @akshayka or @dmadisetti, as Myles is out I was wondering if anyone could take over here. Thanks!

@Light2Dark Light2Dark requested review from akshayka and dmadisetti May 28, 2026 08:12
Light2Dark
Light2Dark previously approved these changes Jun 1, 2026

@Light2Dark Light2Dark left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'll check with the team, thank you!

Comment thread marimo/_server/export/exporter.py Outdated
Comment thread marimo/_server/export/exporter.py Outdated
Comment thread marimo/_server/export/exporter.py Outdated
Comment thread marimo/_server/export/__init__.py Outdated

@akshayka akshayka left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey nojaf, thanks for this PR.

For the configuration, can we introduce a third optional field, molab, instead of riding on the wasm field?

dmadisetti
dmadisetti previously approved these changes Jun 1, 2026

@dmadisetti dmadisetti left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the clean up too!

nojaf added 2 commits June 2, 2026 08:54
Annotate the static-notebook config as MarimoConfig so display and
sharing assignments type-check without casts or type: ignore, and remove
the now-redundant DisplayConfig casts in the export helpers.
Gate the "Create molab notebook" action and the exported HTML "Open in
molab" button on a new sharing.molab flag instead of riding on
sharing.wasm, so molab sharing can be disabled independently of
WebAssembly links.
@nojaf

nojaf commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for all the reviews and feedback! I think the failing Windows build is unrelated to my changes, looks like a timeout. Let me know if this needs any more changes.

@Light2Dark

Copy link
Copy Markdown
Member

Thank you!

@Light2Dark Light2Dark merged commit 8ca34dd into marimo-team:main Jun 2, 2026
43 of 44 checks passed
dmadisetti pushed a commit that referenced this pull request Jun 4, 2026
## 📝 Summary

<!--
If this PR closes any issues, list them here by number (e.g., Closes
#123).

Detail the specific changes made in this pull request. Explain the
problem addressed and how it was resolved. If applicable, provide before
and after comparisons, screenshots, or any relevant details to help
reviewers understand the changes easily.
-->
This is a follow up to #9578. That PR made the `sharing.wasm`,
`sharing.html`, and `sharing.molab` config flags control what sharing
affordances the UI surfaces, and added the `molab` flag. During #9578
the maintainers (@mscolnick via DM) asked that the
`MARIMO_RESTRICT_SHARING` env var be split out into its own PR, so this
is that separate change.

### What changed

- **`marimo/_config/settings.py`**: add `RESTRICT_SHARING` to
`GlobalSettings`, reading the `MARIMO_RESTRICT_SHARING` env var.
- **`marimo/_config/manager.py`**: when the env var is set,
`EnvConfigManager.get_config()` injects `sharing = {"wasm": False,
"html": False, "molab": False}`. `EnvConfigManager` is the last (highest
priority) partial in `get_default_config_manager`, so this override
takes precedence over any per-project or per-user TOML config.
- **`tests/_config/test_manager.py`**: tests that the flag injects the
all-false sharing config, that nothing is injected when the flag is off,
and that the env override wins over a user config that explicitly
enables sharing.

### Why we want this

The `sharing.*` config flags require every project or user to opt in. In
our company we run marimo in managed devpod/container environments, and
there is no reliable way to ensure every user has the flag set in their
own config, since users have admin access to their own environment and
can edit config files. `MARIMO_RESTRICT_SHARING=1` lets infra admins set
the restriction once at the container/pod spec level, outside the user's
control, and have every notebook session inherit it without per-project
configuration.

### Scope and honest framing

This is a UI-hiding/policy control, not a server-side security boundary.
#9578 deliberately removed the server-side 403 gates (`exporting !=
sharing`), so the sharing config only drives what the UI surfaces (the
editor Share dropdown and the molab button baked into exported HTML).
This env var hides those affordances machine-wide but does not block a
determined user: exported HTML still embeds source and endpoints still
serve code. We pair it with network egress filtering (blocking
`marimo.app`, `molab.marimo.io`, `static.marimo.app`) for defence in
depth. Note also that `MARIMO_RESTRICT_SHARING=0` as a command prefix
overrides the env var for that process, so it should be set in the
devpod/container spec rather than inside the container.

## 📋 Pre-Review Checklist
<!-- These checks need to be completed before a PR is reviewed -->

- [x] For large changes, or changes that affect the public API: this
change was discussed or approved through an issue, on
[Discord](https://marimo.io/discord?ref=pr), or the community
[discussions](https://github.com/marimo-team/marimo/discussions) (Please
provide a link if applicable). <!-- Discussed during review of #9578,
where maintainers asked for this to be a separate PR. -->
- [x] 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). <!-- N/A: backend config only, no visual changes. -->

## ✅ Merge Checklist

- [x] I have read the [contributor
guidelines](https://github.com/marimo-team/marimo/blob/main/CONTRIBUTING.md).
- [x] Documentation has been updated where applicable, including
docstrings for API changes.
- [x] Tests have been added for the changes made.

//cc @Light2Dark @akshayka, @dmadisetti
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

6 participants