Skip to content

fix(config): resolve .env relative to the notebook when no pyproject.toml - #10583

Open
DevPranjal wants to merge 7 commits into
marimo-team:mainfrom
DevPranjal:fix/dotenv-without-pyproject
Open

fix(config): resolve .env relative to the notebook when no pyproject.toml#10583
DevPranjal wants to merge 7 commits into
marimo-team:mainfrom
DevPranjal:fix/dotenv-without-pyproject

Conversation

@DevPranjal

@DevPranjal DevPranjal commented Aug 18, 2026

Copy link
Copy Markdown

📝 Summary

Closes #10252

A notebook only loaded a neighbouring .env if a pyproject.toml existed above it. The issue reports this for sandboxed notebooks, but it isn't sandbox-specific: marimo edit ~/scratch/nb.py breaks the same way. Now it falls back to the notebook's own folder (nothing changes if pyproject.toml exists)

@dmadisetti

📋 Pre-Review Checklist

✅ 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.

Copilot AI lite review requested due to automatic review settings August 18, 2026 17:22
@DevPranjal
DevPranjal requested a review from akshayka as a code owner August 18, 2026 17:22
@vercel

vercel Bot commented Aug 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 Aug 20, 2026 4:33pm

Request Review

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Aug 18, 2026
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@DevPranjal DevPranjal changed the title Resolve .env relative to the notebook when no pyproject.toml fix(config): resolve .env relative to the notebook when no pyproject.toml Aug 18, 2026

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

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.dotenv paths when pyproject.toml is absent.
  • Add tests covering .env resolution without a pyproject.toml (file + directory start paths) and preference for the pyproject.toml root.
  • Update configuration docs to reflect the new .env default 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.

Comment thread marimo/_config/manager.py Outdated
Comment on lines 288 to 292
dotenv = runtime.get("dotenv", [".env"])

if not isinstance(dotenv, list):
return config

@DevPranjal

Copy link
Copy Markdown
Author

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.
@akshayka
akshayka requested review from dmadisetti and removed request for akshayka August 19, 2026 15:36

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

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 .env default. 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.create re-adds ScriptConfigManager(...).get_config() with the default hide_secrets=True (marimo/_session/session.py:110-112), which masks runtime.dotenv to []; that later override then wins even when the final kernel config is requested unmasked. Thus a PEP 723 runtime.dotenv still 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.toml exists, 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).

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

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Automatic .env loading in sandbox mode

3 participants