meshcode 2.11.112__tar.gz → 2.11.113__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {meshcode-2.11.112 → meshcode-2.11.113}/PKG-INFO +1 -1
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/__init__.py +1 -1
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/protocol_handler.py +34 -1
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.112 → meshcode-2.11.113}/pyproject.toml +1 -1
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_security_regressions.py +57 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/README.md +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/__main__.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/_session_handoff_template.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/claude_update.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/cli.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/compat.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/daemon.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/doctor.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/hooks/__init__.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/hooks/repo_path_lock.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/hostd.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/invites.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/launcher.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/preferences.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/rpc_allowlist.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/run_agent.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/secrets.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/self_update.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/up.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode/upload.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/setup.cfg +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_autonomous_closegap_1.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_autonomous_closegap_2.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_autonomous_closegap_3.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_autonomous_prompt_inject.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_core.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_doctor.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_file_upload.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_marketplace_ratings.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_run_agent_dry_run.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_run_agent_no_server_import.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_self_update_user_site.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.112 → meshcode-2.11.113}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -207,7 +207,40 @@ def _spawn_terminal_macos(cmd: str) -> tuple[bool, str]:
|
|
|
207
207
|
'cd "$HOME" 2>/dev/null || cd /'] # neutral, non-TCC-protected cwd
|
|
208
208
|
if venv_bin:
|
|
209
209
|
lines.append(f'export PATH={shlex.quote(venv_bin)}:"$PATH"')
|
|
210
|
-
|
|
210
|
+
# RECYCLE-KILL-OLD-TERMINAL (task 2094ff3d, Samuel "que se cierre la terminal
|
|
211
|
+
# pasada"). A recycle/stop ends the agent cleanly but macOS Terminal leaves the
|
|
212
|
+
# window open as "[Process completed]" — an orphan. Run the agent WITHOUT `exec`
|
|
213
|
+
# so bash survives its exit and can close THIS window. `cmd` ends in
|
|
214
|
+
# `exec … meshcode run …`; strip the leading `exec ` so the agent runs as a child
|
|
215
|
+
# (it still execvp's claude internally — that becomes the window's foreground proc).
|
|
216
|
+
lines.append('MC_TTY="$(tty 2>/dev/null)"')
|
|
217
|
+
# `cmd` is a compound line (`unset …; export PATH=…; exec <py> -m meshcode run …`),
|
|
218
|
+
# so the `exec` is NOT leading. Drop the LAST `exec ` token (the one before the
|
|
219
|
+
# interpreter) so the agent runs as a CHILD and bash survives to close the window.
|
|
220
|
+
if "exec " in cmd:
|
|
221
|
+
_head, _, _tail = cmd.rpartition("exec ")
|
|
222
|
+
_run = _head + _tail
|
|
223
|
+
else:
|
|
224
|
+
_run = cmd
|
|
225
|
+
lines.append(_run)
|
|
226
|
+
lines.append('MC_RC=$?')
|
|
227
|
+
# Close THIS Terminal window ONLY on a clean exit (recycle/stop => 0). On a CRASH
|
|
228
|
+
# (non-zero) leave it OPEN so the scrollback is available for debugging (commander
|
|
229
|
+
# condition: debugging > clean window). The own-$MC_TTY filter closes ONLY this
|
|
230
|
+
# window — never another agent's. saving no => no "close?" prompt. macOS-only path.
|
|
231
|
+
lines.append('if [ "$MC_RC" = "0" ] && [ -n "$MC_TTY" ]; then')
|
|
232
|
+
lines.append(
|
|
233
|
+
" /usr/bin/osascript"
|
|
234
|
+
" -e 'tell application \"Terminal\"'"
|
|
235
|
+
" -e 'repeat with w in windows'"
|
|
236
|
+
" -e 'try'"
|
|
237
|
+
" -e \"if (tty of selected tab of w) is \\\"$MC_TTY\\\" then\""
|
|
238
|
+
" -e 'close w saving no'"
|
|
239
|
+
" -e 'end if'"
|
|
240
|
+
" -e 'end try'"
|
|
241
|
+
" -e 'end repeat'"
|
|
242
|
+
" -e 'end tell' >/dev/null 2>&1")
|
|
243
|
+
lines.append('fi')
|
|
211
244
|
try:
|
|
212
245
|
launch_dir.mkdir(parents=True, exist_ok=True)
|
|
213
246
|
script_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
@@ -169,3 +169,60 @@ class TestRLSPoliciesExist:
|
|
|
169
169
|
def test_mc_api_keys_has_rls(self):
|
|
170
170
|
all_sql = _read_all_migrations()
|
|
171
171
|
assert "mc_api_keys ENABLE ROW LEVEL SECURITY" in all_sql
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class TestP1_4_LaunchBatchAgentNameRCE:
|
|
175
|
+
"""P1-4: cmd_launch_batch interpolates the agent name into the platform launch
|
|
176
|
+
string (Windows `cmd /k`, POSIX shell). The name MUST be allowlisted + hard-
|
|
177
|
+
rejected at the loop boundary BEFORE it reaches the shell, or a crafted name like
|
|
178
|
+
`x" & calc & "` breaks out of the quoting and executes arbitrary code (and a leading
|
|
179
|
+
`-` could be read as a flag / a 300-char name is a DoS). Landed v2.11.109; these
|
|
180
|
+
tests lock the allowlist + its wiring so the fix can't silently regress."""
|
|
181
|
+
|
|
182
|
+
# Real RCE / arg-injection / DoS payloads that MUST be rejected.
|
|
183
|
+
MALICIOUS = [
|
|
184
|
+
'x"&calc', # cmd.exe quote-break + chain
|
|
185
|
+
'x" & calc & "', # the canonical P1-4 payload
|
|
186
|
+
';rm -rf', # POSIX command chain
|
|
187
|
+
'$(id)', # POSIX command substitution
|
|
188
|
+
'`id`', # POSIX backtick substitution
|
|
189
|
+
'|nc', # pipe to netcat
|
|
190
|
+
'&& curl evil', # chain
|
|
191
|
+
'a b', # whitespace (token split)
|
|
192
|
+
'-rf', # leading dash -> flag/arg injection
|
|
193
|
+
'--version', # leading dashes -> flag injection
|
|
194
|
+
'a' * 65, # length cap (DoS via 300-char names)
|
|
195
|
+
'', # empty
|
|
196
|
+
'x\n', # trailing newline (\Z anchor, not $)
|
|
197
|
+
'../etc', # path traversal chars
|
|
198
|
+
]
|
|
199
|
+
# Plain, real agent identifiers that MUST be allowed (no false-rejects).
|
|
200
|
+
LEGIT = ['back', 'qa', 'front-2', 'mesh_dev', 'commander', 'a', 'Agent_1', 'x' * 64]
|
|
201
|
+
|
|
202
|
+
def test_allowlist_blocks_malicious_names(self):
|
|
203
|
+
from meshcode.protocol_handler import is_valid_agent_name
|
|
204
|
+
for payload in self.MALICIOUS:
|
|
205
|
+
assert not is_valid_agent_name(payload), \
|
|
206
|
+
f"SECURITY: malicious agent name was NOT rejected: {payload!r}"
|
|
207
|
+
|
|
208
|
+
def test_allowlist_allows_legit_names(self):
|
|
209
|
+
from meshcode.protocol_handler import is_valid_agent_name
|
|
210
|
+
for name in self.LEGIT:
|
|
211
|
+
assert is_valid_agent_name(name), \
|
|
212
|
+
f"REGRESSION: legitimate agent name was wrongly rejected: {name!r}"
|
|
213
|
+
|
|
214
|
+
def test_cmd_launch_batch_validates_before_shell(self):
|
|
215
|
+
"""Wiring guard: cmd_launch_batch MUST call is_valid_agent_name BEFORE it
|
|
216
|
+
builds the `... run "<name>"` shell string — else the allowlist exists but
|
|
217
|
+
isn't enforced. Catches a removed/moved call site, not just a missing regex."""
|
|
218
|
+
src = (Path(__file__).parent.parent / "meshcode" / "protocol_handler.py").read_text()
|
|
219
|
+
start = src.index("def cmd_launch_batch")
|
|
220
|
+
body = src[start:src.find("\ndef ", start + 1)]
|
|
221
|
+
assert "is_valid_agent_name" in body, \
|
|
222
|
+
"SECURITY: cmd_launch_batch does not call is_valid_agent_name at all"
|
|
223
|
+
# The validation must come BEFORE the name is interpolated into the launch cmd.
|
|
224
|
+
guard_pos = body.index("is_valid_agent_name")
|
|
225
|
+
sink = re.search(r'run\s+"?\{name\}|run\s+\{shlex\.quote\(name\)', body)
|
|
226
|
+
assert sink is not None, "cmd_launch_batch shell-interpolation sink not found (test stale?)"
|
|
227
|
+
assert guard_pos < sink.start(), \
|
|
228
|
+
"SECURITY: is_valid_agent_name is checked AFTER the shell interpolation — RCE boundary broken"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|