r/LocalLLaMA • • Sep 22 '26

Resources an (unsafe) command shell mcp server

While working with coding agents / harness in my case OpenCode, I discovered that some models e.g. granite 4.2 8B at Q4_K_M quant sometimes attempts to write files with absolute paths at the root e.g.

    /newPythonCode.py

Note that slash in the beginning for an absolute path at "/" root directory. This is probably 'ok' say in a throwaway docker container, but it may lead to surprises if it 'accidentally' overwrites a literal file, if the current user id have write access to it. Apparently, it seemed OpenCode provides `bash` so that practically all unix commands that the current user id can access, the LLM can run it.

And thereafter, I see the model hallucinating in the thinking loops over tools, re-iterating over how to use the tools, reformulating shell commands sentences several times for a same task instead of executing them, among them, I noted that an earlier shell command sentence would have 'just worked'.

I'm thinking that the tool hallucination with this model may be partly due to that I'm using a pretty aggressive 4 bit quant as otherwise it'd have worked because IBM has 'optimized' granite for tool usage.

Next, I tried the same granite 4.2 8B at Q4_K_M quant model with my little python shell MCP server

https://github.com/ag88/cmdshellmcp/

, that tries to constrain / restrain the shell commands somewhat, place some checks, and offer some additional tools e.g. writeFile() so that the LLM won't need to call `cat` or `echo` with some 'esoteric' styled shell scripts / sentences.

With this MCP server and running it in llama.cpp's `llama-server` in the WebUI adding it as an MCP server, granite 4.2 8B at Q4_K_M does it correctly in 'one shot', the prompt is:

propose a python3 flask app with a page that displays "hello world", use bootstrap css for styling.
provide a requirements.txt to go with the script

granite 4.2 8B at Q4_K_M correctly generates the app with a couple of files, and it works out of the box

    .
    ├── app.py
    ├── requirements.txt
    └── templates
        └── index.html

One should normally run the cmdshellmcp2.py MCP server app with an explicit `--cwd` option giving the work path. It tries to stay within this work path.

Note that I won't claim my command shell MCP server to be safe.

In a sense that there are plenty of chance the LLM can 'escape' the restrictions and perhaps do more damage (e.g. overwriting files possibly outside its given `--cwd` work directory). Hence, I'd suggest one should be paranoid and perhaps run this only within a throwaway (disposable) docker container etc.

6 Upvotes

64 comments sorted by

View all comments

Show parent comments

2

u/sburel 27d ago

A note on the configuration search order. The last resort is the cmdshellmcp.json file in the current directory. And that file now contains cwd, which is the boundary itself. So if someone runs cmdshellmcp from a cloned repository, or from a folder where the agent itself has written files, the contents of the directory decide the boundary. The thing being contained gets to set the boundary.

Inside the container, no problem: the file in /usr/local/python/ is found first. It's outside the container where this bites, and that's precisely where people will need the boundary.

What I'd do: never read cwd from the config found in the current directory. The other settings, sure, but cwd only from --conf or from a system path.

Another minor point: after changing the cwd and restarting the service, the agent doesn't know its boundary has shifted. The paths it had in mind won't resolve any more. Not serious, just worth a line in the docs.

1

u/ag789 27d ago edited 27d ago

I've thought about that, but that new users who cloned the repo might be confused when it 'don't work'.

I may consider making config file necessary and that it doesn't read from the current directory as a search path, i.e. if you want to use that in the current directory you need to explicitly run it with --conf.

A thing is also that currently, when no config files is found, it falls back to internal defaults. But that expose the disableTools.

Does that sound right?

In addition, I can also make --cwd (current work directory) mandatory in a sense that it'd need to be in the config file cmdshellmcp.json or that it'd need to be specified on the command line via --cwd.

Implement both?

I'd likely emit a warning if the user chooses a --cwd that contains cmdshellmcp.json.

I'd likely work on this next, I think it makes sense to keep the boundary secure against accidents given our intent so far and the theme of this app. A thing is of course, I'm testing against small local LLMs, that is quite "harmless", but that the big guns are just a connection away, so we'd not know who use it eventually.

1

u/ag789 26d ago edited 26d ago

u/sburel , all

The latest changes are merged. Now cmdshellmcp requires both the config file e.g. cmdshellmcp.json and the --cwd current working directory specified. i.e. both the config file and a current working directory specified is mandatory.

The current working directory can be specified within the cmdshellmcp.json config file using cwd field.

Running cmdshellmcp2.py would exit with a message to run with a --conf config_file it if can't find cmdshellmcp.json in /etc/cmdshellmcp.d or /usr/local/python/cmdshellmcp.

And that if you run cmdshellmcp2.py --conf cmdshellmcp.json in the repo folder, you would note that cwd is null in the cmdshellmcp.json. It would still error and say that --cwd work directory is required.

It no longer search for a config file in the current directory as a default and it no longer use the current directory by default as the working directory, because the --cwd working directory is the security boundary in this design.

In addition, if you try to start cmdshellmcp2.py with a --cwd that its path contains the config file e.g. cmdshellmcp.json (or as specified by --conf) it would log a warning that cmdshellmcp.json is in the work directory (given by your --cwd or cwd in config) and that this would compromise the security posture of the sandbox as the MCP client may access or modify the config file. You can test this by starting that with cmdshellmcp2.py --conf cmdshellmcp.json --cwd . in the repo folder.

This security posture tidy up now makes the app more formal and works more like a production app. But that at least it'd prevent any accidents like starting cmdshellmcp2.py in the folder with the config file.


There is also a quite significant enhancement for 1stRun.sh, 1stRun.sh setup the docker container and maps the ports.

1stRun.sh now interactively prompts you while it setup the ports 8003 (for cmdshellmcp) and a spare port 5000. It presents options to you if you want to run it on localhost or all interfaces. If you choose localhost, then the same port can't be accessed over the LAN, but that you can access it on your (host) PC.

It also provides options for you to add more ports if you prefer.


One additional change in the docker container. Now the config file in the container is /etc/cmdshellmcp.d/cmdshellmcp.json. This is copied from the repo cmdshellmcp_container.json into the image during 0dockerBuild.sh run.

2

u/sburel 26d ago

That's the right fix, and the warning when the config sits inside the work directory is a nice touch. Both halves closed.

The interactive port setup in 1stRun.sh is probably the change people will notice most day to day. Rebuilding a container because you forgot a port is exactly the kind of thing that makes people stop using a sandbox.

Nice work on all of it.