feat(#93): filesystem permission pre-flight (0600/0700) + one-click fix (P1) #99

Closed
the_og wants to merge 2 commits from feat/93 into feat/91
Owner

What (P1 — validation)

ssh-mcp refuses to start if its config is group/world-readable — it throws when mode & 0o077 is set, requiring dir 0700 / file 0600. A GUI user has no idea what chmod 600 means; they just get a dead server. This pre-flights the mode and offers a one-click fix. Generalised so any credential-bearing config can be checked.

Core (pure bcc_core, POSIX-only, fully injectable — tests never touch a real file)

  • permission_status(path) — the ssh-mcp rule: any group/other bit set (mode & 0o077) is not-ok; the file must be 0600, its directory 0700. Returns None on Windows (POSIX modes don't apply) or when the file is absent (nothing to pre-flight). Emits plain-language problems that name the offending octal mode. stat_mode is injectable.
  • fix_permissions(path) — chmod the file to 0600 and its directory to 0700. No-op on Windows; reports an OSError rather than raising. chmod is injectable.
  • 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 (minimal)

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. The fix chmods the user's own config in place (their explicit click); a failure is surfaced in-place. Smoke-tested headlessly with QT_QPA_PLATFORM=offscreen.

Testing

ruff + format --check clean; pytest green (529 passed). Fixture-driven tests: 0600 ok, 0644 file+dir blocked with octal in the message, file-bad/dir-ok, Windows + missing-file → None, chmod fix (file+dir), Windows no-op, OSError reporting, and the sidecar-path integration + fix target.

Closes #93. Part of epic #94.


Stacked PR — base is feat/91 (depends on #91's sidecar path resolution to know which file to check). Merge order: #90 → #91 → #93.
🤖 Generated with Claude Code

## What (P1 — validation) `ssh-mcp` refuses to start if its config is group/world-readable — it throws when `mode & 0o077` is set, requiring dir `0700` / file `0600`. A GUI user has no idea what `chmod 600` means; they just get a dead server. This pre-flights the mode and offers a one-click fix. Generalised so **any** credential-bearing config can be checked. ### Core (pure `bcc_core`, POSIX-only, fully injectable — tests never touch a real file) - **`permission_status(path)`** — the ssh-mcp rule: any group/other bit set (`mode & 0o077`) is not-ok; the file must be `0600`, its directory `0700`. Returns `None` on Windows (POSIX modes don't apply) or when the file is absent (nothing to pre-flight). Emits plain-language problems that name the offending octal mode. `stat_mode` is injectable. - **`fix_permissions(path)`** — chmod the file to `0600` and its directory to `0700`. No-op on Windows; reports an `OSError` rather than raising. `chmod` is injectable. - **`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 (minimal) 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. The fix chmods the user's own config in place (their explicit click); a failure is surfaced in-place. Smoke-tested headlessly with `QT_QPA_PLATFORM=offscreen`. ### Testing `ruff` + `format --check` clean; `pytest` green (529 passed). Fixture-driven tests: 0600 ok, 0644 file+dir blocked with octal in the message, file-bad/dir-ok, Windows + missing-file → `None`, chmod fix (file+dir), Windows no-op, OSError reporting, and the sidecar-path integration + fix target. Closes #93. Part of epic #94. --- **Stacked PR — base is `feat/91`** (depends on #91's sidecar path resolution to know which file to check). Merge order: **#90 → #91 → #93**. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
the_og added 1 commit 2026-08-12 03:10:46 -04:00
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
0b2827e6b8
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>
the_og added 1 commit 2026-08-12 03:14:18 -04:00
test(#93): key injected mode/chmod maps on as_posix() for the Windows runner
CI / Lint (ruff) (pull_request) Successful in 9s
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 23s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 12s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 16s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 13s
CI / Catalog signature (pull_request) Successful in 8s
603d24566d
permission_status/fix_permissions wrap paths in the running OS's Path class, so
on the windows-latest CI job str(p) uses backslashes and the fixture dict lookups
miss — the same portability trap fixed for #91. Normalise with Path(p).as_posix()
in the four affected lambdas. Pure test fix; production code unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
the_og closed this pull request 2026-08-12 23:53:48 -04:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.