CNSeniorious000 / CNSeniorious000/dsh-py-codeact

One cell can corrupt the MCP modules for every agent in the process, permanently and invisibly

Open
#13 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
1
Forks
0
Avg merge
2h 47m
Merged PRs (30d)
27

Description

`__dsh__.tools.mcp` and its server modules are process-global singletons. #9 added a `__setattr__` guard for exactly this, and `mcp_server_module` so a deleted server module comes back. Two holes remain, both reproduced with two live shells in one process at `3c0195f`.

### The `__setattr__` guard is bypassed by the inherited `ModuleType.__init__`

```python
# shell A
from __dsh__.tools import mcp
import types
types.ModuleType.__init__(mcp.gh, "x") # no attribute assignment, so no guard
sorted(dir(mcp.gh)) # -> []
```
```python
# shell B, a different agent
sorted(dir(mcp.gh)) # -> []
```

The server is blanked for the whole process. `__setattr__` refuses non-dunder writes, but `ModuleType.__init__` resets `__dict__` without going through it. Survives every rebind — `mcp_server_module` only rebuilds a module that is *missing* from `sys.modules`, and this one is still there.

### `del sys.modules["__dsh__.tools.mcp"]` kills the root process-wide

```python
# shell A
import sys; del sys.modules["__dsh__.tools.mcp"]
```
```python
# shell B
from __dsh__.tools.mcp import gh # -> ModuleNotFoundError: No module named '__dsh__.tools.mcp'
```

`mcp_server_module` is the single construction site so a deleted **server** module is rebuilt — confirmed working. The **root** is registered once at import with no equivalent, so nothing brings it back. `dir(mcp)` still answers and the deep import form still resolves, so one of the two import forms the README advertises is dead behind a namespace that looks healthy.

### Both are the same shape

Cross-agent, permanent, and invisible to the listing. A subagent's stray cell breaks its parent, and the parent's diagnosis (`dir(mcp)`) reports nothing wrong. The fix has to make the state recoverable rather than merely guarded — the guard was already there and the process still lost the module.

### Related, and the root cause of a third symptom

`sys.modules['__main__']` belongs to whichever shell was created LAST:

```
shell A: sys.modules['__main__'].__dict__ is globals() -> False
shell B: sys.modules['__main__'].__dict__ is globals() -> True
```

The kernel neutralises `sys.stdout`, `sys.displayhook` and `__dsh__.tools` against exactly this per-shell aliasing; `__main__` is not covered, and IPython's `init_sys_modules` re-points it per shell. Anything routed through `__main__` then breaks the README's "globals are not shared between shells" claim — `pickle` most visibly, both loudly (`PicklingError: Can't pickle `) and silently (a parent unpickling **its own** blob gets its data back inside a subagent's same-named class, no error).

Contributor guide

No contributing guide indexed for this repository

Research direction

Reproduce the two-shell examples first, then inspect the mcp_server_module construction site, the root __dsh__.tools.mcp registration, and IPython's init_sys_modules handling of __main__. Done means a ModuleType.__init__ reset and deletion of the root module recover safely, while globals and pickle behavior remain isolated as promised by the README.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
tooling
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.