Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Force shell option #2552

Open
liquidaty opened this issue Dec 28, 2024 · 11 comments
Open

Force shell option #2552

liquidaty opened this issue Dec 28, 2024 · 11 comments

Comments

@liquidaty
Copy link

I'm looking for a way to safely run arbitrary Justfiles. I've considered several approaches and they generally require being able to specify a custom "safe" shell that cannot be subsequently changed (i.e. set shell in a Justfile would be disregarded, and the custom shell must be propagated for any nested just calls).

For example, the shell might be specified in the invocation e.g. just --shell-force X --shell-force-arg Y, or in an environment variable such as JUSTFILE_SHELL_FORCE (which perhaps requires less work to ensure propagation to nested just calls).

Thoughts? Is there a better way to do this? If we submit a PR for this or otherwise assist in the work to implement, would you consider accepting?

@casey
Copy link
Owner

casey commented Dec 28, 2024

Regardless of which shell is used, doesn't a justfile potentially contain arbitrary code which might be dangerous?

@liquidaty
Copy link
Author

I was assuming no but perhaps that is not correct? If we assume that the shell is safe (because this would be a custom shell that is designed to be safe... or perhaps the shell is a wrapper to making a call inside a container), then are there native justfile commands that could be unsafe? import might read something it shouldn't, so that might need to be disabled. Are there others that could read or write files (or make network calls, though that's less of a concern for me) without going through the shell?

Hm, maybe an easier alternative would be to have a standalone justfile parser and have the caller parse the justfile before running, and raise an error if it contains import or shell-set commands...

@laniakea64
Copy link
Contributor

just is, as its description says, "just a command runner". In otherwords, it is specifically designed to run basically arbitrary code. As such, an untrusted justfile would be able to find a way around any hypothetical "force shell option".

Note also that the shell for a justfile can be anything: what if you end up forcing a bash-like shell on a justfile that sets its shell to a Python interpreter? 😱

In the interest of avoiding a XY problem in this issue, could you please explain what is the end goal you would like to achieve?

@liquidaty
Copy link
Author

an untrusted justfile would be able to find a way around any hypothetical "force shell option"

Are you sure? The shell could be a wrapper to a sandbox, in which case it could try to run python but best case, it would still run in the sandbox.

shell for a justfile can be anything: what if you end up forcing a bash-like shell on a justfile that sets its shell to a Python interpreter

Yea, but then your bash-like shell isn't safe and is not the one I would choose to force.

There are many other ways to achieve a "safe" shell without permitting circumvention. The "safe" shell could simply be a wrapper to which runs bash inside a safe container-- e.g. docker or wasmtime-- in which case even if the shell invokes Python, it is still contained. Or it could be a custom shell such as is the subject of this discussion.

could you please explain what is the end goal you would like to achieve

A simple example use would be an online Justfile playground. This isn't exactly my use case but faces similar requirements (and could probably be pretty useful to add to this repo!). The user can enter whatever justfile they want, and hit "Run", and then they see the output of that.

Typically, "playground" means it runs on the client via e.g. JS and/or wasm. However, there are a few problems with this when it comes to just:

  • the current just source code does not compile to wasm (afaik)
  • even if it could compile to wasm, it would probably not be able to invoke a shell command since that is equivalent to fork() which in-browser wasm can't (yet) do

A force-shell option would be a simple solution by making it possible to ensure that the shell is always invoked inside a container even if the just running is not in the container.

@laniakea64
Copy link
Contributor

Thanks for the clarification!

For cases like that, it would definitely be best to sandbox/containerize the entire just process, probably also setting up that sandbox/container such that the only executables just has access to run (other than itself) are the "safe" ones. As a bonus, this type of approach would also cover shebang recipes, which bypass the set shell, without any extra effort.

And having just unsandboxed/uncontainerized while relying on containerization of subprocesses is not a stable approach to safety anyway. just evolves new functionality quickly at times, so a "deny list" approach to blocking "unsafe" justfile functionality risks becoming dangerously incomplete with newer versions of just. One example, a perfect "deny list" for just current release 1.38.0 would not include the upcoming read() function which as you noted is functionality which uncontained would pose a concern.

@liquidaty
Copy link
Author

it would definitely be best to sandbox/containerize the entire just process

Yea, but that can't always work. For example, just won't work in e.g. wasmtime

@laniakea64
Copy link
Contributor

laniakea64 commented Dec 29, 2024

For example, just won't work in e.g. wasmtime

How does this prevent being able to sandbox/containerize the just process in the environment where it will work/run?

@liquidaty
Copy link
Author

liquidaty commented Dec 29, 2024

How does this prevent being able to sandbox/containerize the just process in the environment where it will work/run?

My target sandbox-- for where shell commands run (does not need to be where just runs) is wasmtime on Windows, and alternative containers (e.g. if I wanted to run wasmtime inside a docker container) are not an option

@casey
Copy link
Owner

casey commented Dec 29, 2024

So I'm honestly pretty skeptical of this 😅 just's whole raison d'etreis to run arbitrary commands, and so trying to lock it down so it can't seems like a can of worms, and I do agree that sandboxing the whole thing is the best idea. Maybe trying to get it to work in wasmtime?

But for the sake of the discussion:

Just has three sources of commands which it invokes:

  1. Linewise recipe lines, backticks, and shell(…) function invocations, run by the command set by --shell / --shell-args / set windows-shell, and some others.
  2. Shebang recipes, run by the #! line
  3. Script recipes, set by the [script] attribute or set script-interpreter

If you control or can audit, with a custom shell, the arguments and environment variables passed to nested just invocations, then you can only guarantee that the latter two have a particular value:

  1. Can be forced with --shell / --shell-args
  2. Cannot be forced, since it's set with #! lines
  3. Cannot be forced, since there is no --script-interpreter CLI option

You could add new flags which could force the second two somehow. But probably the best thing would be to add a single flag or environment variable which forced the command in all cases to match a given path, like:

just --force-command /path/to/my/command

And then invocation of any command which wasn't /path/to/my/command would fail.

This seems pretty dangerous though, since just wasn't designed with this in mind, and I have no idea if there are other things which could be unsafe. Also, the custom shell itself would be extremely limited, since it would have to not invoke any arbitrary commands, and could only, like you mention, be a wrapper around guaranteed sandboxed operations like operating on a docker container.

@laniakea64
Copy link
Contributor

Thanks again liquidaty for providing more details.

Hm, maybe an easier alternative would be to have a standalone justfile parser and have the caller parse the justfile before running, and raise an error

In the absence of being able to sandbox the just process, this does indeed seem easier. Your standalone justfile parser could be a wasmtime-compatible program (so that this step is sandboxed) that has an "allow list" of allowed just syntaxes and allowed subprocess commands, and raises an error if it encounters any just syntax that is not allowed, not parseable, or not recognized.

I do agree that sandboxing the whole thing is the best idea. Maybe trying to get it to work in wasmtime?

What makes just not currently work in wasmtime and what would be involved in making it work in wasmtime?

But for the sake of the discussion:

Would a CLI option to run all subprocesses via a custom "wrapper command" help? (which as noted is not a complete solution, but would seem to be more comprehensive and less issue-prone than forcing shell?)

@liquidaty
Copy link
Author

liquidaty commented Dec 29, 2024

(updated to respond to @laniakea64's contemporaneous post)

Maybe trying to get it to work in wasmtime?

Could probably get it to run but it would not be able to invoke any shell at all because of wasm limitations (fork, threading etc). But, maybe this is a compatible goal with the experimental no-shell version and could be a key part of the solution described below

And then invocation of any command which wasn't /path/to/my/command would fail.

That would definitely be sufficient

I have no idea if there are other things which could be unsafe

If all occurrences of read/write/invoke in the source are centralized and any safety issues handled there, is anything else left? But I understand the concern. To work with a wasmtime version, the problem is that I need some for looping, and so without a shell (a wasm limitation we can't get around) I need a built-in for looping (e.g. #1570) (as an aside, I think there are lots of benefits to this anyway-- see e.g. analogous Makefile example and comments here)

If those three things could be done, then I would be set:

  1. forced /path/to/my/command shell prefix
  2. can run (shell-less) in wasmtime and simply print (rather than try to invoke) the commands
  3. can handle for looping without shell

So maybe I should first be trying to prod those other issues along...

absence of being able to sandbox the just process, this does indeed seem easier. Your standalone justfile parser

Will put some more thought into this

What makes just not currently work in wasmtime and what would be involved in making it work in wasmtime?

see above-- main limitation is that it basically has to be single-process, single-threaded. But, that does not have to be a fatal limitation for my purposes-- see above

Would a CLI option to run all subprocesses via a custom "wrapper command" help?

Yes! 100%

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

No branches or pull requests

3 participants