Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,45 @@ jobs:
- name: Build
run: go build -v ./cmd/sshx

test-windows:
name: Test (windows-latest)
runs-on: windows-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.25.13"

- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~\AppData\Local\go-build
~\go\pkg\mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-

- name: Download dependencies
run: go mod download

- name: Run unit tests (cross-platform packages)
# Full-suite Windows enablement is tracked in issue #50: several
# pre-existing app/plugin/skillinstall/sshclient tests assume POSIX
# permission and symlink semantics. Packages listed here must stay
# green; grow this list as tests are ported.
run: go test -short ./cmd/... ./internal/execution/... ./internal/keyringstore/... ./internal/sqlsafe/... ./pkg/...

- name: Vet
run: go vet ./...

- name: Build
run: go build -v ./cmd/sshx

e2e:
name: E2E (${{ matrix.os }})
runs-on: ${{ matrix.os }}
Expand Down
27 changes: 19 additions & 8 deletions AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,11 @@ the project's mission:

**Out of scope (will not be accepted by default):**

- ❌ **MCP server / Model Context Protocol** — removed on purpose. `sshx` is
CLI-only. Do not reintroduce an `mcp-stdio` mode or MCP tools.
- ❌ **HTTP/SSE MCP server, daemons, or resident protocol services** — the
stdio MCP server (`sshx mcp`) is in scope: it is spawned and owned by an MCP
client, lives for exactly one client session, and re-enters sshx as one-shot
child processes per tool call. Do not add an HTTP/SSE transport, a listening
socket, or any server that outlives its client.
- ❌ **Daemons / long-running services / connection pools** — every command opens
a connection, does its work, and exits. There is no background process.
- ❌ **Resident remote agent / control plane** — do not require a service to be
Expand All @@ -90,7 +93,11 @@ correctness. Read-only host inspection, local plugin lifecycle, explicit plugin
trust, and bounded observation reuse are also in scope. Guarded SQL execution
(`sshx sql`) and guarded file apply (`sshx apply`) are deliberate scope
expansions: they absorb mutation risk (classify → precondition → backup →
atomic change → structured result) without becoming a workflow engine.
atomic change → structured result) without becoming a workflow engine. The
stdio MCP server (`sshx mcp`) is a thin adapter over the same contract: tools
map 1:1 to CLI verbs, results are the CLI's versioned JSON, every call is a
one-shot child invocation audited with `entry=mcp`, and password management is
never exposed as a tool.

**Convergence test:** every new sshx feature must remove an Agent judgment, not
add a command the Agent has to learn. Absorb remote tax (host, credential,
Expand Down Expand Up @@ -118,6 +125,7 @@ internal/app/ → CLI surface (argument parsing, routing, sub-comman
inspect.go → one-shot capability execution + observation caching
sql.go → sshx sql: guarded SQL pipeline (classify → gate → explain → backup → execute)
apply.go → sshx apply: guarded single-file mutation (hash → backup → atomic write)
mcp.go → sshx mcp: stdio MCP server; tools self-exec sshx as one-shot children
internal/execution/ → versioned request/result model, selectors, executor
internal/plugin/ → manifests, schemas, scaffolds, trust, built-ins
internal/runtimepath/ → ~/.sshx / SSHX_HOME runtime-root resolution
Expand Down Expand Up @@ -362,7 +370,9 @@ Items must respect the boundaries in §3.

**Now / recently shipped**

- ✅ CLI-only refactor (MCP server + connection pool removed).
- ✅ CLI-only refactor (resident MCP server + connection pool removed), later
followed by the deliberate reintroduction of a **stdio-only** MCP adapter
(`sshx mcp`) over the same one-shot execution contract.
- ✅ Per-host SSH keys and per-host password keys.
- ✅ Strict host-key verification with opt-in overrides.
- ✅ Hardened sudo password handling (stdin), atomic config writes, secure
Expand All @@ -388,8 +398,9 @@ Items must respect the boundaries in §3.

- ⬜ Pluggable secret backends behind the existing keyring abstraction.

Anything implying a daemon, MCP, tunneling, or a GUI is explicitly **rejected**
unless the mission in §1–§3 is formally revised.
Anything implying a daemon, a resident protocol server (including HTTP/SSE
MCP), tunneling, or a GUI is explicitly **rejected** unless the mission in
§1–§3 is formally revised.

## 11. Release Process

Expand All @@ -406,8 +417,8 @@ unless the mission in §1–§3 is formally revised.
When working in this repo:

1. **Stay within the mission.** Re-read §3 before adding features. Default to a
smaller change. Never reintroduce MCP, a daemon, a connection pool, tunneling,
or a GUI.
smaller change. Never introduce a daemon, a connection pool, an HTTP/SSE
protocol server, tunneling, or a GUI.
2. **Hold the toolchain line.** Keep `go.mod` at `go 1.25.13`. If a dependency
forces a newer directive, pin an older compatible version instead of bumping
the directive (CI runs Go 1.25.13).
Expand Down
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.7.0] - 2026-08-16

### Added

- Add `sshx mcp`, a stdio Model Context Protocol server built on the official
`modelcontextprotocol/go-sdk`. Tools (`sshx_run`, `sshx_sql`, `sshx_apply`,
`sshx_inspect`, `sshx_sftp`, `sshx_transfer`, `sshx_host_list`) map 1:1 to
the CLI execution contract; every tool call re-enters sshx as a one-shot
child process and returns the CLI's versioned JSON verbatim. The server is
spawned and owned by an MCP client, holds no connections, and exits with its
client — HTTP/SSE transports and resident services remain out of scope.
- `--host-list --json` now emits a machine-readable `sshx.hosts.v1` document
(names, addresses, groups, tags, and credential key references only).
- Audit events record an `entry` field (currently `mcp`) so MCP-originated
executions are distinguishable from direct CLI use. The marker is metadata
only and never affects trust, safety, or credential decisions.
- Add Windows CI coverage: build, vet, and unit tests for the cross-platform
core packages (`cmd`, `execution`, `keyringstore`, `sqlsafe`, `pkg`);
full-suite Windows enablement is tracked in issue #50.
- Add `make test-keychain-macos` and `scripts/macos-dev-keychain.sh`: run the
real-keyring E2E suite locally inside an ephemeral macOS Keychain with no
GUI authorization prompts, restoring the original keychain afterwards.
- Add `CONTRIBUTING.md` and unit coverage for `internal/keyringstore` (system
and `sshx_e2e` backends).

### Security

- Password management is deliberately not exposed over MCP; secret set/get
remains CLI-only. `force` / `no_safety_check` require an explicit
`bypass_reason` tool parameter, mirroring the CLI contract.

## [0.6.0] - 2026-08-16

### Added
Expand Down
88 changes: 88 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Contributing to sshx

Thanks for your interest in improving sshx. This guide covers the practical
workflow; **read [AGENT.md](./AGENT.md) first** — it defines the project's
mission, scope boundaries (non-goals), and architecture, and every change is
reviewed against it.

## Ground Rules

- **Scope discipline**: features listed as non-goals in AGENT.md §3 (daemons,
resident remote agents, orchestration, GUI/TUI, plaintext secrets, …) will
not be accepted without a prior discussion issue. When in doubt, open an
issue before writing code.
- **Convergence test**: a new feature must remove an Agent judgment, not add a
command an Agent has to learn.
- **Security first**: never weaken host-key verification, keyring-only secret
storage, safety-check, or audit semantics for convenience.

## Development Setup

Requirements: Go (version pinned in `go.mod`), `make`, and optionally
`golangci-lint`.

```bash
git clone https://github.com/talkincode/sshx.git
cd sshx
make setup-hooks # install pre-commit/pre-push hooks (fmt, vet, tests)
make build # build ./bin/sshx
```

## Testing

| Command | What it runs |
| --- | --- |
| `make test-short` | unit tests only (`-short`) |
| `make test` | all Go tests including the E2E package |
| `make test-e2e` | compiled-binary E2E suite against an in-process SSH/SFTP server |
| `make test-keychain-macos` | E2E with the real macOS Keychain, in an ephemeral keychain, no GUI prompts |
| `make check` | fmt + vet + tests |

Notes:

- **`sshx_e2e` build tag**: tests and E2E binaries built with `-tags sshx_e2e`
swap the OS keyring for a file-backed isolated keyring
(`internal/keyringstore/backend_e2e.go`, keyed by `SSHX_E2E_KEYRING_FILE`).
This keeps routine test runs off your real Keychain/Credential Manager. The
real OS keyring path is only exercised when `SSHX_E2E_REAL_KEYRING=1`.
- The E2E suite compiles the actual binary and talks real TCP SSH/SFTP to an
isolated in-process server; it observes exit codes, stdout/stderr JSON,
remote state, `known_hosts`, settings, keyring, and audit JSONL.
- macOS contributors: see "macOS Keychain Prompts During Development" in
[docs/troubleshooting.md](./docs/troubleshooting.md).

## Acceptance-Matrix Rule (required for new first-level features)

`docs/roadmap.md` defines hard coverage minimums. Any new first-level
capability must ship with:

1. at least one happy-path E2E through the compiled binary,
2. at least one failure-path E2E if the feature is high-risk,
3. two role/permission states if the feature touches permissions,
4. one failure-recovery/rollback proof if the feature mutates state,
5. an updated acceptance matrix row in `docs/roadmap.md`.

Component tests alone do not count as completion evidence.

## Pull Requests

- Use conventional commit titles (`feat(scope): …`, `fix: …`, `docs: …`,
`ci: …`, `chore: …`), matching the existing history.
- Keep PRs focused; separate refactors from behavior changes.
- Update user-facing docs in the same PR: `README.md`, `README_CN.md`,
`docs/`, `internal/app/usage.go` (help text must stay in sync with flags),
and `CHANGELOG.md` under `[Unreleased]`.
- CI must be green: unit tests (Linux/macOS/Windows), E2E (Linux/macOS), lint,
and security scans.

## Reporting Issues

- Bugs: include the sshx version, OS, the exact command (redact hosts and
secrets), and `--json` output when possible.
- Security vulnerabilities: **do not open a public issue** — follow
[SECURITY.md](./SECURITY.md).

## License

By contributing you agree that your contributions are licensed under the
[MIT License](./LICENSE).
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ test-e2e: ## Run compiled-binary SSH/SFTP E2E tests (native keyring is opt-in)
@echo "Running compiled-binary E2E tests..."
$(GOTEST) -v ./tests/e2e

test-keychain-macos: ## Run real-keyring E2E in an ephemeral macOS Keychain (no GUI prompts)
@echo "Running real-keyring E2E tests in an ephemeral macOS Keychain..."
bash scripts/macos-dev-keychain.sh

test-verbose: ## Run verbose tests
@echo "Running verbose tests..."
$(GOTEST) -v -race ./...
Expand Down
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Agent-Native Remote Execution over SSH
[![Release](https://img.shields.io/github/v/release/talkincode/sshx?style=flat-square&logo=github)](https://github.com/talkincode/sshx/releases)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://github.com/talkincode/sshx/blob/main/LICENSE)
[![Go Report Card](https://goreportcard.com/badge/github.com/talkincode/sshx?style=flat-square)](https://goreportcard.com/report/github.com/talkincode/sshx)
[![Coverage](https://img.shields.io/badge/coverage-20.0%25-yellow?style=flat-square&logo=go)](https://github.com/talkincode/sshx)
[![Coverage](https://img.shields.io/badge/coverage-48.4%25-yellowgreen?style=flat-square&logo=go)](https://github.com/talkincode/sshx)

[![GitHub Stars](https://img.shields.io/github/stars/talkincode/sshx?style=flat-square&logo=github)](https://github.com/talkincode/sshx/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/talkincode/sshx?style=flat-square&logo=github)](https://github.com/talkincode/sshx/network/members)
Expand Down Expand Up @@ -73,6 +73,8 @@ It remains a single binary with one-shot invocations and no resident component o
9. One-shot host inspection with built-in system/network capabilities, local
sshx-owned plugins, explicit digest trust, and freshness-bounded observations.
10. Guarded single-file apply: hash precondition, backup, and atomic replace.
11. Built-in stdio MCP server (`sshx mcp`): the same execution contract, safety
gates, and audit trail exposed as Model Context Protocol tools.

## Installation

Expand Down Expand Up @@ -344,6 +346,29 @@ sshx -h=prod-web --pty "top -b -n1"

The timeout can also be set via the `SSH_TIMEOUT` environment variable.

### MCP server (stdio)

MCP-capable agents can consume the same execution contract as native tools:

```bash
sshx mcp
```

```json
{
"mcpServers": {
"sshx": { "command": "sshx", "args": ["mcp"] }
}
}
```

The server speaks MCP over stdio only, is spawned and owned by the client, and
re-enters sshx as a one-shot child process per tool call — identical safety
gates, keyring roles, and audit trail (events carry `entry: "mcp"`). Exposed
tools: `sshx_run`, `sshx_sql`, `sshx_apply`, `sshx_inspect`, `sshx_sftp`,
`sshx_transfer`, `sshx_host_list`. Password management is deliberately not
exposed over MCP. See [docs/mcp.md](docs/mcp.md).

## Guarded SQL Execution

Use `sshx sql` instead of sending raw `psql` or `sqlite3` commands through
Expand Down Expand Up @@ -870,6 +895,13 @@ make lint
The normal E2E run uses an isolated, test-only keyring provider. CI additionally
checks the production binary against an ephemeral macOS Keychain.

## Contributing

Contributions are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) for the
development workflow, testing requirements (including the acceptance-matrix
rule for new features), and PR expectations, and [AGENT.md](AGENT.md) for the
project's mission and scope boundaries.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
Expand Down
25 changes: 24 additions & 1 deletion README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ $$\ $$ |$$\ $$ |$$ | $$ |$$ /\$$\
[![Release](https://img.shields.io/github/v/release/talkincode/sshx?style=flat-square&logo=github)](https://github.com/talkincode/sshx/releases)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://github.com/talkincode/sshx/blob/main/LICENSE)
[![Go Report Card](https://goreportcard.com/badge/github.com/talkincode/sshx?style=flat-square)](https://goreportcard.com/report/github.com/talkincode/sshx)
[![Coverage](https://img.shields.io/badge/coverage-20.0%25-yellow?style=flat-square&logo=go)](https://github.com/talkincode/sshx)
[![Coverage](https://img.shields.io/badge/coverage-48.4%25-yellowgreen?style=flat-square&logo=go)](https://github.com/talkincode/sshx)

[![GitHub Stars](https://img.shields.io/github/stars/talkincode/sshx?style=flat-square&logo=github)](https://github.com/talkincode/sshx/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/talkincode/sshx?style=flat-square&logo=github)](https://github.com/talkincode/sshx/network/members)
Expand Down Expand Up @@ -73,6 +73,7 @@ Agent 需要的不是另一个交互式 SSH shell,而是一份稳定、可组
9. 单次主机环境探测:内置系统/网络能力,应用级插件归 sshx 本地运行目录管理,
支持摘要信任和有有效期的观察快照。
10. 受控单文件 apply:哈希前置条件、备份和原子替换。
11. 内置 stdio MCP server(`sshx mcp`):以 Model Context Protocol 工具形式暴露同一套执行契约、安全门禁与审计留痕。

## 安装

Expand Down Expand Up @@ -337,6 +338,24 @@ sshx -h=prod-web --pty "top -b -n1"

超时也可以通过环境变量 `SSH_TIMEOUT` 设置。

### MCP server(stdio)

支持 MCP 的 Agent 可以把同一套执行契约当作原生工具消费:

```bash
sshx mcp
```

```json
{
"mcpServers": {
"sshx": { "command": "sshx", "args": ["mcp"] }
}
}
```

server 仅通过 stdio 通信,由 MCP 客户端拉起并随之退出;每个 tool call 都以一次性子进程重新进入 sshx——安全门禁、keyring 凭据角色与审计留痕完全一致(审计事件带 `entry: "mcp"` 标记)。暴露的工具:`sshx_run`、`sshx_sql`、`sshx_apply`、`sshx_inspect`、`sshx_sftp`、`sshx_transfer`、`sshx_host_list`。密码管理刻意不经 MCP 暴露。详见 [docs/mcp.md](docs/mcp.md)。

## 主机探测与本地插件

面对陌生服务器时,用一次结构化探测替代多轮零散命令:
Expand Down Expand Up @@ -703,6 +722,10 @@ make lint

常规 E2E 使用仅供测试的隔离 keyring 后端;CI 还会让生产构建连接临时 macOS Keychain,验证真实系统 keyring 生命周期。

## 贡献

欢迎贡献。请阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 了解开发流程、测试要求(包括新功能的验收矩阵规则)与 PR 规范,并阅读 [AGENT.md](AGENT.md) 了解项目使命与边界。

## 许可证

本项目采用 MIT 许可证 - 有关详细信息,请参阅 [LICENSE](LICENSE) 文件。
Expand Down
4 changes: 4 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ First, update `CHANGELOG.md` to record the changes for this release:
- Bug fixes description
```

Then update the supported-versions table in `SECURITY.md` so it matches the
N / N-1 policy for the new release (e.g. releasing `v0.7.0` means `0.7.x` and
`0.6.x` are supported and everything older is not).

### 2. Commit Changes

```bash
Expand Down
6 changes: 3 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ We take security seriously. The following versions of SSHX are currently support

| Version | Supported |
| ------- | ------------------ |
| 0.5.x | :white_check_mark: |
| 0.4.x | :white_check_mark: |
| < 0.4.0 | :x: |
| 0.7.x | :white_check_mark: |
| 0.6.x | :white_check_mark: |
| < 0.6.0 | :x: |

Security updates are provided for the latest minor release and the previous
minor release (N-1). Older lines do not receive patches; please upgrade.
Expand Down
1 change: 1 addition & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [Guarded File Apply](apply.md)
- [Agent and Script Mode](agent-scripting.md)
- [Inspection Capabilities and Local Plugins](inspection-plugins.md)
- [MCP Server (stdio)](mcp.md)
- [Usage Scenarios](usage-scenarios.md)
- [Security Guidelines](security-guidelines.md)
- [Troubleshooting](troubleshooting.md)
Expand Down
Loading
Loading