Skip to content

interp: add ProcSubstOpen handler for process substitution - #1404

Closed
aki-kong wants to merge 1 commit into
mvdan:masterfrom
aki-kong:feat/fifo-handler
Closed

aki-kong wants to merge 1 commit into
mvdan:masterfrom
aki-kong:feat/fifo-handler

Conversation

@aki-kong

@aki-kong aki-kong commented Aug 27, 2026 •

Copy link
Copy Markdown

Process substitution always creates a real named pipe on disk via mkfifo
and os.OpenFile, no matter what OpenHandler/StatHandler/AccessHandler are
set to, so it's a hole straight to the real filesystem for anyone
sandboxing file I/O.

This is #1120, where you left the door open for a handler:

perhaps they do need to be split apart from the open handler entirely...
I think FIFOs could become another handler if users need that.

I've taken a crack at implementing it. ProcSubstOpen owns the whole FIFO
lifecycle (create, open, remove), since a plain (path, flag, perm) open
can't express mkfifo + cleanup. DefaultProcSubstOpen is the old logic
moved into handler.go, same behavior, except the actual open is now
deferred to the first read/write, since opening a FIFO blocks until a
peer connects and that peer isn't running yet when the path is returned.

This also meant removing the bit in r.open that special-cased FIFO
paths to skip OpenHandler, since creation is now handler-owned. Happy
to reconsider that if you'd rather keep it separate.

Updated the tests accordingly, including a new one (ProcSubstVirtual)
that pairs ProcSubstOpen with OpenHandler over an in-memory io.Pipe and
checks nothing touches the real filesystem.

Motivation

I'm using this shell intepreter package to implement a simple Bash sandbox with WASI as the execution provider which supports fs.FS. I had implemented the OpenHandler based on the fs.FS but just for the ProcessSubstitution there seemed to be a sandbox breakage. In this case, I can implement the ProcSub + OpenHandler to handle the Pipe implementation (plan to use channels). Left the DefaultHandler to the original behaviour.

@aki-kong

Copy link
Copy Markdown
Author

cc @mvdan Can you please take a look

@mvdan

mvdan commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Thanks. This PR is largely in the right direction but I'm afraid it is incorrect. You are removing part of a test which existed explicitly to cover #1120, and that bug is reintroduced here. I will take a look at a fix myself.

@mvdan mvdan closed this Aug 31, 2026
@aki-kong
aki-kong deleted the feat/fifo-handler branch August 31, 2026 15:22
@aki-kong

Copy link
Copy Markdown
Author

Thanks. This PR is largely in the right direction but I'm afraid it is incorrect. You are removing part of a test which existed explicitly to cover #1120, and that bug is reintroduced here. I will take a look at a fix myself.

I felt the same. Somewhere there should be some sort of overlap/dependency between the pipe handler and the open handler. Let me know the approach, I can help with the changes as well (if you feel like it 😀)

mvdan added a commit that referenced this pull request Aug 31, 2026
Process substitutions always created a real named pipe on disk via
mkfifo, regardless of any handlers, breaking interpreters which sandbox
all file I/O via a custom OpenHandler; see the discussion in #1120.

The new handler owns the whole lifecycle of a process substitution:
it names the path, opens the subshell's end, opens the path whenever the
shell itself consumes it, and cleans up once the subshell has finished.
The default handler keeps the old behavior using real named pipes.

Since the runner now tracks its active process substitutions, Runner.open
routes their paths to the handler directly, replacing the detection of
FIFO paths by temporary directory and name prefix.

An alternative to #1404.
See #1120 for previous work in this space.
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.

2 participants