Skip to content

mount import directory for pyodide sandbox#2073

Merged
paulfitz merged 4 commits intomainfrom
paulfitz/pyodide-import
Jan 30, 2026
Merged

mount import directory for pyodide sandbox#2073
paulfitz merged 4 commits intomainfrom
paulfitz/pyodide-import

Conversation

@paulfitz
Copy link
Member

It turns out the sandbox used for importing files is reused for a given document, meaning that a change to copying vs mounting the import doesn't work after the first import.

This reverts that change, and drops read permissions selectively.

It turns out the sandbox used for importing files is reused for a
given document, meaning that a change to copying vs mounting the
import doesn't work after the first import.

This reverts that change, and drops read permissions selectively.
Copy link
Collaborator

@fflorent fflorent left a comment

Choose a reason for hiding this comment

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

It looks good to me. I have asked you some questions, so I am sure to have understood the logic behind your PR

Thank you! :)

// everything else. See --allow-read in SandboxPyodide.ts
const readDir = fs.realpathSync(__dirname);
const gristDir = fs.realpathSync(path.join(__dirname, "..", "grist"));
const reqFile = fs.realpathSync(path.join(__dirname, "..", "requirements.txt"));
Copy link
Collaborator

Choose a reason for hiding this comment

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

For my own understanding, why is --allow-read=/PATH/TO/sandbox/requirements.txt added in a first place in SandboxPyodide.ts?

I think I see the reason for adding --allow-read=/PATH/TO/sandbox/grist: it is used to make a copy in loadCode. Deno.permissions.revoke() allows here to revoke permissions previously granted by the --allow-read arguments, right?

(Thanks for the comment BTW, it is very helpful to understand a bit more the context!)

Copy link
Member Author

Choose a reason for hiding this comment

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

For requirements.txt, because that is how the code figures out the libraries Grist needs

const txt = fs.readFileSync(path.join(__dirname, "..", "requirements.txt"), "utf8");

This could be done a different way, but is what is there right now.

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct about the rest.

this.log("Setting up import from", process.env.IMPORTDIR);
// Could mount directly, but instead copy so we can drop read perms early.
await this.copyFiles(process.env.IMPORTDIR, "/import_src", "/import");
// All imports for a given doc live in the same root directory.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Alright, IMPORTDIR is a subdirectory to a temporary directory attributed exclusively to the ActiveDoc?

Also after adding some logs, I found that a sandbox is spawned during the import, am I right? It is fine here to share the same import directory because it is scoped to the ActiveDoc? (in which case it would make sense to me too)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes that is what @Spoffy found when working on a Grist Desktop issue.

Copy link
Contributor

Choose a reason for hiding this comment

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

That was my understanding / findings on how this all worked, yeah. The logic connecting parseFile in DocPluginManager to the sandbox is convoluted, but I'll do my best to summarise:

  • File parsers (e.g. CSV, JSON, Excel) are defined as plugins in plugins/core/manifest.yml.
  • These are instantiated as PluginInstance objects in DocPluginManager (which is created on a per-doc basis) when DocPluginManager is created. DocPluginManager is created one-per-doc.
  • When DocPluginManager initialises, it creates a new temporary directory (on a per-doc basis) that's passed to each plugin instance.
  • When a plugin instance is first used (more specifically - when it first tries to make an RPC call to the sandbox, e.g. to parse a file), it's activated, which spawns a new sandbox
  • Plugin instances are shut down when the document is, or after 5 minutes of inactivity. This shuts down that plugin's sandbox.

In short: yes, the temp directory is per active document. 🙂

Copy link
Collaborator

@fflorent fflorent left a comment

Choose a reason for hiding this comment

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

Thanks for your explanation, LGTM!

this.log("Setting up import from", process.env.IMPORTDIR);
// Could mount directly, but instead copy so we can drop read perms early.
await this.copyFiles(process.env.IMPORTDIR, "/import_src", "/import");
// All imports for a given doc live in the same root directory.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it worth adding to this comment, something like:

Copying import files in and dropping read permissions isn't possible, due to the same sandbox being re-used for subsequent imports. The files would only copied once on startup, meaning the files for these later imports won't be available to Pyodide, resulting in errors.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added.

@paulfitz paulfitz requested a review from Spoffy January 30, 2026 15:22
@paulfitz paulfitz merged commit 4f187f0 into main Jan 30, 2026
15 checks passed
@paulfitz paulfitz deleted the paulfitz/pyodide-import branch January 30, 2026 18:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants