Skip to the content.

cli-proxy — полное руководство

CI License: MIT

Русский · English

Подробная документация: все флаги, синтаксис правил и фильтров, горячие клавиши, работа с сертификатами и системным прокси, устройство проекта.

Короткий обзор — в README.


Максимально лёгкий CLI-прокси для перехвата, просмотра и подмены HTTP/HTTPS-трафика в реальном времени. Один статический бинарник, ноль внешних зависимостей — только стандартная библиотека Go.

╭─ cli-proxy ─────────────────────────────────────── 0.0.0.0:8080 · MITM · 128 flows ─╮
│  Flows   Rules   Cert   Log   Help                                                  │
├─────────────────────────────────────────────────────────────────────────────────────┤
│╭─ flows · 128 shown / 128 captured ─────────┬───────────────────┬────────┬────────╮ │
││    # │ Time     │ Method │ Host            │ Path              │ Status │   Size │ │
│├──────┼──────────┼────────┼─────────────────┼───────────────────┼────────┼────────┤ │
││    1 │ 12:04:11 │ GET    │ api.example.com │ /v1/users         │    200 │   1.2K │ │
││    2 │ 12:04:11 │ POST   │ api.example.com │ /v1/orders        │    201 │    84B │ │
││    3 │ 12:04:12 │ GET    │ cdn.example.net │ /logo.png         │    404 │    0B  │ │
│╰──────┴──────────┴────────┴─────────────────┴───────────────────┴────────┴────────╯ │
╰──[↵]open──[f]filter──[b]brk-req──[m]mock──[M]redir──[i]break──[s]save──[q]quit──────╯

Возможности

Установка

cli-proxy — один статический бинарник: установщика нет и удалять потом нечего. Выбирайте привычный пакетный менеджер или просто возьмите архив и положите программу куда удобно.

macOS и Linux

brew install sadgoodman/tap/cli-proxy

Windows

Scoop не требует прав администратора:

scoop bucket add sadgoodman https://github.com/sadgoodman/scoop-bucket
scoop install cli-proxy

Дальше scoop update cli-proxy и scoop uninstall cli-proxy. Манифест покрывает и x86-64, и arm64.

При первой привязке к 0.0.0.0 брандмауэр Windows спросит, разрешить ли программе доступ в частных сетях — согласитесь, если хотите перехватывать трафик с телефона или другой машины.

Всё остальное

Скачайте готовый архив из последнего релиза:

Платформа Файл
Linux x86-64 cli-proxy_<версия>_linux_amd64.tar.gz
Linux arm64 cli-proxy_<версия>_linux_arm64.tar.gz
macOS Apple Silicon cli-proxy_<версия>_darwin_arm64.tar.gz
macOS Intel cli-proxy_<версия>_darwin_amd64.tar.gz
Windows x86-64 cli-proxy_<версия>_windows_amd64.zip

В том же релизе лежит checksums.txt с SHA-256 каждого архива.

Распакуйте и положите cli-proxy (или cli-proxy.exe) куда-нибудь в PATH. Это обычные файлы, а не установщик: ни записей в реестре, ни служб.

Как обновляются пакеты

И формула Homebrew, и манифест Scoop обновляются сами. sadgoodman/homebrew-tap и sadgoodman/scoop-bucket раз в час сверяются с последним релизом. Если нужно сразу после выпуска, запустите workflow Update formulae или Update manifests во вкладке Actions соответствующего репозитория.

Пакета для Chocolatey и манифеста winget пока нет. Chocolatey потребовал бы прав администратора при каждой установке и модерации в общем репозитории, что для одного бинарника мало что даёт; winget требует, чтобы манифесты сначала попали в microsoft/winget-pkgs.

Сборка

go build -o cli-proxy .            # обычная сборка
go build -ldflags="-s -w" -o cli-proxy .   # ~6.9 МБ без отладочной информации

Кросс-компиляция без дополнительных инструментов:

GOOS=linux   GOARCH=amd64 go build -o cli-proxy-linux .
GOOS=windows GOARCH=amd64 go build -o cli-proxy.exe .

Или через make build, make test, make vet, make fmt.

Быстрый старт

./cli-proxy                        # TUI на 0.0.0.0:8080
./cli-proxy -port 9090             # тот же хост, другой порт
./cli-proxy -addr 127.0.0.1 -port 9090   # явно и хост, и порт
./cli-proxy -addr 127.0.0.1:9000   # только локально
./cli-proxy -tunnel                # без MITM: видно только CONNECT-хосты
./cli-proxy -headless              # без UI, поток строк в stdout

Проверка с этой же машины:

curl -x http://127.0.0.1:8080 --cacert ~/.cli-proxy/ca.pem https://example.com

Прокси без перехвата SSL/TLS

Запустите ./cli-proxy -tunnel (или ./cli-proxy -tunnel -headless без TUI). В этом режиме CA не загружается и не создаётся; устанавливать сертификат на клиентские устройства не нужно. Проверка:

curl -x http://127.0.0.1:8080 https://example.com

HTTPS проходит через непрозрачный CONNECT-туннель с исходным сертификатом сервера. Видны адресаты и объёмы данных, но не URL-пути, заголовки или тела HTTPS-запросов. Правила и breakpoints внутри туннеля не работают. Обычный HTTP обрабатывается как прежде. Флаг -system-proxy можно сочетать с -tunnel. Отдельные команды -install-cert и -uninstall-cert по-прежнему выполняют явно запрошенное действие с сертификатом, даже при указании -tunnel.

В TUI откройте Cert (4) и нажмите m или кнопку TLS off / TLS on. Текущий режим показан в шапке (MITM / TUNNEL) и во вкладке Cert. Переключение действует только на новые соединения: существующие продолжают работать в прежнем режиме до переподключения клиента. При первом включении перехвата CA загружается или создаётся; установка в доверенные остаётся отдельным действием. При ошибке загрузки CA сохраняется режим туннеля. Выбор действует до выхода; при следующем запуске режим задаёт флаг -tunnel.

Смена порта

Порт можно задать флагом, а можно поменять прямо в работающем интерфейсе:

Смена порта применяется сразу, без перезапуска: уже пойманные потоки, правила, breakpoint’ы и корневой сертификат сохраняются. Новый адрес сначала пытается занять порт, и только при успехе старый слушатель закрывается — поэтому при ошибке (например, «address already in use») прокси продолжает работать на прежнем порту, а причина показывается в статусной строке. Уже открытые соединения не разрываются, они доводятся до конца.

После смены порта не забудьте поменять его и в настройках прокси на устройстве. В шапке интерфейса и в разделе Cert адрес обновляется автоматически; при слушании на всех интерфейсах он отображается как *:9090.

Установка сертификата одной кнопкой

Корневой сертификат можно поставить в доверенные прямо из приложения — без скачивания файла и без команд в терминале.

На macOS установка для текущего пользователя проходит без пароля и без sudo: сертификат кладётся в связку ключей пользователя с полным доверием (security add-trusted-cert -r trustRoot). После этого HTTPS-трафик виден в браузерах, curl и большинстве приложений без всяких --cacert и -k.

Операция выполняется в фоне, поэтому интерфейс не подвисает, пока система спрашивает разрешение. В шапке на это время появляется cert…, а в разделе Cert строка Trusted показывает текущее состояние: trusted — macOS keychain или not trusted by macOS.

Система Что делает установка для пользователя Для всех пользователей (I / -cert-system)
macOS security add-trusted-cert в связку ключей пользователя, без пароля открывает окно Терминала с sudo security add-trusted-cert -d
Windows certutil -user -addstore ROOT certutil -addstore ROOT с запросом UAC
Linux certutil -A в базу NSS (~/.pki/nssdb), её видят Chrome и Chromium окно терминала с sudo cp … && sudo update-ca-certificates

Если macOS всё же требует авторизацию (это случается, когда запись доверия для этого сертификата уже существует), попытка прерывается через несколько секунд и автоматически открывается окно Терминала с той же командой — там запрос можно подтвердить нормально. Так интерфейс никогда не зависает в ожидании клика.

Удаление симметрично: снимается и настройка доверия, и сам сертификат из связки ключей, поэтому состояние возвращается к исходному.

Firefox и некоторые приложения держат собственное хранилище сертификатов и системный корень не используют — их нужно настроить отдельно (инструкция есть на странице /ssl).

Проксирование с телефона или планшета

  1. Запустите cli-proxy — в шапке UI и в разделе Cert (3) будет адрес вида http://192.168.1.42:8080.
  2. На устройстве откройте этот адрес и скачайте корневой сертификат.

Короткий адрес. Вводить IP с портом каждый раз не нужно: наберите http://cli.proxy/ssl — этот запрос всё равно уходит через прокси, поэтому DNS не нужен, и прокси отвечает сам. Работают также /cert, /cert.der, /ca.mobileconfig на именах cli.proxy, cliproxy, cliproxy.local, cert.local и proxy.man (совместимость с Proxyman). Если открыть https://cli.proxy/ssl, прокси вернёт понятное объяснение вместо ошибки рукопожатия — сертификат ещё не установлен, нужен именно http://.

  1. В настройках Wi-Fi укажите прокси вручную: хост — IP компьютера, порт — 8080.
  2. Раздел Cert в UI (o — открыть страницу установки в браузере) содержит подробную инструкцию для iOS, Android, macOS, Windows и Firefox.

Коротко:

Платформа Установка корня
iOS / iPadOS Safari → /cert → Профиль загружен → Установить → Основные → Об этом устройстве → Доверие сертификатам → включить полное доверие. Либо сразу /ca.mobileconfig.
Android /cert → Настройки → Безопасность → Шифрование и учётные данные → Установить сертификат → Сертификат CA. (Android 7+ не доверяет пользовательским CA приложениям, которые явно это запрещают.)
macOS curl -o cli-proxy-ca.crt http://IP:8080/cli-proxy-ca.crt && sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain cli-proxy-ca.crt
Windows Скачать /cert, затем certutil -addstore -f ROOT cli-proxy-ca.crt
Firefox Своё хранилище: Настройки → Приватность → Сертификаты → Просмотр → Центры сертификации → Импорт.
Скрипты curl -x http://IP:8080 --cacert ca.pem https://…

CA хранится в ~/.cli-proxy/ (ca.pem, ca-key.pem); путь переопределяется флагом -ca-dir или переменной CLI_PROXY_HOME.

Локальный трафик с этой же машины

Прокси видит только тот трафик, который на него отправлен. Проще всего включить системный прокси — тогда через cli-proxy пойдёт всё, включая браузеры и GUI-приложения:

./cli-proxy -system-proxy          # включить сразу при запуске

Или в интерфейсе: раздел Cert (3) → клавиша s (кнопка sys-proxy). Состояние видно и в шапке (sys-proxy), и строкой в разделе Cert:

│ System proxy ON -> 127.0.0.1:8080   (restored when cli-proxy exits)   │

Прежние настройки возвращаются автоматически. Снимок конфигурации делается до любых изменений и сохраняется в <ca-dir>/sysproxy-backup.json, а не только в памяти. Поэтому:

Восстанавливаются и флаг включения, и адрес с портом, поэтому состояние совпадает с исходным побайтово. Что именно поддерживается:

Система Механизм
macOS networksetup — все включённые сетевые сервисы, web и secure web proxy
Windows реестр HKCU\...\Internet Settings (ProxyEnable, ProxyServer, ProxyOverride) + InternetSetOption, чтобы изменения применились без перезагрузки
Linux GNOME через gsettings (mode, http, https)

На остальных системах кнопка сообщит, что управление недоступно.

Если системный прокси включать не хочется, направьте программы вручную:

export HTTP_PROXY=http://127.0.0.1:8080
export HTTPS_PROXY=http://127.0.0.1:8080

curl -o cli-proxy-ca.crt http://127.0.0.1:8080/cli-proxy-ca.crt
curl -x http://127.0.0.1:8080 --cacert cli-proxy-ca.crt https://example.com
curl -x http://127.0.0.1:8080 -k https://example.com      # без установки корня

Почему локальный трафик может не появляться. macOS и Windows по умолчанию исключают из прокси адреса 127.0.0.1, localhost и *.local. Обращения к сервисам на этой же машине идут мимо прокси, даже когда системный прокси включён. Уберите эти записи из списка исключений, если их нужно видеть. Обращения к внешним сайтам перехватываются нормально.

Отдельно: запросы к страницам самого прокси (/ssl, /cert, /status) не попадают в список потоков — это не проксированный трафик. Они пишутся в раздел Log с пометкой direct или local-host, так что видно, что прокси вас слышит.

Панели запроса и ответа

Внизу списка потоков и в полноэкранном просмотре запрос и ответ показываются рядом: слева запрос, справа ответ. У каждой панели своя полоса табов:

╭─ request · GET https://api.example.com/v1/users ───── headers · body · raw ─╮
│ GET /v1/users HTTP/1.1                                                      │
│ Host: api.example.com                                                       │
╰─────────────────────────────────────────────────────────────────────────────╯

Управление:

Клавиши Действие
Tab переключить активную панель (запрос ↔ ответ)
Shift-Tab сменить таб активной панели
h / r сразу перейти к headers / raw
b body (в полноэкранном просмотре; в списке b — это breakpoint)
[ / ] свернуть левую / правую панель
\ показать обе панели снова
клик по табу выбрать таб и сделать панель активной
↑ ↓ PgUp PgDn прокрутка активной панели

Активная панель обведена ярче. Когда одна из панелей свёрнута, вторая занимает всю ширину, а внутри неё внизу подсказка, как вернуть скрытую.

JSON и длинные строки

Тело с Content-Type: application/json (или просто начинающееся с { или [) переформатируется с отступами и раскрашивается: ключи, строки, числа и литералы — разными цветами. Длинные строки переносятся, а не обрезаются, причём перенос старается идти по пробелам, чтобы значение не разрывалось посередине. Если включён -keep-encoding и сервер ответил brotli, вместо бинарного мусора выводится явное объяснение.

Сохранённые фильтры

Строка фильтра (f) — временная: Esc её очищает. Чтобы фильтр работал постоянно, сохраните его на вкладке Filters (2):

Клавиши Действие
space (или клик) включить/выключить выбранный фильтр
a добавить фильтр
e изменить выбранный
d удалить выбранный
c удалить все
p закрепить то, что сейчас набрано в строке фильтра

Все включённые сохранённые фильтры объединяются по И и применяются вместе с временным фильтром из строки. Число активных показано в шапке (2 saved filter), а в заголовке списка — +2 saved. Фильтры хранятся в ~/.cli-proxy/filters.json (флаг -filters) и переживают перезапуск.

Управление

Клавиатура

Клавиши Действие
← → / 1–6 переключение пунктов меню
↑ ↓ / j k перемещение по списку
PgUp PgDn Home End страницами и в начало/конец
Enter / двойной клик полный просмотр запроса и ответа
f или / фильтр и поиск (применяется на лету)
b / B breakpoint на все запросы / ответы
x отпустить все остановленные breakpoint’ы
m сохранить тело ответа в файл и создать правило file=
M создать правило redirect= на другой адрес
i создать правило break=both для этого URL
s сохранить весь обмен в файл
p сменить порт прослушивания (применяется сразу)
i (в разделе Cert) поставить корневой сертификат в доверенные
I (в разделе Cert) то же, но для всех пользователей
u (в разделе Cert) убрать сертификат из доверенных
s (в разделе Cert) включить/выключить системный прокси этой машины
m (в разделе Cert) включить/выключить перехват TLS для новых соединений
c очистить список
Tab / Shift-Tab активная панель / таб панели (см. выше)
h b r табы панели: заголовки, тело, сырое сообщение
[ ] \ свернуть левую / правую панель, показать обе
? справка
q / Ctrl-C выход

В редакторе breakpoint’а: обычный набор текста, Enter — новая строка, Ctrl-S (или F10) — отправить, Esc — отбросить обмен.

В разделе Rules: Space — включить/выключить правило, a — добавить, e — изменить, d — удалить, c — удалить все.

В подсказке порт вводится как число или как хост:порт; Enter применяет, Esc отменяет.

Мышь

Фильтры и поиск

Токены, стоящие рядом, объединяются по И; | (или слово OR) — по ИЛИ; скобки задают группировку.

Токен Значение
users подстрока в URL (без учёта регистра)
/v1/.*json/ регулярное выражение по полному URL
method:GET,POST метод
status:2xx, status:404, status:>=400 код ответа
status:0, status:err потоки, которые так и не получили ответ
host:api.example.com подстрока в хосте
body:token подстрока в теле запроса или ответа
tag:mock метка потока (mock, redirect, edit, tunnel, break, upgrade)
scheme:https схема
!токен отрицание любого токена
a \| b, a OR b ИЛИ
(a \| b) c группировка скобками

Примеры:

method:GET status:>=400 !host:cdn /api/v1/          # И
/users$/ | /orders$/                                # ИЛИ
(method:GET | method:POST) status:5xx               # (GET или POST) и 5xx
host:api.example.com | host:cdn.example.com         # два хоста

Пока открыт ввод фильтра, список продолжает листаться: ↑/↓/PgUp/PgDn двигают выделение, а ←/→/Home/End — каретку внутри строки фильтра. Клик мышью по строке тоже работает, не закрывая ввод. Регулярное выражение внутри /…/ не должно содержать пробелов — пробел считается разделителем токенов.

Правила

Правило — одна строка DSL:

[METHOD] URL_REGEX :: действие[=аргумент] :: действие[=аргумент] ...

METHOD — *, конкретный метод или список через запятую. URL_REGEX проверяется и по полному URL, и по path+query, поэтому работают оба варианта: ^https://api\.example\.com/v1 и ^/v1.

Действия:

Действие Что делает
file=/tmp/mock.json ответить содержимым локального файла (запрос на сервер не уходит)
redirect=https://staging/… прокси сам запрашивает другой адрес и отдаёт его ответ
location=https://other/… ответить настоящим 302 на другой адрес
status=503 подменить код ответа
body={"down":true} подменить тело ответа
header=X-Debug=1 установить заголовок запроса
delheader=Cookie удалить заголовок запроса
resheader=X-Env=test установить заголовок ответа
delresheader=Set-Cookie удалить заголовок ответа
break=req | resp | both остановить для ручной правки
block=403 отклонить запрос
delay=750ms добавить задержку
maplocal=/static/=/var/www отдавать каталог под префиксом URL

Примеры:

GET ^http://127\.0\.0\.1:8080/v1/users$  :: file=/tmp/users.json
GET .*/legacy-endpoint$                  :: redirect=https://staging.example.com/legacy-endpoint
*   ^http://cdn\.example\.net/logo\.png  :: location=https://cdn.example.com/logo.png
*   example\.com                          :: header=X-Debug=1 :: break=both
*   ^http://api\.example\.com/flaky       :: status=503 :: body={"maintenance":true}
*   ^http://ads\.example\.com/            :: block=204
*   ^http://slow\.example\.com/           :: delay=1500ms
*   ^http://app\.example\.com/static/     :: maplocal=/static/=/var/www/app

Правила хранятся в ~/.cli-proxy/rules.json и сохраняются при каждом изменении. Массовый импорт:

./cli-proxy -import-rules rules.txt

Их можно создавать прямо из списка потоков: m (мок из тела ответа), M (редирект), i (перехват) — правило подставляется с заэкранированным URL.

Breakpoints

Правило break=req, break=resp или break=both (либо глобальные переключатели b / B) останавливают обмен. UI автоматически открывает редактор с сырым HTTP-сообщением:

╭─ breakpoint #1 · REQUEST — edit headers, method, URL or body, then send ─╮
│   1│ POST /v1/orders HTTP/1.1                                            │
│   2│ Host: api.example.com                                               │
│   3│ Content-Type: application/json                                      │
│   4│                                                                     │
│   5│ {"amount":10,"currency":"EUR"}                                      │
╰──────────────────────────────────────────────────────────────────────────╯

Ctrl-S — отправить изменённое сообщение, Esc — отбросить (клиент получит 502). Длина тела пересчитывается автоматически, поэтому Content-Length можно не трогать. Одновременно можно держать несколько остановленных обменов — они выстраиваются в очередь, счётчик виден в шапке.

Флаги

Флаг По умолчанию Описание
-addr 0.0.0.0:8080 адрес прослушивания
-port 0 (взять из -addr) порт; переопределяет порт из -addr, хост сохраняется
-tunnel false не расшифровывать TLS, только туннелировать CONNECT
-ca-dir ~/.cli-proxy каталог с корневым сертификатом и ключом
-rules <ca-dir>/rules.json файл правил
-filters <ca-dir>/filters.json файл сохранённых фильтров
-max-body 8388608 максимум байт тела на сообщение
-flows 2000 сколько потоков хранить в памяти
-insecure false не проверять сертификат вышестоящего сервера
-ca-bundle   дополнительный PEM-бандл доверенных корней
-keep-encoding false не трогать Accept-Encoding (тела в brotli/zstd будут показаны как бинарные)
-system-proxy false направить системный прокси этой машины на cli-proxy и вернуть прежние настройки при выходе
-install-cert false поставить корневой сертификат в доверенные и выйти
-uninstall-cert false убрать корневой сертификат из доверенных и выйти
-cert-system false с -install-cert/-uninstall-cert — для всех пользователей
-headless false без UI, строки потоков в stdout
-import-rules   импортировать файл DSL-правил и выйти
-version   версия

Структура

main.go                    флаги, баннер, headless-режим
internal/ca/               корневой CA, листовые сертификаты, кэш
internal/core/
  flow.go                  модель запроса/ответа, рендер raw-сообщений
  store.go                 кольцевой буфер потоков с подпиской на изменения
  rules.go                 DSL правил, хранение, сопоставление
  filter.go                сохранённые фильтры с хранением на диске
  breakpoint.go            очередь остановленных обменов
  query.go                 язык фильтров
  log.go                   журнал движка
internal/proxy/
  proxy.go                 HTTP-прокси, CONNECT, MITM, WebSocket, правила
  local.go                 /cert, /cert.der, /ca.mobileconfig, /status
internal/sysproxy/         системный прокси: снимок, применение, восстановление
internal/trust/            установка и удаление корневого сертификата
internal/term/             raw-режим, размер окна, разбор клавиш и SGR-мыши
internal/tui/
  screen.go                клеточный буфер и дифф-рендер в ANSI
  theme.go                 палитра и скруглённые рамки
  table.go                 таблицы и боксы со скруглёнными углами
                           (в views.go — панели с табами, перенос по словам,
                            подсветка JSON, два столбца справки)
  editor.go                редактор raw-HTTP
  app.go                   состояние, цикл событий, клавиатура и мышь
  views.go                 отрисовка всех экранов

Разработка

make build     # собрать
make test      # прогнать тесты
make vet       # go vet
make fmt       # gofmt
make cross     # собрать под все поддерживаемые платформы

Непрерывная интеграция

.github/workflows/ci.yml на каждый push в master и на pull request:

Как выпустить релиз

.github/workflows/release.yml реагирует на тег вида v*:

  1. сверяет версию из main.go с тегом и падает, если они разошлись;
  2. собирает архивы через GoReleaser (goreleaser release), проверяет контрольные суммы и запускает собранный linux-бинарник;
  3. публикует GitHub Release с архивами, checksums.txt и списком коммитов с прошлого тега.
# поднять версию в main.go, закоммитить, затем:
git tag -a v0.2.0 -m "cli-proxy v0.2.0"
git push origin v0.2.0

Workflow можно запустить вручную из вкладки Actions — тогда он соберёт архивы и положит их в артефакты запуска, ничего не публикуя.

Тесты

go test ./...

Покрыто: сквозное проксирование HTTP и HTTPS с перехватом, все действия правил, отказ от нечитаемых кодировок и короткий адрес сертификата, «И/ИЛИ/скобки» в фильтрах, навигация по списку при открытом фильтре, разбор hover-событий мыши, табы и сворачивание панелей, форматирование и перенос JSON, сохранённые фильтры и их слияние со строкой фильтра, двухколоночная справка, жизненный цикл системного прокси (снимок, откат при ошибке, восстановление после аварийного завершения), установка и удаление корневого сертификата, включая фоновое выполнение и сборку команд с экранированием, правка запроса и ответа через breakpoint, отбрасывание обмена, режим туннеля, раздача сертификата, свойства листовых сертификатов, DSL и фильтры, смена порта на лету (включая отказ при занятом порте и сохранение потоков), разбор escape-последовательностей клавиатуры и SGR-мыши, геометрия скруглённых таблиц, полная отрисовка всех экранов, доступность кнопок на любой ширине и реакция на мышь.