{"id":50712108,"url":"https://github.com/ba0f3/lunacli","last_synced_at":"2026-06-09T16:04:10.187Z","repository":{"id":359396109,"uuid":"1245672093","full_name":"ba0f3/lunacli","owner":"ba0f3","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-31T17:06:31.000Z","size":283,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-31T19:08:13.915Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ba0f3.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-21T12:51:10.000Z","updated_at":"2026-05-31T17:06:35.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ba0f3/lunacli","commit_stats":null,"previous_names":["ba0f3/lunacli"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/ba0f3/lunacli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ba0f3%2Flunacli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ba0f3%2Flunacli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ba0f3%2Flunacli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ba0f3%2Flunacli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ba0f3","download_url":"https://codeload.github.com/ba0f3/lunacli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ba0f3%2Flunacli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34114456,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-09T16:04:07.261Z","updated_at":"2026-06-09T16:04:10.175Z","avatar_url":"https://github.com/ba0f3.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Luna — Zero-Trust Remote SSH Agent\n\n[![Go Version](https://img.shields.io/badge/Go-1.25+-00ADD8?logo=go)](https://go.dev)\n[![License](https://img.shields.io/badge/license-MIT-green)]()\n\n**Luna** is a stdio MCP server that gives LLM agents secure, auditable access to remote Linux servers over SSH — with **mandatory out-of-band human approval** for every mutating command.\n\n```\n┌─────────────┐     MCP stdio      ┌──────────────┐     SSH      ┌──────────────┐\n│  AI Agent   │ ◄──────────────►   │  luna serve  │ ◄─────────► │  Remote Host │\n│ (Claude,    │    JSON-RPC        │              │              │              │\n│  OpenCode,  │                    │  • classify  │              │  • commands  │\n│  goclaw)    │                    │  • gate      │              │  • file I/O  │\n└─────────────┘                    │  • approve   │              │  • inventory │\n                                   │  • audit     │              └──────────────┘\n                                   └──────┬───────┘\n                                           │\n                                           │ Telegram / CLI\n                                           ▼\n                                   ┌──────────────┐\n                                   │    Human     │\n                                   │  Approver    │\n                                   └──────────────┘\n```\n\n**Never trust the AI agent.** Every command is classified before execution. Read-only ops run immediately. Mutating commands require out-of-band approval. There is no `allow_mutations` self-approval path.\n\n---\n\n## Features\n\n- **🔒 Two‑layer security engine** — Compiled forbidden patterns (irreversible ops like `rm -rf /`, `sudo`, reverse shells) + YAML policy rules\n- **🧠 Shell‑aware classification** — Parses commands with `mvdan/sh` to defeat obfuscation (escapes, quotes, substitutions)\n- **✅ Out‑of‑band approval** — Mutating commands require human approval via Telegram before execution\n- **🔑 Multi‑source SSH auth** — ssh-agent, `IdentityFile` entries from `~/.ssh/config`, and default `~/.ssh/id_*` keys, all with known‑hosts verification\n- **📂 Remote file access** — Read files via `cat` or SFTP; fetch files via SCP\n- **📊 Host inventory** — Scan remote hosts for OS, packages, containers, and hardware\n- **🔍 CVE lookup** — Search bundled CVE data locally\n- **📝 Audit logging** — JSON‑lines structured logging to stderr + optional file\n\n---\n\n## Quick Start\n\n### 1. Build\n\n```bash\ngit clone https://github.com/ba0f3/lunacli.git\ncd lunacli\nmake build\n# → ./bin/luna\n```\n\n### 2. Configure\n\nOr run interactive setup:\n\n```bash\n./bin/luna onboard\n```\n\nThis writes `luna.config.json`, `luna.d/policy.yml`, skeleton `hosts.yml`, and configures Telegram.\n\nCreate a policy file (required) in `./luna.d/policy.yml`:\n\n```yaml\nversion: 1\nrules:\n  - action: allow\n    hosts: [\"*\"]\n    commands:\n      - binary: uptime\n      - binary: whoami\n      - binary: cat\n      - binary: grep\n\n  - action: approve\n    hosts: [\"*\"]\n    commands:\n      - binary: systemctl\n        args_prefix: [\"restart\"]\n      - binary: apt\n```\n\nSet up Telegram approval (see [Configuration → Telegram](#telegram)), or use `luna.config.json`:\n\n```json\n{\n  \"config_dir\": \"./luna.d\",\n  \"approval\": { \"ttl\": \"10m\" },\n  \"telegram\": {\n    \"bot_token_file\": \"/path/to/bot-token\",\n    \"approver_user_id\": \"123456789\"\n  }\n}\n```\n\nFull example configs in [`examples/`](examples/).\n\n### 3. Run\n\n```bash\n./bin/luna serve\n```\n\nStart your MCP client pointing at `[\"/path/to/luna\", \"serve\"]`.\n\n### 4. Use\n\n```json\n// Read-only command — runs immediately\n{\n  \"host\": \"web1\",\n  \"command\": \"uptime\"\n}\n\n// Mutating command — returns PERMISSION_REQUIRED\n{\n  \"host\": \"web1\",\n  \"command\": \"systemctl restart nginx\"\n}\n// → approval_id: abc-123, expires_at: 2026-05-21T17:06:02Z\n\n// After human approves in Telegram, retry with approval_id\n{\n  \"host\": \"web1\",\n  \"command\": \"systemctl restart nginx\",\n  \"approval_id\": \"abc-123\"\n}\n// → executes\n```\n\n---\n\n## Architecture\n\n### Security Engine\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                  Command Classification                      │\n├─────────────────────────────────────────────────────────────┤\n│  Layer 1 (compiled, immutable)                              │\n│  • Max length check (4 096 chars)                           │\n│  • Forbidden patterns: rm -rf /, mkfs, dd, sudo,            │\n│    reverse shells, iptables -F, useradd, passwd, ...        │\n├─────────────────────────────────────────────────────────────┤\n│  Layer 2 (YAML policy)                                      │\n│  • Policy rules evaluated top-to-bottom; first match wins   │\n│  • Actions: allow (read-only) / approve (OOB) / deny        │\n│  • Deny-by-default: unmatched commands → mutating            │\n└─────────────────────────────────────────────────────────────┘\n```\n\n### Approval Flow\n\n```\nAgent                     luna serve                    Human\n  │                          │                            │\n  │── execute_remote ──────► │                            │\n  │   (mutating command)     │                            │\n  │                          │── Create pending ──────────│\n  │                          │── Notify (Telegram) ──────►│\n  │◄── PERMISSION_REQUIRED   │                            │\n  │     approval_id: abc     │                            │\n  │                          │                   Approve? │◄──── tap button\n  │                          │◄─── Approve ──────────────│\n  │── execute_remote ──────► │                            │\n  │   (same params + id)     │                            │\n  │                          │── Verify \u0026 Consume         │\n  │                          │── SSH execute              │\n  │◄── { stdout, exit code } │                            │\n```\n\n### SSH transport and authentication\n\nBy default (`transport.mode: proxy`), lunacli obtains **signed SSH credentials** from **luna-proxy** via in-process **luna-sdk** (mTLS). The proxy runs **access approval** on its own Telegram; lunacli still **dials targets directly** over TCP/SSH (the proxy does not relay SSH).\n\nMutating **commands** still require lunacli **command approval** (Telegram in `luna.config.json`) — two separate human gates.\n\n```text\nMCP agent → luna serve → luna-proxy (sign)     access Telegram (proxy)\n                      ↘ target host (SSH dial)   command Telegram (lunacli)\n```\n\n| `transport.mode` | Connect path | Credentials |\n|------------------|--------------|-------------|\n| `proxy` (default) | lunacli → target | Proxy-signed cert via SDK |\n| `direct` | lunacli → target | ssh-agent / disk keys (not recommended) |\n| `luna-agent` | lunacli → target | `SSH_AUTH_SOCK` → luna-agent (not recommended) |\n\nExample `luna.config.json`:\n\n```json\n{\n  \"transport\": {\n    \"mode\": \"proxy\",\n    \"proxy\": {\n      \"endpoint\": \"https://proxy.example:8443\"\n    }\n  }\n}\n```\n\nmTLS client material defaults to `~/.config/luna/certs/{client.crt,client.key,ca.crt}` or `LUNA_PROXY_TLS_*` / `transport.proxy.tls_*`.\n\nKnown‑hosts verification respects `StrictHostKeyChecking` (`no`, `accept-new`, `ask`).\n\n---\n\n## Configuration\n\n### Config Directory Resolution\n\nLuna looks for `policy.yml` and `hosts.yml` in the first directory found:\n\n| Priority | Path | Set via |\n|----------|------|---------|\n| 1 | `$LUNA_CONFIG_DIR` | Environment variable |\n| 2 | `config_dir` in `luna.config.json` | JSON key |\n| 3 | `./luna.d/` | Auto-detect |\n| 4 | `~/.config/luna/` | Auto-detect |\n| 5 | `/etc/luna/` | Fallback |\n\n### `policy.yml` (Required)\n\n| Field | Purpose |\n|-------|---------|\n| `version` | Schema version (use `1`) |\n| `deny_patterns` | Extra regex patterns → forbidden (supplements compiled list) |\n| `rules[]` | Ordered allow/approve/deny rules; first match wins |\n\nEach rule matches by:\n- **Hosts** — host identifiers (`*` = all)\n- **Tags** — from `hosts.yml` (`*` = any)\n- **Commands** — binary name + optional `args_prefix` (prefix match on argv)\n\n**Unmatched commands default to mutating** → `PERMISSION_REQUIRED`.\n\n### `luna.config.json` (Optional)\n\n```json\n{\n  \"config_dir\": \"./luna.d\",\n  \"approval\": { \"ttl\": \"10m\" },\n  \"telegram\": {\n    \"bot_token_file\": \"/path/to/bot-token\",\n    \"approver_user_id\": \"123456789\",\n    \"chat_id\": \"123456789\"\n  },\n  \"audit\": { \"file\": \"/var/log/luna/audit.jsonl\" }\n}\n```\n\nConfig files can be overridden by `LUNA_*` environment variables. See [`docs/oob-approval.md`](docs/oob-approval.md).\n\n### Telegram\n\n1. Create a bot via [@BotFather](https://t.me/BotFather); save the token to a file (`chmod 0600`)\n2. Send `/start` to the bot from your Telegram account\n3. Find your numeric user ID (use [@userinfobot](https://t.me/userinfobot))\n4. Set `approver_user_id` and `bot_token_file` in config or env\n\n---\n\n## MCP Tools\n\n| Tool | Description | Approval |\n|------|-------------|----------|\n| `execute_remote` | Run a command over SSH | Read-only: no | Mutating: Telegram |\n| `read_file` | Read a remote file via shell | No |\n| `fetch_remote_file` | Download a remote file via SFTP | No |\n| `list_hosts` | List configured hosts from inventory | No |\n| `scan_host_inventory` | OS, packages, containers, hardware scan | No |\n| `lookup_cve` | Search local CVE database | No |\n\n---\n\n## Commands\n\n```bash\nmake build        # Build to ./bin/luna\nmake install      # Build + install to ~/.local/bin/luna\nmake test         # Run all tests\nmake lint         # golangci-lint run ./...\nmake fmt          # go fmt ./...\nmake fuzz         # Fuzz command classification (5 min)\nmake tidy         # go mod tidy\n```\n\n---\n\n## Project Layout\n\n```\n./\n├── cmd/                  # cobra CLI (root, serve, ssh-debug)\n├── internal/\n│   ├── approval/         # OOB approval (Telegram, fingerprints, store)\n│   ├── audit/            # JSON event logging\n│   ├── config/           # Settings \u0026 hosts file loading\n│   ├── engine/           # Command classification\n│   ├── policy/           # Policy YAML schema \u0026 loader\n│   ├── security/         # Legacy allowlist (engine supersedes)\n│   ├── ssh/              # Connection pool, auth, SFTP\n│   └── tools/            # MCP tool handlers\n├── docs/                 # Design docs \u0026 guides\n├── examples/             # Starter configs\n└── main.go               # Entry point\n```\n\n---\n\n## Related Docs\n\n| Document | What it covers |\n|----------|----------------|\n| [`docs/oob-approval.md`](docs/oob-approval.md) | Telegram setup, config reference, troubleshooting |\n| [`docs/zero-trust-interceptor.md`](docs/zero-trust-interceptor.md) | Security model, migration guide, full config reference |\n| [`docs/goclaw-integration.md`](docs/goclaw-integration.md) | Running with goclaw / untrusted MCP clients |\n| [`docs/superpowers/`](docs/superpowers/) | Design specs \u0026 implementation plans |\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fba0f3%2Flunacli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fba0f3%2Flunacli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fba0f3%2Flunacli/lists"}