feat(#93): filesystem permission pre-flight for credential configs (0600/0700)
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 13s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 13s
CI / Lint (ruff) (pull_request) Successful in 8s
CI / Tests (py3.12 / windows-latest) (pull_request) Failing after 24s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 12s
CI / Catalog signature (pull_request) Successful in 8s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 13s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 13s
CI / Lint (ruff) (pull_request) Successful in 8s
CI / Tests (py3.12 / windows-latest) (pull_request) Failing after 24s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 12s
CI / Catalog signature (pull_request) Successful in 8s
ssh-mcp refuses to start if its config is group/world-readable (mode & 0o077 → throws, requiring dir 0700 / file 0600). A GUI user has no idea what chmod 600 means — they just get a dead server. This checks it and offers a one-click fix. Core (pure, POSIX-only, injectable stat/chmod so tests never touch a real file): - permission_status(path): the ssh-mcp rule — any group/other bit set (mode & 0o077) is not-ok; file must be 0600, its dir 0700. Returns None on Windows (modes don't apply) or when the file is absent (nothing to pre-flight). Plain-language problems naming the offending octal mode. - fix_permissions(path): chmod file → 0600, dir → 0700. No-op on Windows; reports an OSError instead of raising. - sidecar_permission_warnings() / sidecar_permission_fix_target(): tie the check to #91's sidecar path resolution so it knows WHICH file to inspect. Mirror the sidecar-warnings shape. GUI: a warning label + "Fix permissions" button in the stdio editor (mirrors the removed-flag surface), shown only when the sidecar exists and is too open. Hidden on Windows and for non-sidecar servers. Smoke-tested headlessly. pytest green (529 passed), ruff + format clean. Closes #93. Part of epic #94. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
2e5d0351b4
commit
0b2827e6b8
@@ -908,6 +908,21 @@ class ServerEditor(QFrame):
|
||||
self.sidecar_warn.setWordWrap(True)
|
||||
self.sidecar_warn.hide()
|
||||
v.addWidget(self.sidecar_warn)
|
||||
# Shown when the sidecar config is group/other-accessible (#93): ssh-mcp
|
||||
# refuses to start unless it's 0600 / its dir 0700. One-click chmod fix.
|
||||
# POSIX only — hidden on Windows where modes don't apply.
|
||||
self.perm_warn = QLabel("")
|
||||
self.perm_warn.setStyleSheet(f"color: {WARN};")
|
||||
self.perm_warn.setWordWrap(True)
|
||||
self.perm_warn.hide()
|
||||
self.perm_fix_btn = QPushButton("Fix permissions")
|
||||
self.perm_fix_btn.setToolTip("chmod the config file to 0600 and its directory to 0700")
|
||||
self.perm_fix_btn.clicked.connect(self._fix_permissions)
|
||||
self.perm_fix_btn.hide()
|
||||
perm_row = QHBoxLayout()
|
||||
perm_row.addWidget(self.perm_warn, 1)
|
||||
perm_row.addWidget(self.perm_fix_btn)
|
||||
v.addLayout(perm_row)
|
||||
v.addWidget(self._lbl("Environment variables"))
|
||||
self.env = KeyValueTable(
|
||||
"Variable", "Value", on_change=self._emit, before_change=self._before_change
|
||||
@@ -1010,6 +1025,8 @@ class ServerEditor(QFrame):
|
||||
self.removed_flag_warn.hide()
|
||||
self.removed_flag_fix_btn.hide()
|
||||
self.sidecar_warn.hide()
|
||||
self.perm_warn.hide()
|
||||
self.perm_fix_btn.hide()
|
||||
self._loading = False
|
||||
return
|
||||
self.setEnabled(True)
|
||||
@@ -1100,6 +1117,8 @@ class ServerEditor(QFrame):
|
||||
self.removed_flag_warn.hide()
|
||||
self.removed_flag_fix_btn.hide()
|
||||
self.sidecar_warn.hide()
|
||||
self.perm_warn.hide()
|
||||
self.perm_fix_btn.hide()
|
||||
return
|
||||
_, notes = core.split_suspicious_args(self._current_arg_lines())
|
||||
if notes:
|
||||
@@ -1140,6 +1159,15 @@ class ServerEditor(QFrame):
|
||||
self.sidecar_warn.show()
|
||||
else:
|
||||
self.sidecar_warn.hide()
|
||||
# Sidecar filesystem permissions (#93). Real platform/fs; no-op on Windows.
|
||||
perm_warnings = core.sidecar_permission_warnings(stdio)
|
||||
if perm_warnings:
|
||||
self.perm_warn.setText("⚠ " + "\n".join(perm_warnings))
|
||||
self.perm_warn.show()
|
||||
self.perm_fix_btn.show()
|
||||
else:
|
||||
self.perm_warn.hide()
|
||||
self.perm_fix_btn.hide()
|
||||
|
||||
def _fix_args(self):
|
||||
if self._before_change:
|
||||
@@ -1160,6 +1188,23 @@ class ServerEditor(QFrame):
|
||||
self.env.load(migrated.get("env", {}))
|
||||
self.args.setPlainText("\n".join(migrated.get("args", [])))
|
||||
|
||||
def _fix_permissions(self):
|
||||
"""chmod the sidecar config to 0600 / its dir to 0700 (#93)."""
|
||||
stdio = {
|
||||
"command": self.command.text().strip(),
|
||||
"args": self._current_arg_lines(),
|
||||
"env": self.env.dump(),
|
||||
}
|
||||
target = core.sidecar_permission_fix_target(stdio)
|
||||
if target is None:
|
||||
return
|
||||
changed, note = core.fix_permissions(target)
|
||||
if not changed:
|
||||
# Surface the failure in-place rather than silently doing nothing.
|
||||
self.perm_warn.setText("⚠ " + (note or "could not change permissions"))
|
||||
return
|
||||
self._check_args() # re-check; the warning clears when perms are now tight
|
||||
|
||||
# --- dependency ------------------------------------------------------ #
|
||||
def refresh_dependency(self, auto_open=False):
|
||||
if not self.isEnabled():
|
||||
|
||||
Reference in New Issue
Block a user