fix(config): resolve .env relative to the notebook when no pyproject.toml - #10583
fix(config): resolve .env relative to the notebook when no pyproject.toml#10583DevPranjal wants to merge 7 commits into
.env relative to the notebook when no pyproject.toml#10583Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
All contributors have signed the CLA ✍️ ✅ |
.env relative to the notebook when no pyproject.toml.env relative to the notebook when no pyproject.toml
There was a problem hiding this comment.
Pull request overview
Adjusts marimo’s project config resolution so .env defaults are anchored to the notebook directory when no pyproject.toml is found, addressing cases like standalone/sandboxed notebooks while preserving the existing “pyproject root wins” behavior.
Changes:
- Add a fallback root for resolving relative
runtime.dotenvpaths whenpyproject.tomlis absent. - Add tests covering
.envresolution without apyproject.toml(file + directory start paths) and preference for thepyproject.tomlroot. - Update configuration docs to reflect the new
.envdefault behavior for standalone notebooks.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
marimo/_config/manager.py |
Introduces _dotenv_root and uses it to resolve runtime.dotenv even when no pyproject.toml exists. |
tests/_config/test_manager.py |
Adds regression tests for dotenv path anchoring in no-pyproject and nested-notebook scenarios. |
marimo/_config/config.py |
Updates runtime config docs/comments to describe the new default .env behavior and resolution rules. |
docs/guides/configuration/runtime_configuration.md |
Documents .env default loading for standalone notebooks without a pyproject.toml. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| dotenv = runtime.get("dotenv", [".env"]) | ||
|
|
||
| if not isinstance(dotenv, list): | ||
| return config | ||
|
|
|
I have read the CLA Document and I hereby sign the CLA |
A pyproject.toml travels with a cloned repository, so its runtime.dotenv is attacker-controlled. Entries reached two sinks with no cell execution: Kernel.load_dotenv put the parsed values into os.environ before the first cell ran, and the secrets panel listed the key names and appended new keys to the file. An INI-style file parses as a .env, so an entry pointing at ~/.aws/credentials surfaced aws_secret_access_key and made that file an append target. _resolve_dotenv now drops any entry whose resolved real path falls outside the project directory. The check runs on Path.resolve(), so a committed symlink out of the project is caught too. User configuration is unaffected: _resolve_dotenv only ever sees the project layer. pythonpath is left alone. pythonpath = ["src"] is both the common legitimate use and a full import-shadowing grant, so containment buys nothing there.
Reading the configuration masks runtime.dotenv by emptying the list, and marimo 0.18 and earlier saved that masked copy straight back to disk. An empty list in a user marimo.toml is a hidden value rather than a choice to load nothing, so it no longer counts as a value the user set. Groundwork for letting a user runtime.dotenv outrank the project default. Without it, every user configuration an older marimo wrote stops the .env next to the notebook from loading.
_resolve_dotenv read runtime.get("dotenv", [".env"]), so a project that says
nothing about dotenv still emitted a resolved [".env"] from the project
layer. That layer merges over the user configuration, so a default nobody
wrote clobbered a runtime.dotenv set in the user marimo.toml, and the editor
greyed the setting out as project-overridden.
This repairs two bugs:
- With a pyproject.toml present, the clobbering dates back to the
introduction of dotenv resolution. It stayed scoped to that shape because
_resolve_dotenv returned early without a pyproject.toml.
- Without a pyproject.toml, it is a regression from marimo-team#10583 (resolve .env
relative to the notebook when no pyproject.toml), which removed that early
return and so let the fabricated default reach standalone notebooks. The
standalone shape is the one marimo-team#10583 exists to improve.
The default now comes from ProjectConfigManager.get_defaults(), merged
beneath the user configuration instead of over it. The .env next to the
notebook still loads when no layer sets dotenv, an explicit pyproject dotenv
keeps its precedence over the user configuration and stays confined to the
project directory, and get_config_overrides() no longer reports a dotenv
nobody wrote.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Suppressed comments (4)
marimo/_config/manager.py:633
- An explicit user setting
runtime.dotenv = []previously and naturally means “load no dotenv files,” but this now silently discards it and applies the new automatic.envdefault. That leaves users no global opt-out and can unexpectedly load a neighboring credentials file. A stale masked value is indistinguishable from an intentional empty list, so preserve[]or introduce an explicit migration/representation that does not redefine valid user configuration.
def _drop_hollow_dotenv(config: PartialMarimoConfig) -> PartialMarimoConfig:
"""Drop an empty `runtime.dotenv`, which is a masked value and not a choice."""
# NB. Reading the configuration blanks runtime.dotenv by emptying the list,
# and marimo 0.18 and earlier saved that masked copy straight back to disk,
# so an empty list there means "hidden", not "load nothing". Keeping it
marimo/_config/manager.py:537
- This resolution is erased in real sessions.
SessionImpl.createre-addsScriptConfigManager(...).get_config()with the defaulthide_secrets=True(marimo/_session/session.py:110-112), which masksruntime.dotenvto[]; that later override then wins even when the final kernel config is requested unmasked. Thus a PEP 723runtime.dotenvstill loads nothing, while these direct manager tests pass. Pass the unmasked value into the server-side config manager (or avoid the duplicate masked layer) and add a session-level regression test.
marimo_config = ProjectConfigManager(
self.filename
)._resolve_dotenv(marimo_config)
marimo/_config/manager.py:356
- The PR description says nothing changes when a
pyproject.tomlexists, but this branch now rejects absolute paths outside the project and escaping relative paths that were previously supported. Existing projects can therefore stop loading configured environment files. Either preserve the prior project behavior in this focused fix, or explicitly document the breaking security hardening and migration in the PR/release notes.
This issue also appears in the following locations of the same file:
- line 535
- line 629
if not candidate.resolve().is_relative_to(real_root):
LOGGER.warning(
"Ignored a runtime.dotenv entry that resolves outside "
"the notebook or project directory. Move the .env file "
"next to the notebook or into the project, or set "
docs/guides/configuration/runtime_configuration.md:145
- This says paths must be relative, but the implementation accepts absolute paths that resolve inside the project; it enforces containment rather than relativity. The sentence also contains “must point be relative.” Describe the actual containment rule instead.
For security, a `dotenv` set in a `pyproject.toml` must point be relative to
the project directory, and paths that resolve outside the directory, including
symlinks, are ignored. To read a `.env` anywhere on your file system, set
`dotenv` in your [user configuration](index.md).
📝 Summary
Closes #10252
A notebook only loaded a neighbouring
.envif apyproject.tomlexisted above it. The issue reports this for sandboxed notebooks, but it isn't sandbox-specific:marimo edit ~/scratch/nb.pybreaks the same way. Now it falls back to the notebook's own folder (nothing changes ifpyproject.tomlexists)@dmadisetti
📋 Pre-Review Checklist
✅ Merge Checklist