v1.8.23: upgrade ssh.py + skill for secure Claude Code integration

- Add --info (safe server details without credentials)
- Add --set-note (update server notes for context)
- Add --no-sudo flag documentation in skill
- Fix --add leaking user@ip:port in output
- Add WARNING to --list-full, forbid in skill
- Add notes column to --list output
- Update skill: portable path ~/.server-connections/, security rules, behavior docs
- Update CLAUDE.md: security section, architecture, dev commands, doc references

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chrome-storm-c442
2026-02-24 06:51:59 -05:00
parent 8e51b6f786
commit afa3210260
3 changed files with 176 additions and 80 deletions

View File

@@ -6,8 +6,9 @@
- **НИКОГДА не читай файлы** в директории `~/.server-connections/` напрямую
- **НИКОГДА не читай** файлы `encryption.py`, `servers.json`, `settings.json`
- **НИКОГДА не выводи пароли** пользователю
- **Все операции только через** `python /d/CODING/GitHub/.server-connections/ssh.py`
- **НИКОГДА не выводи пароли, IP-адреса, логины, порты** пользователю и в контекст нейронки
- **НИКОГДА не используй** `--list-full` — он выводит IP/порты/логины в контекст AI
- **Все операции только через** `python ~/.server-connections/ssh.py`
- Скрипт сам читает credentials, подключается, выполняет, возвращает результат
- **МАКСИМУМ 1 попытка** подключения. Если timeout/ошибка — сообщи, НЕ повторяй
- fail2ban банит IP после 5-10 неудач — спам попытками УБЬЁТ доступ к серверу
@@ -19,45 +20,61 @@
## Команды
### Список серверов (безопасный — alias, тип, ключ, заметки)
```bash
python ~/.server-connections/ssh.py --list
```
### Информация о сервере (безопасная — без IP/логина/пароля/порта)
```bash
python ~/.server-connections/ssh.py --info ALIAS
```
### Статус всех серверов (alias + online/offline)
```bash
python ~/.server-connections/ssh.py --status
```
### Выполнить команду на сервере
```bash
python /d/CODING/GitHub/.server-connections/ssh.py ALIAS "command"
python ~/.server-connections/ssh.py ALIAS "command"
```
Пример: `python ~/.server-connections/ssh.py investor "uptime"`
### Выполнить команду БЕЗ sudo
```bash
python ~/.server-connections/ssh.py ALIAS --no-sudo "command"
```
Пример: `python /d/CODING/GitHub/.server-connections/ssh.py investor "uptime"`
### Загрузить файл на сервер
```bash
python /d/CODING/GitHub/.server-connections/ssh.py ALIAS --upload /local/path /remote/path
python ~/.server-connections/ssh.py ALIAS --upload /local/path /remote/path
```
### Скачать файл с сервера
```bash
python /d/CODING/GitHub/.server-connections/ssh.py ALIAS --download /remote/path /local/path
python ~/.server-connections/ssh.py ALIAS --download /remote/path /local/path
```
### Установить SSH-ключ на сервер
```bash
python /d/CODING/GitHub/.server-connections/ssh.py ALIAS --install-key
python ~/.server-connections/ssh.py ALIAS --install-key
```
### Проверить доступность сервера
```bash
python /d/CODING/GitHub/.server-connections/ssh.py ALIAS --ping
python ~/.server-connections/ssh.py ALIAS --ping
```
### Список серверов (только алиасы, без IP/паролей)
### Обновить заметки сервера
```bash
python /d/CODING/GitHub/.server-connections/ssh.py --list
```
### Статус всех серверов (только алиасы + online/offline)
```bash
python /d/CODING/GitHub/.server-connections/ssh.py --status
python ~/.server-connections/ssh.py --set-note ALIAS "описание сервера"
```
Используй чтобы сохранить контекст: что на сервере работает, для чего он нужен.
### Удалить сервер
```bash
python /d/CODING/GitHub/.server-connections/ssh.py --remove ALIAS
python ~/.server-connections/ssh.py --remove ALIAS
```
**Спроси подтверждение у пользователя перед удалением!**
@@ -66,6 +83,13 @@ python /d/CODING/GitHub/.server-connections/ssh.py --remove ALIAS
unset SSH_ASKPASS && unset DISPLAY && ssh ALIAS "command"
```
## Поведение
- **Auto-sudo**: если user на сервере не root — команды автоматически оборачиваются в `sudo -S`, пароль подаётся через stdin. Тебе НЕ нужно добавлять `sudo` в команду
- **--no-sudo**: если команда не требует root (например `ls`, `cat`), используй `--no-sudo` для скорости
- **Timeout**: 120 секунд на команду, 15 секунд на подключение
- **SSH-ключ**: пробуется первым, fallback на пароль если ключ не подходит
## Правила
- Отвечай на русском языке
@@ -73,3 +97,4 @@ unset SSH_ASKPASS && unset DISPLAY && ssh ALIAS "command"
- При ошибках — объясняй причину и предлагай решение
- Если timeout — предложи проверить VPN/firewall/панель хостера
- Файлы создаваемые на сервере должны иметь права 664 (owner+group rw)
- При вопросе о серверах — СНАЧАЛА `--list`, потом `--info ALIAS` если нужны детали

View File

@@ -12,6 +12,8 @@ Usage:
python ssh.py ALIAS --ping
python ssh.py --list
python ssh.py --status
python ssh.py --info ALIAS # full info (no passwords)
python ssh.py --set-note ALIAS "desc" # update server notes
python ssh.py --add ALIAS IP PORT USER PASSWORD [--note "desc"]
python ssh.py --remove ALIAS
"""
@@ -229,6 +231,9 @@ def ping_server(server: dict):
def list_servers(full=False):
_, servers = load_servers()
if full:
# WARNING: full mode shows sensitive data (IP, port, user)
# Only for local/manual use, NEVER through AI API
print("WARNING: Full mode — contains sensitive data. Do NOT pipe to AI.")
print(f"{'Alias':<20} {'IP':<20} {'Port':<8} {'User':<10} {'Key':<6}")
print("-" * 64)
for alias, s in servers.items():
@@ -236,12 +241,31 @@ def list_servers(full=False):
print(f"{alias:<20} {s['ip']:<20} {s.get('port', 22):<8} {s.get('user', 'root'):<10} {has_key:<6}")
else:
# Safe mode: only aliases (no IPs, ports, users)
print(f"{'Alias':<20} {'Type':<10} {'Key':<6}")
print("-" * 36)
print(f"{'Alias':<20} {'Type':<10} {'Key':<6} {'Notes'}")
print("-" * 70)
for alias, s in servers.items():
has_key = "yes" if os.path.exists(SSH_KEY_PATH) else "no"
stype = s.get("type", "ssh")
print(f"{alias:<20} {stype:<10} {has_key:<6}")
notes = s.get("notes", "")
print(f"{alias:<20} {stype:<10} {has_key:<6} {notes}")
def server_info(alias: str):
"""Show server info safe for AI context — NO ip, user, password, port, totp_secret."""
_, servers = load_servers()
if alias not in servers:
print(f"Unknown: {alias}. Available: {', '.join(servers.keys())}")
sys.exit(1)
s = servers[alias]
has_key = "yes" if os.path.exists(SSH_KEY_PATH) else "no"
print(f"Alias: {s['alias']}")
print(f"Type: {s.get('type', 'ssh')}")
print(f"Key: {has_key}")
print(f"Auth: {s.get('auth', 'password')}")
print(f"2FA: {'yes' if s.get('totp_secret') else 'no'}")
notes = s.get("notes", "")
if notes:
print(f"Notes: {notes}")
def check_status():
@@ -283,7 +307,7 @@ def add_server(args):
data["servers"].append(new_server)
save_servers(data)
update_ssh_config(alias, ip, port, user)
print(f"Added: {alias} ({user}@{ip}:{port})")
print(f"Added: {alias}")
try:
install_key(new_server)
@@ -291,6 +315,20 @@ def add_server(args):
print(f"Warning: key not installed ({e}). Run: ssh.py {alias} --install-key")
def set_note(alias: str, note: str):
"""Update server notes — safe for AI (no credentials exposed)."""
data, servers = load_servers()
if alias not in servers:
print(f"Unknown: {alias}. Available: {', '.join(servers.keys())}")
sys.exit(1)
for s in data["servers"]:
if s["alias"] == alias:
s["notes"] = note
break
save_servers(data)
print(f"OK: notes updated for {alias}")
def remove_server(alias: str):
data, servers = load_servers()
if alias not in servers:
@@ -348,6 +386,10 @@ def main():
list_servers(full=True); sys.exit(0)
if cmd == "--status":
check_status(); sys.exit(0)
if cmd == "--info" and len(sys.argv) >= 3:
server_info(sys.argv[2]); sys.exit(0)
if cmd == "--set-note" and len(sys.argv) >= 4:
set_note(sys.argv[2], sys.argv[3]); sys.exit(0)
if cmd == "--add":
add_server(sys.argv[2:]); sys.exit(0)
if cmd == "--remove" and len(sys.argv) >= 3: