gori 커맨드라인 레퍼런스입니다. 서브커맨드 없이 gori를 실행하면 TUI가 시작됩니다.
gori [command] [options]
| Command | Description |
|---|---|
tui |
프록시와 터미널 UI 시작 (기본값) |
run |
프로젝트 단위 비대화형 스위트 |
mcp |
Model Context Protocol stdio 서버 |
ca |
루트 CA 경로 / PEM 출력, 또는 CA 재생성 / 가져오기 |
settings |
settings.json 표시 또는 편집 |
wizard |
대화형 최초 실행 설정 |
tutorial |
가이드형 TUI 투어 (탐색, space 메뉴, 팔레트, 편집 모드, 프록시와 CA, 캡처, 인터셉트) |
update |
채널 인식 자체 업데이트 (바이너리 / Chocolatey / Homebrew / Snap / AUR / Nix) |
전역 플래그: -v / -V / --version, -h / --help, 그리고 --config PATH(이번 실행에만 쓸 설정 파일, 아래 --config PATH 참고).
gori tui
인터셉트 프록시와 TUI를 시작합니다. 서브커맨드를 주지 않으면 이것이 기본값입니다.
gori
gori tui --listen 0.0.0.0 --port 8080
| Option | Description |
|---|---|
-l, --listen=HOST |
이 프로세스의 전역 바인드 주소 (settings.json 기본값, 없으면 127.0.0.1). 저장되지 않음. 프로젝트 자체 바인드가 설정되어 있으면 그쪽이 우선 |
-p, --port=PORT |
이 프로세스의 전역 바인드 포트, 0-65535 (settings.json 기본값, 없으면 8070). 저장되지 않음. 프로젝트 net.bind_port가 설정되어 있으면 그쪽이 우선 |
--db=PATH |
SQLite 데이터베이스 경로. 프로젝트 선택기를 건너뛰고 이 파일을 바로 엽니다 |
--ca-dir=PATH |
루트 CA 디렉터리 |
--insecure-upstream |
업스트림 TLS 인증서를 검증하지 않음 |
GORI_HOME은 플래그가 아니라 환경 변수입니다. TUI에서는 프로젝트 피커로 프로젝트를 고릅니다. 바인드 플래그는 이번 실행에 한해 전역 계층만 설정합니다. 설정을 참고하세요. 루트 CA 경로는gori ca를 사용하세요.
gori run
비대화형 스위트입니다. 각 서브커맨드는 프로젝트 단위로 동작합니다. --project와 --db가 모두 없으면 GORI_PROJECT가 가리키는 프로젝트, project switch로 고정한 프로젝트, 가장 최근에 활성화한 프로젝트 순으로 씁니다. 어느 것을 골랐는지는 stderr에 한 번 알립니다(gori run: using project demo (from GORI_PROJECT)). 셋 중 마지막은 다른 프로젝트에 쓰기가 일어날 때마다 옮겨 가기 때문입니다. 존재하지 않는 프로젝트를 가리키는 GORI_PROJECT나 고정은 건너뛰지 않고 거부합니다. --project와 --db는 택일입니다. 둘 다 주면 --db가 조용히 이기는 게 아니라 사용법 오류로 거절합니다. 실제 사용 패턴은 스크립팅 가이드를 참고하세요.
gori run <subcommand> [verb] [options]
| Subcommand | Description |
|---|---|
capture |
프록시를 실행하고 캡처한 플로우를 STDOUT으로 스트리밍 |
shell · shell --print |
실행 중인 gori를 프록시로 쓰고 그 CA를 신뢰하는 $SHELL을 열거나(-- CMD로 명령 실행), export 줄을 출력 |
android devices · setup · undo |
adb로 Android 에뮬레이터나 루팅된 기기를 실행 중인 gori로 라우팅하고 CA를 신뢰시키거나, 되돌림 |
history (ls) |
캡처한 플로우 목록 / 쿼리 |
history delete <id>… · delete -q QL · clear |
id로 플로우를 완전 삭제(모든 id가 존재해야 하며, 아니면 아무것도 삭제하지 않음), 쿼리에 매칭되는 플로우 전부 삭제 (--yes), 또는 프로젝트 History 전체 비우기 (--yes) |
show <flow-id> |
플로우 하나의 요청과 응답 출력 |
compare <id-a> <id-b> |
두 플로우의 요청 또는 응답 diff |
diff --from A --to B |
리테스트 리포트: 프로젝트 두 개를 엔드포인트 단위로 비교 (added / gone / changed / unchanged / removed) |
intercept |
캡처 중인 TUI의 라이브 인터셉트 큐 조회 및 조작 |
send [URL] |
Repeater 세션을 만들지 않고, URL(curl 형태) 또는 원시 요청으로 조립한 요청 하나를 전송 |
repeater <flow-id> · list · create · send |
캡처한 플로우 재전송, 또는 Repeater 세션 목록 / 생성 / 실행 (WebSocket 포함) |
repeater ws <id> |
저장된 repeater로 지속 WebSocket 세션을 열고 stdin/stdout으로 프레임 스트리밍 |
repeater race <id> <id>… |
저장된 세션 여러 개를 하나의 동기화된 레이스로 발사(HTTP/1.1 last-byte sync, HTTP/2 single-packet) |
repeater timing <id> <id> |
저장된 세션 두 개의 차등 타이밍 분석: 응답 순서와 사분위수로 어느 쪽이 일관되게 느린지 판정 |
repeater minimize <id> |
저장된 요청을 응답이 유지되는 최소 형태로 축약 |
repeater h2 |
순서가 있는 HPACK 필드 목록으로 필드 단위 HTTP/2 요청 전송 |
repeater move <id> · delete <id>… |
탭 번호로 워크벤치 스트립 재정렬, 또는 저장된 세션 하나 이상 닫기 |
fuzz [<flow-id>] |
Intruder 스타일 퍼저 |
fuzz save · list · show · delete |
스윕의 모든 결과를 영구 저장하고, 그 아카이브를 페이지 단위로 읽거나 정리 |
mine [<flow-id>] |
숨은 파라미터 탐색 |
sequence (seq) [<flow-id>] |
토큰 무작위성 평가 (라이브 리플레이, 또는 붙여넣은 목록은 --tokens) |
authorize [<flow-id>…] |
캡처된 플로우를 여러 아이덴티티로 재전송하고 각 응답을 기준선과 비교 (접근 제어 결함) |
cache-deception [<flow-id>…] |
플로우의 웹 캐시 디셉션 검사: 인증·익명 요청 후 캐시 무효화 익명 요청과 비교 |
probe [QL] |
패시브 보안 스캔 (요청 없음) |
probe issues · dismiss · promote · delete |
저장된 Probe 발견 항목 트리아지 |
probe rules · mode |
스캔 규칙 목록 / 무장, 스캔 모드 조회 및 설정 |
discover |
엔드포인트를 스파이더링 & 브루트포스하여 Sitemap으로 반영 |
wordlist (list) · show · save · rename · delete |
전역 wordlist 카탈로그: 모든 --wordlist / -w가 이름으로 받는 이름 붙은 목록(delete는 --yes 필요) |
import |
HAR / URL 목록 / OpenAPI / Postman / Insomnia / Burp / WSDL 파일 또는 curl 명령에서 History로 플로우 일괄 임포트 |
sitemap [QL] |
호스트 → 경로 엔드포인트 트리 |
sitemap tag |
Sitemap 경로에 자유 텍스트 메모를 고정 / 해제 / 목록 |
sitemap params [QL] |
엔드포인트별 파라미터 목록: 위치별 이름, 등장 횟수, 샘플 값, 반사된 값 |
sitemap js |
캡처된 JavaScript가 참조하지만 아무도 요청하지 않은 엔드포인트(--scan은 새 번들을 읽음, 요청은 보내지 않음) |
sitemap export [QL] |
캡처된 API를 OpenAPI 3.0.3 문서(JSON 또는 YAML)로 출력 |
oast listen · presets |
아웃오브밴드 콜백 리스너 (interactsh 및 유사 서비스) |
oast list · resume · release |
프로젝트에 저장된 OAST 리스닝 세션 목록 / 재개 / 릴리스 |
oast providers |
저장된 OAST 프로바이더 목록 / 추가 / 수정 / 활성화 / 비활성화 / 삭제 |
jwt [<token>] |
JWT 디코드, 재서명, 또는 공격 페이로드 생성 |
cookie [<cookie>] |
Flask / Rack / Django 세션 쿠키 디코드, 검증, 브루트포스, 위조 |
decoder <chain> [input] |
Decoder 인코드 / 디코드 / 해시 체인 실행 |
notes [<n>] · create · update · append · delete |
프로젝트 노트 읽기, 작성, 편집, 삭제 (delete는 --yes 필요) |
notify <summary> |
스크립트에서 gori TUI의 오퍼레이터에게 한 줄 보여주기 (알림 링과 Miss Ring) |
issues · create · update · delete |
이슈 목록 / 내보내기, 또는 이슈 작성과 삭제 (delete는 --yes 필요) |
links · add · delete |
이슈나 노트에서 플로우, Repeater 세션, 잡으로 이어지는 증거 포인터 |
evidence |
고정한 요청+응답 사본을 만들고, 나열·조회·연결·연결 해제·삭제 |
retest · add · update · remove · move · clear · run · runs · show · forget |
이슈의 재테스트 단계: 나열·추가·수정·재정렬·제거, 재테스트 실행(통과하지 않으면 종료 코드 1), 실행 이력 나열 |
redact |
안전한 내보내기용 리댁션 프로필 관리(profiles, use, default, set, rm) |
rewriter · add · rm · enable · disable · preview |
Match & Replace 규칙 관리 |
rewriter preset list · add |
응답 수정 프리셋 목록, 그리고 하나를 평범한 Match & Replace 규칙으로 설치 |
rewriter extract · bindings |
세션 바인딩 추출 규칙 관리, 그 규칙이 선언한 $BIND.NAME 목록 |
colormarker · add · update · rm · enable · disable · move · preview · color |
History 행 색상 규칙 관리 |
views · add · set · rename · scope · rm |
저장된 History 뷰 관리: 목록을 좁히는 이름 붙은 QL 쿼리를 렌즈로 적용 |
session · add · from-flow · edit · rm · baseline · show · refresh · from-request |
세션 슬롯: 전송이나 Authorize 실행이 그 이름으로 나가는 신원과, 그 신원을 다시 인증하는 Repeater 단계 |
grpc [schema] · reflect · forget |
gRPC .proto 렌즈: 무엇이 로드됐는지 보기, 서버 리플렉션으로 디스크립터 받기, 캐시된 대상 버리기 |
project [list] |
캡처된 트래픽이 있는 프로젝트 목록 (--all이면 전부) |
project create <name> |
이름으로 프로젝트 생성 (같은 이름이면 다시 열기) |
project switch <name> (use) · --clear |
--project 없는 모든 명령이 읽을 프로젝트 고정 |
project export <name> |
압축된 WAL 안전 .gori 프로젝트 아카이브 저장 |
project import <archive> |
프로젝트 아카이브를 새 프로젝트로 추가 |
project delete <name> |
프로젝트와 그 안에 캡처된 모든 것 삭제 (--yes로 확인) |
project scope |
스코프 규칙 목록 / 추가 / 수정 / 삭제 / 활성화 / 비활성화 |
project sandbox |
하드 컨테인먼트 샌드박스 게이트 조회 / 설정 (status, on, off) |
project env |
프로젝트 env 변수 목록 / 설정 / 삭제 ($ENV.KEY 치환) |
project host-override |
프로젝트 호스트 → IP 접속 오버라이드 목록 / 추가 / 수정 / 삭제 |
project network |
프로젝트 자체 네트워크 설정(net.*: 업스트림 프록시와 자격증명, 목적지 호스트, 타임아웃, 캡처 상한, bind) 목록 / 조회 / 설정 / 해제 |
읽기 서브커맨드에 공통인 플래그: --project=NAME, --db=PATH, --format=FMT (보통 text 또는 json), 그리고 --format이 json을 제공하는 모든 명령에서 --format=json과 같은 --json. 모르는 옵션이나 서브커맨드를 주면 가장 가까운 실제 이름(did you mean --format?)과 --help 위치를 한 줄로 stderr에 출력합니다. 전역 플래그는 동사 뒤에 옵니다. gori run rewriter rm 1 --project=x는 되지만 gori run rewriter --project=x rm 1은 조용히 목록만 찍는 대신 사용법 오류로 거부됩니다.
읽기 서브커맨드는 스토어를 읽기 전용으로 열고 캡처 락을 잡지 않으므로, 라이브 TUI가 캡처 중인 프로젝트를 대상으로 실행해도 안전합니다. body: 질의는 검색 인덱스를 비우므로 쓰기입니다. gori 프로젝트가 아닌 --db 파일(다른 도구의 SQLite 데이터베이스나 빈 파일)은 파일에 손대기 전에 거부합니다. 데이터베이스를 만드는 명령(import --db, capture --db)은 빈 파일은 계속 초기화하지만, 다른 도구의 테이블이 든 파일은 거부합니다.
쓰기 서브커맨드는 그 프로젝트의 WAL 데이터베이스를 TUI, MCP와 공유합니다. Store 라이터를 통해 직렬화되므로 TUI가 열려 있어도 실행할 수 있지만, 캡처 커밋이 SQLite 라이터 슬롯을 잠시 차지할 수 있습니다. 짧게 끝나는 서브커맨드는 SQLite 열기/라이터 대기에 1초 예산을 둡니다. 그때까지 슬롯이 바쁘면 필요한 쓰기는 0이 아닌 코드로 종료하며, 다른 gori가 프로젝트를 잠그고 있다고 알리고 해결책(다시 시도하거나 읽기 전용 서브커맨드로 읽기)을 함께 보여 줍니다. 실행 내내 프로젝트를 열어 두는 서브커맨드(discover, fuzz, import, --macro를 준 mine, probe, retest run, oast listen/resume, intercept, sitemap js --scan)는 표준 5초 대기를 그대로 씁니다. repeater 전송도 네트워크 응답을 저장하지 못했다면 성공이라고 하지 않고 실패하므로, 스크립트가 완료된 쓰기와 확인이 필요한 응답을 구분할 수 있습니다.
출력 계약
STDOUT은 데이터를 나릅니다. 경고, 개수, 내보내기 확인 메시지는 STDERR로 가므로 파이프가 깨끗하게 유지됩니다. 읽는 쪽이 파이프를 먼저 닫아도(… | head) 조용히 0으로 끝납니다.
text 출력의 캡처 텍스트는 STDOUT이 터미널이든 아니든 제어 문자와 보이지 않는 문자를 이름으로 보여 줍니다(⟨ESC⟩, ⟨NBSP⟩, ⟨ZWSP⟩, ⟨RLO⟩). 그래서 요청에 든 이스케이프 시퀀스가 터미널을 조작하지 못하고, 숨은 문자도 눈에 보입니다. show와 repeater의 text 뷰는 CRLF를 포함해 줄바꿈을 그대로 유지합니다. --format json은 이런 문자를 있는 그대로 싣고 잘못된 UTF-8만 치환하며, --format raw는 정확한 바이트입니다.
모든 명령에서 --format json은 JSON 문서 하나이고, --format jsonl은 한 줄에 객체 하나입니다.
| 서브커맨드 | --format json |
--format jsonl |
|---|---|---|
history |
배열 하나, 행 단위로 스트리밍 | 한 줄에 객체 하나 |
capture |
배열 하나, 시작할 때 열고 캡처가 멈출 때(--for, --max, 시그널) 닫음 |
완료된 플로우마다, 완료되는 대로 객체 하나 |
fuzz, mine, discover, authorize, cache-deception |
JSON 배열 하나. fuzz는 완료 순서가 아니라 인덱스 순서 |
결과가 나올 때마다 한 줄씩 |
sequence |
보고서 하나 | 샘플이 나올 때마다 한 줄씩, 마지막에 보고서 |
| 종료 코드 | 의미 |
|---|---|
0 |
성공 |
1 |
오류: 전송 실패, 열 수 없는 프로젝트, 적용되지 못한 변경, 또는 어떤 요청도 응답을 받지 못한 fuzz / mine / discover / sequence / authorize / cache-deception 실행(죽었거나 거부하는 대상은 깨끗한 "결과 없음"이 아닙니다) |
3 |
판정 게이트 발동: run fuzz --fail-if-no-matches가 완료했지만 매칭이 없음(그리고 --stop-on / --stop-after-matches 조건도 충족되지 않음), 또는 run probe --fail-on=LEVEL이 LEVEL 이상의 이슈를 보고함 |
126 / 127 |
run shell -- CMD가 CMD를 실행하지 못함(126) 또는 찾지 못함(127). CMD가 실행되면 종료 코드는 CMD 자신의 것입니다 |
130 |
SIGINT/SIGTERM으로 중단. capture(--for나 --max로 끝나면 0), fuzz, mine, discover, sequence, authorize, cache-deception, repeater minimize는 모아 둔 것을 먼저 내보낸 뒤 130으로 종료하므로, 스크립트의 && next-step이 잘린 실행을 끝난 실행으로 오해하지 않습니다 |
--fail-if-no-matches 없이 실행하면, 매칭이 없으면서 동시에 모든 전송이 실패한 fuzz는 1로 끝납니다. "결과 없음"과 "대상에 닿지도 못함"이 구분됩니다. 플래그를 주면 3이 우선합니다. 정지 조건이 충족된 실행은 두 규칙 모두에서 빠집니다. 시간 초과된 전송은 매칭되지 않은 오류 행으로 남으면서도 --stop-on 'time:>=5000'을 충족할 수 있고, 그것이 바로 실행이 찾던 결과이기 때문입니다.
생성 명령은 --format json을 받고, 새로 만든 행으로 답합니다. repeater create, issues create, notes create, views add, colormarker add, rewriter add, rewriter extract add, probe rules add, oast providers add, links add, project scope add, project host-override add는 쓰기가 커밋된 뒤 다시 읽어 온 객체 하나를 STDOUT에 출력하며, 그 모양은 같은 계열의 목록이 같은 행에 대해 출력하는 것과 정확히 같습니다. 그래서 스크립트는 문장에서 id를 긁어내는 대신 jq .id로 가져가면 됩니다. id는 그 계열의 다른 명령이 받는 값 그대로입니다. 대부분은 숫자이고, probe 규칙은 규칙 이름(custom_p_7), OAST 프로바이더는 프로바이더 키(p_3)입니다. 뷰는 이름으로 다루므로 id 대신 name과 key를 담습니다. notes create는 텍스트 줄이 가리키는 위치인 index를, links add는 created를 더합니다. created는 이미 링크돼 있던 쌍이면 false이고, 그때는 기존 링크의 id가 담깁니다. 텍스트 출력은 바뀌지 않습니다.
issue=$(gori run issues create --title "IDOR on /v1/users/{id}" --severity high --format json | jq .id)
gori run links add --owner=issue --id="$issue" --ref=flow --ref-id=42 --format json
run capture
gori run capture --port 8070 --format json --for 5m
| Option | Description |
|---|---|
-l, --listen; -p, --port |
이 프로세스의 전역 바인드 (설정 기본값; 프로젝트 오버라이드가 여전히 우선) |
--project=NAME |
기록할 프로젝트 (기본값: GORI_PROJECT나 project switch로 정한 프로젝트, 없으면 default) |
--db=PATH |
데이터베이스 경로 |
-k, --insecure-upstream |
업스트림 TLS 검증 생략 |
--ca-dir=DIR |
루트 CA 디렉터리, gori --ca-dir과 같은 형식 |
--format=FMT |
text, jsonl(플로우마다 객체 하나, 스트리밍), 또는 json(배열 하나, 캡처가 멈출 때 닫힘) |
--for=DURATION |
예: 30s, 5m, 1h 이후 중지 |
--max=N |
완료된 플로우 N개 이후 중지. 업그레이드된 터널은 닫힌 뒤에 셈 |
run shell
OS 설정을 건드리지 않고, 도구들이 실행 중인 gori를 거치며 gori의 CA를 신뢰하는 터미널을 엽니다. 팔레트의 Open browser에 대응하는 터미널 기능입니다.
gori run shell # 대화형 $SHELL; `exit`로 나감
gori run shell -- curl https://target/ # 명령 하나를 실행하고 그 종료 코드로 끝남
eval "$(gori run shell --print)" # 지금 쓰는 셸에 적용할 export 줄
gori run shell --print --shell fish | source
주소는 프로젝트를 캡처 중인 gori에서 가져오고(포트 폴백 뒤에도 실제 포트), CA는 그 gori가 서명에 쓰는 것을 씁니다. 프로젝트를 캡처하는 gori가 없으면 --proxy로 주소를 주지 않는 한 거부합니다. 캡처가 일시정지 상태면 경고하고 진행합니다.
| Option | Description |
|---|---|
--project=NAME; --db=PATH |
가리킬 실행 중인 gori (기본값: GORI_PROJECT나 project switch로 정한 프로젝트, 없으면 가장 최근에 활동한 프로젝트) |
--proxy=HOST:PORT |
실행 중인 캡처를 찾는 대신 이 프록시 주소 사용 |
--ca-dir=DIR |
CA 디렉터리 (기본값: 캡처 중인 gori의 것, 없으면 ~/.gori/ca) |
--print |
셸을 띄우지 않고 export 줄을 출력 |
--shell=SYNTAX |
--print의 문법: sh (Windows 외 기본값; bash, zsh도 가능), fish, 또는 powershell (pwsh도 가능; Windows 기본값) |
--keep-no-proxy |
물려받은 NO_PROXY를 지우지 않고 유지 |
설정되는 것: http_proxy, https_proxy, HTTP_PROXY, HTTPS_PROXY는 gori를 가리키고, 로컬 대상도 캡처되도록 NO_PROXY/no_proxy는 지웁니다. SSL_CERT_FILE, CURL_CA_BUNDLE, REQUESTS_CA_BUNDLE, GIT_SSL_CAINFO, AWS_CA_BUNDLE, PIP_CERT, CARGO_HTTP_CAINFO, DENO_CERT, NIX_SSL_CERT_FILE는 ~/.gori/shell/ 아래 번들을 가리킵니다. 이 변수들 대부분은 도구의 신뢰 저장소에 추가하는 게 아니라 교체하기 때문에, 번들에는 터미널이 원래 신뢰하던 저장소(직접 설정한 SSL_CERT_FILE, 없으면 시스템 루트)와 gori 루트가 함께 들어 있습니다. 특정 도구에만 설정해 둔 CA 변수(예: REQUESTS_CA_BUNDLE)는 공용 번들 대신 그 파일에 gori 루트를 더한 별도 파일을 받습니다. NODE_EXTRA_CA_CERTS는 gori 루트를 추가하고, NODE_USE_ENV_PROXY=1은 Node의 프록시 지원을 켜고, GODEBUG=x509sslcertoverrideplatform=1은 macOS의 Go가 SSL_CERT_FILE을 읽게 합니다. GORI_SHELL=1과 GORI_PROXY=HOST:PORT는 프롬프트에서 쓸 수 있는 표식입니다:
# ~/.zshrc 또는 ~/.bashrc
[ -n "$GORI_SHELL" ] && PS1="(gori) $PS1"
셸이 바꾸거나 지운 값은 GORI_SHELL_ORIG_<NAME>(예: GORI_SHELL_ORIG_HTTPS_PROXY)으로 기록됩니다. 셸 안에서 띄운 gori(gori run send, 두 번째 캡처)는 셸의 프록시 변수를 자기 업스트림으로 쓰지 않으므로, 그 트래픽이 두 번 캡처되지 않습니다. 대신 기록된 프록시와 NO_PROXY를 쓰고, 명시적으로 설정한 업스트림은 그대로 적용됩니다. gori 셸 안에서 다시 연 셸도 기록된 값에서 시작하므로 바깥 gori의 CA를 계속 신뢰하지 않습니다.
다루지 않는 것: 1.27보다 오래된 툴체인으로 빌드한 macOS의 Go 프로그램은 키체인으로 검증합니다. Go는 localhost와 루프백 주소를 절대 프록시하지 않습니다. Node는 NODE_USE_ENV_PROXY를 지원하는 릴리스(v22.21, v24.10에서 stable)에서만 프록시를 씁니다. Java는 JAVA_TOOL_OPTIONS로 truststore가 필요합니다. 자체 신뢰 저장소를 고정한 도구는 다루지 않습니다.
run browser
실행 중인 gori를 거치고 gori의 CA만 신뢰하는 브라우저를 엽니다. 팔레트의 Open browser를 스크립트에서 쓰는 형태입니다. ~/.gori/browser/<id>의 같은 프로필을 쓰고, 브라우저가 끝날 때까지 포그라운드에 머물며, Ctrl-C를 누르면 브라우저를 닫습니다.
gori run browser # 처음 감지된 브라우저
gori run browser --browser brave
gori run browser --remote-debugging-port 9222 # 브라우저 자동화 MCP 서버용
프록시 주소는 run shell과 같은 방식으로 찾고, 프로젝트를 캡처 중인 gori가 없으면 같은 문구로 거부합니다.
| 옵션 | 설명 |
|---|---|
--project=NAME; --db=PATH |
가리킬 실행 중인 gori (기본값: GORI_PROJECT 또는 project switch로 고른 프로젝트, 없으면 가장 최근에 쓴 프로젝트) |
--proxy=HOST:PORT |
실행 중인 캡처를 찾지 않고 이 프록시 주소를 사용 |
--ca-dir=DIR |
CA 디렉터리 (기본값: 캡처 중인 gori의 것, 없으면 ~/.gori/ca) |
--browser=ID |
chrome, chromium, brave, edge, vivaldi, firefox (기본값: 처음 감지된 브라우저, --remote-debugging-port를 주면 처음 감지된 Chromium 계열) |
--remote-debugging-port=N |
루프백에 Chrome DevTools Protocol 포트를 엽니다. 0이면 브라우저가 포트를 고릅니다. Chromium 계열만 지원 |
--remote-debugging-port를 주면 브라우저가 알려 준 엔드포인트(http://127.0.0.1:N)를 출력합니다. 브라우저 자동화 MCP 서버를 여기에 연결하면 그 서버가 조작하는 페이지가 gori에 캡처됩니다.
npx @playwright/mcp@latest --cdp-endpoint http://127.0.0.1:9222
npx chrome-devtools-mcp@latest --browserUrl http://127.0.0.1:9222
그 프로필이 이미 실행 중인 브라우저에 열려 있으면 실행을 거부합니다. 실행 중인 브라우저는 이를 새 창 요청으로 받아들이고 포트를 열지 않기 때문입니다.
DevTools 포트가 열려 있으면 로컬의 어떤 프로세스든 인증 없이 브라우저를 완전히 조작할 수 있습니다. 쿠키를 읽고 모든 사이트에서 스크립트를 실행할 수 있으며, 그 프로필에 로그인된 세션도 포함됩니다. 그래서 요청할 때만, 루프백에만 열리고, 명령이 끝나면 닫힙니다. 브라우저를 조작하는 에이전트는 MCP send_request처럼 프로젝트 스코프로 제한되지 않으며, 그 트래픽은 다른 브라우저와 똑같이 캡처됩니다.
run android
adb로 Android 에뮬레이터나 루팅된 테스트 기기를 실행 중인 gori에 연결합니다. 팔레트의 Set up Android device를 스크립트에서 쓰는 형태입니다. gori는 loopback 바인드를 유지하고, adb reverse가 기기의 127.0.0.1:PORT를 gori로 이어 주며, 기기의 전역 HTTP 프록시가 그 주소로 설정됩니다.
gori run android devices # serial, 상태, 모델, api=N,emulator,root (adbd가 root일 때; 목록은 su를 묻지 않음)
gori run android setup --dry-run # adb 명령만 출력하고 아무것도 바꾸지 않음
gori run android setup --system # root로 CA를 시스템 저장소에 신뢰 등록
gori run android undo
setup은 run shell과 같은 방식으로 gori를 찾습니다. --user(기본값)는 CA를 /sdcard/Download/gori-ca.crt에 올리고 보안 설정을 엽니다. 거기서 CA 인증서로 설치하세요. API 24 이상을 대상으로 하는 앱은 network security config에서 허용해야만 사용자 CA를 신뢰합니다. --system은 기존 인증서와 gori의 루트(<subject_hash_old>.0)를 담은 tmpfs를 /system/etc/security/cacerts 위에 마운트합니다. root(root로 실행 중인 adbd, 또는 su)로 실행되고, --dry-run에서 모든 명령을 먼저 보여 주며, 기기를 재부팅하면 사라집니다. root가 없으면 --user로 낮추지 않고 거부하며, 시스템 CA를 Conscrypt APEX에서 읽는 Android 14+(API 34)는 지금은 거부합니다.
undo는 http_proxy를 :0으로 되돌리고, reverse 매핑을 지우고(포트는 기기의 프록시 값에서 읽거나 --device-port로 지정), 올린 파일을 삭제하며, root가 있으면 tmpfs를 언마운트합니다. 두 번 실행해도 안전합니다. 직접 설치한 사용자 CA는 설정에서 지울 때까지 남습니다.
| 옵션 | 설명 |
|---|---|
--serial=S |
대상 기기 (기본값: 연결된 유일한 기기) |
--user; --system |
setup: CA를 신뢰시키는 방식 (기본값 --user) |
--device-port=N |
기기가 자기 loopback에서 접속할 포트 (기본값: gori의 포트, undo: 기기에서 읽음) |
--project=NAME; --db=PATH; --proxy=HOST:PORT |
setup: 연결할 gori, run shell과 같음. 주소는 이 머신의 loopback이어야 함 |
--ca-dir=DIR |
setup: CA 디렉터리 (기본값: 캡처 중인 gori의 것, 없으면 ~/.gori/ca) |
--dry-run |
아무것도 바꾸지 않고 계획만 출력. 기기 정보(API 레벨, root)는 읽음 |
다루지 않는 것: 인증서 피닝, 전역 프록시를 무시하는 트래픽.
run history / ls
gori run history -q 'status:5xx' --limit 100 --format json
| Option | Description |
|---|---|
-q, --query=QL |
쿼리 언어 필터 (위치 인자로도 허용) |
-n, --limit=N |
최대 행 수 (기본값 50) |
--count |
목록 대신 걸리는 플로우 수를 출력합니다. 숫자 하나, --format json/jsonl이면 {"count": N}. 목록과 같은 필터(-q, --view, --in-scope, --hide-static)를 쓰므로 개수와 행이 어긋나지 않습니다. --column, --no-columns, --format har, --redact는 함께 쓰면 거절하고 -n은 적용되지 않습니다 |
--view=NAME |
저장된 뷰를 적용합니다. 그 쿼리는 -q를 대체하지 않고 AND로 얹힙니다. TUI의 v 피커가 필터 바 위에 얹히는 것과 같습니다. 없는 이름은 무시하지 않고 거절하며(있는 이름들을 알려 줍니다), 목록과 --count에서만 씁니다 |
--in-scope |
프로젝트에 설정된 스코프 안의 플로우만 출력합니다. TUI의 s 렌즈로, 옵트인이며 그 렌즈의 활성화 여부와 무관합니다. 캡처는 여전히 전부 기록하며, 스코프 규칙이 없으면 빈 결과 |
--hide-static |
이미지, 폰트, 오디오·비디오를 뺍니다. TUI의 정적 에셋 숨기기 렌즈로, -q -static:true와 같고 그 렌즈가 켜져 있는지와는 무관합니다 |
--lenient |
없는 필드 이름을 쓴 쿼리를 거절하지 않고 그 토큰을 텍스트로 검색 |
--column=SPEC |
행마다 추출한 값을 함께 출력합니다 (반복 가능). [LABEL=][req|res:]kind:selector 형식으로, 예: header:x-request-id, RID=req:header:authorization, jsonpath:data.id, regex:token=(\w+), position:0:32. --column을 하나라도 주면 이 프로젝트에 설정된 History 컬럼을 대체합니다 |
--no-columns |
이 프로젝트에 설정된 History 컬럼을 그리지 않습니다 |
--format=FMT |
text, json(배열 하나), jsonl(한 줄에 객체 하나), 또는 har |
--include-sensitive |
Authorization / Cookie / Set-Cookie / Proxy-Authorization / API 키 값을 [REDACTED] 대신 그대로 냅니다. json의 행별 headers와 header:/cookie: 컬럼에 적용됩니다. 다른 형식에서는 아무 효과가 없으며, 그 사실을 STDERR로 알립니다 |
--redact [PROFILE] |
리댁션 프로파일로 요청/응답 본문을 정제한 뒤 씁니다. 본문을 싣는 목록 형식은 --format har 하나뿐이므로 그 형식에서만 동작하고, 나머지에서는 무시하지 않고 거부합니다 |
--no-redact |
리댁션이 기본으로 설정돼 있어도 캡처한 본문을 그대로 씁니다 |
--redact-preview |
--redact가 무엇을 바꿀지 값마다 한 줄씩 나열하고 HAR은 쓰지 않습니다 |
서브커맨드: history show <id> (run show와 동일), history delete <id>… (id 하나 또는 여러 개를 한 트랜잭션으로. 모르는 id가 하나라도 있으면 호출 전체를 거부), history delete -q QL --yes, history clear --yes.
이 프로젝트의 History 컬럼은 기본으로 함께 그려지므로, 헤드리스 목록도 TUI의 History 탭과 같은 값을 보여 줍니다. 컬럼 없는 기본 목록으로 돌아가려면 --no-columns를 쓰세요. text에서는 행 끝에 label=value로 붙고(빈 값도 포함해서 전부입니다. "이 디스크립터는 여기서 아무것도 못 찾았다"도 봐야 할 답입니다), json에서는 columns 객체로 실립니다(컬럼이 없으면 키 자체가 없습니다). =는 첫 :보다 앞에 올 때만 라벨 구분자이므로 regex:token=(\w+)는 regex:token이라는 컬럼이 아니라 패턴 그대로입니다. 컬럼 하나당 출력되는 행마다 읽기 한 번이 추가되고, 본문을 읽는 세 종류는 본문을 최대 512 KiB까지 읽습니다.
json/jsonl의 각 행은 플로우의 절대 url과 요청 헤더를 담은 headers 객체를 함께 싣습니다 (같은 이름이 반복되면 배열이 됩니다). 본문은 넣지 않습니다. 그건 run show의 몫입니다.
그 객체의 민감한 헤더 값은 기본으로 [REDACTED]로 가려지며, 무엇이든 가려졌으면 행에 sensitive_headers_redacted: true가 붙습니다. 목록은 인벤토리이고, "내가 무엇을 캡처했나"를 확인하려고 한 번 실행한 명령이 살아 있는 세션 쿠키를 터미널 로그나 에이전트 대화 기록에 남겨서는 안 되기 때문입니다. 정확한 바이트가 필요하면 --include-sensitive를 쓰세요. 어느 쪽이든 헤더 이름과 와이어 순서, 반복 횟수는 그대로 남으므로 가려진 행도 그 요청이 무엇이었는지는 여전히 말해 줍니다. obs-fold 이어짐 줄은 별도 헤더로 나오지 않고 원래 필드에 합쳐진 뒤 함께 가려집니다.
같은 규칙이 민감한 헤더를 셀렉터로 지정한 header: 컬럼과 모든 cookie: 컬럼에도 적용됩니다. 이름을 지정한 쿠키의 값은 그 행이 이미 가리고 있는 Cookie 헤더의 일부이기 때문입니다. 반대로 포함되지 않는 것들은 이렇습니다. regex:, jsonpath:, position: 컬럼은 메시지의 어느 바이트에서든 자격 증명을 뽑아낼 수 있고 디스크립터만으로는 그런지 알 수 없어 원리적으로 다룰 수 없습니다. 쿼리 스트링에 담긴 자격 증명은 url과 target의 일부입니다. text 목록은 스크립트가 받아 가는 피드가 아니라 사람이 보는 화면이므로 그대로 둡니다. --format har 역시 교환용 문서로 다시 재생할 수 있어야 하므로 캡처된 메시지를 의도적으로 온전히 담습니다.
각 행에는 source도 실립니다. 이 플로우가 어디서 왔는지(proxy, repeater, fuzzer, discover, import …)이며, 출처를 기록하기 전에 캡처된 플로우는 null입니다. 값이 있을 때 source_surface(tui / cli / mcp)와 source_ref도 함께 나옵니다. text 형식은 평범한 캡처 트래픽이 아닌 행에 [repeater] 같은 칩을 찍습니다. src:로 필터링하며, MCP list_history와 get_flow도 같은 키를 냅니다.
history delete -q QL은 쿼리에 매칭되는 플로우를 전부 삭제하며 --yes가 필요합니다. --yes 없이 실행하면 몇 개가 지워질지 출력하고 거부합니다. QL이 모르는 필드를 쓴 쿼리(methd:)도 조용히 아무것도 지우지 않는 대신 거부합니다. id도 -q도 없으면 거부합니다. 프로젝트를 통째로 비우는 건 history clear --yes입니다.
--format har은 결과 집합 전체를 하나의 HAR 1.2 log로 STDOUT에 씁니다. 오래된 항목이 먼저 오므로, 쿼리 결과를 동료에게 넘기거나 Burp, Charles, 브라우저 네트워크 패널에 그대로 불러올 수 있습니다. HAR 내보내기를 참고하세요.
run show
gori run show <flow-id> --format raw
--format은 text, json, raw(정확한 바이트), har(항목 하나짜리 HAR log), 또는 요청을 코드로 직렬화하는 curl, python(requests), fetch(JavaScript), go(net/http), httpie, csrf(스스로 제출하는 HTML CSRF PoC)입니다. 각각 TUI의 Space → Y **Copy as…**에서 같은 이름의 항목이 복사하는 것과 바이트 단위로 동일한 텍스트를 냅니다. --request-only / --response-only로 출력을 제한하며, har에는 적용되지 않습니다. 요청을 코드로 내는 형식은 모두 요청 그 자체이므로 --response-only는 거부됩니다. 두 가지 주의사항은 STDOUT의 스니펫이 아니라 STDERR로 나갑니다. 캡처 한도에서 잘린 요청 본문은 짧은 채로 실리고, WebSocket 플로우는 업그레이드 핸드셰이크만 직렬화되며 프레임은 담기지 않습니다. 디코드된 SAML/JWT/GraphQL/파라미터, WebSocket 메시지, SSE 이벤트가 있으면 함께 포함됩니다.
--headers-only와 --max-body=BYTES는 text와 json 뷰를 간추립니다. --headers-only는 각 본문을 크기를 알려주는 한 줄로 바꾸고([body omitted by --headers-only: 149504 bytes]; JSON에서는 body 객체가 encoding과 size를 유지한 채 omitted: true를 얻습니다), --max-body는 디코드된 각 본문의 앞쪽 BYTES바이트를 찍은 뒤 [… truncated by --max-body: showing 2048 of 149504 bytes]를 붙입니다(JSON에서는 text/base64가 그 앞부분을 담고, size는 전체 크기, shown_size는 보여준 부분, truncated는 true입니다). 둘 중 하나만 있어도 본문에서 파생되는 섹션(디코드 뷰, gRPC 메시지, WebSocket 프레임, SSE 이벤트)은 빠집니다. 트랜스크립트는 그래도 개수와 함께 이름이 남습니다(=== SSE EVENTS (40) — not printed under --max-body ===, 또는 {"count": 40, "omitted": true}). 이 둘은 전체 메시지를 그대로 쓰는 다른 형식들과 함께 쓸 수 없고, 서로도 함께 쓸 수 없습니다.
--ws-match=QUERY는 트랜스크립트를 싣는 모든 형식에서 매칭되는 WebSocket 메시지만 찍습니다. ws:needle(대소문자 무시), ws~regex, 또는 접두어 없는 needle을 받고, ws: 쿼리 필드와 똑같이 매칭합니다. 소켓의 메시지 중 몇 개를 남겼는지는 STDERR 한 줄로 알려 줍니다.
--original은 Match & Replace 규칙이나 Intercept 편집이 바꾸기 전의 플로우, 곧 클라이언트의 요청과 원본 서버의 응답을 어느 --format으로든 찍습니다. 바뀌지 않은 쪽은 저장된 그대로 나옵니다. 아무것도 바뀌지 않은 플로우는 거부합니다.
안전한 증거 내보내기
--redact [PROFILE]는 캡처한 바이트 대신 플로우의 정제된 파생본을 씁니다. 리댁션 프로파일이 지목한 값이 키가 붙은 자리표시자 [REDACTED:3f1c9ab4]로 바뀝니다. 같은 값은 같은 태그를 받으므로, 읽는 사람은 둘 중 어느 것도 보지 않은 채로 "요청의 토큰이 응답의 토큰과 같다"는 사실만 확인할 수 있습니다. 태그는 값의 해시가 아니라 설치마다 한 번 만들어지는 비밀 키로 계산한 HMAC입니다. 네 자리 PIN이나 워드리스트에 있는 비밀번호의 잘린 해시는 몇 밀리초면 복원되고, 그러면 자리표시자 자체가 유출이 되기 때문입니다.
플로우를 렌더링 전에 한 번만 정제하므로 모든 --format에 적용됩니다. raw, text, json, har, 그리고 요청을 코드로 내는 여섯 형식 전부입니다. --no-redact는 이 프로젝트나 이 설치에서 리댁션이 기본이더라도 캡처한 바이트를 그대로 씁니다. --redact-preview는 무엇이 바뀔지(요청/응답, JSON 포인터 또는 폼 키, 발동한 규칙, 자리표시자)를 나열하고 문서는 출력하지 않습니다.
무엇을 덮고 무엇을 덮지 않는지:
- 본문만 다룹니다. 헤드, URL, 쿼리 스트링은 건드리지 않으며 STDERR의 문장이 매번 그 사실을 말합니다.
Cookie헤더나?token=쿼리 스트링 속 크리덴셜은 소비자(HAR의url, curl의 인자, 행의target)가 함께 움직여야 하는 다른 축이고, 프로파일의 대상이 아닙니다. - 저장된 바이트는 바뀌지 않습니다. History, Repeater, Comparer,
get_response_body_chunk는 캡처 그대로를 계속 읽습니다. 정제는 나가는 길에서만 일어나므로, 내보내기 전후의 재전송은 바이트 단위로 동일합니다. - JSON 본문은 다시 직렬화됩니다. 따라서 정제된 JSON은 원본이 아니라 gori의 공백과 키 순서를 갖습니다. 정확한 프레이밍 자체가 증거라면
--no-redact를 쓰세요. - 헤드는 정제된 본문을 설명하도록 고쳐집니다.
Content-Length가 다시 쓰이고, 읽기 위해 압축을 풀거나 청크를 해제해야 했던 본문은Content-Encoding/Transfer-Encoding을 잃습니다(STDERR로 알립니다). - gori가 읽을 수 없는 본문은 통째로 보류됩니다. 일부만 정제하지 않습니다. 유효한 UTF-8이 아닌 본문과 모든
multipart/*가 여기에 해당합니다(gori는 아직 파트를 분해하지 않으므로 업로드된 파일과 폼 필드를 구분할 수 없습니다). 자리표시자가 몇 바이트가 빠졌고 왜 그랬는지 말해 줍니다. - 파싱되지 않는 본문은 보수적인 텍스트 패스로 물러납니다. 프로파일 자신의 패턴, 필드 이름을
"name": "value"/name=value텍스트 규칙으로 다시 표현한 것(잘린 값도 허용), 그리고 어디에 나타나든 모호하지 않은 두 가지 내장 형태(JWS/JWE 컴팩트 직렬화와 PEMPRIVATE KEY블록)입니다.
모든 주의사항과 개수는 STDERR로 나가고 STDOUT은 문서로 남습니다. 그래서 gori run show 42 --format har --redact > evidence.har는 여전히 순수한 HAR입니다.
HAR 내보내기
gori가 쓴 HAR은 다시 gori로 가져와도(gori run import --har) 같은 플로우가 되므로 왕복이 보장됩니다. 네 가지를 알아두세요.
- 본문은 와이어 바이트입니다. chunked만 풀고 압축은 풀지 않으며, 유효한 UTF-8이 아니면 base64로 인코딩합니다.
Content-Encoding헤더가headers에 그대로 남아 본문과 헤드가 같은 메시지를 가리킵니다. - 캡처 상한에 잘린 본문은 표시되며, 온전한 것처럼 나가지 않습니다.
bodySize와content.size는 실제 와이어 크기를 유지하고 텍스트에는 캡처된 앞부분만 담기며,content/postData의comment가 그 사실을 적습니다. 명령은 해당 개수도 STDERR로 보고합니다. - WebSocket 플로우는 메시지와 함께 내보내집니다. 실제
101핸드셰이크에 캡처된 전송 기록이 Chrome DevTools의_webSocketMessages필드로 나란히 실리며,gori run import --har로 다시 복원됩니다. 방향, opcode(제어 프레임 포함), 바이트(유효한 UTF-8이 아니면 base64), 밀리초 단위 타임스탬프가 유지됩니다. 프레임별 형태(FIN/RSV/마스크 키)는 이 형식에 담을 필드가 없으므로 필요하면--format json또는raw를 쓰세요. - 응답이 캡처되지 않은 플로우는 건너뜁니다. 전송 기록이 비어 있는 소켓도 마찬가지입니다. 핸드셰이크만으로는 교환이 아니기 때문입니다. 개수와 이유는 STDERR로 나가고 STDOUT은 순수한 HAR 문서로 유지됩니다.
run compare
두 플로우의 줄 단위 diff이며, Comparer 탭과 동일한 결과를 냅니다.
gori run compare 41 42 --pane response --changes-only
| Option | Description |
|---|---|
--pane=PANE |
비교 대상: request 또는 response (기본값) |
--changes-only |
변경되지 않은 문맥은 빼고 추가 / 삭제된 줄만 출력 |
--context=N |
변경 지점 주변 N줄만 남기고 나머지 동일 구간은 @@ N unchanged lines @@ 마커로 접기 (--changes-only와 함께 쓸 수 없음) |
--format=FMT |
text (기본값) 또는 json |
--redact [PROFILE], --no-redact, --redact-preview |
show와 같음: 정제된 본문을 비교합니다. 바뀐 줄 수와 판정은 여전히 캡처된 바이트 기준입니다 |
diff 위에 양쪽의 status · size · time과 A→B 델타가 출력됩니다. 상태 코드가 뒤집혔는지, 크기가 얼마나 달라졌는지를 첫 줄을 읽기 전에 알 수 있습니다. --format=json에도 같은 값이 meta로 들어가고, 접힌 구간은 빈칸이 아니라 {"kind":"fold","hidden":N} 행이 됩니다.
--changes-only는 무엇이 바뀌었는지는 알려주지만 어디서 바뀌었는지는 지웁니다. 400줄 본문에서 한 줄만 다르면 위치 없는 두 줄만 남습니다. --context는 변경 지점을 제자리에 두고, 건너뛴 양을 함께 적습니다.
run diff
리테스트 리포트입니다. run compare가 메시지 두 개를 비교한다면, 이건 프로젝트 두 개를 엔드포인트 단위로 비교합니다. 실무의 절반을 차지하는 질문, 지난번 대비 뭐가 바뀌었나에 답합니다.
gori run diff --from q1-audit --to q3-retest --format md
| Option | Description |
|---|---|
--from=NAME |
기준 프로젝트, 즉 이전 점검(이름, slug, 짧은 id). 필수 |
--to=NAME |
이후 프로젝트 (기본값: GORI_PROJECT나 project switch로 정한 프로젝트, 없으면 가장 최근에 활성화한 프로젝트) |
--from-db=PATH / --to-db=PATH |
레지스트리 프로젝트 대신 SQLite 파일을 직접 지정 |
-q, --query=QL |
양쪽 모두를 QL 쿼리로 좁힘 |
--in-scope |
각 프로젝트 자신의 스코프 규칙 안에 있는 호스트만 |
-n, --limit=N |
한쪽에서 읽을 엔드포인트 그룹 최대치(기본 40000) |
--verdict=LIST |
지정한 판정만 나열 (added,gone,changed,unchanged,removed) |
--unchanged |
변화 없는 엔드포인트도 나열 (개수는 항상 집계됨) |
--no-issues |
이슈 리테스트를 건너뜀 |
--format=FMT |
text (기본값), json, 또는 md(리테스트 산출물에 그대로 붙여 넣을 섹션) |
아무것도 보내지 않습니다. 양쪽 모두 캡처된 트래픽만 비교합니다. 발견 항목이 아직 재현되는지 확인하려면 요청을 보내야 하고, 그건 Repeater로 직접 하는 선택으로 남습니다.
엔드포인트 동일성
서로 다른 두 점검이 같은 식별자를 캡처하는 일은 없습니다. 따라서 리터럴 경로로 키를 잡으면 모든 행이 removed 한 번 added 한 번으로 두 번 보고되고, 결국 아무 말도 하지 못합니다. 그래서 Sitemap이 그리는 폴딩 템플릿을 그대로 키로 씁니다: /users/{uuid}, /items/{n}, /search(쿼리 변형은 경로로 접힘). 폴딩은 양쪽의 합집합에 대해 한 번만 돌기 때문에, 한쪽에서만 폴딩 임계치를 넘긴 라우트도 반대쪽과 서로 매칭됩니다.
다섯 가지 판정
| 판정 | 의미 |
|---|---|
added |
B에는 캡처됐고 A에는 없음 |
gone |
양쪽 모두 캡처됨. 그런데 A는 도달 가능했던 반면 B가 받은 응답은 전부 404/410. 캡처만으로 "정말 사라졌다"를 말할 수 있는 유일한 근거 |
changed |
양쪽 모두 캡처됨. 상태 클래스, 인증, content type, 크기 중 최소 하나가 허용 범위를 넘어 움직임 |
unchanged |
양쪽 모두 캡처됐고 동등함 |
removed |
A에는 있는데 B는 그 엔드포인트로 요청을 아예 안 함. 커버리지 공백이지 삭제의 근거가 아님 |
removed/gone 구분이 이 명령의 핵심입니다. 리테스트가 얕으면 방문한 엔드포인트도 적어지는데, 이 둘을 한 바구니에 넣으면 짧은 오후 작업이 대규모 수정처럼 보고됩니다. 모든 출력이 그 단서를 맨 앞에 두고, --verdict로 목록을 좁혀도 개수는 항상 다섯 판정 전부를 덮습니다.
changed 판정은 바이트 동일성이 아니라 허용 밴드로 내립니다. repeater minimize와 mine이 쓰는 바로 그 캘리브레이션입니다. 그래서 캡처 사이에 길이가 흔들리는 페이지는 unchanged로 읽힙니다. 상태 코드는 클래스로 비교합니다. 200이 201이 된 건 리테스트 발견이 아니지만 200이 403이 된 건 발견이고, 그건 별도의 auth 축으로 보고됩니다.
이슈 리테스트
리포트 끝에는 기준 프로젝트에서 아직 열려 있는 이슈들과, 그 이슈가 걸려 있던 엔드포인트가 어떻게 됐는지가 붙습니다: "여전히 같은 방식으로 응답함, 발견 항목이 아직 유효할 가능성이 높음", "이제 404/410으로 응답함", "새 캡처에서 요청된 적 없음, 닫기 전에 리테스트할 것". 요청은 보내지 않습니다. 수정 확인은 사용자의 판단입니다.
커버리지와 스코프
개수 위에 양쪽의 플로우 수, 엔드포인트 수, 호스트 수, 캡처 기간이 함께 출력됩니다. B의 커버리지가 A보다 얇으면 바로 보입니다. 두 프로젝트의 스코프 규칙이 다르면 그 사실도 적습니다. 그쪽 프록시가 애초에 기록하지 않아서 엔드포인트가 없는 것일 수 있으니까요.
run intercept
캡처 락을 쥔 TUI의 라이브 인터셉트 큐를 조작합니다. 인터셉트는 TUI 전용입니다. 헤드리스 gori run capture는 메시지를 붙잡지 않으며, list를 뺀 서브커맨드는 상태를 게시하는 캡처 인스턴스가 없으면 거부합니다(list는 STDERR로 알리거나 --format json에서 {"available": false, …}를 내고 0으로 끝납니다).
gori run intercept # 붙잡힌 항목 + 인터셉트 상태
gori run intercept get 3 --format json
gori run intercept forward 3
gori run intercept edit 3 --raw-file edited.txt
gori run intercept direction request
gori run intercept inject 42 --direction in --data '{"role":"admin"}'
| Subcommand | Description |
|---|---|
list (기본값) |
붙잡힌 항목과 캐치 상태, 방향, 필터 |
get <item-id> |
붙잡힌 항목 하나의 전체 상세 |
forward <item-id> |
바이트 그대로 통과 |
drop <item-id> |
폐기. 클라이언트는 정해진 502를 받음 |
edit <item-id> |
편집한 바이트로 통과: --raw=RAW 또는 --raw-file=PATH. 그대로 전달되며($ENV.KEY / $BIND.NAME 확장 없음) Content-Length만 다시 맞춤. --no-update-content-length를 주면 선언한 값을 그대로 보냄(CL 디싱크 프리미티브) |
enable / disable |
라이브 캐치 켜기 / 끄기 |
filter <query> |
조건부 인터셉트 쿼리 설정. ""를 넘기면 해제 |
direction <both|request|response> |
캐치가 붙잡을 구간 선택 (기본값 request) |
inject <flow-id> |
TUI가 아직 중계 중인 WebSocket에 프레임 하나를 밀어 넣음(HTTP/1.1 전용): --direction out|in(기본값 out, 클라이언트 → 서버), --opcode text|binary|ping|pong|close(기본값 text), --data=TEXT 또는 정확한 바이트는 --file=PATH. 그대로 보내며 붙잡힌 메시지보다 먼저 나감. 데이터 프레임은 그 방향의 조각난 메시지가 끝나기를 최대 5초 기다림. 플로우의 호스트에 스코프 게이트(--allow-unscoped로 면제). 프레임 주입 참고 |
list와 get은 --include-sensitive를 주지 않으면 민감한 헤더 값을 가립니다. 쓰기 서브커맨드는 프로젝트 데이터베이스를 거쳐 TUI의 ack를 폴링합니다.
run repeater
캡처한 플로우 하나를 재전송하거나, TUI와 공유되는 Repeater 워크벤치 세션을 관리합니다.
gori run repeater <flow-id> --target https://staging.example.com --http2 --diff
| Option | Description |
|---|---|
--target=URL |
다른 오리진으로 전송. 경로와 쿼리는 유지 |
--path=TARGET |
같은 오리진에서 다른 request-target(경로와 쿼리, 예: /api/v1/items/42?lang=en)으로 전송. 요청 라인의 나머지 전부, 모든 헤더와 본문은 바이트 그대로 유지되며, 값은 쓴 그대로 라인에 실립니다 |
--http2 / --http1 (--no-http2) |
프로토콜 강제. 기본값은 플로우가 캡처된 방식을 따름 |
--sni=HOST |
TLS SNI 오버라이드 |
--tls-preset=NAME |
이번 전송의 ClientHello를 chrome, firefox, safari, curl처럼 구성 |
--slot=NAME |
이 세션 슬롯으로 전송: 그 헤더 오버레이와 $BIND 테이블 |
-k, --insecure-upstream |
업스트림 TLS 검증 생략 |
--timeout=SEC |
작업당 연결 + 유휴 타임아웃 |
--sse-events=N · --sse-time=SEC |
text/event-stream 응답: 이벤트 N개나 스트리밍 SEC초 뒤에 읽기를 멈춥니다(기본 100 / 10, 0은 그 한도를 끕니다). 그때까지의 이벤트가 본문이고, bounded가 어느 한도에서 멈췄는지 알려 줍니다. 이벤트 스트림 참고 |
-H, --header=HEADER |
요청 헤더 덮어쓰기/추가 (반복 가능). 같은 이름을 반복하면 중복 헤더 줄을 보냅니다. 명시한 Content-Length는 그대로 존중되어 CL 불일치 테스트에 쓸 수 있습니다 |
--rm-header=NAME |
해당 이름의 헤더를 모두 삭제 (반복 가능). Content-Length를 지우면 자동 재계산이, Host를 지우면 --target 동기화가 꺼집니다 |
-X, --method=METHOD |
캡처된 메서드를 교체. 요청 라인의 나머지는 바이트 그대로 유지됩니다 |
-d, --data=DATA (--body) |
요청 본문 오버라이드. Content-Length는 새 본문에 맞춰 다시 프레이밍됩니다. 반복하면 &로 이어 붙입니다 |
-b, --cookie=NAME=VALUE |
curl의 -b처럼 캡처된 Cookie 헤더를 교체. 반복하면 하나로 합칩니다. =가 없는 값(curl에서는 쿠키 jar 파일)은 거부되며, -H 'Cookie: …'와 함께 쓴 -b도 거부됩니다 |
--verbatim |
오버라이드를 정확히 그대로 전송: -H/-d/-b/--path/-X에서 토큰을 확장하지 않고, HTTP/2에서 필드 이름을 소문자화하지 않습니다. 캡처된 바이트는 어느 쪽이든 확장되지 않습니다 |
--record-history |
재전송을 History에 새 플로우로도 기록하고 그 id를 출력 |
--save-as-repeater |
요청과 응답을 새 Repeater 세션으로 저장하고 id를 출력 (--format json에서는 saved_repeater_id) |
--apply-rules |
라이브 프록시처럼 프로젝트의 활성 Match & Replace 규칙(요청 쪽)을 먼저 요청에 적용. 기본값은 꺼짐: 직접 전송은 바이트 그대로입니다 |
--keep-request-line |
저장된 요청 라인을 그대로 전송. 절대 형식(GET http://h/p)을 origin 형식으로 고치지 않습니다 |
--grpc-field=PATH=VALUE |
gRPC: 이번 전송에서만 플로우의 단항 메시지에 필드를 설정하거나 추가하고, -PATH로 지웁니다. -H보다 먼저 적용됩니다(반복 가능, repeater send --grpc-field와 같은 스펙) |
--diff |
원본 응답과 비교 |
--allow-unscoped |
프로젝트 스코프 밖으로도 전송. 샌드박스는 매 전송을 여전히 거부합니다. 제외 규칙은 프록시와 마찬가지로 샌드박스가 켜져 있을 때만 직접 작성한 전송을 막습니다 |
--headers-only |
상태 라인과 헤더만 출력. 본문은 크기를 알려주는 한 줄로 대체 |
--max-body=BYTES |
디코드된 응답 본문을 최대 BYTES바이트까지 출력한 뒤, 전체 크기를 알려주는 마커를 붙임 |
--format=FMT |
text (기본값) 또는 json |
--headers-only와 --max-body는 출력되는 것만 바꿀 뿐, 무엇이 전송되거나 저장되는지는 절대 바꾸지 않습니다. 본문은 [body omitted by --headers-only: 149504 bytes]로 대체되거나, 잘린 뒤 [… truncated by --max-body: showing 2048 of 149504 bytes]가 뒤따르므로, 잘린 본문이 짧은 본문처럼 읽히는 일은 없습니다. 크기는 디코드된 본문의 크기(청크 해제, 압축 해제 후)이며, 자르지 않은 덤프가 찍는 것과 같은 값입니다. --format json에서는 body 객체가 size로 전체 디코드 크기를 유지합니다. --max-body는 text/base64에 그 앞부분을 담고 shown_size를 더하며 truncated: true를, --headers-only는 바이트를 빼고 omitted: true를 더합니다. 두 플래그는 함께 쓸 수 없습니다. --headers-only --diff는 두 헤드만 비교하고, --max-body는 --diff와 함께 쓸 수 없습니다. --diff의 비교 대상은 메시지 전체이기 때문입니다. 이 둘은 repeater send, repeater h2, send, show에서 같은 형태로 동작합니다.
repeater list: 저장된 Repeater 세션 목록 (--format text|json).
repeater create: Repeater 세션 생성:
gori run repeater create --target https://api.example.com --request-file req.txt --name "login probe"
gori run repeater create --flow 42 --name "clone of 42"
generate-request | gori run repeater create --target https://api.example.com --request-stdin
pbpaste | gori run repeater create --curl - --name "from devtools"
네 가지 요청 소스는 함께 쓸 수 없습니다. 두 개를 지정하면 플래그 순서로 하나를 고르는 대신
거부됩니다. 또한 빈 요청(빈 파일, --request-raw '', 아무것도 내보내지 않은 파이프)은 전송할 수
없는 세션을 만드는 대신 거부됩니다. --flow는 이 넷에 속하지 않습니다. 출처 역할도 하므로
넷 중 어느 하나와도 함께 쓸 수 있습니다. --curl은 curl 명령 하나를 읽고
(Repeater → Paste cURL 참고), 따로 주지 않으면
--target과 --http2도 그 명령에서 가져옵니다.
--request-stdin은 파이프나 리다이렉트(--request-stdin < req.http)를 읽으며, 터미널은
거부합니다. 터미널은 입력한 바이트를 그대로 되돌려 출력하므로, Cookie와 Authorization을
포함한 원시 요청 전체가 스크롤백에, 그리고 PTY로 캡처한 로그에 남습니다. 이는 이 플래그가
프로세스 목록과 셸 히스토리에서 막으려던 바로 그 노출입니다. 또한 ^D는 EOF가 아니라 대기 중인
줄을 내보내기만 하므로, 끝에 개행이 없는 요청은 ^D가 두 번 필요하고 한 번만 보내는 PTY 자동화는
무한히 대기합니다.
같은 규칙은 플래그로 지정한 모든 stdin 경로에 적용됩니다. issues --notes-stdin,
그리고 stdin을 -로 쓰는 네 가지(sequence --tokens -, authorize --identities=-,
rewriter --response-file=-, 그리고 --…-file 계열 플래그의 -)가 그렇고, 터미널로 해석되는
경로(tty에서의 --request-file /dev/stdin 등)까지 포함됩니다. 워드리스트 경로도 포함됩니다. fuzz -w, mine --wordlist,
discover --wordlist는 /dev/tty(그리고 tty 상태의 /dev/stdin)를 무한 대기 대신
wordlist is a terminal, not a file: …로 거부합니다.
반대로 플래그 없이 gori가 fallback으로 읽는 stdin 경로(fuzz, mine, sequence, decoder,
jwt, cookie, notes)는 해당하지 않습니다. 거기서 터미널은 "소스를 주지 않았다"는 뜻이고, 각
명령이 자체 usage를 출력합니다. 다만 이 경로들도 이제 읽을 수 없는 stdin(cron/systemd가 fd 0을 닫은
경우)을 backtrace가 아니라 한 문장으로 보고합니다. 플래그로 지정한 경로는 이미 그렇게 해 왔습니다.
| Option | Description |
|---|---|
-t, --target=URL |
대상 URL (--flow로 복제하는 경우가 아니면 필수) |
-f, --request-file=FILE |
FILE에서 원시 HTTP 요청을 읽음 (--request-raw / --request-stdin과 함께 쓸 수 없음) |
-r, --request-raw=RAW |
원시 HTTP 요청 문자열 그대로 (--request-file / --request-stdin과 함께 쓸 수 없음) |
--request-stdin |
원시 HTTP 요청을 stdin에서 바이트 그대로 읽음 (--request-file이 파일을 읽는 방식과 동일). 요청을 인자 벡터 밖에 둡니다. 파이프나 리다이렉트가 필요하며 터미널은 거부됩니다 (--request-file / --request-raw과 함께 쓸 수 없음) |
--curl=PATH |
PATH의 curl 명령으로 요청을 만듦(-는 stdin). 따로 주지 않으면 대상과 HTTP/2도 가져오며, 무시한 전송 플래그는 stderr에 밝힘 |
--flow=ID |
캡처한 플로우에서 요청 / 대상 / HTTP/2 복제 |
--name=NAME, --tags=TAGS |
사용자 지정 탭 이름, 그리고 TUI 하위 탭 라벨이 되는 자유 텍스트 태그 |
--http2 / --http1 (--no-http2) |
프로토콜 선택. --http1은 h2로 캡처된 --flow를 덮어씁니다 |
--no-auto-cl, --sni=HOST |
자동 Content-Length 생략, SNI 오버라이드 |
--keep-request-line |
--flow와 함께: 요청 라인을 캡처된 그대로(절대 형식 포함) 저장 |
--ws-keep-key |
WebSocket: 요청 자신의 Sec-WebSocket-Key를 전송. 키가 없거나 짧거나 중복이거나 base64가 아닌 경우를 테스트할 수 있습니다 |
--ws-http-only |
WebSocket: 이 세션을 평범한 HTTP로 저장. 업그레이드를 일반 요청으로 보내고 101을 응답으로 읽습니다 |
--format=FMT |
text(기본값: Repeater session #7 created successfully.) 또는 json: repeater list --format json이 찍는 것과 같은 새 세션(id, tui_index, position, name, target, http2, …)에 websocket, 저장된 ws_messages 개수, 그리고 --flow 시드의 라인이 다시 쓰였을 때의 request_line_rewritten을 더한 형태 |
id=$(gori run repeater create -t https://api.example.com -f req.http --format json | jq .id)
gori run repeater send "$id"
repeater send <repeater-id>: 저장된 세션을 실행합니다. HTTP와 WebSocket 모두 해당됩니다.
gori run repeater send 3 --diff
gori run repeater send 5 --message '{"op":"subscribe"}' --idle-ms 5000
| Option | Description |
|---|---|
--diff |
세션에 마지막으로 저장된 응답과 비교 |
--verbatim |
저장된 바이트를 정확히 그대로 전송: 토큰 확장(프로젝트 env 변수, 세션 바인딩, 제너레이터 모두. $ENV.KEY, $BIND.NAME, $GEN.UUID가 와이어에 리터럴로 나감), 단독 LF 승격, Content-Length 재계산, HTTP/2→1.1 버전 보정, h2 필드명 소문자화를 모두 하지 않음. 시길 문법을 아예 해석하지 않으므로 $$ENV.KEY 이스케이프도 소비되지 않으니 $ENV.KEY로 쓰세요. 저장된 §…§ 마커도 거부되지 않고 리터럴 바이트로 나갑니다. 활성 --slot의 헤더 오버레이는 계속 적용됩니다. 어떤 바이트가 아니라 누구로서 보낼지에 답하는 옵션이기 때문입니다. 저장된 헤더 그대로 보내려면 --slot을 주지 마세요 |
--reframe-grpc |
HTTP/2 전용: 실제로 전송되는 본문에 맞춰 gRPC 5바이트 길이 접두사를 다시 계산합니다(길이가 바뀐 단항 메시지용). 기본값은 꺼짐입니다. 페이로드와 어긋나는 접두사는 표준적인 파서 테스트이므로 쓴 그대로 나갑니다 |
--grpc-field=PATH=VALUE |
gRPC: 이번 전송에서만 세션의 단항 메시지에 필드를 설정하거나 추가합니다(반복 가능, 순서대로 적용). PATH는 .proto가 rpc를 해석하면 선언된 이름(role=ROLE_ADMIN, profile.age=30), 아니면 필드 번호(4.1=30)입니다. 메시지에 없고 선언되지도 않은 필드는 타입을 붙입니다(7:varint=1; varint, i32, i64, string, hex로 쓰는 bytes, 또는 .proto 스칼라). -PATH만 쓰면 그 필드의 모든 등장을 지우고(-profile.role, -7), 메시지에 없는 필드는 거부합니다. 압축된 메시지는 다시 압축되고 길이 접두사는 다시 계산됩니다 |
--message=TEXT |
WebSocket: 보낼 텍스트 메시지 (반복 가능; 세션에 저장된 메시지를 대체) |
--message-frame=SPEC |
WebSocket: 형태를 명시한 프레임 하나. 쉼표로 구분한 key=value: opcode=text|bin|cont|close|ping|pong|<0-15>, fin, rsv, mask, mask_key, len, 그리고 hex=/b64=/text= 중 하나 |
--idle-ms=N |
WebSocket: 첫 수신 프레임 이후 서버 침묵 타임아웃 (100-60000, 기본값 3000) |
--http |
WebSocket: 이번 전송에 한해 핸드셰이크를 일반 HTTP 요청으로 전송. 바이트를 고치는 게 아니라 엔진을 고르는 것입니다 |
--record-history |
나가는 요청 + 응답을 History에 캡처 플로우로 기록하고 flow id를 출력(텍스트 모드는 STDERR, --format json은 recorded_flow_id)(HTTP 전용; Repeater 전송은 기본적으로 플로우를 남기지 않음) |
--path=TARGET |
이번 전송 한 번만, 저장된 것 대신 이 request-target(경로와 쿼리)을 전송 |
-H, --header=HEADER · -b, --cookie=NAME=VALUE |
이번 전송 한 번만 헤더를 덮어쓰거나 추가하고, 또는 Cookie 헤더를 교체(repeater <flow-id>와 같음). 세션은 자신의 헤더를 유지합니다. --verbatim이 아니면 요청의 나머지와 함께 확장됩니다 |
--apply-rules |
repeater <flow-id>와 동일 |
--slot=NAME, --tls-preset=NAME |
repeater <flow-id>와 동일 |
--ws-keep-key, -k, --timeout, --sse-events, --sse-time, --allow-unscoped, --headers-only, --max-body, --format |
위와 동일 (--headers-only / --max-body는 HTTP 전용입니다: WebSocket 교환은 트랜스크립트를 출력합니다) |
--path는 경로만 다른 엔드포인트들에 걸쳐 세션 하나의 요청을 스윕합니다. 경로마다 세션을 만들 필요가 없습니다.
for n in $(seq 1 38); do
gori run repeater send 1 --path "/api/v1/items/$n" --headers-only
done
이 옵션은 이번 전송을 위해 저장된 요청의 사본을 고칩니다(-H와 -b도 마찬가지입니다). 세션은 자신의 요청과 마지막 응답을 그대로 유지합니다. 다른 대상의 응답을 그 옆에 저장하면 TUI 탭이 자신이 가지고 있지 않은 요청에 대한 응답을 보여주게 되고, 다음 --diff가 엉뚱한 엔드포인트와 비교하게 되기 때문입니다. 그래서 --format json에는 response_saved가 빠지고, path 필드에 전송된 대상이 담기며, text 상태 줄은 그 값으로 끝납니다(→ 200 in 218.4ms · /api/v1/items/42). --record-history는 실제로 나간 대로(새 경로 포함) 요청을 여전히 기록하며, --diff는 세션에 저장된 응답과 계속 비교합니다.
오리진에 닿은 전송은 그 뒤의 쓰기가 실패해도 0으로 종료하므로, 셸이 같은 요청을 다시 보내지 않습니다. 어느 쓰기가 실패했는지는 --format json이 알려 줍니다. response_saved(세션에 응답을 썼으면 나타나고, 프로젝트가 쓰기를 거부했거나 전송 중에 세션이 삭제됐으면 false와 함께 response_save_error가 붙습니다. 이때 다음 --diff는 이전 응답과 비교합니다)와, --record-history를 주었다면 history_saved가 나오며, 실패하면 history_error가 붙고 recorded_flow_id는 빠집니다. 텍스트 모드는 같은 문장을 STDERR에 찍습니다.
repeater ws <repeater-id>: 저장된 repeater로 지속 HTTP/1.1 WebSocket 세션을 엽니다(TUI의 Live session 및 MCP의 ws_session_open / ws_session_send / get_job / stop_job에 대응하는 gori run 표면). stdin에서 읽은 각 줄은 TEXT 메시지로 프레이밍해 보내고(--binary를 주면 헥스로 디코드한 BINARY 메시지), 보낸 프레임과 받은 프레임 모두 도착하는 즉시 stdout에 찍습니다. stdin EOF(--for가 없을 때), --for=DURATION, 또는 Ctrl-C가 오면 CLOSE 프레임을 보내고 깨끗하게 종료합니다. Windows에서는 파이프로 연결한 stdin 읽기가 블로킹되므로 서버 프레임은 다음 줄이 들어올 때 찍힙니다(콘솔 stdin은 괜찮습니다).
printf '{"op":"ping"}\n' | gori run repeater ws 5 --engine-io --for 30s
gori run repeater ws 5 --for 30s --format json
| Option | Description |
|---|---|
--auto-reply=MATCH=REPLY |
정확히 일치하는 수신 TEXT 페이로드에 TEXT 프레임으로 자동 응답 (반복 가능, 예: --auto-reply=PING=PONG) |
--engine-io |
Engine.IO / Socket.IO '2' 하트비트 핑에 '3'으로 자동 응답 (--auto-reply=2=3 축약형) |
--for=DURATION |
stdin이 열려 있어도 DURATION(30s, 5m, 1h)이 지나면 세션을 닫음 |
--binary |
각 stdin 줄을 헥스 바이트(선택적 0x 접두사)로 읽어 BINARY(opcode 2) 프레임으로 전송 |
--verbatim, --slot=NAME, --ws-keep-key, --tls-preset=NAME, -k, --allow-unscoped, --format=text|json (--json) |
repeater send와 동일. --format json은 프레임마다 MCP get_job 행 모양(seq, direction, opcode, origin, type, frame, payload / payload_base64, auto_reply, close_code)의 JSON 객체 한 줄을 스트리밍합니다 |
repeater move <repeater-id>: 워크벤치 스트립의 순서를 바꿉니다. --to N은 repeater list가 출력하는 1부터 시작하는 탭 번호이고, --up / --down은 한 칸씩 옮깁니다. 셋 중 하나만 주세요. --to와 방향을 함께 주면 임의로 해석하지 않고 거절하며, 1-<개수> 범위를 벗어난 --to도 잘라 맞추지 않고 거절합니다. 명령이 지목하지 않은 자리에 세션이 놓이는 일이 없도록 하기 위해서입니다. --format json은 from_index / to_index / moved를 보고합니다.
gori run repeater move 5 --to 1 # 첫 번째 탭으로
gori run repeater move 5 --down
repeater delete <repeater-id> [<repeater-id>…] --yes: 저장된 세션 하나 이상을 닫고 스트립의 번호를 다시 매깁니다. --yes는 필수이며, 첫 삭제 전에 모든 id를 검사합니다. 하나라도 모르는 id가 있으면 호출 전체를 거절하므로, 오타 하나로 워크벤치가 절반만 비는 일은 없습니다. 각 줄은 그 세션이 가지고 있던 탭 번호를 말합니다(무엇이든 밀려나기 전에 한 번만 읽습니다). --format json은 deleted(was_tui_index 포함), failed, remaining을 반환합니다. 지우지 못한 세션이 있으면 종료 코드가 0이 아닙니다.
repeater race <repeater-id> <repeater-id> [<repeater-id>…]: 저장된 세션 여러 개를 하나의 동기화된 레이스로 발사해, 서로 다른 N개의 요청이 좁은 한 순간에 서버에 닿게 합니다(서로 다른 엔드포인트에 걸친 TOCTOU). HTTP/1.1에서는 요청마다 연결을 따로 열고 모든 요청의 마지막 바이트를 함께 풀어 주며, HTTP/2에서는 한 연결을 공유하는 single-packet 공격으로 보냅니다. 모든 세션은 하나의 오리진으로 해소되고 같은 전송을 써야 하며, 아니면 레이스를 거부합니다. --http2 / --http1은 세션에 저장된 설정을 덮어쓰고, --max-requests=N은 N보다 큰 묶음을 나누지 않고 거부합니다. --verbatim, --slot=NAME, --reframe-grpc, --tls-preset=NAME, -k, --timeout, --allow-unscoped, --format text|json은 repeater send와 같습니다. 요청 하나의 복사본 여러 개로 레이스하려면 fuzz --race=N을 쓰세요.
gori run repeater race 3 4 --http2
repeater timing <idA> <idB>: 정확히 두 개의 저장된 세션을 차등 타이밍으로 분석합니다. 한 번 보내서는 보이지 않을 만큼 작은 차이를 위한 것입니다. 버리는 --warmup=N쌍(기본 3, 0이면 첫 쌍부터 측정)을 보낸 뒤 이 쌍을 --count=N번(1-500, 기본 30) 보냅니다. 각 쌍은 repeater race처럼 함께 풀어 주거나(HTTP/2 single-packet, HTTP/1.1 last-byte sync), --interleaved를 주면 순서를 번갈아 가며 하나씩 차례로 보냅니다. 판정은 지연 시간 한 번이 아니라 응답 순서에서 나옵니다. A가 B보다 늦게 도착한 쌍이 몇 개인지를 양측 부호 검정으로 봅니다. p < 0.01일 때만 a_slower나 b_slower이고, 그렇지 않으면 no_difference, 쓸 수 있는 쌍이 20개 미만이면 inconclusive입니다(어느 한쪽이라도 실패한 쌍은 버립니다). 변형마다 최솟값, 사분위수, 최댓값이 함께 찍힙니다. 두 세션은 하나의 오리진과 연결 모양을 공유해야 하며, HTTP/1.1과 HTTP/2가 섞인 쌍에는 --http1이나 --http2가 필요합니다. --verbatim, --slot=NAME, --reframe-grpc, --tls-preset=NAME, -k, --timeout, --allow-unscoped, --format text|json은 repeater send와 같고, JSON은 MCP timing_requests가 돌려주는 객체와 같습니다. 쓸 수 있는 쌍이 하나도 없으면 1로 끝납니다.
gori run repeater timing 3 4 --count 100
repeater minimize <repeater-id>: 응답이 그대로 재현되는 최소 형태까지 요청을 줄입니다. --apply는 결과를 세션에 다시 씁니다. --verbatim은 저장된 바이트를 그대로 보내며, 이때 본문 파라미터는 프레이밍을 정직하게 유지할 수 없어 후보에서 빠집니다. --slot=NAME은 그 세션 슬롯으로 보냅니다. -k/--insecure, --allow-unscoped, --format은 위와 같습니다.
repeater h2: 순서가 있는 HPACK 필드 목록으로 필드 단위 HTTP/2 요청을 보냅니다. 중복되거나 순서가 뒤바뀐 의사 헤더를 스크립트로 만들 수 있습니다.
gori run repeater h2 --target https://api.example.com --fields fields.json
--fields=FILE은 [[name, value], …] 배열이거나 {"fields": [[name, value], …], "body": "…"} 형태의 JSON 파일입니다(바이너리는 body_base64). 목록의 어떤 것도 정규화하지 않습니다. 앞의 콜론, 앞 공백이 붙은 값, 대문자 이름이 곧 페이로드입니다. --target은 접속할 오리진을 정하므로, :authority와 :scheme 필드는 의도적으로 그와 어긋나게 둘 수 있습니다. -k/--insecure-upstream, --timeout=SEC, --sse-events/--sse-time, --allow-unscoped, --tls-preset=NAME, --headers-only, --max-body, --format text|json은 repeater send와 같습니다.
run send
요청 하나를 보내고 응답을 출력합니다. 같은 코드로 만들어진 MCP send_request{url}의 헤드리스 형태입니다. 다른 모든 gori 전송과 마찬가지로 프로젝트의 업스트림 프록시, 호스트 오버라이드, 스코프, 샌드박스를 거쳐 나가며, --record-history나 --save-as-repeater를 주지 않으면 아무것도 남기지 않습니다.
gori run send https://api.example.com/v1/items/42 -H 'Accept: application/json' -b 'sid=abc'
gori run send --url https://api.example.com/v1/items -d '{"name":"x"}' -H 'Content-Type: application/json' --record-history
gori run send --url https://api.example.com --request-file req.http --headers-only
-X, -H, -d, -b는 curl에서와 같은 뜻입니다. -d는 본문이며(-X나 -H가 달리 정하지 않으면 요청을 폼 Content-Type의 POST로 만듭니다), -b는 쿠키입니다. #1383 전까지는 여기서 -b가 본문이었으므로, =가 없는 -b 값(예전의 -b '{"a":1}')은 거부하고 -d를 쓰라고 안내합니다.
| Option | Description |
|---|---|
--url=URL (또는 URL을 유일한 인자로) |
절대 http:// / https:// URL. 경로와 쿼리가 request-target이 됩니다. 원시 요청과 함께 쓰면 접속할 곳만 정합니다 |
-X, --method=METHOD |
HTTP 메서드 (기본값 GET, 본문이 있으면 curl처럼 POST) |
-H, --header=HEADER |
Name: value, 반복 가능, 쓴 순서대로 전송. Host와 Content-Length는 빠뜨렸을 때만 추가됩니다. 두 줄로 쪼개질 헤더나 토큰이 아닌 이름은 거부됩니다. 잘못된 바이트를 보내려면 원시 요청을 쓰세요 |
-d, --data=DATA (--body) |
curl의 -d처럼 요청 본문: 반복하면 &로 이어 붙이고, -H가 지정하지 않으면 Content-Type: application/x-www-form-urlencoded를 추가합니다. $ENV.KEY 토큰이 확장됩니다 |
--body-file=FILE |
요청 본문을 바이트 그대로 읽음, 절대 확장하지 않음(curl의 --data-binary @FILE: 메서드와 Content-Type 기본값은 -d와 같음) |
-b, --cookie=NAME=VALUE |
curl의 -b처럼 쿠키 하나: 반복하면 하나의 Cookie 헤더로 합칩니다(a=1;b=2, curl의 결합 방식). =가 없는 값은 거부되며, -H 'Cookie: …'와 함께 쓴 -b도 거부됩니다 |
-f, --request-file=FILE · -r, --request-raw=RAW · --request-stdin |
요청을 조립하는 대신 이 원시 HTTP 요청을 전송. -X/-H/-d/-b/--body-file과 함께 쓰면 거부됩니다. 그렇지 않으면 그 값들이 조용히 버려지기 때문입니다. 헤드의 단독 LF는 --verbatim이 아닌 한 CRLF로 승격됩니다 |
--verbatim |
-H, -d, -b 또는 원시 요청에서 토큰을 확장하지 않고, 단독 LF도 승격하지 않으며, -X를 대문자로 바꾸지 않고, HTTP/2에서 필드 이름을 소문자화하지 않습니다. URL은 접속할 곳을 정하는 값이므로 여전히 확장됩니다 |
--apply-rules |
MCP send_request{apply_rules}처럼 전송 전에 프로젝트의 활성 Match & Replace 규칙(요청 쪽)을 요청에 적용 |
--http2, --sni=HOST, --tls-preset=NAME, -k, --timeout=SEC, --sse-events, --sse-time, --slot=NAME, --allow-unscoped |
repeater send와 같음 |
--record-history |
요청과 응답을 History에 플로우로도 기록(source: repeater, source_surface: cli)하고 id를 출력. repeater send와 마찬가지로 기본값은 꺼짐 |
--save-as-repeater |
요청과 응답을 새 Repeater 세션으로 저장하고 id 출력 (--format json에서는 saved_repeater_id) |
--headers-only, --max-body=BYTES, --format=FMT, --grpc-field=PATH=VALUE |
repeater send와 같음 |
종료 코드는 HTTP 판정이 아니라 교환 성공 여부를 알립니다. 404나 500도 0으로 끝나므로(JSON에서는 ok: true) status를 확인하세요. 1(ok: false)은 응답이 없었거나 gori가 응답을 프레이밍하거나 끝까지 읽지 못했다는 뜻입니다.
WebSocket 핸드셰이크인 요청은 평범한 요청으로 나가고 그 101이 응답이 되며, 명령은 이를 STDERR에 알립니다. 프레임을 주고받는 교환에는 세션이 필요합니다: repeater create 다음 repeater send를 쓰세요.
send, repeater <flow-id>, repeater send의 --format json은 원시 head 옆에 MCP send_request의 오류 계약을 싣습니다. 실패한 전송에는 error_kind(connect, timeout, protocol, no_response, truncated_request, budget, busy, other), error_code, retryable, delivered가 붙으므로, 스크립트는 error 문장을 매칭하지 않고도 거부된 연결과 타임아웃을 구분할 수 있습니다. 응답에는 reason, http_version, 파싱된 headers([{"name","value"}], 와이어 순서, UTF-8이 아닌 값에는 value_lossy/value_base64)가 붙습니다. match_replace_applied: true는 --apply-rules가 요청을 바꿨다는 뜻이고, bounded(events 또는 time)는 이벤트 스트림이 --sse-events / --sse-time 한도에서 멈췄다는 뜻입니다.
run fuzz
소스: --flow=ID, --repeater=ID, --request=FILE, 또는 stdin. 위치: §…§ 마커, --auto, --mark=TOKEN, 또는 스키마가 아는 gRPC 필드용 --field=SPEC.
| Group | Options |
|---|---|
| Source | --flow=ID(캡처 플로우), --repeater=ID(저장된 리피터 세션. WebSocket 세션이면 핸드셰이크와 저장된 프레임을 함께 시드), --request=FILE, 또는 bare <flow-id> / stdin |
| Transport | --target=URL (--request/stdin에 필수), --http2, --sni=HOST, --tls-preset=NAME(실행 전체에 쓰는 TLS 지문 하나), -k/--insecure-upstream |
| Mode | --mode= sniper (기본값), batteringram, pitchfork, clusterbomb. 앞의 둘은 페이로드 세트를 하나만, 뒤의 둘은 표시된 위치마다 하나씩 사용합니다. 모드가 쓰지 않을 세트는 실행 전에 알려 줍니다 |
| gRPC fields | --field=SPEC(반복 가능)는 단항 gRPC 요청의 옥텟 대신 스키마가 아는 필드를 스윕합니다. SPEC은 필드 이름, 중첩 메시지 경로(profile.age), 필드 번호, 반복 필드의 특정 occurrence(name[i])이며, name¦chain은 선언된 타입이 바이트로 인코딩하기 전에 Decoder 체인을 돌립니다. 필드는 캡처된 메시지에 이미 있어야 하며(gori는 기존 occurrence를 바꿀 뿐 새로 추가하지 않습니다), bytes 필드의 페이로드는 hex(de ad be ef)로 읽습니다. 페이로드는 필드 선언을 거쳐 바이트가 되고(-3은 int32·sint32·bool·enum마다 다른 옥텟입니다), 메시지의 나머지 바이트는 캡처에서 그대로 복사되며, 5바이트 길이 접두사는 다시 계산됩니다. 해당 rpc를 해석할 descriptor set이 필요합니다(gori run grpc schema). 필드 위치는 템플릿 자신의 §…§ 위치 뒤에 붙으므로 --mode와 페이로드 세트의 의미는 그대로입니다. 스키마가 선언하지 않은 필드, 선언과 와이어 타입이 충돌하는 필드, 선언된 타입이 담을 수 없는 페이로드는 모두 첫 요청 전에 거부됩니다 |
| Payloads | -w/--wordlist(파일, 또는 저장한 목록의 이름), --preset=NAME[:FILE] (내장: sqli, xss, traversal, format-string, bad-strings, command-injection, cache-delimiters), --payloads=LIST, --numbers=FROM-TO[:STEP], --null=N, --brute=CHARSET:MIN-MAX |
| Project data | --payload-from='<QL> <projection>'(반복 가능)은 프로젝트가 이미 캡처한 데이터에서 읽은 페이로드 세트입니다: param-names, param-values, path-segments, js-endpoints, extracted. 프로젝트를 읽기만 하고 아무것도 보내지 않으며, 직접 지정한 프로젝트(--flow, --repeater, --project, --db)가 필요합니다. --payload-from-sensitive는 자격 증명 값을 읽게 하고(기본은 제외, extracted는 필수), --payload-from-locations=LIST는 headers/cookies를 더하며, --payload-from-max-flows=N(2000, 최대 20000)과 --payload-from-max-values=N(10000, 최대 100000)은 한도를 올립니다. 모두 모든 --payload-from에 적용됩니다. 각 소스가 무엇을 읽었는지는 stderr에 나옵니다. 프로젝트에서 가져오는 페이로드 참고 |
| Encoding | 쿼리 문자열이나 form-urlencoded 본문 값에 치환되는 페이로드는 기본으로 URL 인코딩됩니다. 경로 세그먼트·JSON/원시 본문·헤더·쿠키는 그대로 나갑니다. --no-encode는 쿼리/폼 위치도 원시로 보냅니다. 페이로드 자체가 이미 퍼센트 이스케이프인 경우에 쓰세요(%00이 %2500으로 나가므로, origin의 디코더 자체를 겨눈 %00 / %c0%af / %2e%2e%2f 탐침은 그냥 텍스트로 도착합니다). --encode를 명시하면 기본 인코딩을 대체하며, 그 파이프라인이 모든 위치에 적용됩니다. --prefix / --suffix / --case / --hash / --regex-replace는 대체하지 않습니다: 페이로드가 무엇인지를 말할 뿐 와이어가 그것을 어떻게 적는지는 말하지 않으므로, 그 출력도 쿼리/폼 위치에서는 인코딩됩니다 |
| Processors | --prefix, --suffix, --encode (url|urlall|base64|hex), --case (upper|lower), --hash (md5|sha1|sha256), --regex-replace=/pat/rep/ |
| Rate | --concurrency (20), --rate=RPS, --throttle=MS, --timeout=SEC, --retries=N, --max-requests=N (총 요청 상한. 재시도와 리다이렉트 홉도 포함), --follow-redirects, --no-keep-alive, --sse-events=N / --sse-time=SEC (이벤트 스트림 응답은 기본적으로 이벤트 50개 / 5초에서 멈춥니다. 0은 그 한도를 끕니다) |
| Macro | --macro=STEPS는 후보 앞에서 저장된 Repeater 세션(repeater list의 id 또는 탭 이름, 쉼표 구분, 반복 가능)을 다시 실행해서, 회전하는 CSRF 토큰이나 nonce가 후보가 $BIND.NAME을 풀 때 항상 새 값이 되게 합니다. --macro-every=request|N|off(기본 request: 후보마다 새 값을 받고 그래서 스윕은 후보를 한 번에 하나씩 보냅니다. N은 값 하나를 N개의 후보가 공유하며 최대 N개까지 동시에 나갑니다). --macro-expect=NAME(반복 가능)은 그 바인딩을 다시 바인딩하지 못하면 매크로를 실패로 처리합니다. --macro-on-failure=skip|stop(기본 skip: 후보를 보내지 않고, 연속 세 번 실패하면 실행을 끝냅니다). 단계는 활성 --slot으로 실행되고 History에 src:macro로 기록되며 --max-requests에 합산되고 --rate에 묶입니다. 이름을 지정한 프로젝트가 필요합니다. --race=N과 함께 쓰면 --macro-every는 N 이상이어야 합니다. 매크로로 회전하는 토큰 다루기 참고 |
| Race | --race=N은 연결 N개를 열어 각 요청을 마지막 1바이트 직전까지 보낸 뒤 동시에 풀어 놓습니다(last-byte sync). 레이스 그룹은 요청 하나의 복사본 N개이므로 --mode와 모든 페이로드/위치 플래그는 무시됩니다. --race-warmup=FILE은 레이스 요청을 붙잡기 전에 각 연결에서 이 원시 요청을 먼저 보내고 읽습니다 |
| Framing | --verbatim은 템플릿의 Content-Length를 쓰인 그대로 전송합니다. 페이로드 치환 후에도 재계산하지 않고, 길이 선언이 없는 본문에 추가하지도 않습니다 (CL / CL-TE 디싱크 페이로드용. Content-Length도 청크 Transfer-Encoding도 없는 본문은 오리진이 길이 0으로 읽으므로 경고합니다). --reframe-grpc는 페이로드가 단항 gRPC 메시지에 삽입된 뒤 5바이트 길이 접두사를 다시 계산합니다(기본값은 꺼짐: 오래된 접두사는 고치지 않고 보고만 합니다) |
| WebSocket | Upgrade: websocket 핸드셰이크를 선언한 템플릿은 프레임 교환으로 스윕합니다. 페이로드 하나가 세션 하나(완전한 RFC 6455 세션)입니다. --message=TEXT / --message-frame=SPEC로 송신 프레임을 작성하며(반복 가능, 지정한 순서대로; SPEC은 gori run repeater send와 같은 문법: opcode=, fin=, rsv=, mask=, mask_key=, len=, 그리고 hex=|b64=|text= 중 하나), --flow/--repeater 시드가 가져온 프레임을 대체합니다. §…§ 위치는 프레임 안에 표시합니다. 핸드셰이크도 위치 공간이며 둘은 한 번의 실행에서 함께 스윕됩니다. --idle-ms=N은 세션별 침묵 대기(100-60000, 기본 3000), --ws-keep-key는 템플릿 자체의 Sec-WebSocket-Key를 보냅니다. --ws-http-only는 핸드셰이크를 평범한 요청으로 스윕합니다. 업그레이드가 성공하면 모든 행이 101이므로 행마다 ws_close_code와 ws_frames_in이 붙습니다. 프레임 경로에서 --race, --http2, --record-history는 거부됩니다(셋 다 --ws-http-only 아래에서는 동작하며, 그쪽은 평범한 HTTP 스윕이라 History에도 기록됩니다). --follow-redirects / --timeout / --ac는 무의미하여 한 번 알려 줍니다. 송신 프레임이 없는 WebSocket 시드는 빈 프레임 세션이 아니라 평범한 HTTP로 스윕합니다 |
| Matchers | --mc/--fc status, --mg/--fg grpc-status 트레일러(HTTP/2 트레일러, grpc-web은 본문 안의 트레일러 프레임)의 gRPC 상태 (7, >0, 1-16), --ms/--fs size, --mw/--fw words, --ml/--fl lines, --mt/--ft 왕복 시간(ms, --mt '>=5000'. 시간 기반 블라인드 페이로드가 유일하게 움직이는 차원이며, 타임아웃된 전송도 여기서는 매치로 셉니다), --mr/--fr body regex, --mh/--fh 응답 HEAD의 대소문자 무시 부분 문자열 (--mh 'x-powered-by: php'. body regex는 헤더를 보지 않습니다), --extract=REGEX, --extractor=SPEC 행마다 이름 붙은 값(반복 가능, 최대 8개. history --column과 같은 [LABEL=]kind:selector, 응답 쪽만. 텍스트는 LABEL=⟦값⟧, JSON은 extracts), --ac auto-calibrate |
| Stop | --stop-after-matches=N은 매처가 N번 맞으면 실행을 끝냅니다(1 = 첫 히트). --stop-on=DIM:SPEC(반복 가능)은 별도 조건이 성립하면 끝냅니다. DIM은 status|grpc|size|words|lines|time|header|regex, !DIM은 부정입니다(--stop-on '!regex:Invalid password'는 본문에 그 문자열이 더 이상 없을 때 멈춤). 어느 쪽이든 실행은 stopped가 아니라 condition_met 상태로 끝나고, CLI는 0으로 종료합니다. 조건 충족이 목표였기 때문입니다. 진행 중이던 요청은 ^C처럼 마무리됩니다. 완료 줄은 정지를 일으킨 결과를 밝히고, fuzz save는 그것을 기록합니다: fuzz list는 stop:#N, fuzz show는 stopped on result N을 보여 주며, 두 명령의 JSON에는 stop_index가 담깁니다(기록되지 않았으면 null). --race와는 함께 쓸 수 없습니다 |
| Keep | --keep=all(기본) | interesting: fuzz save가 어떤 결과 행을 저장할지 정합니다. interesting은 매치된 행과 관측된 사실을 담은 행(오류, 재전송, 잘린 캡처, 정지 행)만 남겨, 대규모 스윕이 요청마다 아카이브 행 하나씩 늘리지 않게 합니다. 실행의 sent/matched/errors 카운트는 전체 기준으로 유지되고 각 행은 실제 페이로드 인덱스를 지킵니다. fuzz list/show가 keep:interesting과 몇 개 중 몇 개를 남겼는지 표시합니다 |
| Session bindings | --bind-from=FLOW-ID는 캡처된 그 플로우를 먼저 재생해, 응답이 남은 실행 동안 쓸 $BIND.NAME 바인딩을 채우게 합니다 |
| Session slot | --slot=NAME은 이 세션 슬롯으로 전송합니다: 그 슬롯의 헤더 오버레이, 그리고 $BIND.NAME을 위한 그 슬롯의 바인딩 테이블. --bind-from보다 먼저 적용됩니다 |
| Scope | --allow-unscoped는 프로젝트 스코프 밖으로도 전송합니다. 샌드박스와 명시적 제외 규칙은 매 전송을 여전히 거부합니다 |
| Output | --format (text|json|jsonl), --force, --fail-if-no-matches (매칭이 없으면 종료 코드 3) |
| Evidence | --record-history=none|matched|all은 전송한 각 요청 + 응답을 History에 플로우로 기록합니다(기본 none; matched는 매칭된 행만, all은 매 전송, 5000개 상한). gori run history / get_flow로 다시 읽습니다 |
영구 퍼즈 실행
gori run fuzz …는 일회성입니다. 같은 소스/옵션 앞에 save 동사를 붙이면 모든 결과를(완성된 렌더링 요청, 최종 와이어 요청, 응답 헤드, 응답 본문까지) 영구 저장합니다:
gori run fuzz save 42 --auto --preset sqli
gori run fuzz save --request request.txt --target https://api.example.com --project acme --payloads a,b
파일/stdin에서 저장할 때는 --project나 --db가 필요합니다. 프로젝트를 지정하지 않은 스윕을 가장 최근 프로젝트에 조용히 써넣지 않기 때문입니다. --record-history는 이와 별개로 남습니다. 저장된 결과 집합이 아니라 History 플로우를 제어합니다.
| 명령 | 설명 |
|---|---|
fuzz list |
저장된 실행을 최신순으로 나열합니다. --session=ID는 TUI Fuzzer 세션 하나로 좁히고, --offset, --limit(기본 50, 최대 1000), --format text|json이 페이지와 형식을 정합니다 |
fuzz show RUN_ID |
실행 하나의 요약과, 보관된 BLOB을 읽지 않는 스칼라 전용 결과 지표 페이지를 보여 줍니다. --offset, --limit(기본 200, 최대 5000), --matched-only, --format text|json|jsonl을 지원하며, 진행 중인 실행에 --format json을 주면 보관 행 전체를 버퍼링하지 않고 유효한 배열 하나를 스트리밍합니다 |
fuzz show RUN_ID --clusters |
실행의 결과를 응답 모양으로 묶습니다. 서로 다른 응답마다 한 줄씩(페이로드 반사, 숫자, id, 타임스탬프, 매번 바뀌는 헤더는 정규화해 무시) id, 결과 개수, 상태 또는 오류 분류, 길이/단어 범위, 매처 히트 수, 대표 결과를 보여 줍니다. --order rare|common|first(기본 rare, 작은 묶음부터), --matched-only는 히트가 있는 묶음만 남기고, --limit/--offset은 묶음 단위로 페이지를 나누며, --format json|jsonl은 MCP get_fuzz_run{clusters}와 같은 필드를 내보냅니다. 모양이 기록되기 전에 저장된 실행은 상태/오류/단어/줄 수로 묶고 그 묶음에 ≈(approximate)를 표시합니다 |
fuzz show RUN_ID --cluster ID |
묶음 하나(--clusters가 준 id)의 결과를 일반 fuzz show 행 형식으로 페이지 단위로 보여 줍니다 |
fuzz show RUN_ID RESULT_INDEX |
보관된 요청/와이어/응답 바이트를 포함해 결과 하나를 정확히 보여 줍니다. 텍스트 출력은 터미널 제어 시퀀스를 무력화하고, JSON은 유효하지 않은 UTF-8을 base64로 내보냅니다. 상세 보기는 text 또는 json을 지원하며, 현재 형식 이전의 불완전한 스냅숏은 실행 메타데이터에 legacy로 표시됩니다 |
fuzz delete RUN_ID --yes |
종료된 실행 하나와 저장된 결과 행 전부를 삭제합니다. 저장이 진행 중이면 거부하며, --force-stale은 죽은 기록자가 남긴 running/saving 행을 지웁니다. 다른 gori가 저장 중일 때는 절대 쓰면 안 됩니다 |
run mine
gori run mine <flow-id> --locations query,headers --wordlist params.txt
| Option | Description |
|---|---|
--flow, --request, --target, --sni, --http2, -k |
요청 소스와 트랜스포트 |
--allow-unscoped |
대상이 프로젝트 스코프 밖이어도 전송(샌드박스와 명시적 제외 규칙은 그대로 적용) |
--locations=LIST |
query, form, multipart, json, headers, cookies. 기본값은 query이고, 요청 본문이 폼이나 JSON이면 form이나 json이 더해집니다. multipart, headers, cookies는 명시해야만 실행됩니다 |
--wordlist, --bucket=N |
후보 이름(파일, 또는 저장한 wordlist의 이름)과 버킷 크기 |
--name=NAME |
워드리스트보다 먼저 시험할 이름(여러 번 지정하거나 쉼표로 구분). 예: sitemap params가 다른 엔드포인트에서 찾은 이름 |
--payload-from='<QL> param-names' |
프로젝트의 캡처 데이터에서 읽은 후보 이름. --name 다음, 내장 목록과 --wordlist보다 앞서 시험합니다(반복 가능, --flow/--project/--db 필요). --payload-from-sensitive, --payload-from-locations, --payload-from-max-flows, --payload-from-max-values는 fuzz와 똑같이 적용됩니다 |
--concurrency (10), --rate, --throttle, --timeout, --retries (1), --max-requests=N |
속도 제어 |
--sse-events=N · --sse-time=SEC |
이벤트 스트림 응답은 이벤트 N개나 SEC초 뒤에 멈춥니다(기본 50 / 5, 0은 그 한도를 끕니다) |
--no-keep-alive |
연결 재사용 대신 프로브마다 새로 연결 |
--hook=ARGV |
조립된 각 요청을 보내기 전에 외부 명령(argv, 셸 없음)으로 변환합니다. 서명 / HMAC이 붙는 API용. 프로세스 훅 참고 |
--macro=STEPS, --macro-every, --macro-expect, --macro-on-failure |
fuzz와 같은 요청 시점 매크로를, 마이닝이 보내는 모든 요청 앞에서(기준선 포함) 실행합니다. 회전하는 CSRF 토큰이나 nonce에 대한 기본 해법이며, 값을 명령이 계산해야 할 때는 --hook이 같은 일을 합니다. 이름을 지정한 프로젝트가 필요합니다. 매크로로 회전하는 토큰 다루기 참고 |
--bind-from=FLOW-ID |
캡처된 그 플로우를 먼저 재생해, 응답이 남은 실행 동안 쓸 $BIND.NAME 세션 바인딩을 채우게 합니다 |
--slot=NAME |
이 세션 슬롯으로 전송합니다: 그 슬롯의 헤더 오버레이, 그리고 $BIND.NAME을 위한 그 슬롯의 바인딩 테이블. --bind-from보다 먼저 적용되므로 시드가 채우는 슬롯이 곧 실행이 나가는 슬롯입니다 |
--format |
text, json, 또는 jsonl |
기본적으로 연결을 재사용합니다. 마이닝 한 번이 프로브마다가 아니라 워커마다 TCP(https라면 TLS) 핸드셰이크를 한 번씩만 치릅니다. 실행이 끝날 때 나오는 connections · N dialed · M reused 줄에서 대상이 이를 지켰는지 확인할 수 있습니다. 대상이 연결 단위로 동작한다면 --no-keep-alive로 끕니다.
run sequence
토큰의 무작위성을 평가합니다. 라이브: 요청을 리플레이하며 각 응답에서 토큰을 추출합니다. 수동: --tokens로 붙여넣은 목록을 분석합니다(네트워크 없음). 별칭 seq.
gori run sequence 42 --cookie SESSIONID --count 500
gori run sequence --tokens tokens.txt # '-'는 stdin을 읽음
| Option | Description |
|---|---|
--flow=ID, --request=FILE, stdin |
라이브 리플레이의 요청 소스(또는 맨 앞의 <flow-id>) |
--tokens=FILE |
붙여넣은 토큰 목록 분석(한 줄에 하나, -=stdin은 파이프나 리다이렉트만 받고 터미널은 거부), 네트워크 없음 |
| 토큰 위치(하나만 선택) | --token-cookie=NAME (--cookie), --token-header=NAME (--header), --regex=RE, --position=A:B, --jsonpath=EXPR |
--count=N |
목표 토큰 개수(기본값 500) |
--target, --http2, --sni, -k |
트랜스포트(--request/stdin에는 target 필요) |
--allow-unscoped |
대상이 프로젝트 스코프 밖이어도 전송(샌드박스와 명시적 제외 규칙은 그대로 적용) |
--concurrency (1), --rate, --throttle, --timeout, --retries, --max-requests=N |
속도 제어(상태 기반 토큰을 위해 concurrency는 1 유지) |
--no-keep-alive |
연결 재사용 대신 샘플마다 새로 연결 |
--bind-from=FLOW-ID |
캡처된 그 플로우를 먼저 재생해, 응답이 남은 실행 동안 쓸 $BIND.NAME 세션 바인딩을 채우게 합니다 |
--slot=NAME |
이 세션 슬롯으로 전송합니다: 그 슬롯의 헤더 오버레이, 그리고 $BIND.NAME을 위한 그 슬롯의 바인딩 테이블. --bind-from보다 먼저 적용되므로 시드가 채우는 슬롯이 곧 실행이 나가는 슬롯입니다 |
--format |
text, json, jsonl, 또는 markdown(TUI의 Export가 쓰는 리포트) |
run authorize
선택한 플로우를 아이덴티티마다 재전송합니다. 아이덴티티는 관리자 세션, 저권한 사용자, 익명 클라이언트를 대신하는 헤더 오버레이이며, 각 응답을 기준선과 비교합니다. 기준선이 받은 것을 그대로 받는 아이덴티티가 있다면 접근 제어 우회일 가능성이 높습니다. Authorize 탭의 헤드리스 버전입니다.
gori run authorize 12 13
gori run authorize --query 'host:acme.test method:GET' --identities identities.json
| Option | Description |
|---|---|
<flow-id>…, --flow=ID |
재전송할 캡처 플로우(지정한 순서대로, 반복 가능) |
-q, --query=QL |
QL 쿼리에 매칭되는 플로우도 재전송(id 뒤에 이어 붙습니다) |
-n, --limit=N |
--query가 기여할 수 있는 최대 플로우 수(기본값 50). 한 행이 아이덴티티 수만큼의 요청이 됩니다 |
--identities=FILE |
아이덴티티 집합 JSON(-=stdin은 파이프나 리다이렉트만 받고 터미널은 거부). 기본값은 프로젝트에 저장된 집합 |
--unsafe-methods (--unsafe) |
POST/PUT/PATCH/DELETE도 재전송합니다. 아이덴티티마다 부수 효과가 다시 실행됩니다 |
--allow-unscoped |
대상이 프로젝트 스코프 밖이어도 전송(샌드박스와 exclude는 그대로 적용) |
--timeout=SEC, -k/--insecure-upstream |
요청당 연결 + 유휴 타임아웃, 업스트림 TLS 검증 생략 |
--project, --db |
읽을 프로젝트 |
--format |
text(기본), json(마지막에 배열 하나), jsonl(스트리밍) |
--identities로 파일을 지정하지 않으면 아이덴티티는 프로젝트, 즉 TUI Authorize 탭의 목록에서 옵니다.
[{"name": "anonymous", "remove": ["Cookie", "Authorization"]},
{"name": "low-priv", "set": [{"name": "Cookie", "value": "session=…"}]}]
set은 헤더를 upsert하고 remove는 제거합니다. 어떤 항목도 "baseline": true를 갖지 않으면 캡처된 그대로의 요청이 기준선입니다. 기준선 외에 최소 한 개의 아이덴티티가 필요하며, 그렇지 않으면 비교할 것이 없습니다.
의미 있게 재전송할 수 없는 플로우는 아무것도 보내기 전에 이유와 함께 STDERR에 나열됩니다(no identity changes them, not a safe method to repeat, never completed, answered by gori, outside project scope, already queued). 선택한 플로우가 전부 건너뛰어지면 실행하지 않고 거부합니다. 모든 전송이 소켓을 열기 전에 거부되면 깨끗한 결과를 보고하는 대신 1로 종료하며 그 사실을 말합니다. 아무것도 보내지 않은 실행은 접근 제어가 동작한다는 증거가 아니기 때문입니다.
run cache-deception
선택한 각 플로우를 웹 캐시 디셉션으로 검사합니다: 캡처된(인증된) 아이덴티티로 재전송해 캐시를 채우고, 세션 없이 같은 url을 다시 요청한 뒤 고유한 캐시 무효화 쿼리 매개변수를 붙여 익명 제어 요청을 보냅니다. 제어 응답이 일치하고 캐시 히트 신호가 없을 때 공개 콘텐츠(served)로 봅니다. 제어 응답도 캐시 히트라면 쿼리가 무시됐을 수 있으므로 판정은 review이며, 익명 응답이 캐시 히트를 보이고 제어 응답은 다르면 디셉션 가능성(cached)이 있습니다. 플로우 하나당 최대 세 번 요청합니다. Authorize 엔진을 차용하며, 이를 유발하는 조작된 경로(;, .css, %00, dot-segment)는 Fuzzer의 cache-delimiters 페이로드 세트입니다.
gori run cache-deception 12
gori run cache-deception --flow 12 --flow 13 --format json
| 옵션 | 설명 |
|---|---|
<flow-id>…, --flow=ID |
검사할 캡처 플로우(순서대로, 반복 가능) |
--unsafe-methods |
POST/PUT/PATCH/DELETE도 검사; 부작용이 최대 세 번(프라임, 익명, 제어) 실행될 수 있음 |
--allow-unscoped |
대상이 프로젝트 스코프 밖이어도 전송(샌드박스·제외 규칙은 여전히 적용) |
--timeout=SEC, -k/--insecure-upstream |
요청별 연결 + 유휴 타임아웃; 업스트림 TLS 검증 생략 |
--project, --db |
읽을 프로젝트 |
--format |
text(기본), json(끝에 배열 하나), jsonl(스트리밍) |
플로우마다 판정 하나를 보고합니다: cached(디셉션: 익명이 인증된 응답을 캐시에서 받았고 캐시 무효화 제어 응답은 다름), served(캐시 히트 증거가 없거나 캐시 히트가 없는 제어 응답과 일치), review(비슷하지만 동일하지 않거나 제어 결과가 불분명하거나, 일치한 제어 응답도 캐시 히트임), protected(익명이 다른 응답을 받음), blocked(gori가 전송 거부), errored. 각 시도의 cache는 해당 응답의 캐시 상태이며 최상위 cache는 익명 응답 상태입니다. --unsafe-methods 없이는 안전한 메서드(GET/HEAD/OPTIONS)만 검사합니다. 재전송할 수 없는 플로우는 STDERR로 알리고 다음 플로우로 넘어가며, 검사한 플로우가 하나도 없으면 1로 끝납니다.
run session
프로젝트의 세션 슬롯입니다. 이름 붙은 신원 각각이 헤더 오버레이 하나와, 그 값을 묶어 주는 extract 규칙들로 이루어집니다. TUI Authorize 탭의 identities 카드가 편집하고 MCP의 *_session_slot 도구가 관리하는 바로 그 목록입니다. Authorize 실행은 모든 슬롯으로 재생하고, 전송은 --slot이 지목한 하나로 나갑니다.
gori run session # 목록 (값은 [REDACTED])
gori run session show admin --show-values
gori run session add --name admin --set 'Cookie: session=…' --rule SESSION
gori run session edit admin --clear-set --set 'Cookie: session=new'
gori run session baseline as-captured
gori run session rm admin
gori run session edit admin --refresh 12,14 --refresh-before jwt-exp
gori run session refresh admin
| 동사 | 옵션 |
|---|---|
list(기본) |
--show-values([REDACTED] 대신 헤더 값 출력), --format text|json |
show <name> |
--show-values, --format text|json |
add |
--name, --set 'Name: value'(반복 가능), --remove NAME(반복 가능), --rule NAME(반복 가능), --baseline / --no-baseline(플래그 해제. 그러면 첫 슬롯이 기준선을 물려받습니다), --refresh ID,ID(슬롯을 다시 인증하는 Repeater 세션, 실행 순서대로), --refresh-before off|jwt-exp|ttl=10m |
from-flow <flow-id> |
--name(필수), --baseline, --show-values. 오버레이를 직접 타이핑하는 대신 캡처된 로그인 교환에서 만듭니다 |
from-request <flow-id> |
--name(필수), --copy-header NAME(반복 가능, 하나 이상 필요), --baseline, --show-values. 캡처된 요청에서 지정한 헤더를 복사합니다 |
edit <name> |
같은 플래그에 --clear-set / --clear-remove / --clear-rules / --clear-refresh 추가. 컬렉션 플래그는 그 컬렉션 전체를 교체하고, 생략한 것은 그대로 둡니다 |
rm|delete <name> |
그 슬롯이 주장하던 extract 규칙은 다시 전역 바인딩 테이블에 쓰게 됩니다 |
baseline <name> |
Authorize 기준선 이동(정확히 한 슬롯이 갖습니다) |
refresh <name> |
슬롯의 갱신 단계를 지금 실행합니다. --allow-unscoped, -k/--insecure-upstream, --format text|json. 갱신이 실패하면 1로 끝납니다 |
모든 동사가 --project=NAME / --db=PATH를 받습니다.
--set 값은 TUI 폼과 같은 헤더 파서를 지납니다. 이름은 RFC 7230 토큰이어야 하고 값에 CR이나 LF가 들어갈 수 없으며, 통과하지 못한 줄은 버려지지 않고 지목되어 거부됩니다.
from-flow는 캡처된 로그인에서 슬롯을 만듭니다. 로그인한 플로우를 지목하면 gori가 그 플로우의 응답을 읽어 오버레이를 채웁니다. 모든 Set-Cookie의 name=value를 Cookie: 헤더 하나로 접고(속성은 버리며, 응답이 삭제하는 쿠키는 건너뜁니다), 이어서 응답 자신의 Authorization을, 없으면 JSON 본문 최상위의 access_token / token / id_token 문자열을 Authorization: Bearer <value>로, 그것도 없으면 요청 자신의 Authorization을 씁니다.
gori run session from-flow 4211 --name admin
gori run repeater 900 --slot admin # 플로우 900을 그 신원으로 재전송
오버레이는 리터럴입니다. 로그인이 돌려준 바이트 그대로 프로젝트에 저장됩니다. 스스로 재인증하지는 않으므로, 회전하는 토큰(수명 짧은 JWT, 요청마다 바뀌는 CSRF 값)은 extract 규칙 경로가 맞습니다: gori run rewriter extract에 --bind-from FLOW를 더하면 실행마다 값을 새로 발급받습니다. 슬롯에 갱신 단계를 붙이는 방법도 있고, 요청마다 바뀌는 값이라면 Fuzzer나 Miner의 요청 시점 매크로(--macro)를 쓰세요. 이름은 플로우를 읽기 전에 검사하므로, 중복된 이름은 "그 플로우는 로그인이 아니다"가 아니라 이름 충돌로 보고됩니다.
from-request는 캡처된 요청에서 지정한 헤더를 복사합니다. 인증 정보나 CSRF 값이 요청에 이미 있거나, 로그인 교환의 모든 헤더가 아닌 필요한 헤더만 의도적으로 스냅샷할 때 유용합니다. --copy-header를 헤더마다 반복하고, 하나 이상 지정해야 합니다. Content-Length, Transfer-Encoding, Host는 거부됩니다. 슬롯은 본문과 대상이 다른 메시지에 적용되므로, 이 헤더를 복사하면 이후 모든 전송의 프레이밍이나 라우팅이 어긋납니다. 저장되는 값은 리터럴 바이트이며, --show-values를 주지 않으면 표준 출력은 [REDACTED]로 가립니다. STDERR의 provenance는 복사한 헤더 이름만 출력하고 값은 출력하지 않습니다. 슬롯은 호스트 범위가 없습니다. --slot NAME을 명시한 모든 전송에 해당 슬롯의 헤더가 적용되므로, 슬롯은 의도한 신원 하나에만 사용하세요.
gori run session from-request 4211 --name admin \
--copy-header Cookie --copy-header X-CSRF-Token
gori run repeater 900 --slot admin # 해당 헤더 스냅샷으로 전송
이는 로그인 매크로가 아닌 리터럴 스냅샷입니다. 스스로 재인증하거나 회전하는 토큰을 갱신하지 않습니다. 수명이 짧은 JWT, 요청마다 바뀌는 CSRF 값처럼 다시 발급해야 하는 값은 gori run rewriter extract와 --bind-from FLOW를 사용해 실행마다 새로 추출하거나, 슬롯에 갱신 단계를 붙이세요. 요청마다 바뀌는 값이라면 Fuzzer나 Miner의 요청 시점 매크로(--macro)를 쓰세요.
갱신 단계는 슬롯을 다시 인증합니다. --refresh 12,14는 로그인하는 Repeater 세션(gori run repeater list)을 실행 순서대로 지정합니다. 보통 CSRF를 가져오는 요청, 그다음 $BIND.CSRF를 싣는 로그인 요청입니다. 각 단계의 응답은 슬롯 자신의 extract 규칙을 거치고, 그것이 슬롯을 다시 바인딩합니다. 단계는 그 슬롯의 $BIND.NAME 값을 해소하고 슬롯 헤더 오버레이는 싣지 않으므로, 로그인 요청이 교체하려는 만료된 자격 증명을 보내지 않습니다. 모든 단계는 source refresh(src:refresh)로 History에 기록되고, 갱신마다 이벤트 하나(list_events, source session)가 남습니다. 이벤트에는 바인딩 이름만 있고 값은 없습니다.
--refresh-before를 주면 슬롯으로 나가는 전송(모든 전송 명령의 --slot NAME, 그리고 Authorize 실행의 각 신원) 직전에 갱신이 스스로 실행됩니다.
| 정책 | 갱신 시점 |
|---|---|
off(기본) |
스스로는 하지 않습니다. session refresh만 |
jwt-exp |
슬롯 테이블에 바인딩된 JWT의 exp가 30초 이내로 남았을 때 |
ttl=10m |
마지막으로 성공한 갱신(없으면 슬롯에서 가장 오래된 바인딩) 이후 그 기간이 지났을 때(s, m, h. 숫자만 쓰면 초) |
정책이 있고 아직 아무것도 바인딩되지 않은 슬롯은 첫 전송 전에 갱신합니다. 401을 받은 뒤 요청을 재시도하지는 않습니다. 정책은 전송 전에만 동작하고 응답을 읽지 않으므로, 로그인이 Authorize 판정을 가리지 않습니다. 자동 갱신이 실패하면 전송은 가진 값으로 그대로 나가고, 30초 동안 다시 시도하지 않으며, 3번 연속 실패하면 수동 갱신이 성공할 때까지 자동 갱신이 꺼집니다. 동시에 들어온 전송은 진행 중인 갱신 하나를 기다립니다. 자동 갱신은 gori run이 여느 전송을 제한하듯 프로젝트 스코프로 제한되며 명령의 --allow-unscoped를 물려받지 않습니다. 로그인 호스트가 스코프 안에 있어야 합니다. 명령에 -k를 주면 그 전송처럼 갱신 단계도 업스트림 TLS 검증을 건너뜁니다.
슬롯이 단계로 쓰는 Repeater 세션을 삭제하면 그 단계는 제자리에 삭제됨으로 표시되어 남고, 갱신은 그 id를 다음에 차지한 세션을 실행하는 대신 그 단계를 거부합니다. --refresh나 --clear-refresh로 제거하세요.
바인딩 값은 메모리에, 프로세스마다 따로 있습니다. session refresh는 이 명령 자신의 테이블을 다시 바인딩하고 명령이 끝나면 사라지므로, 로그인 순서가 동작하는지 확인하는 용도입니다. --slot NAME 스윕은 자기 프로세스에서 갱신하고, TUI와 실행 중인 gori mcp는 각자의 테이블을 가집니다.
session activate는 없습니다. gori run 프로세스는 보내고 끝나므로 활성 포인터가 걸칠 시간이 없고, 저장해 두면 다음 실행에서 비어 있는 바인딩 테이블로 해소되어 $BIND.SESSION이 리터럴인 오버레이를 보내게 됩니다. 대신 전송할 때 신원을 지목하세요: send, repeater, repeater send, repeater race, repeater timing, repeater minimize, fuzz, mine, sequence, discover, retest run에서 --slot NAME. 실행은 첫 요청 전에 STDERR로 slot: sending as NAME을 찍습니다.
run probe
gori run probe --severity high --category cors
gori run probe -a
--severity는 info|low|medium|high|critical 중 하나입니다. --fail-on=LEVEL은 스캔이 보고하는 이슈(--severity/--category/--in-scope 적용 후)가 LEVEL 이상이면 3으로 종료하게 하므로, CI 잡의 게이트로 쓸 수 있습니다. --category는 headers|cookies|tech|infoleak|cors|client|active|custom입니다. -a/--active는 가벼운 액티브 검사도 포함합니다. -q/--query로 QL 필터를 겁니다. --lenient는 없는 필드 이름을 쓴 쿼리를 거절하지 않고 받아들입니다. --in-scope는 프로젝트 스코프 안의 호스트에 대한 이슈만 보고합니다. TUI의 s 렌즈로, --active/--allow-unscoped와 무관하게 옵트인이며 모든 플로우는 여전히 스캔됩니다.
--active와 함께: --unsafe는 안전하지 않은 메서드(POST/PUT/PATCH/DELETE)도 프로브하며, 이 재전송은 서버 데이터를 변경할 수 있습니다. --aggressive는 룰별 상한을 높이고 forbidden-bypass 헤더 집합을 넓힙니다(그리고 --unsafe를 함의합니다). 둘 다 --allow-unscoped를 함께 주지 않는 한 스코프 게이트를 따릅니다. -k/--insecure-upstream은 액티브 재전송의 업스트림 TLS 검증을 생략합니다. 인가된 대상에만 사용하세요.
probe만 쓰면 스캔하고 출력합니다. --persist를 주면 찾은 결과를 라이브 스캐너와 같은 방식으로 합쳐 저장된 발견 항목에도 기록하므로, TUI로 연 적 없는 프로젝트에도 판정 목록이 생깁니다. 기록이 실패하면 보고서를 출력한 뒤 그렇다고 알리고 1로 종료합니다. TUI Probe 탭 뒤에 저장되는 발견 항목은 별개의 표면입니다.
gori run probe issues --severity high # 아래 동사들이 받는 id가 함께 나오는 트리아지 목록
gori run probe promote 12 # 하나를 Issue로 확정
gori run probe dismiss --code missing_hsts # 발견 코드나 --host로 일괄 무시
gori run probe delete --all --yes
gori run probe rules --kind active # 스캔 룰 목록과 무장 여부
gori run probe rules enable <rule-id> # id는 `probe rules`나 Probe 룰 레퍼런스에서
gori run probe mode passive # off | passive | active | aggressive
| Verb | Options |
|---|---|
issues |
-a/--all(무시·확정·해결된 항목 포함), --severity, --category, --host |
dismiss <id> |
id를 주면 그 발견 항목을 무시 ⇄ 열림으로 토글하고, --code=CODE / --host=HOST는 그 값을 공유하는 열린 항목을 모두 무시합니다. 코드는 룰 id가 아니라 발견의 코드(probe issues에 나오는 missing_hsts 같은 값)이며 정확히 일치해야 합니다. 프로젝트에 쓰지 못한 dismiss는 항목을 바꾸지 않고 1로 끝납니다 |
promote <id> |
발견 항목을 사람이 확인한 Issue로 승격 |
delete <id> (rm) |
또는 --all --yes |
rules [list|enable|disable|add|delete] |
list는 --kind=passive|active|custom. enable/disable/delete는 그 목록의 <rule-id>를 받습니다(내장 룰은 Probe 룰에 있습니다). add는 -t/--title(필수), -p/--pattern(필수), --description, --side(request|response, 기본 response), --region(whole|header|body, 기본 body), --regex, --exec(--pattern을 프로세스 훅으로 실행: exit 0이면 발견, stdout이 근거), -s/--severity(기본 info) |
mode [off|passive|active|aggressive] |
프로젝트의 스캔 모드를 출력하거나 설정 |
run discover
대상을 스파이더링하고 링크되지 않은 경로를 브루트포스합니다. --no-store가 아니면 결과는 Sitemap으로 반영됩니다. 대상이 청하지 않은 실제 트래픽을 보내므로 인가된 대상에만 실행하세요.
gori run discover --target https://target.example --max-depth 3 --extensions php,json,bak --format jsonl
| Option | Description |
|---|---|
--target=URL |
탐색할 시드 origin 또는 경로 하위 트리(필수) |
--max-depth=N |
시드로부터의 스파이더 깊이(기본값 4) |
--no-spider / --no-bruteforce |
링크 크롤링 / 디렉터리 브루트포스 비활성화 |
--wordlist=PATH |
내장 목록과 병합할 추가 경로 워드리스트(파일, 또는 저장한 wordlist의 이름) |
--extensions=LIST |
이 확장자도 프로브(예: php,json,bak) |
-H, --header=HEADER |
모든 프로브에 붙일 커스텀 헤더(반복 가능) |
--containment=MODE |
same-origin | scope-aware(기본) | host+subdomains |
--concurrency (20), --rate, --throttle, --timeout, --retries, --max-requests=N |
속도 제어 |
--no-keep-alive |
origin별 연결 재사용 대신 프로브마다 새로 연결 |
--assets |
링크된 이미지·폰트·미디어·아카이브도 내려받기(기본은 디렉터리만 기록하고 다운로드는 생략) |
-k, --insecure-upstream |
업스트림 TLS 검증 생략 |
--http2, --sni=HOST |
HTTP/2 강제, TLS SNI 오버라이드 |
--bind-from=FLOW-ID |
캡처된 그 플로우를 먼저 재생해, 응답이 남은 실행 동안 쓸 $BIND.NAME 세션 바인딩을 채우게 합니다 |
--slot=NAME |
이 세션 슬롯으로 전송합니다: 그 슬롯의 헤더 오버레이, 그리고 $BIND.NAME을 위한 그 슬롯의 바인딩 테이블. --bind-from보다 먼저 적용되므로 시드가 채우는 슬롯이 곧 실행이 나가는 슬롯입니다 |
--allow-unscoped |
대상이 프로젝트 스코프 밖이어도 실행. 사전(Layer 1) 검사만 면제되며 Sandbox 모드와 명시적 exclude 룰은 매 전송마다 그대로 거부합니다. 거부 메시지는 둘 중 어느 쪽이 막았는지 밝힙니다 |
--force |
무제한 실행 안전 게이트 우회 |
--no-store |
결과를 프로젝트에 기록하지 않음 |
--format |
text, json, 또는 jsonl |
기본적으로 origin별로 연결을 재사용합니다. 브루트포스 한 번이 프로브마다가 아니라 워커마다 TCP(https라면 TLS) 핸드셰이크를 한 번씩만 치릅니다. 실행이 끝날 때 나오는 connections · N dialed · M reused 줄에서 대상이 이를 지켰는지 확인할 수 있습니다. 대상이 연결 단위로 동작한다면 --no-keep-alive로 끕니다.
run wordlist
전역 wordlist 카탈로그: $GORI_HOME/wordlists(~/.gori/wordlists) 아래의 이름 붙은 목록이며 하나하나가 평범한 파일입니다. wordlist 경로를 받는 곳(fuzz -w, mine --wordlist, discover --wordlist, cookie --crack --wordlist)이면 어느 작업 디렉터리에서든 이름을 쓸 수 있습니다. /가 있는 값은 경로이므로 주어진 그대로 읽고, 이름만 있으면 현재 디렉터리를 먼저, 그다음 카탈로그를 찾습니다. 여기서는 프로젝트가 필요 없고 요청도 보내지 않습니다. 전체 모델은 가이드를 보세요.
gori run sitemap params --host api.example.com --format names | gori run wordlist save api-params.txt
gori run wordlist # 가진 목록: 이름과 크기
gori run mine 42 --wordlist api-params.txt
gori run wordlist show api-params.txt --head 5
gori run wordlist rename api-params.txt api-v2-params.txt
gori run wordlist delete api-v2-params.txt --yes
| Verb | Description |
|---|---|
wordlist · list (ls) |
이름, 크기, 수정 시각. 값은 절대 출력하지 않습니다. --format text | json |
show <name> |
경로, 크기, 줄 수(최대 32 MiB까지 세며, 목록이 더 길면 more than N). --head=N은 첫 N줄(최대 1000)도 출력합니다. 값이므로 민감할 수 있습니다 |
save <name> (add) |
소스를 정확히 하나만 골라 목록을 저장: --from=FILE(-는 stdin), --value=V 하나 이상, stdin으로 넘긴 목록, 또는 --project/--db와 함께 쓰는 --payload-from='<QL> <projection>'(그 프로젝트의 캡처 데이터에서 읽은 값. fuzz와 같은 --payload-from-* 정책이 적용되고, 줄바꿈이 든 값은 빼고 셉니다). 바이트는 준 그대로 유지하므로 빈 줄이나 # 줄도 그대로 남고, --value에는 줄바꿈을 넣을 수 없습니다. 원자적이고 소유자 전용이며, --overwrite가 아니면 이미 있는 이름은 거부합니다 |
rename <old> <new> (mv) |
목록 이름 변경. --overwrite가 아니면 이미 있는 <new>는 거부합니다 |
delete <name> (rm) |
목록 삭제(--yes가 확인이며 프롬프트는 없습니다). 심볼릭 링크는 링크만 지우고 가리키는 파일은 지우지 않습니다 |
이름은 어느 문자든 글자와 숫자, _, ., +, -, 안쪽 공백(최대 200바이트, 글자·숫자·_로 시작해야 하고 .이나 공백으로 끝날 수 없음)이며 경로 구분자가 든 값은 거부합니다. 모든 verb가 -h를 받고, 거부된 변경 verb는 1로 종료하며 이유를 알려 줍니다.
명령줄에서 세션 바인딩 쓰기
세션 바인딩(로그인 응답에서 채워지는 $BIND.SESSION 같은 것. 세션 바인딩 참고)은 그것을 관측한 gori 프로세스의 메모리에만 존재합니다. settings.json에도, 프로젝트 데이터베이스에도 기록되지 않습니다. 복원된 토큰은 이미 낡은 것이고, 다시 추출하는 비용은 요청 한 번이기 때문입니다.
gori run은 호출마다 프로세스 하나이며, 스윕은 의도적으로 추출 소스가 아닙니다(공격 페이로드를 그대로 되비추는 응답이 세션을 그 값으로 바꿔버릴 수 있기 때문입니다). 그래서 선언된 바인딩을 참조하는 헤드리스 fuzz / mine / sequence / discover 템플릿은 그것을 채울 수단이 없어, 토큰이 리터럴 텍스트 그대로 나갑니다(거부되지 않습니다).
--bind-from FLOW-ID가 그 빠진 단계입니다. 캡처된 플로우 하나(로그인)를 의도적 전송 경로로 재생해 그 응답이 바인딩 테이블을 채우게 하고, 같은 프로세스 안에서 스윕을 이어 실행합니다.
gori run fuzz 42 --wordlist ids.txt --bind-from 17
# bind-from: flow #17 replayed → bound $BIND.SESS
하나의 stdio 세션에서 gori mcp 도구를 두 번 호출하는 경우도 원래부터 같은 방식으로 동작합니다.
run import
프로젝트의 History로 플로우를 일괄 임포트합니다. TUI의 Import 오버레이에 대응하는 CLI입니다(Proxy & History → 임포트 참고). 소스 플래그는 정확히 하나만 지정해야 하며, 모든 소스는 PATH가 -이면 stdin을 읽습니다(generator | gori run import --urls -). stdin은 파이프나 리다이렉트여야 합니다. 트래픽은 전혀 보내지 않습니다.
gori run import --postman api.postman_collection.json --db ./assessment.db --format json
| Option | Description |
|---|---|
--har=PATH |
브라우저/프록시 HAR(HTTP Archive) 익스포트. 전체 요청/응답 플로우 |
--urls=PATH |
한 줄에 URL 하나씩 담긴 텍스트 파일(# 주석과 빈 줄은 무시) |
--oas=PATH |
OpenAPI 3.x 또는 Swagger 2.0(JSON 또는 YAML). 로컬 JSON Pointer 참조는 해소하고, 원격 참조는 보고만 하고 가져오지 않습니다 |
--postman=PATH |
Postman Collection v2 익스포트(JSON) |
--insomnia=PATH |
Insomnia v4 익스포트(JSON) |
--burp=PATH |
Burp Suite 항목 익스포트(XML). 요청과 응답, 바이트 단위 그대로 |
--wsdl=PATH |
WSDL 1.1 서비스 설명서(XML). 오퍼레이션마다 SOAP 요청 템플릿 하나 |
--curl=PATH |
curl 명령. 요청마다 플로우 하나이며 -는 stdin을 읽음(pbpaste | gori run import --curl -). 무시한 전송 플래그는 stderr에 밝힘(JSON에서는 notes) |
--project=NAME |
임포트할 프로젝트(기본값: GORI_PROJECT나 project switch로 정한 프로젝트, 없으면 가장 최근에 활성화한 프로젝트) |
--db=PATH |
임포트할 SQLite db 파일을 직접 지정(없으면 생성) |
--format |
text(기본) 또는 json |
임포트는 플로우를 기록하므로 discover와 같은 방식으로 대상을 정합니다. --db를 주면 생성하거나 다시 열고, 주지 않으면 기본 프로젝트를 몰래 만들지 않고 기존 프로젝트에 씁니다.
형식이 잘못된 항목은 파일 전체를 중단시키지 않고 건너뛰며, 결과에 양쪽 개수가 모두 담깁니다({"count": 12, "attempted": 12, "skipped": 3}). 프로젝트가 파싱한 플로우를 모두 커밋하지 못하면(저장소가 바쁘거나 쓸 수 없는 경우) count가 attempted보다 작아지고, text 줄에 커밋되지 않은 개수가 나오며, 명령은 1로 끝납니다. 임포트를 다시 실행하면 그 플로우를 재시도합니다. 응답까지 가져오는 것은 --har와 --burp뿐이고, 나머지는 요청 템플릿이라 보내기 전까지 History에서 Pending으로 보입니다.
run sitemap
gori run sitemap --in-scope --format paths
-q/--query=QL는 history와 같은 QL로 엔드포인트를 거릅니다(위치 인자로도 넘길 수 있습니다). Sitemap 전용 경로 메모 필드 tag:도 받습니다(적용 범위). -n/--limit=N은 스캔할 엔드포인트 수를 제한합니다(기본값 10000). --in-scope는 스코프 내 호스트로 한정하고, --hide-static은 이미지·폰트·오디오·비디오를 뺍니다(TUI 트리처럼 플로우 단위). --no-group은 id 접기를, --no-fold-query는 쿼리 문자열 접기를 끕니다(서로 다른 축입니다). --js-refs는 캡처한 JavaScript가 참조하지만 아무도 요청하지 않은 경로도 함께 그립니다(sitemap js 참고. JSON에서는 js_refs와 unrequested로 나오며, paths에는 나오지 않습니다). --format은 text(트리), json, paths 중에서 고릅니다. --lenient는 없는 필드 이름을 쓴 쿼리를 거절하지 않고 받아들입니다.
트리의 루트는 오리진(스킴, 호스트, 포트)마다 하나라서, http://127.0.0.1:19021, http://127.0.0.1:19022, https://127.0.0.1:8443은 루트 세 개가 됩니다. paths는 엔드포인트마다 기본 포트를 뺀 전체 URL을 출력합니다(GET https://127.0.0.1:8443/only-tls). json의 호스트 객체는 host에 포트 없는 호스트를 그대로 두고 scheme, port, origin(paths가 앞에 붙이는 값)을 더합니다. 태그는 호스트에 붙으므로 그 호스트의 모든 오리진 아래에 보입니다.
sitemap tag: 경로 하나에 자유 텍스트 메모를 고정합니다. TUI Sitemap에 보이는 그 메모입니다.
gori run sitemap tag --host api.example.com --path /v1/users --tag "IDOR candidate"
gori run sitemap tag --host api.example.com --path /v1/users --clear
gori run sitemap tag --list
sitemap params: TUI Params 서브탭과 같은 파라미터 목록입니다. 오리진(스킴, 호스트, 포트), 메서드, 경로, 위치, 이름마다 한 줄씩 나오며, 그 이름이 나온 플로우 수, 서로 다른 값 최대 --samples개(기본값 5), 그리고 4바이트 이상인 값이 디코딩된 응답 본문의 앞 256 KiB 안에 그대로 나타나면 reflected가 붙습니다(관찰일 뿐 취약점 판정은 아닙니다).
gori run sitemap params --host api.example.com
gori run sitemap params 'method:POST' --location json,form --format json
gori run mine 42 --wordlist <(gori run sitemap params --host api.example.com --format names)
-q/--query=QL(위치 인자로도 가능), --in-scope, --hide-static은 읽을 플로우를 좁힙니다. history처럼 플로우 단위로 적용됩니다. --host는 정확한 호스트, --origin=URL은 그 호스트의 오리진 하나(http://127.0.0.1:19021, --host와 함께 쓸 수 없음), --path=PREFIX는 경로 접두사, --location=LIST는 위치를 고릅니다(기본값 전체). 표준 브라우저 헤더는 --all-headers를 주지 않으면 빠집니다. --max-flows=N은 조건에 맞는 최신 플로우 N개를 읽고(기본값 2000), 더 오래된 플로우를 건너뛰었으면 stderr에 알립니다. 쿠키, 자격 증명 헤더, password나 token처럼 자격 증명 이름을 가진 필드의 값은 --include-sensitive를 주지 않으면 [REDACTED]로 출력됩니다. 가리는 기준은 이름과 JWT / 개인 키 형태뿐이라, 다른 이름의 비밀 값(presigned X-Amz-Signature, 임의의 sig= 등)이나 URL 경로 안의 자격 증명은 그대로 출력됩니다. 텍스트는 행을 오리진(https://api.example.com) 아래에 묶고, json 행에는 scheme, host, port가 들어갑니다. --format은 text, json, names 중에서 고릅니다. names는 한 줄에 이름 하나(JSON은 마지막 키 이름, --location에 지정하지 않으면 헤더 제외)로, Miner나 Fuzzer 워드리스트로 바로 쓸 수 있습니다.
sitemap js: 캡처된 JavaScript가 참조하는 엔드포인트입니다. TUI Sitemap이 js 행으로 그리는 것과 같습니다. 기본값은 같은 오리진에서 캡처된 요청이 닿지 않은 것만 오리진별로 보여주며(http://h:9090과 https://h는 서로 다른 묶음), 각 줄에 읽어 온 플로우와 줄 번호, 문자열, 표시(comment, templated, base: referer|guessed)가 붙습니다.
gori run sitemap js --scan
gori run sitemap js --host api.example.com --format json
gori run sitemap js --format urls | httpx -silent
--scan은 먼저 아직 스캔하지 않은 캡처된 JavaScript 응답과 HTML 페이지를 최신 것부터 --max-flows개(기본값 500) 읽고 참조를 저장합니다. 요청은 보내지 않습니다. -q/--query=QL(위치 인자로도 가능)은 읽을 플로우를 좁히고, --rescan은 이미 읽은 것도 다시 읽습니다. 목록은 --host(정확한 호스트), --path=PREFIX, --all(트래픽이 이미 닿은 참조도 포함), --all-hosts(gori가 캡처한 적 없고 스코프 include도 가리키지 않는 호스트도 포함. 기본값은 숨김), --no-comments, --in-scope로 좁힙니다. --format은 text, json, urls(한 줄에 URL 하나. 그대로 보낼 수 없는 템플릿 참조는 빠집니다) 중에서 고릅니다. 참조는 원본 플로우와 함께 지워집니다.
sitemap export: 캡처된 API를 OpenAPI 3.0.3 문서로 stdout에 출력합니다. TUI Sitemap에서 ⇧E로 쓰는 문서와 같습니다. 빠진 것과 그 이유는 stderr로 나옵니다.
gori run sitemap export --host api.example.com > api.json
gori run sitemap export --host api.example.com --format openapi-yaml > api.yaml
gori run sitemap export --origin http://127.0.0.1:19021 > one-service.json
gori run sitemap export --in-scope --examples > api.json
- 경로는 경로마다 템플릿으로 바꿉니다. 숫자, UUID, 긴 16진수, 날짜 세그먼트는 앞 세그먼트 이름을 딴 파라미터가 되어
/users/123/orders/9f1c2b7d0a4e가/users/{userId}/orders/{orderId}가 됩니다. 트리 표시용 접기와 달리 한 번만 캡처된 id도 바꿉니다. 템플릿이 같은 엔드포인트는 하나의 operation으로 합치고, 쿼리 문자열만 다른 변형도 하나로 합칩니다. - 파라미터(query, header, cookie)는 그 operation의 모든 샘플에 있을 때만
required입니다. 표준 브라우저 헤더는 빼고, 반복된 쿼리 키는 배열로 씁니다. - 본문: 요청 본문과 상태 코드별 응답에 모든 샘플에서 추론한 스키마가 붙습니다. JSON 스키마는 타입을 합치고(
integer와number는number로, 정말 다른 타입은oneOf로), 객체 속성은 합집합으로 모으며, 모든 샘플에 있던 멤버만required로 둡니다. 폼은 객체 스키마, 그 밖의 미디어 타입은 문자열이 됩니다. - 보안:
Authorization헤더는httpbearer, basic, digest 스킴이 됩니다. 다른 자격 증명 헤더(X-Api-Key,X-Auth-Token등)와 세션 쿠키는apiKey스킴이 됩니다. 값은 절대 쓰지 않습니다. - 건너뜀: gori가 직접 보낸 요청(Repeater, Fuzzer, Miner, Discover 등.
--include-gori를 주면 포함), WebSocket, gRPC, SSE, 응답이 완료되지 않은 플로우, 그리고 OpenAPI에 자리가 없는 메서드(CONNECT, WebDAV)는 건너뛰고 stderr에 개수를 적습니다. 첫 번째 규칙이 없으면 Discover 브루트포스가 추측한 경로가 모두 들어가고, 퍼징이 모든 타입을 느슨하게 만들어 버립니다.
-q/--query=QL(위치 인자로도 가능), --in-scope, --hide-static은 history처럼 플로우 단위로 읽을 대상을 좁힙니다. --host는 정확한 호스트, --path=PREFIX는 경로 접두사입니다. 여러 호스트에 걸친 플로우는 문서 하나에 모든 origin을 servers로 적고, 경로마다 응답한 origin을 따로 적습니다. API 하나당 문서 하나가 필요하면 --host를 쓰세요. --max-samples=N(기본값 20)은 operation마다 읽을 플로우 수, --max-flows=N(기본값 5000)은 전체 플로우 수, --max-endpoints=N(기본값 1000)은 남길 operation 수의 상한입니다. 상한 때문에 문서가 잘리면 stderr에 알려줍니다.
--examples를 주지 않으면 example 값은 없습니다. 예시 값은 샘플 하나에서 가져와 redaction 프로필(--redact=PROFILE, 기본값은 프로젝트 프로필, 없으면 전역 프로필, 없으면 default)을 거칩니다. 프로필의 필드 이름과 폼 키 이름은 쿼리 파라미터에도 적용됩니다. 자격 증명처럼 보이는 이름(X-Access-Token, apiKey, userPassword, sig)은 프로필과 상관없이 placeholder가 되고, 쿠키 값은 항상 placeholder입니다. 경로 id는 비밀을 뜻하지 않는 세그먼트 아래의 짧은 숫자나 날짜일 때만 예시를 붙이고, UUID·16진수·토큰 모양 세그먼트에는 붙이지 않습니다. 경로 속 자격 증명과 구별할 수 없기 때문입니다. 토큰 모양 경로 세그먼트(JWT, 긴 무작위 문자열)와 ;jsessionid= 같은 matrix 파라미터는 경로 키에 들어가지 않습니다. 출력은 결정적이어서 같은 플로우를 두 번 내보내면 바이트가 같습니다. 그래서 두 문서의 diff가 곧 API의 변화입니다.
run oast
아웃오브밴드 리스너입니다. listen은 기본적으로 즉석에서 쓰는 저장소 없는 리스너로, 페이로드를 등록하고 출력한 뒤 콜백을 스트리밍합니다. --save를 붙이면 프로젝트 세션이 됩니다. list / resume / release는 그렇게 저장된 세션(TUI의 RESUME LISTENER 피커가 보여주는 것과 같은 행)을 다룹니다.
gori run oast presets # 내장 공개 프로바이더 목록
gori run oast presets --check # …각 프리셋의 도달 가능성 프로브
gori run oast listen # interactsh, Ctrl-C까지 폴링
gori run oast listen --provider webhook.site --once --json
gori run oast listen --save # …프로젝트 세션으로 저장
presets는 공개 프로바이더를 나열하고, presets --check는 각각을 네트워크로 프로브해 실패한 단계(dns / connect / proxy / tls-verify / tls / timeout / exchange / dial)를 출력합니다. 커스텀 트러스트 스토어나 제한된 리졸버를 프로바이더 장애와 갈라 주는 정보입니다. 아무것도 응답하지 않았을 때만 비정상 종료 코드를 반환합니다.
gori run oast presets --check
gori run oast presets --check --format json
| 옵션 | 설명 |
|---|---|
--check |
각 프리셋을 네트워크로 프로브해 도달 가능성 보고 |
--format=FMT |
text(기본) 또는 json |
--project=NAME · --db=PATH |
--check와 함께: 그 프로젝트가 접속하는 방식(고정 업스트림 프록시·타임아웃)으로 프로브 |
listen이 실패할 때도 같은 단계를 이름으로 밝힙니다. tls-verify의 해법은 다른 프로바이더가 아니라 이 머신의 트러스트 스토어입니다. SSL_CERT_FILE=/path/to/ca-bundle.crt(또는 SSL_CERT_DIR=/path/to/certs)로 실행해 사설 CA나 TLS 검사 프록시의 CA를 신뢰시키세요. gori 자신의 서비스 트래픽에서 이 번들은 가산적이라(시스템 스토어를 그대로 로드) 공개 프리셋도 계속 동작합니다. --ca-file 플래그는 없습니다.
listen 옵션:
| Option | Description |
|---|---|
--provider=KIND |
interactsh(기본) | custom-http | webhook.site | BOAST | postbin |
--server=URL |
프로바이더 서버 / 베이스 URL(기본값: 프로바이더의 공개 프리셋) |
--token=TOK |
선택적 프로바이더 인증 토큰 |
--interval=SEC |
폴링 간격(기본값 5) |
--once |
한 번만 폴링하고 종료 |
--save |
등록을 프로젝트 OAST 세션으로 저장(oast list 참고) |
--json |
각 콜백을 JSON 라인으로 출력(MCP와 동일한 형태) |
--save는 즉석 리스너를 프로젝트 리스너로 바꾸고, 세 가지가 달라집니다. 모든 콜백이 프로젝트에 기록되고(그래서 TUI OAST 탭에 같은 히트가 보이고), 종료해도 등록이 유지되어 나중에 oast resume ID로 이어받을 수 있으며, 아웃오브밴드 프로브 룰(ssrf_oast, xxe_oast, cmd_injection_oast, rfi_oast)이 페이로드를 만들어낼 세션을 갖게 됩니다. 세션이 없으면 이 룰들은 아무것도 계획하지 않고, gori run probe --active가 그 사실을 알려줍니다. 세션 명령들과 마찬가지로 --project / --db를 받으며, 정리는 oast release ID입니다.
oast list / resume / release: 프로젝트에 저장된 리스닝 세션입니다(아래의 프로바이더는 어디서 듣는지를, 세션은 그 위의 살아 있는 등록 하나를 뜻합니다). 등록은 그것을 만든 프로세스보다 오래 남고, 그래서 어제 심어둔 페이로드를 오늘도 지켜볼 수 있습니다.
gori run oast list # id, 프로바이더, 페이로드 호스트, 히트 수, 마지막 폴링
gori run oast list --format json
gori run oast resume 7 # 세션 #7 재개 후 콜백 스트리밍
gori run oast resume 7 --once --json # 한 번만 폴링하고 JSON 라인 출력 후 종료
gori run oast release 7 # 서버 측 등록 해제
resume과 release는 세션 id(7, 또는 list가 출력하는 #7)를 받습니다. resume은 서버 측 상태를 다시 살려 이미 심어둔 페이로드가 계속 resolve되게 한 뒤 폴링합니다. 받은 콜백은 모두 프로젝트에 기록되므로 TUI OAST 탭에서 같은 hit를 보게 되고, last_poll_at도 TUI 리스너처럼 갱신됩니다. Ctrl-C는 폴링만 멈추고 등록은 유지합니다. 정리는 release로 명시적으로 하며, 어느 쪽이든 저장된 콜백은 남습니다. 자동으로 재개되는 것은 없습니다.
| Option | Description |
|---|---|
--project=NAME · --db=PATH |
어느 프로젝트의 세션인지(기본값: GORI_PROJECT나 project switch로 정한 프로젝트, 없으면 가장 최근에 활성화한 프로젝트) |
--format=FMT |
list에서: text(기본) 또는 json |
--interval=SEC |
resume에서: 폴링 간격(기본값 5) |
--once |
resume에서: 한 번만 폴링하고 종료 |
--json |
resume에서: 페이로드와 각 콜백을 JSON 라인으로 출력 |
oast providers: 위의 즉석 listen과 달리 프로젝트에 저장되는 프로바이더입니다. 동사: list(기본), add, update, enable, disable, delete(rm).
gori run oast providers # 토큰은 [REDACTED]로 출력
gori run oast providers add --name lab --kind custom-http --host https://oast.lab.internal
gori run oast providers enable p_1
enable, disable, update, delete는 표시 이름이 아니라 프로바이더 id(p_1 또는 그냥 1)를 받습니다. add가 부여한 id를 출력하고, list에도 나옵니다. list는 settings.json의 전역 프로바이더도 g_<hex>로 보여 주며, 이것들은 여기서 읽기 전용입니다.
| Option | Description |
|---|---|
--name=NAME |
표시 이름. add에서는 필수 |
--kind=KIND |
interactsh(기본) | custom-http | webhook.site | BOAST | postbin |
--host=URL |
서버 / 베이스 URL(기본값: 해당 종류의 공개 프리셋) |
--token=TOK |
프로바이더 인증 토큰 |
--enabled / --disabled |
add / update 시 프로바이더를 켜거나 끔 |
--show-tokens |
list에서 [REDACTED] 대신 토큰을 그대로 출력 |
run jwt
JWT를 디코드, 검증, 재서명하거나 공격 페이로드를 생성합니다. 저장소 없는 계산이며, 토큰은 <token> 인자나 stdin에서 받습니다. 5개 세그먼트의 암호화 토큰(JWE)은 보호 헤더까지만 디코드합니다. gori는 alg/enc/kid를 읽을 뿐 클레임을 복호화하지 않습니다.
gori run jwt eyJhbGci... # 디코드(기본)
gori run jwt eyJhbGci... --encode --alg HS256 --secret s3cret
gori run jwt eyJhbGci... --encode --set role=admin --secret s3cret
gori run jwt eyJhbGci... --encode --alg ES256 --key ./private.pem
gori run jwt eyJhbGci... --verify --key ./public.pem
gori run jwt eyJhbGci... --attacks --key ./public.pem
| Option | Description |
|---|---|
--decode |
header / payload / signature 디코드(기본) |
--encode |
--alg와 --secret / --key로 토큰 클레임 재서명 |
--verify |
토큰 자신의 서명을 --secret / --key로 검증(둘 중 하나 필수, --secret ''은 빈 secret 확인); verified: yes|no와 reason을 출력하고 검증되지 않으면 1로 종료합니다. --format json은 code(signature_mismatch, unsigned, alg_unsupported, …)를 더합니다 |
--attacks |
테스트 페이로드 생성(alg:none, weak-secret, header injection) |
--alg=ALG |
--encode용 서명 alg: HS256(기본) | HS384 | HS512 | RS256/384/512 | PS256/384/512 | ES256/384/512 | EdDSA | none |
--secret=SECRET |
HS 알고리즘용 HMAC 시크릿 |
--key=PEM |
RS/PS/ES/EdDSA 알고리즘용 PEM 키: PEM 본문을 그대로 넣거나 .pem 파일 경로. --encode는 개인키가 필요하고, --verify는 공개키·인증서·개인키 중 아무거나 받으며, --attacks는 서버의 공개키를 받아 알고리즘 혼동(algorithm confusion) 페이로드를 추가합니다. --secret과 상호 배타적 |
--payload=JSON |
--encode: 재서명 전에 클레임을 통째로 교체(--set과 상호 배타적) |
--set=CLAIM |
--encode: 재서명 전에 클레임 하나를 key=value로 패치, 반복 가능; 값이 JSON으로 파싱되면(true/3) 그 타입, 아니면 문자열 |
--format |
text(기본) 또는 json |
run cookie
서명된 Flask / Rack / Django 세션 쿠키를 디코드, 검증, 브루트포스, 위조합니다. 저장소 없는 계산이며, 쿠키는 <cookie> 인자나 stdin에서 받습니다.
gori run cookie 'eyJ1c2VyIjoi...' # 기본은 decode, 형식은 자동 판별
gori run cookie 'eyJ1c2VyIjoi...' --crack --wordlist secrets.txt
gori run cookie --forge --type flask --secret s3cret --payload '{"user":"admin"}'
| Option | Description |
|---|---|
--decode |
payload / timestamp / signature로 파싱(기본) |
--verify |
--secret으로 서명 검증 |
--crack |
--secrets 또는 --wordlist로 시크릿 브루트포스 |
--forge |
--payload(Rack은 --value)를 --secret으로 재서명 |
--type=T |
flask | rack | django(기본: 자동 판별) |
--secret=S, --secrets=LIST, --wordlist=PATH |
서명 시크릿, 쉼표로 구분한 후보 목록, 또는 줄 단위 파일(혹은 저장한 wordlist의 이름) |
--payload=JSON |
서명할 세션 JSON(Flask / Django --forge) |
--value=B64 |
base64 Marshal 쿠키 값(Rack --forge, 불투명) |
--salt=SALT |
Flask / Django 서명 솔트 |
--algorithm=ALG |
Django HMAC 알고리즘: sha256(기본) 또는 sha1. 지정하지 않으면 --verify와 --crack은 자동으로 감지합니다 |
--timestamp=UNIX |
--forge에 찍을 유닉스 초(기본: 현재) |
--format |
text(기본) 또는 json |
run decoder
값에 대해 Decoder 체인을 실행합니다. 단계는 |, >, ,로 구분합니다.
exec:COMMAND로 쓴 단계는 컨버터가 아니라 외부 프로세스 훅입니다.
현재 값이 COMMAND의 stdin으로 가고 그 stdout이 단계의 출력이 됩니다. 셸 없이 exec되므로 세 구분자는
인자 안에 넣을 수 없습니다.
gori run decoder 'base64-decode | jwt-decode' "$TOKEN"
echo -n secret | gori run decoder 'sha256 | base64'
gori run decoder 'base64-decode > exec:./parse-envelope --json' "$BLOB"
gori run decoder list # 모든 컨버터(이름, 분류, 방향)
| Option | Description |
|---|---|
--input=STR |
변환할 값(없으면 두 번째 위치 인자, 그것도 없으면 stdin) |
-o, --output=MODE |
최종 바이트 렌더링: auto(기본) | text | base64 | hex |
--format |
text(기본) 또는 json(단계별 상세) |
run issues / notes
gori run issues --format markdown --export report.md
gori run issues --format sarif --export issues.sarif # GitHub code scanning / CI 대시보드에 업로드
gori run notes --all
스크립트에서 create / update로 이슈를 작성합니다:
gori run issues create --title "Reflected XSS on /search" --cvss 8.8 --host app.example.com --flow 42
gori run issues update 7 --status confirmed --notes "Verified on staging" --severity critical
gori run issues delete 7 --yes
# 노트 본문은 인자 벡터 대신 파일이나 파이프에서 읽을 수 있습니다
gori run issues create --title "IDOR on /v1/users/{id}" --severity high --notes-file writeup.md
report-generator | gori run issues update 7 --status confirmed --notes-stdin
--notes, --notes-file, --notes-stdin은 함께 쓸 수 없고, 본문은 바이트 그대로 읽힙니다. 여러 줄 UTF-8도 CRLF도 보존됩니다(프로젝트 env var로 바인딩된 값은 다른 이슈 필드와 마찬가지로 $ENV.NAME으로 마스킹됩니다). 파일이나 파이프로 넘긴 긴 작성물은 프로세스 목록과 셸 히스토리에 남지 않습니다. create에서는 이슈와 한 트랜잭션에 기록되므로 스크립트가 create 후 update를 이어 붙일 필요가 없습니다. 노트를 비울 때는 update에 --notes ''를 쓰고, 아무 바이트도 주지 않은 파일이나 파이프는 거부됩니다. 리포트 생성기가 죽었다고 해서 기존 작성물이 조용히 지워지면 안 되기 때문입니다. --notes-stdin도 --request-stdin과 같은 이유로 파이프나 리다이렉트(--notes-stdin < notes.md)를 요구하고 터미널은 거부합니다.
| Option | Description |
|---|---|
--format |
text(기본) | json | markdown | sarif. TUI의 Export가 쓰는 것과 같은 리포트 |
--export=PATH |
STDOUT 대신 PATH에 기록(바이트 그대로. STDOUT은 이스케이프를 제거) |
--include-sensitive |
sarif의 webRequest/webResponse 헤더에서 Authorization / Cookie / Set-Cookie / Proxy-Authorization / API 키 값을 [REDACTED] 대신 그대로 씁니다. 다른 형식에서는 효과가 없으며 STDERR로 알려 줍니다 |
create |
-t/--title (필수), --cvss (점수 또는 벡터. 이 값에서 severity를 자동 산정), -s/--severity (info|low|medium|high|critical), --host, --flow=ID, -n/--notes, --notes-file=FILE, --notes-stdin |
update <id> |
-t/--title, --cvss (새 점수/벡터. 빈 문자열로 초기화), -s/--severity, -n/--notes (빈 문자열로 초기화), --notes-file=FILE, --notes-stdin, --status (open|confirmed|false-positive|resolved) |
delete <id> (rm) |
이슈와 그 증거 링크를 삭제합니다. -y/--yes가 필요합니다. 보고서에는 남기고 닫힌 상태로만 표시하려면 update <id> --status=resolved를 쓰세요 |
--format sarif는 SARIF 2.1.0 로그를 씁니다. GitHub code scanning, DefectDojo, Azure DevOps가 그대로 읽는 형식입니다. 이슈 하나가 result 하나가 되며, severity는 SARIF level로 매핑되고(5단계 원본은 rank와 룰의 security-severity에 보존), false-positive/resolved 상태는 suppression으로 나가 정리한 이슈가 다시 열린 것으로 보이지 않습니다. 연결된 플로우는 실제 헤더와 (디코딩·64 KiB 상한) 본문을 담은 webRequest/webResponse로 함께 실립니다.
**이 로그에서 자격 증명 헤더 값은 기본적으로 [REDACTED]**이며, 무언가 가려졌으면 메시지에 gori/sensitiveHeadersRedacted: true가 붙습니다. SARIF 로그는 머신 밖으로 나가도록 만들어지는 문서라서 history --format json, evidence show와 같은 기본값을 따르고, --include-sensitive를 주면 값을 그대로 씁니다. 반복된 헤더는 그 필드가 허용하는 방식으로 합칩니다. 리스트 값은 , 로, Cookie 쌍은 ; 로 잇습니다. Set-Cookie는 합칠 수 없으므로 필드들을 줄바꿈으로 이어 쓰고, 메시지의 gori/setCookie 속성에도 필드마다 하나씩 나열합니다.
노트도 읽고 쓸 수 있습니다. 인자 없이 notes를 실행하면 목록을 보여주고(*가 활성 노트), notes <n>은 인덱스로 하나를 출력합니다:
gori run notes # 목록
gori run notes 2 # 2번 노트 출력
gori run notes create --text "SSRF candidate on /fetch"
echo "pasted from a scratchpad" | gori run notes create
gori run notes append 1 "confirmed on staging"
gori run notes update 1 --text "rewritten from scratch"
gori run notes delete 2 --yes
| Option | Description |
|---|---|
list |
--all은 요약 한 줄 대신 모든 노트를 전문으로 출력 |
create |
--text=TEXT, 위치 인자, 또는 STDIN |
update <n> (edit) · append <n> |
노트 텍스트를 교체하거나, 새 줄에 덧붙임(append, 또는 update --append). 텍스트는 --text, <n> 뒤의 단어들, 또는 STDIN이며 빈 텍스트는 거부됩니다. 쓰기 안에서 노트의 고정 id로 적용되므로, 바로 앞서 다른 쪽이 한 편집은 덮어쓰지 않고 그 뒤에 덧붙습니다 |
delete <n> (rm) |
인덱스 n의 노트 삭제. -y/--yes 필요 |
노트는 어디에도 다시 없는 글입니다. 캡처를 다시 하거나 실행을 반복해서 복원할 수 있는 대상이 아닙니다. 게다가 인덱스는 목록 위치라서, 앞의 노트가 하나 사라지면 notes delete 2가 가리키는 노트도 달라집니다. 그래서 delete는 -y/--yes 없이는 거부하고(대화형 확인 절차는 없습니다), 거부 메시지에 노트의 첫 줄(빈 노트라면 생략)을 인용해 번호를 잘못 짚었는지 삭제 전에 확인할 수 있게 합니다.
run notify
notify는 에이전트가 MCP reply_to_operator로 하는 일을 스크립트가 하는 방법입니다. summary는 알림 링과 Miss Ring 말풍선에 뜨는 한 줄이고, --detail(또는 --detail-file, -면 STDIN)은 링에서 ↵로 여는 긴 본문입니다:
gori run fuzz --flow 42 --auto --preset sqli > hits.txt && gori run notify "fuzz on flow 42 done" --level success --detail-file hits.txt
./nightly-scan.sh 2>&1 | tail -20 | gori run notify "nightly scan finished" --detail-file -
| Option | Description |
|---|---|
--detail=TEXT / --detail-file=PATH |
긴 본문(≤32 KiB). -는 STDIN. 둘은 함께 쓸 수 없음 |
--level=LEVEL |
info(기본) | success | warn | error. 그 밖의 값은 거부 |
--format json / --json |
{ok, id, project, summary, tui}. tui는 {live, windows} 또는 {unknown: true}이며, MCP reply_to_operator, get_current_context와 같은 형태 |
이 줄은 프로젝트 이벤트 피드에 script 소스로 기록되므로, 링에는 에이전트 답장에 붙는 ai 표시 없이 뜨고 Activity 패널에서 따로 걸러 볼 수 있습니다. TUI가 열려 있든 아니든 기록되고 notify는 어느 쪽이든 0으로 끝나며, 출력이 창이 있어 보여줬는지를 알려줍니다. 창이 없었다면 다음에 그 프로젝트를 여는 TUI가, 그동안 도착한 에이전트 답장과 함께 노트 하나로 요약해 보여줍니다.
run links
이슈나 노트가 가리키는 증거입니다. 캡처된 플로우, Repeater 세션, Fuzz / Miner 실행이 대상이 됩니다. Markdown 이슈 내보내기는 이미 이 포인터를 해석해 넣고, 여기서는 목록 조회와 편집을 합니다.
gori run links --owner=issue --id=7
gori run links add --owner=issue --id=7 --ref=flow --ref-id=42
gori run links delete --owner=note --id=2 --ref=repeater --ref-id=3
| Option | Description |
|---|---|
--owner=KIND |
소유자 종류: issue (기본값) 또는 note |
--id=N |
소유 이슈 / 노트 id. --note-position으로 노트를 지목하지 않으면 필수 |
--issue=N · --note=N |
--owner=issue --id=N / --owner=note --id=N의 줄임. evidence와 retest가 쓰는 철자입니다 |
--note-position=N |
안정 id 대신 gori run notes가 보여 주는 1부터 시작하는 목록 위치로 노트를 지목 |
--ref=KIND |
add / delete의 대상 종류: flow, repeater, fuzz, miner |
--ref-id=M |
add / delete의 대상 id |
--format=FMT |
list와 add에서 text (기본값) 또는 json |
대상이 정리(prune)된 포인터는 사라지지 않고 (stale)로 표시되므로, "증거가 없음"과 "증거가 사라짐"을 구분할 수 있습니다. add는 멱등이며, 양쪽 대상이 모두 존재해야 합니다.
run evidence
Issue의 동결된 증거입니다. 캡처된 플로우나 Repeater 탭의 교환 하나를, 그것이 취약점을 확인해 준 순간에 그대로 복사한 변경 불가 사본입니다. links가 live 출처 포인터라면 이것은 보관된 바이트입니다. Repeater의 다음 전송이나 History 보존 정리는 사본에 닿지 못하므로, 응답이 취약점을 확인해 줄 때 한 번, 재테스트 뒤에 한 번 더 동결하세요. 스냅숏 하나를 여러 Issue에 연결할 수 있고, 마지막 Issue 연결을 끊어도 다시 연결하거나 명시적으로 삭제할 때까지 고아 상태로 남습니다.
gori run evidence freeze --issue=7 --ref=repeater --ref-id=3 # 사본 + live 링크
gori run evidence freeze --issue=7 --ref=flow --ref-id=42 --no-link
gori run evidence --issue=7 # 목록: 출처, 시각, 상태, 크기, SHA-256
gori run evidence # 프로젝트 전체 보관함(고아 포함)
gori run evidence show 12 # 사본 출력, 자격 증명은 가려짐
gori run evidence show 12 --include-sensitive --format=json
gori run evidence link 12 --issue=9 # 다른 Issue에도 연결
gori run evidence unlink 12 --issue=7 # 고아가 되어도 스냅숏 유지
gori run evidence delete 12 --yes
| Option | Description |
|---|---|
--issue=N |
연결할 Issue. freeze, link, unlink에서 필수. list에서는 한 Issue의 사본으로 좁히고, 생략하면 프로젝트 전체 보관함(최신순, 고아 포함)을 출력 |
--ref=KIND |
freeze의 출처 종류: flow 또는 repeater. fuzz / miner 세션은 교환이 하나가 아니라 대상이 아닙니다 |
--ref-id=M |
freeze의 출처 id |
--no-link |
freeze가 사본만 만듭니다. 기본값은 links add가 만들 live 링크도 같은 트랜잭션에 함께 기록 |
--allow-drift |
저장된 응답이 도착한 뒤에 요청이 수정된 Repeater 탭도 freeze합니다. 이 옵션 없이는 거부됩니다(아래 참고) |
--include-sensitive |
show가 Authorization / Cookie / Set-Cookie / API-key 값을 [REDACTED] 대신 그대로 출력. SHA-256은 저장된 원본 바이트를 대상으로 하므로 검증에는 이 옵션이 필요합니다 |
--format=FMT |
freeze, list, show에서 text (기본값) 또는 json |
한 번도 보내지 않은 Repeater 탭은 요청만 동결하지 않고 거부하며, 응답이 아직 도착하지 않은 플로우도 거부합니다. Repeater 복사본은 탭에 저장된 요청(바인딩은 펼치지 않은 상태)과 저장소의 마지막 응답(성공한 전송의 것)을 짝지으므로, 취약점을 확인해 준 전송 직후에 동결하세요.
요청 드리프트. Repeater 탭은 요청 하나와 응답 하나를 들고 있고, 요청을 고쳐도 옆의 응답은 그대로입니다. 즉 전송 → 요청 수정 → 동결 순서로 하면 수정된 요청과 이전 교환의 응답이 한 사본에 묶입니다. gori는 각 전송이 실제로 나간 요청의 SHA-256을 기록해 이를 구분합니다. freeze는 그런 탭을 이름을 밝혀 거부하고 다시 보내라고 안내하며, --allow-drift를 주면 어긋난 쌍을 그대로 기록하고, TUI는 기록하기 전에 묻습니다. 이 버전보다 오래된 gori가 저장한 응답에는 기록된 다이제스트가 없으며, 그런 행은 드리프트가 아니라 "알 수 없음"으로 취급합니다. WebSocket 복사본은 핸드셰이크이며 프레임 기록은 복사되지 않습니다. 프로젝트의 동결 증거 총량은 256 MB로 제한되며, 넘으면 사본을 삭제할 때까지 freeze가 거부합니다. show는 본문을 디코딩해 텍스트 형식에서는 64 KB에서 자르고(저장된 사본은 온전합니다), JSON 형식은 get_flow와 같은 모양입니다.
run retest
Issue의 리테스트: 결함을 재현하는 Repeater 전송을 순서대로 나열하고, 각 단계에 역할과 기대 결과 하나를 붙인 것, 그리고 최근 실행 기록입니다. links는 무엇이 관련되어 있는지를, evidence는 결함을 증명한 바이트를 남기지만, 둘 다 실행할 수는 없습니다. "먼저 #4로 로그인하고, 그다음 #5가 403을 주어야 한다"는 서술을 CI가 실행할 수 있는 형태로 바꾸는 표면입니다.
gori run retest add --issue=7 --repeater=4 --role=setup # 먼저 로그인
gori run retest add --issue=7 --repeater=5 --role=baseline --assert=status:200
gori run retest add --issue=7 --repeater=6 --role=variant --assert=status:403
gori run retest --issue=7 # 각 단계가 무엇을 보낼지 포함한 계획
gori run retest move 9 --to=1 # 순서 변경(id는 --format=json)
gori run retest update 9 --role=control --assert=body:same
gori run retest remove 9 # 단계 하나 제거(`rm`도 됨)
gori run retest clear --issue=7 --yes # 모든 단계 삭제
gori run retest run --issue=7 # `pass`일 때만 종료 코드 0
gori run retest runs --issue=7 # 보존된 실행 기록
gori run retest show 3 # 한 실행의 결과 표
gori run retest forget 3 # 기록에서 실행 하나를 지움
| Option | Description |
|---|---|
--issue=N |
대상 Issue. steps, add, clear, run, runs에서 필수 |
--repeater=M |
add: 이 단계가 보낼 Repeater 세션(id는 gori run repeater list) |
--role=ROLE |
setup | baseline | variant(기본값) | control | cleanup |
--assert=EXPR |
기대 결과 하나(아래). 생략하면(또는 update에서 빈 값) 결과만 기록하고 아무것도 단언하지 않습니다 |
--to=POS |
move: 새 위치(1부터, 목록 범위로 클램프) |
-y, --yes |
run: 상태를 바꾸는 메서드가 포함된 배치를 확인. clear: 삭제를 확인 |
--allow-cleanup |
run: gori가 전송을 거부한 뒤에도 cleanup 단계를 보냅니다 |
--allow-unscoped |
run: 프로젝트 스코프 밖으로도 전송. Sandbox와 명시적 exclude는 그대로 적용됩니다 |
--no-record-history |
run: 각 전송을 History에 기록하지 않습니다(기본값은 기록이며, 리테스트도 증거입니다) |
-k, --insecure-upstream |
run: 업스트림 TLS 인증서를 검증하지 않습니다 |
--slot=NAME |
run: 모든 단계를 이 세션 슬롯으로 전송(헤더 오버레이와 $BIND.NAME 테이블) |
--timeout=SEC |
run: 단계별 연결 + 유휴 타임아웃(기본 20) |
--limit=N |
runs: 출력할 실행 개수 (기본값 20, 보존되는 전부) |
--format=FMT |
text(기본값) 또는 json |
forget RUN은 실행 요약과 결과 행을 지웁니다. 그 실행을 만든 단계도, 각 전송이 기록한 History 플로우도 그대로 남습니다. 지우는 것은 보고이지 증거가 아닙니다. 기록은 최근 20개로 알아서 정리되므로, 이 명령은 애초에 기록에 남으면 안 되는 실행(잘못된 대상으로 보냈거나, 이후 수정된 스코프에서 돈 배치)을 위한 것입니다.
단언(단계당 하나):
| 표현식 | 통과 조건 |
|---|---|
status:200 |
응답 상태가 정확히 200 |
status:2xx |
상태가 그 클래스 안 |
status:200-299 |
상태가 그 범위(양끝 포함) 안 |
json:data.user.id |
JSON 필드가 존재(값이 null이어도 "있는" 것으로 셉니다) |
json:data.role=admin |
JSON 필드가 리터럴과 같음. 리터럴은 타입 없는 텍스트라 n=3은 숫자 3과 문자열 "3" 모두에 일치 |
json-absent:data.token |
JSON 필드가 없음 |
body:same |
디코딩된 본문이 직전 baseline 단계와 동일 |
body:diff |
그것과 다름 |
JSON 경로는 sequence --jsonpath와 같은 문법입니다: 점 표기(data.items.0.id) 또는 괄호 표기($.data.items[0]["id"]). 와일드카드, 필터, .., 닫히지 않은 괄호처럼 gori가 읽을 수 없는 경로는 단계를 추가할 때 거부되므로, 경로를 한 번도 읽지 못해서 json-absent:가 통과하는 일은 없습니다. 읽을 수 있는 경로가 없는 필드를 가리키면 평범한 부재입니다.
각 단계는 실행 시점에 Repeater 탭이 들고 있는 요청을 그대로 보냅니다. 그래서 리테스트는 고쳐지는 요청을 따라가고, gori run evidence는 당시 모습 그대로를 얼립니다. 모든 전송은 프로젝트 스코프와 Sandbox 게이트를 통과하며 History에 src:retest로, 이슈와 단계 번호를 달고 기록됩니다. 그래서 탭이 한참 뒤에 바뀌어도 결과 행은 자기가 보고한 바로 그 응답을 열 수 있습니다. 탭에 저장된 응답은 덮어쓰지 않습니다.
setup 단계가 실패하면 측정 단계는 중단되지만(그 전제 위에서 이후가 측정되므로) cleanup은 실행됩니다. gori가 전송을 거부하면(스코프, Sandbox, exclude 규칙) 그 뒤는 모두 건너뛰며, cleanup도 --allow-cleanup 없이는 보내지 않습니다. 건너뛴 단계도 이유가 적힌 행을 남기므로 부분 실행이 통과처럼 읽히지 않습니다. body: 단언은 기준이 없을 때는 물론, 직전 baseline 단계가 자기 기대 결과를 못 맞췄을 때도 통과가 아니라 inconclusive입니다. 읽기를 확립하지 못한 기준은 아무것도 고정하지 못하므로, 그렇지 않으면 status:200 기준이 받은 403 오류 페이지와 변형을 비교해 둘 다 오류 페이지인데도 "본문이 그대로다"라고 보고하게 됩니다.
판정이 pass가 되려면 모든 단계가 실행되고 모든 단언이 결정되어야 합니다. blocked는 fail보다 우선합니다. gori가 끝내기를 거부한 실행은 대상이 아니라 스코프 설정에 대한 사실이기 때문입니다. run은 pass에서 0, 그 외에는 1로 종료합니다. Issue당 최근 20개 실행이 보존됩니다.
run rewriter
스크립트에서 Match & Replace 규칙을 관리합니다. Rewriter 탭이 편집하는 것과 같은 규칙이며, 실시간 프록시 트래픽에 적용됩니다:
gori run rewriter # 적용 순서대로 규칙 목록
gori run rewriter add --op set_header --target request \
--find X-Forwarded-For --value 127.0.0.1 --host '*.example.com'
gori run rewriter add --op replace --target response --part body \
--match regex --find 'secret=(\w+)' --value 'secret=[redacted]'
gori run rewriter add --op remove_header --target response \
--find Content-Security-Policy --scope global # 모든 프로젝트에 적용
gori run rewriter preview --op replace --part body --find password --value hunter2
gori run rewriter add --op short_circuit --map-dir ./tampered \
--strip-prefix /static/ --fallthrough # 없는 파일만 원본으로
gori run rewriter add --op short_circuit --find /api/pay --fault reset
gori run rewriter add --op short_circuit --from-flow 42 # 캡처된 응답을 스텁으로
gori run rewriter disable 3
gori run rewriter disable 2 --scope global # 이 프로젝트에서만 끄기
gori run rewriter disable 2 --scope global --everywhere # 기본값을 꺼서 모든 곳에서 끄기
gori run rewriter rm 3
| Option | Description |
|---|---|
--op=OP |
replace(기본값), add_header, set_header, remove_header, short_circuit, pipe, json_set, json_remove |
--side=SIDE (--target) |
request(기본값) 또는 response |
--part=PART |
head(기본값, json 동작은 body), body, 또는 ws(WebSocket 메시지). replace, pipe, json 동작에서만 의미가 있고 json 동작은 head를 거부 |
--match=MODE |
literal(기본값) 또는 regex. replace, pipe, short_circuit에 적용됩니다. 정규식 치환은 $1, $2를 쓰고 $$는 리터럴 $ |
--response-file=PATH |
short_circuit: 미리 준비한 응답을 PATH에서 읽음(-는 stdin이며 파이프나 리다이렉트만 받고 터미널은 거부) |
--body-file=PATH |
short_circuit: PATH를 응답 본문으로 제공하며, 파일이 바뀌면 다시 읽음 |
--map-dir=DIR |
short_circuit: 요청 경로가 가리키는 파일을 DIR에서 제공(Map Local). --value는 선택 사항인 헤드 템플릿이 됨 |
--strip-prefix=PATH |
--map-dir와 함께: 경로를 DIR 아래에 붙이기 전에 떼어 낼 URL 접두사(/static/). --find가 없으면 요청 줄에서 이 접두사로 매칭 |
--fallthrough |
--map-dir와 함께: 파일이 없는 요청을 502 대신 원본으로 넘김 |
--fault=KIND |
short_circuit: 응답 없이 close, reset, hang 중 하나로 답함 |
--hang=MS |
--fault=hang과 함께: 닫기 전까지 붙잡는 시간(기본 30000) |
--delay=MS |
short_circuit: 답하기 전에 기다리는 시간(최대 120000) |
--from-flow=ID |
short_circuit: flow ID의 캡처된 응답을 규칙에 복사. --find, --host, --value가 초안을 덮어씀 |
-f, --find=FIND |
--from-flow나 --map-dir --strip-prefix가 아니면 필수. 대상이 되는 리터럴, 패턴, 헤더 이름, 또는 (json 동작) $.user.role 같은 JSON 경로 |
-v, --value=VALUE |
치환할 텍스트, 헤더 값, json_set의 새 값(JSON 텍스트: '"admin"', true), 또는 --op=pipe일 때 실행할 명령. 프로세스 훅 참고 |
--host=GLOB |
규칙을 그 호스트와 서브도메인으로 한정(example.com은 api.example.com에도 매칭되지만 xexample.com에는 매칭되지 않음). *는 명시적 와일드카드. 생략하면 전체 적용 |
--name=NAME |
규칙 목록에 표시할 라벨 |
--disabled |
규칙을 만들되 활성화하지 않음 |
--scope=SCOPE |
project(기본값) 또는 global. 전역 규칙은 settings.json에 저장되어 모든 프로젝트에 적용됨. 목록에서는 그 저장소의 규칙만 표시(기본: 둘 다) |
--everywhere |
전역 규칙의 enable/disable에서, 이 프로젝트의 오버라이드 대신 규칙 자체의 기본값을 변경 |
preview는 같은 규칙 플래그를 받아, 규칙을 저장하지 않고 저장된 플로우 중 몇 개가 바뀌었을지 보고합니다. rm(delete), enable, disable은 목록의 규칙 id와 함께 --scope도 받습니다. 두 저장소가 규칙 번호를 각자 매기므로 id 하나가 서로 다른 두 규칙을 가리키기 때문입니다. 목록은 범위를 G/P 접두어로 출력하고(G*는 이 프로젝트가 해당 전역 규칙의 기본값을 오버라이드했다는 뜻), 프록시가 적용하는 순서 그대로 전역 규칙을 먼저 보여 줍니다. 전역 규칙과 프로젝트 규칙을 참고하세요.
본문 규칙은 HTTP/1.1과 HTTP/2 모두에서 필요에 따라 Content-Length를 다시 맞추고 청크를 해제합니다. 대화형 편집기는 Proxy & History를 참고하세요.
rewriter preset: 응답 수정 프리셋 설치. 평범한 Match & Replace 규칙을 써 주는 이름 붙은 출발점입니다. 동사: list, add <name>.
gori run rewriter preset list
gori run rewriter preset add unhide-hidden-fields
gori run rewriter preset add remove-csp --scope global --disabled
이름은 unhide-hidden-fields, enable-disabled-fields, remove-length-limits, strip-validation, remove-csp, remove-security-headers, disable-sri입니다. add는 --scope=project|global과 --disabled(무장하지 않고 설치해 먼저 검토)를 받습니다. 설치되는 규칙은 rewriter add와 같은 경로를 지나므로 이후에도 목록에 나오고 편집·삭제됩니다. 같은 프리셋을 두 번 설치하면 병합되지 않고 눈에 보이게 중복됩니다.
rewriter extract: 세션 바인딩을 선언하는 규칙입니다. $BIND.NAME을 어느 응답의 어디에서 읽을지 정합니다. 동사: list(기본), add, rm(delete), enable, disable.
gori run rewriter extract add --name SESS --kind cookie --selector session --host '*.example.com'
gori run rewriter extract add --name CSRF --kind regex --selector 'name="csrf" value="([^"]+)"'
| Option | Description |
|---|---|
--name=NAME |
$를 뺀 바인딩 이름. 필수 |
--kind=KIND |
cookie(기본), header, regex, position, jsonpath |
--selector=SEL |
쿠키 / 헤더 이름, 정규식, 또는 JSON 경로 |
--range=A:B |
position 전용: 디코드된 본문의 반열린 바이트 범위 |
--when=FILTER |
어떤 메시지를 읽을지, 인터셉트 필터 문법으로(''는 전부) |
--host=GLOB |
호스트 글롭으로 한정(''는 전부) |
--disabled |
규칙을 만들되 활성화하지 않음 |
rewriter bindings: 그 규칙들이 선언한 이름을 나열합니다(--format text|json). 값은 여기에 나오지 않으며, 나올 수도 없습니다. 바인딩 값은 실행 중인 gori의 메모리에만 있고 어디에도 기록되지 않으므로 다른 프로세스가 읽을 것이 없기 때문입니다. 살아 있는 값 테이블은 Rewriter 탭의 bindings 하위 탭에서 봅니다. 헤드리스 스윕에서는 --bind-from이 같은 프로세스 안에서 값을 채웁니다. 명령줄에서 세션 바인딩 쓰기를 참고하세요.
run grpc
gRPC .proto 렌즈를 명령줄에서: 이 프로젝트가 캡처된 gRPC를 어떤 스키마로 렌더하는지, 각 조각이 어디서 왔는지.
gori run grpc # 무엇이 로드됐는지 (schema가 기본 동사)
gori run grpc schema --format json
gori run grpc reflect https://api.test:443 # ACTIVE: 대상에게 디스크립터를 요청
gori run grpc forget https://api.test:443 # 캐시된 대상 하나 버리기 (`rm`도 받습니다)
gori run grpc forget --all
schema와 forget은 프로젝트 DB 밖을 건드리지 않습니다. 보내는 쪽은 reflect 하나입니다. 대상의 grpc.reflection.v1 서비스에(없으면 v1alpha로, 실제 배포된 서버 대부분은 아직 이쪽입니다) 서비스 목록, 각 서비스를 선언한 파일, 그 파일들의 import 순으로 그래프가 닫힐 때까지 요청해 결과를 프로젝트에 캐시합니다. 다른 액티브 gori run 명령과 같은 스코프 게이트를 지나므로 범위를 벗어난 대상은 연결하기 전에 거부됩니다. 두 리플렉션 버전 모두 응답하지 않는 서버는 조용히 실패하지 않고 그렇다고 말하며, 무엇도 스스로 다시 받아오지 않습니다.
| 옵션 | 설명 |
|---|---|
--format=FMT |
schema와 reflect에서 text(기본) 또는 json |
--allow-unscoped |
reflect: 대상이 프로젝트 스코프 밖이어도 보냅니다 |
-k, --insecure-upstream |
reflect: 대상의 TLS 인증서를 검증하지 않습니다 |
--timeout=SECONDS |
reflect: 작업당 타임아웃(기본값: 프로젝트의 io 타임아웃) |
--all |
forget: 캐시된 리플렉션 대상 전부 버리기 |
디스크립터 셋 파일(Project settings → Proto schema)은 forget으로 해제되지 않습니다. 프로젝트 설정에서 경로를 지우세요. 파일과 리플렉션 페치가 어떤 선언에 대해 어긋나면 그 수는 redefined로 보고되고 대상 자신의 말이 우선합니다.
run colormarker
Colormarker 규칙을 관리합니다. 캡처된 History의 어떤 행을 어떤 방식으로 칠할지 정하는 규칙이며, 표시 전용입니다. 트래픽을 전혀 수정하지 않으므로 Match & Replace 규칙과 달리 잘못 써도 목록이 오해를 부를 뿐, 메시지가 바뀌지는 않습니다.
gori run colormarker # 우선순위 순으로 규칙 목록
gori run colormarker add --when 'status:>=500' --color red --style full --name 'prod 5xx'
gori run colormarker add --when 'host:cdn' --color blue --style strip --scope global
gori run colormarker update 2 --color orange # 우선순위를 지키며 제자리 수정
gori run colormarker move 2 --up # 우선순위 올리기
gori run colormarker preview --when 'method:DELETE'
gori run colormarker preview --when 'resp.body:secret' --scope global
gori run colormarker disable 1 --scope global # 이 프로젝트에서만 끄기
gori run colormarker disable 1 --scope global --everywhere # 모든 프로젝트의 기본값을 끄기
gori run colormarker rm 3
| 옵션 | 설명 |
|---|---|
-w, --when=FILTER |
필수. 플로우가 만족해야 할 조건 (아래 참고) |
--color=NAME |
red, orange, yellow(기본), green, blue, purple. 활성 테마 팔레트로 해석되므로 밝은 테마와 어두운 테마 양쪽에서 제대로 읽힙니다. 또는 사용자 색상의 이름(아래 참고)이며, 이쪽은 절대 hex 값을 그대로 지닙니다 |
--style=STYLE |
full(기본)은 행 전체 배경을 칠하고, strip은 TIME 앞 좁은 컬럼에 색 셀 하나를 칠합니다 |
--name=NAME |
규칙 목록에 표시할 라벨 |
--disabled |
비활성 상태로 생성 |
--scope=SCOPE |
project(기본) 또는 global. 전역 규칙은 settings.json에 저장되어 모든 프로젝트에 적용됩니다. 목록에서는 그 저장소의 규칙만 표시(기본: 둘 다) |
--everywhere |
전역 규칙의 enable/disable 시: 이 프로젝트의 오버라이드가 아니라 규칙 자체의 기본값을 변경 |
--up / --down |
move 시: 우선순위를 올리거나 내림 |
--limit=N |
preview 시: 스캔할 최근 플로우 수(기본 500) |
update에서는 모든 필드가 선택이며 규칙의 현재 값이 기본입니다. --color만 주면 색만 바꾸고 --when만 주면 조건만 바꿉니다. 삭제 후 재생성 대신 이 명령을 쓰세요. 색상 규칙은 위치가 곧 의미이고(첫 번째로 매칭되는 활성 규칙이 행을 칠합니다) 다시 추가한 규칙은 자기 스코프 블록 맨 끝에 놓여, 전에 앞서던 규칙들보다 뒤로 밀립니다. enable / disable은 따로 두었습니다. 전역 규칙에서 그 둘은 라이브러리가 아니라 이 프로젝트에 대한 진술이기 때문입니다.
우선순위가 곧 규칙 집합의 의미입니다. Match & Replace 규칙은 합성되어 활성화된 모든 규칙이 순서대로 실행되지만, 색상 규칙은 해석됩니다. 첫 번째로 매칭되는 활성 규칙이 행을 칠하고 나머지는 조회조차 되지 않습니다. move가 rewriter에는 없고 여기에만 있는 이유입니다. 전역 규칙이 프로젝트 규칙보다 먼저 해석되므로, 상시 정책이 로컬 레이어보다 우선합니다.
--when은 History QL 조건입니다. 자기가 칠하는 목록 위의 필터 바와 문법도, 필드 집합도, 답도 같으며 ~정규식과 AND / OR / NOT, -부정, (그룹)을 모두 포함합니다. 캡처된 행이 스스로 답할 수 있는 항(host: path: url: method: scheme: status: proto:)은 쿼리 없이 메모리에서 매칭되고, 나머지(body: header: size: dur: stub: static: src: scope:)는 다시 그릴 때마다 규칙당 한 번의 배치 쿼리로 프로젝트 DB에 대해 해석됩니다. 그냥 두면 조용히 실패할 네 가지가 있어, gori는 거부하거나 경고합니다.
body:는 여기서 텍스트 인덱스가 아니라 저장된 바이트를 스캔합니다. 그래서 필터 바의body:가 건너뛰는 바이너리 바디까지 닿지만, 각 방향 앞 64 KiB까지만이고 바이트는 캡처된 그대로입니다. 그 경계를 넘어선 매치나 압축된 바디 안의 매치는 칠해지지 않습니다. (경고)host:는 DNS 레이블 글롭이 아니라 부분문자열입니다.host:alpha.test는xalpha.test도 매칭합니다. (경고)- 아직 응답이 없는 플로우에는 status가 없습니다.
status:규칙은 응답이 도착한 뒤에 그 행을 칠합니다. (경고) scope:는s표시 렌즈와 무관하게 프로젝트의 스코프 규칙을 따릅니다. 스코프 규칙이 하나도 없으면 아무것도 스코프 안에 있지 않으므로scope:in과scope:out둘 다 아무것도 칠하지 않고, 반대로 부정형(-scope:in)은 모든 행을 칠합니다. (경고)
경고가 아니라 거부되는 것들: 모르는 필드(그냥 두면 hsot:가 자유 텍스트 검색이 되어 규칙이 영원히 발동하지 않습니다), 컴파일되지 않는 ~ 패턴, 그 필드가 받지 않는 값(size:>bogus처럼 쓰면 항이 버려져 규칙이 말한 것보다 더 많이 칠하게 됩니다), 그리고 모든 플로우에 매칭되는 조건(빈 값이나 입력 중인 host:).
preview는 조건이 최근 플로우 중 몇 개에 매칭되는지와, 실제로 몇 개를 칠하게 되는지를 함께 보고합니다. 앞선 활성 규칙이 이미 그 행을 차지했다면 두 숫자가 달라집니다. preview가 --scope도 받는 이유가 이것입니다. 전역 규칙은 모든 프로젝트 규칙보다 먼저 해석되므로, --scope=global 후보에게서 행을 뺏을 수 있는 프로젝트 규칙은 하나도 없습니다. update, rm(delete), enable, disable, move는 목록의 규칙 id와 --scope를 받습니다. 두 저장소가 서로 독립적으로 번호를 매기므로 id만으로는 서로 다른 두 규칙을 가리키기 때문입니다. 목록은 스코프를 G/P 접두사로 출력합니다(G*는 이 프로젝트가 해당 전역 규칙의 기본값을 오버라이드했다는 뜻).
탭은 기본적으로 바 위에 없습니다. 0으로 열거나, settings:tabs에서 Rewriter 옆에 슬롯을 줄 수 있습니다. 대화형 편집기는 프록시 & History를 참고하세요.
colormarker color
사용자 색상 팔레트입니다. 내장 6색 위에 얹어 모든 프로젝트의 색상 선택기에 함께 제공되는 이름 있는 색상입니다. 내장 색은 활성 테마를 거쳐 해석되므로 밝은 팔레트와 어두운 팔레트 양쪽에서 제대로 읽히지만, 사용자 색상은 절대 hex 값을 그대로 지니며 테마를 따라가지 않습니다. 팔레트가 주지 않는 색조를 얻는 대신 치르는 대가입니다. 색상은 settings.json(colormarker.colors)에 저장되므로 태생적으로 전역입니다.
gori run colormarker color list
gori run colormarker color add --name hotpink --hex '#ff69b4'
gori run colormarker color update hotpink --hex '#e0559b' # 이름은 두고 색만 변경
gori run colormarker color update hotpink --name fuchsia # 색은 두고 이름만 변경
gori run colormarker color rm fuchsia
gori run colormarker add --when 'method:DELETE' --color hotpink
이름이 곧 식별자입니다. 규칙의 --color에 저장되는 값이자 선택기에 보이는 값이므로 소문자로 정규화되고, 중복될 수 없으며, 내장 색 이름과 같을 수 없습니다. update는 두 옵션 중 하나만 줘도 됩니다.
색상을 지우거나 이름을 바꿔도 그 색을 쓰던 규칙은 의도적으로 고쳐 쓰지 않습니다. 규칙은 옛 이름을 그대로 들고 있다가 눈에 띄는 기본색으로 대체되어 그려지므로, 같은 이름으로 색을 다시 추가하면 원래대로 돌아옵니다. 이 명령에서 모든 프로젝트의 데이터베이스에 손을 뻗을 수는 없고, 절반만 적용된 연쇄 수정은 이름 하나가 붕 뜨는 것보다 나쁩니다. 색상 값만 바꾸는 경우는 다릅니다. 규칙은 색을 이름으로 참조하므로 어디서든 새 hex를 그대로 따라갑니다.
run views
History 뷰를 관리합니다. History 목록을 좁히는, 이름 붙은 QL 쿼리입니다. 뷰는 렌즈입니다. 다른 필터를 대체하지 않고 그 위에 AND로 얹히므로, gori run history --view History -q 'status:5xx'는 둘 다를 뜻합니다. 기본 뷰 일곱 개가 모든 프로젝트에 들어 있습니다: 출처 3종 All / History(src:proxy) / History + Repeater(기본값), 그리고 WebSocket·gRPC·SSE·Errors. 저장된 뷰는 컬러 룰과 똑같이 두 저장소에 나뉘어 삽니다.
gori run views # 목록; TUI의 활성 뷰에 ● 표시
gori run views --scope global --format json
gori run views add 'acme errors' -q 'host:api.acme.test status:5xx'
gori run views add 'proxied' -q 'src:proxy' --scope global
gori run views set 'acme errors' -q 'status:>=500' # 이름은 그대로, 쿼리만 교체
gori run views rename 'acme errors' --to 'acme 5xx'
gori run views scope 'acme 5xx' --to global # 두 저장소 사이로 옮기기
gori run views rm 'acme 5xx' --scope global
| Option | Description |
|---|---|
-q, --query=QL |
add와 set에 필수. 뷰의 쿼리이며, 필터 바와 run history -q가 받는 것과 같은 History QL입니다 |
--scope=SCOPE |
project(기본값) 또는 global. 글로벌 뷰는 settings.json에 살며 모든 프로젝트에 나타납니다. 목록에서는 builtin도 받으며, 그 저장소의 뷰만 표시합니다 |
--to=NAME |
rename에서: 새 이름 |
--to=SCOPE |
scope에서: 옮길 저장소, project 또는 global |
뷰는 이름으로 지목합니다. --view와 피커가 받는 것이 이름이고, id를 두면 한 가지를 두 가지로 부르는 셈이기 때문입니다. 이름은 스코프 안에서 유일하며 스코프끼리는 겹칠 수 있습니다. 그럴 때 --view는 project → global → 기본 제공 순으로 고릅니다. 프로젝트 환경변수와 호스트 오버라이드가 이미 쓰는 것과 같은 우선순위입니다. 모든 변경 명령이 --scope를 받는 이유는 colormarker rm과 같습니다. 두 저장소는 각각 따로 지목되며, 어느 쪽을 뜻했는지 추측하면 엉뚱한 뷰를 고치게 됩니다. 목록은 스코프를 G/P/· 접두사로 찍습니다.
쿼리는 실행할 때가 아니라 저장할 때 검사합니다. 없는 필드를 쓴 쿼리, 깨진 정규식, 그리고 모든 항이 버려질 쿼리는 거절합니다. 마지막 것이 중요합니다. 아무것도 좁히지 못하는 뷰인데도 v: 칩은 좁히고 있다고 주장하게 되기 때문입니다. 같은 검사가 세 표면 모두에서 돌아가므로, TUI가 거절한 뷰를 CLI가 받아 주는 일은 없습니다.
기본 제공 뷰는 편집도 삭제도 되지 않으며, 저장된 뷰가 기본 뷰의 이름을 가져갈 수도 없습니다. 가려 버리면 --view로 그 기본 뷰에 다시 닿을 수 없기 때문입니다.
지금 보고 있는 뷰를 지우면 그 프로젝트는 All로 돌아갑니다. 지운 글로벌 뷰를 가리키던 다른 프로젝트의 포인터는 무해하게 남습니다. id는 단조 증가 카운터에서 나오고 재사용되지 않으므로 다른 뷰가 그 자리를 물려받을 수 없습니다. 대화형 피커는 프록시 & History를 보세요.
run project
프로젝트 목록/생성/내보내기/가져오기/삭제, 또는 프로젝트 스코프 설정(스코프 규칙, env 변수, 호스트 오버라이드) 관리:
gori run project --format json
gori run project list
gori run project list --all
gori run project list --query=acme
| Option | Description |
|---|---|
--all |
캡처된 것이 없는 프로젝트까지 모두 출력 |
--query=TEXT |
표시 이름·디렉터리 슬러그·짧은 id·바인딩된 워크스페이스 경로에 TEXT를 포함하는 프로젝트만 남김(대소문자 무시) |
--format=FMT |
text(기본) 또는 json |
list는 비어 있는 프로젝트(캡처된 플로우가 0개)를 숨깁니다. 워크트리나 체크아웃마다 프로젝트를 만들다 보면 수백 개가 쌓여, 정작 트래픽이 든 두세 개가 묻히기 때문입니다. 비어 있는지는 파일 크기가 아니라 행 수로 셉니다. 방금 만든 프로젝트도 3월에 남은 찌꺼기와 크기가 같습니다. 다음 두 개는 아무리 비어 있어도 항상 표시하고 표시자를 붙입니다. ◆는 --project 없이 실행한 gori run이 읽는 프로젝트, ◇는 TUI가 마지막으로 연 프로젝트입니다. --format json에서는 각각 current, tui_active 필드이고, flows 개수와 프로젝트 description(개수를 셀 때 같은 핸들로 함께 읽으므로 추가 open 비용이 없습니다)이 함께 나옵니다. 몇 개를 숨겼는지는 stderr로 나가므로 JSON 파이프는 깨끗한 배열로 남습니다.
--query는 그 나머지 절반입니다. 프로젝트를 가리키는 모든 철자에 대한 부분 문자열이라, 이름이 가물가물해도 찾을 수 있습니다. 정확한 이름·슬러그·짧은 id를 요구하는 --project보다 느슨합니다. 나열된 프로젝트의 데이터베이스를 전부 여는 플로우 집계 앞에 적용되므로, 수백 개를 가진 호스트에서는 빠른 경로이기도 합니다. --all과는 직교하며, 표시가 아니라 행 공급원을 좁히므로 제외한 것을 stderr로 밝힙니다. 매치가 0건이면 이 호스트에 실제로 몇 개가 있는지를 알려 주고, ◆ 프로젝트가 걸러졌다면 그 사실을 이름과 함께 말합니다. 빈 프로젝트 숨김 기본값과 달리 --query는 그 행을 고정해 두지 않기 때문입니다. --format json은 바인딩된 workspace도 함께 실으므로, 소비자도 쿼리와 같은 사실로 걸러낼 수 있습니다. 같은 좁히기를 MCP list_projects{query}가 같은 술어로 제공합니다.
project create
트래픽을 캡처하지 않고 프로젝트를 만듭니다. gori run capture --project=NAME도 필요할 때 만들어 주지만, 이 명령은 요청을 보내지 않으므로 프록시를 띄우기 전에 스코프와 env를 미리 구성할 수 있습니다.
gori run project create "API test"
gori run project create api-test --description="staging sweep"
gori run project create api-test --format json
| Option / subcommand | Description |
|---|---|
<name> |
표시 이름. 공백이 들어가면 따옴표로 감쌉니다 |
--description=TEXT |
프로젝트 설정에 저장됩니다 |
--format=FMT |
text(기본) 또는 json |
이미 있는 이름은 오류가 아니라 그 프로젝트를 다시 여는 것으로 처리하며, --format json은 "created": false로 알려 줍니다. 다시 열 때 저장된 표시 이름은 마지막 create의 대소문자로 갱신되고, --description을 주면 기존 설명을 덮어씁니다. 새 이름이 이미 다른 프로젝트의 디렉터리 slug나 짧은 id라면 거부합니다. 그 이름으로는 --project가 새로 만든 프로젝트에 닿을 수 없기 때문입니다.
project switch
--project 없는 모든 gori run 명령이 읽을 프로젝트를 고정합니다. 고정하지 않으면 가장 최근에 활성화한 프로젝트를 읽는데, 이는 다른 프로젝트에 쓰기를 한 번만 해도 옮겨 갑니다.
gori run project switch api-test # 고정 (이름, slug, 짧은 id)
gori run project switch # 기본 프로젝트와 그것을 고른 근거 출력
gori run project switch --clear # 가장 최근에 활성화한 프로젝트로 복귀
GORI_PROJECT=api-test gori run history # 스크립트 하나를 위한 프로세스 단위 고정
GORI_PROJECT가 고정보다 우선하고, --project/--db는 둘 모두보다 우선합니다. 존재하지 않는 프로젝트를 가리키는 고정이나 GORI_PROJECT는 건너뛰지 않고 거부하며, 고정한 프로젝트를 삭제하면 고정도 풀립니다. project list는 현재 적용 중인 쪽에 ◆를 붙이고, switch의 --format json은 project, id, source(env, pinned, recent), pinned를 보고합니다. gori mcp와 TUI는 이를 읽지 않습니다. MCP는 자체 바인딩(GORI_MCP_PROJECT, 워크스페이스)을 쓰고, TUI는 사용자가 고른 프로젝트를 엽니다.
project export
프로젝트 데이터베이스를 WAL에 남아 있는 커밋된 쓰기까지 포함해 압축 아카이브로 내보냅니다. 원본 프로젝트는 열린 상태로 유지되며 변경되지 않습니다.
gori run project export "API test" -o engagement.gori
gori run project export api-test --output=before-clear.gori --force
| 옵션 / 서브커맨드 | 설명 |
|---|---|
<name> |
프로젝트 표시 이름, 디렉터리 slug 또는 짧은 id |
-o PATH, --output=PATH |
아카이브 저장 경로 (필수) |
--force |
기존 대상 파일 교체 |
쓰기 전에 flow·세션 슬롯·프로젝트 env 변수 개수와 프로젝트 업스트림 자격증명 설정 여부를 stderr에 출력합니다. 아카이브에는 저장된 전체 데이터베이스가 들어가며, 마스킹되지 않습니다. 파일 권한은 소유자만 읽고 쓸 수 있게 설정합니다. 기본적으로 기존 파일은 거부하고, 교체하려면 --force가 필요합니다. 원본 프로젝트 디렉터리 안에는 저장할 수 없습니다.
project import
아카이브를 검증하고 별도의 새 프로젝트로 등록합니다. 기존 프로젝트를 덮어쓰거나 다시 열지 않습니다.
gori run project import engagement.gori
gori run project import engagement.gori --name "API test copy"
| 옵션 / 서브커맨드 | 설명 |
|---|---|
<archive> |
.gori 프로젝트 아카이브 경로 |
--name=NAME |
가져온 프로젝트의 표시 이름 (기본값은 아카이브 이름) |
프로젝트를 만들기 전에 아카이브의 flow·세션 슬롯·env 변수 개수와 프로젝트 업스트림 자격증명 설정 여부를 stderr에 출력합니다. 기존 표시 이름·디렉터리 slug·짧은 id와 충돌하면 거부하며, 거부 메시지가 해결책으로 --name을 알려 줍니다. 가져온 프로젝트에는 새 짧은 id가 발급되며 로컬 워크스페이스 바인딩과 잠금 파일은 포함되지 않습니다. 이전 DB 스키마는 프로젝트를 처음 열 때 마이그레이션하고, 현재 빌드가 지원하는 것보다 새로운 스키마는 거부합니다. 가져온 프로젝트는 목록에 추가되지만 자동으로 열리지는 않습니다.
사본은 데이터로만 들어오며, 이 머신의 설정이나 실행되는 것으로 들어오지 않습니다. 프로젝트 네트워크 설정(net.*), 호스트 오버라이드, Rewriter/Colormarker 전역 규칙 오버라이드, Probe 모드를 버리고, 모든 세션 슬롯의 자동 갱신을 끄며, pipe 규칙·파일 기반 short-circuit 규칙·exec 커스텀 probe 규칙을 비활성화합니다. 검토한 뒤 신뢰하는 것만 다시 켜세요.
project delete
프로젝트 디렉터리와 그 안에 캡처된 모든 것(플로우, 이슈, 노트, 스코프, 규칙)을 삭제합니다. 되돌릴 수 없으므로 두 단계로 동작합니다. --yes 없이 실행하면 대상만 출력하고 0이 아닌 코드로 종료합니다.
gori run project delete api-test # 미리보기만, 아무것도 지우지 않음
gori run project delete api-test --format json
gori run project rm api-test --yes # 실제로 삭제
| Option / subcommand | Description |
|---|---|
<name> |
짧은 id, id 접두사, 디렉터리 slug, 표시 이름 중 하나로 지정 |
--yes |
실제로 삭제. 없으면 아무것도 지우지 않습니다 |
--format=FMT |
text(기본) 또는 json |
미리보기는 플로우/이슈 개수, 디스크 사용량과 함께, 삭제가 지키는 잠금 둘 다를 보여 줍니다. 캡처가 살아 있는지(capture_lock_held), 그리고 다른 gori 인스턴스가 DB를 열어 두고 있는지(open_in_another_instance. MCP 서버는 캡처 잠금 없이도 쓰기를 하기 때문입니다). deletable은 이 둘을 합한 판정이고, 마지막 줄이 --yes가 실제로 통과할지를 말해 줍니다. 둘 중 하나라도 걸린 프로젝트는 삭제를 거부하므로 그 캡처를 중지하거나 그쪽에서 닫아야 합니다. capture_lock_held가 null이면 잠금 자체를 읽지 못한 것이고(쓰기 권한 없는 프로젝트 디렉터리), 이때도 삭제는 거부됩니다.
표시 이름은 유일하지 않습니다(같은 basename을 쓰는 두 워크스페이스는 이름을 공유합니다). 이름이 여러 프로젝트에 걸리면 다른 모든 --project와 마찬가지로 삭제를 거부하고 각각의 slug와 짧은 id를 보여 줍니다. 잘못 고르면 되돌릴 수 없기 때문입니다. slug와 짧은 id는 유일하므로 언제나 하나로 확정됩니다.
project scope
프로젝트의 include/exclude 스코프 규칙을 스크립트에서 관리합니다:
gori run project scope # 규칙 목록 + 활성화 상태
gori run project scope --format json
gori run project scope add --kind=include --type=host --pattern=api.example.com
gori run project scope add --kind=exclude --type=regex --pattern='.*\.(css|js)$'
gori run project scope delete 3
gori run project scope enable
gori run project scope disable
| Option / subcommand | Description |
|---|---|
| (default) | 규칙 목록; --format은 text 또는 json |
add |
--kind=include|exclude (기본 include), --type=host|string|regex (기본 host), --pattern=… (필수). 새 규칙의 id를 출력합니다; --format json은 목록과 같은 형태(id, kind, type, pattern)로 규칙을 출력합니다 |
update <rule-id> (edit) |
규칙의 --kind / --type / --pattern 변경. 생략한 필드는 그대로 유지 |
delete <rule-id> (rm) |
id로 규칙 제거 |
enable / disable |
스코프 필터링 적용 여부 토글 |
목록은 규칙이 하는 또 다른 일을 두 번째 줄로 출력합니다. 규칙이 하나라도 있으면 활성 여부와 관계없이 Active-send gate: ON (N rules)가 찍히는데, 모든 액티브 전송(send, repeater, fuzz, mine, discover, MCP)이 --allow-unscoped 없이는 규칙상 스코프 밖인 대상을 거부하기 때문입니다. --format json에서는 active_send_gate로 실립니다. 규칙이 없으면 gori run 전송은 제한이 없고, MCP는 모든 전송을 거부합니다.
project sandbox
하드 컨테인먼트 샌드박스 게이트를 조회하거나 설정합니다. TUI Project settings 토글의 헤드리스 등가물입니다. 켜면 캡처 프록시가 스코프가 허용하는 요청만 전달하고 나머지는 모두 차단합니다. 표시 렌즈일 뿐인 project scope enable과는 다릅니다.
gori run project sandbox # 현재 상태 표시 (기본은 status)
gori run project sandbox status --format json
gori run project sandbox on # 스코프 밖 트래픽 차단 시작
gori run project sandbox off # 차단 중지
| Option / subcommand | Description |
|---|---|
(default) / status |
게이트 상태 표시; --format은 text 또는 json |
on / enable |
스코프가 허용하지 않는 모든 요청 차단 |
off / disable |
차단 중지 |
include 규칙이 없으면 샌드박스를 켤 때 규칙을 추가하기 전까지 모든 캡처 트래픽이 차단됩니다(
gori run project scope add …). 이 명령은 경고 후 진행하므로 CI에서 컨테인먼트를 부트스트랩할 수 있습니다.
project env
아웃바운드 요청의 $ENV.KEY 치환에 쓰이는 프로젝트 env 변수를 관리합니다. 전역 변수는 settings.json / TUI Settings에 있고, 이 명령은 프로젝트 레이어만 다룹니다. 이름은 bare로 저장되며, 와이어에서 어떤 문법으로 적히는지는 전역 설정인 gori settings env-syntax가 결정합니다.
gori run project env # KEY=[REDACTED] 목록 (--show-values는 값 출력)
gori run project env --format json
gori run project env set TOKEN=secret
gori run project env set HOST api.example.com
gori run project env delete TOKEN
| Option / subcommand | Description |
|---|---|
| (default) | 프로젝트 변수 목록; --format은 text 또는 json |
set KEY=value · set KEY value |
프로젝트 변수 upsert (KEY는 [A-Za-z_][A-Za-z0-9_]*) |
delete KEY (rm) |
프로젝트 변수 제거 |
project host-override
프로젝트 호스트 오버라이드를 관리합니다. /etc/hosts처럼 호스트명에 대해 dial할 TCP 대상만 바꾸고, SNI·인증서 호스트·Host 헤더는 원래 이름을 유지합니다. 값에는 포트를 붙일 수 있어(IP, IP:PORT, [v6]:PORT) 호스트명을 다른 포트의 리스너로 돌릴 수 있고, IP만 주면 요청 자체의 포트를 그대로 씁니다. 충돌 시 프로젝트 항목이 전역 호스트네임 오버라이드보다 우선합니다. 별칭: host-overrides.
gori run project host-override # 목록
gori run project host-override --format json
gori run project host-override add --host=api.example.com --ip=10.0.0.1
gori run project host-override add 10.0.0.1 api.example.com # /etc/hosts 순서
gori run project host-override add --host=api.example.com --ip=127.0.0.1:8443 # 포트까지 함께 옮기기
gori run project host-override update 1 --host=api.example.com --ip=10.0.0.9
gori run project host-override delete 1
| Option / subcommand | Description |
|---|---|
| (default) | 오버라이드 목록; --format은 text 또는 json |
add |
--host=… + --ip=…, 또는 positional IP HOST. --format json은 목록과 같은 형태(id, host, ip)로 새 오버라이드를 출력합니다 |
update <id> |
--host=… + --ip=… (둘 다 필수) |
delete <id> (rm) |
id로 오버라이드 제거 |
project network
프로젝트 자체 네트워크 설정, 즉 TUI의 Project settings 카드가 쓰는 net.* 행을 읽고 고칩니다. 여기서 값을 설정하면 이 프로젝트에 한해 settings.json의 전역 network.*를 이깁니다. unset은 그 키를 다시 전역 값으로 되돌립니다. 각 키가 무엇을 하고 어디에 적용되는지는 프로젝트별 오버라이드를 참고하세요. 별칭: net.
gori run project network # 모든 키: 여기서의 값과 출처
gori run project network --format json
gori run project network set upstream_proxy=http://proxy.corp.example:3128
gori run project network get upstream_proxy
printf %s "$PROXY_PASS" | gori run project network set upstream_auth alice --password-stdin
gori run project network set capture_max_mib 16
gori run project network unset capture_max_mib
gori run project network set outbound_max_rps=10 # 모든 실행, 모든 프로세스 합산
Key (net. 접두사는 생략 가능) |
Value |
|---|---|
bind_host · bind_port |
프록시 리슨 주소와 포트. gori가 실제로 리슨하는 곳, 즉 TUI와 gori run capture에만 적용 |
upstream_proxy |
http://, http+tls://, socks5:// 또는 socks5h:// URI. 비워 두면 직접 연결을 고정합니다: 전역 프록시도, upstream_rules 항목도, HTTP(S)_PROXY도 더 이상 이 프로젝트에 적용되지 않습니다. URI에 담긴 자격증명은 거부됩니다 |
upstream_destination_host |
프로젝트의 프록시 라우팅이 적용될 호스트 패턴. 그 외에는 직접 연결됩니다. *(기본값)는 이 행을 지웁니다 |
upstream_auth |
프록시 자격증명: 값은 사용자명이고, 비밀번호는 --password-stdin으로 stdin에서 읽습니다(프로세스 목록에 남는 인자 벡터에는 절대 두지 않습니다). HTTP 프록시에는 HTTP Basic, SOCKS5에는 RFC 1929 |
connect_timeout_secs · io_timeout_secs |
아웃바운드 연결 및 유휴 타임아웃, 초 단위 (최소 1) |
capture_max_mib |
메시지마다 캡처해 저장하는 본문 바이트, MiB 단위 (1-2047) |
outbound_max_rps · outbound_max_requests |
프로젝트 요청 예산: gori 발신 요청의 초당 상한(양수, 0.5도 가능)과 총량 상한으로, 프로젝트의 모든 실행과 모든 프로세스에 함께 적용됩니다. 프록시 트래픽은 세지 않습니다. unset outbound_max_requests는 카운트를 초기화합니다. 프로젝트별 오버라이드 참고 |
| Subcommand | Description |
|---|---|
(default) / list |
모든 키를 적용 중인 값과 출처(· project, · global)와 함께 표시; --format json은 value(프로젝트 자신의 행, 설정 안 됐으면 null), inherited, effective를 싣습니다 |
get KEY |
적용 중인 값: 프로젝트 자신의 값, 없으면 상속된 값(STDERR에 이름이 나오므로 $(…)는 값만 담습니다). 자격증명은 방식과 사용자명만 출력하며 비밀번호는 절대 출력하지 않습니다 |
set KEY=VALUE · set KEY VALUE |
값을 고정합니다. 전역 값과 같은 값이라도 고정되며, 이렇게 해야 이후의 전역 편집이 프로젝트에 닿지 않습니다. (Project settings 카드는 저장할 때 전역과 같은 값을 다시 inherit으로 접지만, set은 키 하나만 지목하므로 그렇게 하지 않습니다.) |
unset KEY (rm, delete) |
프로젝트 값을 지워 다시 상속되게 합니다. 설정돼 있지 않은 키를 지워도 오류가 아닙니다 |
자격증명은 Project settings 카드에서와 마찬가지로 입력받은 업스트림에 고정됩니다. set upstream_auth는 상속된 전역 업스트림도 같은 쓰기 한 번으로 프로젝트에 고정하므로, 이후의 전역 편집이나 업스트림 규칙이 비밀번호를 다른 프록시로 데려가는 일이 없습니다. set upstream_proxy는 저장된 자격증명을 새 주소로 옮깁니다(그에 맞춰 Basic인지 SOCKS5인지 다시 판정합니다). 그리고 unset upstream_proxy는 unset upstream_auth를 먼저 하기 전까지 거부됩니다. 여러 행에 걸친 편집은 한 트랜잭션으로 처리하므로, 바쁜 프로젝트가 검증되지 않은 주소 옆에 비밀번호를 저장하는 일은 없습니다. 모든 편집은 자격증명 없이 프로젝트 이벤트 피드에 기록됩니다.
이미 프로젝트를 연 gori(TUI, capture, MCP 서버)는 프로젝트를 열 때 이 행들을 읽어 프로젝트를 다시 열기 전까지 유지합니다. 그런 경우 명령이 STDERR에 이를 알립니다.
run redact
안전한 증거 내보내기가 적용하는 리댁션 프로파일과 그 적용 범위를 관리합니다.
gori run redact profiles
gori run redact set pci --json-field card_number --json-field cvv --json-pointer /data/acct
gori run redact use pci
gori run redact default on
| 서브커맨드 | 설명 |
|---|---|
profiles (list, 기본) |
여기서 쓸 수 있는 모든 프로파일(프로젝트, settings.json, 내장 순)을 범위, 규칙 개수, 안전한 내보내기가 실제로 쓸 하나에 붙는 *, 기본 적용 여부와 함께 보여 줍니다. 전체 규칙 목록은 --format json |
use <name> | use --none |
안전한 내보내기가 쓸 프로파일을 고릅니다. --global이 없으면 프로젝트에 씁니다 |
default on|off | default --none |
--redact 없이도 공유용 출력을 정제할지. --global이 없으면 프로젝트 범위이고, --none은 프로젝트의 답을 지워 전역 설정을 따르게 합니다 |
set <name> |
반복 가능한 규칙 플래그로 프로파일을 만들거나 통째로 교체합니다. --global이 없으면 프로젝트 범위 |
rm <name> (delete) |
프로파일을 지웁니다. 내장 프로파일은 지울 수 없고, 같은 이름으로 정의해 덮어쓰면 됩니다 |
set은 네 종류의 규칙(각각 반복 가능)과 --description을 받습니다.
| 플래그 | 매칭 대상 |
|---|---|
--json-field NAME |
JSON 객체의 멤버 이름을 대소문자 구분 없이 모든 깊이에서 찾습니다. 주력 규칙입니다. "어디에 중첩돼 있든 password라는 멤버는 이 기기를 떠나지 않는다" |
--json-pointer PTR |
RFC 6901 포인터로 정확히 한 위치만(/data/user/ssn) 지목합니다. 어디서나 가리기엔 너무 흔한 필드 이름용입니다. RFC가 "마지막 다음 원소"로 예약해 둬서 실제 원소를 가리킬 수 없는 - 토큰은 여기서 아무 인덱스로 읽습니다. /users/-/token은 배열 전체를 덮습니다 |
--form-key KEY |
application/x-www-form-urlencoded 키를 대소문자 구분 없이 찾습니다. 키는 퍼센트 디코딩한 뒤 비교하고, 본문의 나머지 세그먼트는 바이트 그대로 남습니다 |
--pattern REGEX |
본문 텍스트(그리고 JSON 문자열 리프와 디코드된 폼 값)에 대한 정규식입니다. 캡처 그룹이 있으면 그룹 1만 교체되고 나머지는 매칭에 쓴 문맥으로 남습니다. account=(\d+)는 account=를 남기고 숫자만 가져갑니다. 그룹이 없으면 매치 전체가 사라집니다. 위의 세 이름 목록과 마찬가지로 대소문자를 구분하지 않고 컴파일합니다. 컴파일되지 않는 패턴은 이후 모든 내보내기마다 알리는 대신 여기서 거부합니다 |
범위. 프로파일은 프로젝트 데이터베이스나 settings.json 중 한 곳에 살고, 어느 쪽에 있는지도 프로파일의 일부입니다. "password 필드는 절대 내보내지 않는다"는 사용자 자신의 정책이므로 전역이 맞고, "이 타깃은 그걸 pwd_hash라 부르고 계좌번호는 /data/acct에 있다"는 다음 점검로 따라가서는 안 되는 점검 데이터입니다. 해석은 구체적인 쪽 우선(프로젝트, 전역, 내장 순)이고 이름이 같으면 먼저 나온 것이 이깁니다. 그래서 프로젝트 프로파일이 전역을, 전역이 같은 이름의 내장을 가립니다.
**내장 default**는 크리덴셜, 토큰, 흔한 정부/금융 식별자를 필드 이름으로 덮고(password, client_secret, access_token, api_key, session_id, otp, pin, ssn, card_number, cvv, iban 등) 정규식은 하나도 싣지 않습니다. 기본 제공 패턴이 엉뚱한 것에 걸리면 경고 없이 망가진 보고서가 남지만, 기본 제공 이름이 엉뚱하게 걸리면 잃는 건 어차피 잃는 편이 나았을 값 하나이기 때문입니다. 예컨대 email에 대해서는 일부러 아무 말도 하지 않습니다. 주소 자체가 발견 내용인 종류의 취약점에서는 그런 기본값이 증거를 망칩니다. 원하는 것은 직접 만든 프로파일에, 눈에 보이는 곳에 넣으세요.
default on은 한 번만 하는 옵트인입니다. 이미 gori run show --format raw를 읽는 스크립트가 있는 설치에서 기본값을 뒤집으면 그 스크립트가 받는 내용이 조용히 달라지므로, gori는 꺼진 채로 출시합니다. 켠 뒤로는 호출마다 --no-redact가 캡처한 바이트로 돌아가는 명시적인 길입니다. 켜져 있으면 TUI의 Space → Y 복사 메뉴와 **MCP get_flow**도 정제합니다. 복사 제목은 COPY REQUEST AS · SANITIZED (3)로 보이고, get_flow는 프로파일 이름과 개수, 그리고 무엇을 보지 않았는지를 담은 body_redaction 객체를 돌려줍니다. MCP의 include_sensitive: true는 헤더 리댁션과 함께 본문 리댁션도 끕니다. 플래그 하나로 두 축을 함께 다룹니다.
자리표시자 태그는 [REDACTED:<16진수 8자리>]이며, 설치마다 한 번 만들어져 settings.json에 보관되는 비밀 키로 값을 HMAC한 결과입니다. 같은 값은 같은 태그를 공유해 보고서 안에서 상관관계를 유지할 수 있고, 태그에서 값을 복원할 수는 없으며, 이 설치 밖에서는 아무 의미가 없습니다. 공장 초기화는 salt를 남깁니다. 버리면 이미 쓴 모든 산출물의 자리표시자가 깨지기 때문입니다. gori settings export --sections redaction은 규칙과 함께 salt도 가져가므로, 규칙만 건네려면 gori run redact profiles --format json을 쓰세요.
gori mcp
MCP stdio 서버입니다. 도구 세부사항은 MCP 가이드를 참고하세요.
| Option | Description |
|---|---|
--db=PATH |
이 데이터베이스를 제공 (--project보다 우선) |
--project=NAME |
이름이 지정된 프로젝트의 데이터베이스 제공 |
--use-active-project |
Git 워크스페이스 선택을 무시하고 활성 TUI/MRU 프로젝트를 명시적으로 제공 |
--no-project |
Git 워크스페이스 안에서도 unbound로 시작 (에이전트가 list/create/switch로 선택) |
--insecure-upstream |
대상에 요청을 보내는 모든 도구(send_request, fuzz, grpc_reflect, 세션 갱신 등)에서 업스트림 TLS 검증 생략. OAST 서버는 항상 검증합니다. 커스텀 CA 번들 참고 |
--read-only |
액션 도구 비활성화 (send_request, 이슈 생성/수정, fuzz/mine); switch_project(및 unbound 시 create_project)는 --pin-project가 아니면 유지 |
--tools=SPEC |
지정한 도구만 노출: 쉼표로 구분한 이름, 글롭, 프로필(@minimal, @recon)이며, 앞에 -를 붙이면 제외 (@recon, @minimal,send_request 또는 -fuzz_*,-mine_*). 제공하는 카탈로그 크기는 시작 로그에 나옵니다. 노출할 도구 고르기 참고 |
--pin-project |
시작한 프로젝트에 서버를 고정: list_projects, switch_project, create_project, delete_project, import_project, export_project, diff_projects를 노출하지 않음. --no-project와 함께 쓸 수 없고, 바인딩 없이 시작하게 되면 중단 |
--install-claude |
Claude Desktop mcpServers 설정 기록 |
--install-claude-code |
Claude Code ~/.claude.json mcpServers 항목 기록 (또는 $CLAUDE_CONFIG_DIR) |
--install-codex |
OpenAI Codex ~/.codex/config.toml [mcp_servers.gori] 기록 (또는 $CODEX_HOME) |
--install-agy |
Antigravity ~/.gemini/antigravity-cli/mcp_config.json 기록 |
--install-grok |
Grok ~/.grok/config.toml [mcp_servers.gori] 기록 |
--install-hermes |
Hermes ~/.hermes/config.yaml mcp_servers.gori 기록 (또는 $HERMES_HOME) |
--install-pi |
Pi ~/.pi/agent/mcp.json mcpServers.gori 기록 (또는 $PI_CODING_AGENT_DIR); MCP 어댑터 필요 |
--install-*은 한 번에 여러 개 지정할 수 있습니다. 클라이언트마다 따로 설정하고 따로 보고하며, 하나가 실패해도 나머지는 그대로 진행됩니다. 커맨드라인의 다른 플래그(--db, --project, --no-project, --use-active-project, --read-only, --tools, --pin-project, --insecure-upstream, 전역 --config)는 모두 설치되는 커맨드에 기록되고, 경로는 절대 경로로 바뀝니다(--project는 이름 그대로 기록). 기존 설정 파일은 제자리에서 갱신됩니다. 다른 항목·테이블·주석은 유지되고, 권한도 보존되며, 교체는 원자적입니다.
gori ca
gori ca
gori ca --pem
gori ca --ca-dir=DIR
gori ca regenerate
gori ca regenerate --yes
gori ca import --cert root.crt.pem --key root.key.pem --yes
gori 루트 CA 인증서의 경로를 출력합니다(최초 사용 시 생성). 브라우저나 시스템 저장소에서 CA를 신뢰시킬 때, 또는 클라이언트에 --cacert를 지정할 때 사용하세요.
| Option | Description |
|---|---|
--ca-dir=DIR |
CA 디렉터리 (기본값 ~/.gori/ca, 또는 $GORI_HOME/ca) |
--pem |
경로 대신 인증서 PEM을 stdout으로 출력 |
동사(verb)를 먼저 쓰고 플래그를 그 뒤에 씁니다. gori ca --ca-dir=DIR regenerate가 아니라 gori ca regenerate --ca-dir=DIR입니다. 반대 순서는 사용법 오류로 처리합니다. 그러지 않으면 동사가 버려진 채 CA 경로만 출력되어 작업이 수행된 것처럼 보이기 때문입니다. 세 가지 형태 모두 위치 인자를 받지 않습니다.
gori ca는 로드는 되지만 사용할 수 없는 루트 CA(인증서와 일치하지 않는 개인 키, 또는 gori가 서명에 사용할 수 없는 키)도 stderr로 보고합니다. 그렇지 않으면 이 증상은 클라이언트 쪽에서 "unknown CA"나 "bad signature" 핸드셰이크 실패로만 드러나기 때문입니다. 해결책은 regenerate와 import이며, 두 명령은 쌍 중 한 파일만 남은 경우를 포함해 어떤 상태의 CA 디렉터리에서도 동작합니다.
gori ca regenerate
디스크의 루트 CA를 새로 발급한 것으로 교체합니다. 파괴적: 이전 CA를 신뢰하던 모든 클라이언트는 새 인증서를 다시 신뢰해야 합니다. 이미 실행 중인 gori 프로세스는 재시작 전까지 이전 CA를 메모리에 유지합니다.
| Option | Description |
|---|---|
--yes, -y |
대화형 확인 생략 (stdin이 tty가 아닐 때 필수) |
--ca-dir=DIR |
재생성할 CA 디렉터리 |
--yes 없이는 tty에서 프롬프트가 뜨며 regenerate를 입력하도록 요구합니다(TUI 확인과 같은 단어). 스크립트와 CI는 --yes를 전달해야 합니다. 성공하면 새 인증서 경로가 stdout으로 출력됩니다.
gori ca import
외부에서 생성한 루트 CA(인증서 + 일치하는 개인 키, 둘 다 PEM)를 gori 자체 CA 대신 채택합니다. 팀이나 여러 머신에서 하나의 CA를 공유하거나, 조직 CA를 재사용하기 위해서입니다. gori는 호스트별 리프 인증서를 즉석에서 서명하므로 두 파일이 모두 필요합니다. 클라이언트는 인증서만 신뢰합니다. regenerate처럼 파괴적이며, 디스크의 루트를 교체하고 기존 신뢰를 무효화합니다.
| Option | Description |
|---|---|
--cert FILE |
채택할 루트 CA 인증서 PEM (필수) |
--key FILE |
일치하는 개인 키 PEM (필수) |
--yes, -y |
대화형 확인 생략 (stdin이 tty가 아닐 때 필수) |
--ca-dir=DIR |
설치할 CA 디렉터리 |
무엇이든 디스크에 기록하기 전에 쌍을 먼저 검증합니다: 키는 인증서와 일치해야 하고, 인증서는 CA여야 하며(basicConstraints CA:TRUE), gori가 그 키로 리프 인증서를 서명할 수 있어야 합니다. 마지막 검사 때문에 Ed25519 · Ed448 루트는 거부됩니다. gori는 리프를 SHA-256으로 서명하는데 이 키들은 이를 지원하지 않습니다. 따라서 EC P-256이나 RSA 루트를 사용하세요. 거부된 쌍은 현재 CA를 건드리지 않고 중단합니다. 만료되었거나 아직 유효하지 않은 인증서는 경고만 남기고 그대로 가져옵니다. tty에서 import를 입력하여 확인하거나 --yes를 전달하세요. 같은 동작을 TUI 팔레트(Import CA certificate)에서도 사용할 수 있습니다.
OpenSSL로 루트를 생성한 뒤 가져옵니다:
openssl ecparam -genkey -name prime256v1 -out root.key.pem
openssl req -x509 -new -key root.key.pem -days 3650 -subj "/CN=my ca" -out root.crt.pem
gori ca import --cert root.crt.pem --key root.key.pem --yes
클라이언트에서는 root.crt.pem만 신뢰하세요. 개인 키는 절대 배포하지 마세요.
gori settings
gori settings # settings.json 경로 출력
gori settings --edit # $EDITOR로 열기
gori settings sections # 최상위 섹션 목록
gori settings export [-o FILE] # 공유 가능한 프로필 출력(기본 stdout)
gori settings import FILE # 프로필의 섹션들을 적용
gori settings tls-fingerprint # 목적지별로 gori가 보내는 JA3/JA4
gori settings env-syntax [VALUE] # env 토큰 문법 읽기 / 설정
gori settings user-agents # $GEN.USER_AGENT가 고르는 목록
gori settings env-syntax
env 토큰을 어떤 문법으로 적는지 정합니다. namespaced(환경 변수 $ENV.KEY, 세션 바인딩 $BIND.NAME, 요청마다 새 값을 만드는 생성기 $GEN.NAME) 또는 bare($KEY, $NAME, 생성기 표기 없음)입니다. 모든 프로젝트의 토큰을 어떻게 읽을지 결정하므로 전역 설정입니다. 인자 없이 실행하면 지금 적용된 값과 그 출처를 출력합니다.
gori settings env-syntax
# namespaced (from /Users/me/.gori/settings.json)
# $ENV.KEY / $BIND.NAME / $GEN.UUID
gori settings env-syntax bare
# env syntax: bare — $KEY / $NAME
# Each project is re-spelled the next time it opens: its stored tokens are rewritten from
# namespaced to bare, a backup is written beside the database, and the run that does it says
# so. Captured evidence is left exactly as it was. Switch back with `gori settings env-syntax namespaced`.
문법은 모두에게 namespaced입니다. 그래서 settings.json에 env.syntax가 없다는 것은 그 파일이 네임스페이스보다 먼저 쓰였다는 뜻입니다. 다음 시작에서 namespaced를 채택하고, 전역 재작성 규칙을 다시 적고(settings.json.pre-namespaced-<타임스탬프> 복사본을 남깁니다), 키를 파일에 씁니다. 각 프로젝트는 문법이 바뀐 뒤 처음 열릴 때 다시 적힙니다. TUI든, 아무 gori run …이든, gori mcp 서버든 마찬가지입니다. 데이터베이스 옆에 gori.db.pre-<grammar>-<타임스탬프> 백업(VACUUM INTO이므로 WAL까지 포함)을 쓰고, 토큰 몇 개가 옮겨졌는지 프로젝트마다 한 줄씩 stderr로 알려 줍니다. 다시 적는 대상: Repeater 초안(request, target, SNI, 이름)과 그 WebSocket 메시지, Fuzzer 템플릿, Miner·Sequencer 요청, 재작성 규칙의 치환 텍스트, 세션 슬롯 헤더 값, 그리고 이슈 제목·메모와 노트 본문에 마스킹된 토큰입니다. 그대로 두는 것: 출처가 캡처인 모든 행(flow_id가 있는 행이며, 캡처는 확장되지 않습니다), 대상 문법에 같은 바이트를 보내는 표기가 없는 행, 그리고 토큰이 아니라 테이블 키인 이름들(환경 변수, extract 규칙, 규칙 패턴, 페이로드 세트)입니다.
env.syntax = bare가 옵트아웃이며, 각 프로젝트는 다음에 열릴 때 되돌려 다시 적힙니다. 이때 그대로 두면 해석되기 시작할 리터럴 $NAME은 이스케이프됩니다. 환경 변수를 참고하세요. gori settings import는 프로필의 env.syntax가 이 설치와 다르면 stderr로 알려 줍니다. 임포트는 문법을 바꾸지 않습니다.
gori settings user-agents
$GEN.USER_AGENT가 고르는 목록입니다. 플래그 없이 실행하면 출처를 밝히는 # 줄 아래에 현재 쓰는 목록을 출력합니다. --set은 같은 형식을 그대로 읽으므로, 출력을 저장해 고친 뒤 다시 설정할 수 있습니다. --set은 파일의 줄로 내장 목록을 대체합니다. 한 줄에 User-Agent 하나이며, 빈 줄과 # 줄은 건너뜁니다. 헤더에 넣을 수 없는 줄이 하나라도 있으면 파일 전체를 거절하고 아무것도 바꾸지 않습니다. --reset은 내장 목록으로 되돌립니다.
gori settings user-agents > ua.txt # 현재 쓰는 목록
gori settings user-agents --set ua.txt # 내장 목록 대체
generator | gori settings user-agents --set -
gori settings user-agents --reset # 내장 목록으로 되돌리기
settings.json에는 user_agents로 저장되며, TUI에서는 Preferences → Editor & Keys → User-Agents에서 편집합니다.
프로필
export와 import는 설정을 다른 머신으로 옮기거나, 팀과 공유하거나, 재현 가능한 실행을 위해 저장소에 커밋할 때 씁니다. 단위는 최상위 섹션이며 목록은 gori settings sections로 확인합니다.
gori settings export --sections network,scan_rules -o team-profile.json
gori settings import team-profile.json --dry-run # 무엇이 적용될지 미리 보기
gori settings import team-profile.json --sections network
gori settings sections는 gori가 아는 모든 섹션을 나열하고, 이 설치본에 아직 값이 없는 것을 표시합니다:
…
statusline (can carry commands)
…
network
…
editor (can carry commands)
…
env (holds secrets — excluded unless named; not set — at its default)
scan_rules (can carry commands; not set — at its default)
…
decoder (holds secrets — excluded unless named; can carry commands; not set — at its default)
rewriter (can carry commands)
…
not set으로 표시된 섹션도 --sections에 쓸 수 있는 정상적인 이름입니다. export하면 담을 값이 없을 뿐이고(그 사실을 stderr로 알려줍니다), import하면 그 섹션이 처음으로 기록됩니다.
| 플래그 | 대상 | 설명 |
|---|---|---|
--sections a,b |
공통 | 쉼표로 구분한 섹션 이름, 최소 하나. export 기본값은 비밀을 담은 섹션을 제외한 전부, import 기본값은 파일에 있는 전부 |
-o, --out FILE |
export | stdout 대신 파일로 기록 |
--dry-run |
import | 적용될 섹션만 출력하고 아무것도 쓰지 않고 종료 |
--allow-commands |
import | 외부 명령을 실행하거나, 로컬 파일을 제공하거나, 환경 변수를 프록시로 보내는 룰을 적용합니다. 프로필이 그런 룰을 담고 있으면 필수입니다. 없으면 import는 거부되고 아무것도 쓰이지 않습니다 |
--json |
tls-fingerprint | 리포트를 JSON으로 출력. 분해된 JA3 문자열과 ja4_r가 항상 포함됩니다 |
--preset NAME |
tls-fingerprint | 전송 단위 --tls-preset NAME 오버라이드가 대신 보낼 것을 보고 (아래 참고) |
선택하지 않았거나 프로필에 없는 섹션은 그대로 남습니다. --sections가 고르는 것이 바로 이 경계입니다. 프로필이 실제로 담고 있는 섹션 안에서는:
- 리스트/테이블 섹션은 통째 교체됩니다:
upstream_rules,outbound_tls,listeners,scan_rules,hostname_overrides,tabs등."upstream_rules": []를 담은 프로필은 테이블을 비웁니다. "규칙 없음"을 그렇게 표현합니다. - 스칼라 오브젝트 섹션은 키 단위로 적용됩니다:
network,editor,probe. 프로필이 생략한 키는 현재 값을 유지하므로,network.upstream_proxy만 지정한 팀 프로필이 언급한 적도 없는bind_port까지 기본값으로 되돌리지 않습니다.
export는 공장 기본값 상태인 섹션을 아예 쓰지 않으므로, 프로필은 설정 전체의 스냅샷이 아니라 적용할 값들의 묶음입니다. 어떤 값이 기본값인 머신에서 export해도, 그 값이 기본값이 아닌 머신에서 되돌려지지 않습니다. import가 무엇을 건드릴지는 --dry-run으로 확인하세요. 목록에 넣는 쪽으로 넉넉하게 판단하므로, 거기 없는 섹션은 확실히 아무 변화도 없습니다.
import는 TUI와 동일한 저장 경로를 거치므로 원자적 쓰기가 유지되고, 동시에 실행 중인 gori가 건드리지 않은 섹션에 한 편집이나 삭제를 덮어쓰지 않습니다. 파일에 있는 알 수 없는 섹션은 보고하고 무시합니다. 실행 중인 설정에도, 파일에도 반영되지 않습니다.
gori가 settings.json을 읽지 못하는 상태(파싱 실패, 권한 문제, --config가 열 수 없는 대상을 가리키는 경우)라면 export와 import 모두 진행하지 않고 거부합니다. 그 시점의 gori는 모든 섹션이 공장 기본값이므로, import는 프로필이 언급하지 않은 섹션 전부를 기본값으로 디스크에 박고, export는 그 기본값을 원래 설정인 양 파일로 내보내기 때문입니다. 먼저 파일을 고치거나 지우세요. 파싱되지 않은 원본은 옆에 settings.json.corrupt로 보관됩니다. --dry-run은 예외입니다: 아무것도 쓰지 않으므로 그대로 실행되고, 비교 대상이 기본값이라는 사실을 stderr로 알려줍니다.
env, decoder, oast_providers는 export에서 기본 제외됩니다. env는 토큰 값을, decoder는 저장된 체인 라이브러리를(열려 있는 하위 탭은 여기가 아니라 프로젝트 저장소에 있습니다), oast_providers는 자체 호스팅 interactsh 토큰을 담기 때문입니다. 명시적으로 이름을 적는 것(--sections env)이 포함에 대한 동의입니다. upstream_rules는 공유해도 안전합니다. 사용자명과 환경변수 이름만 저장하고 비밀번호는 담지 않습니다.
이 설치의 기존 데이터를 읽는 방식을 정하는 값, 즉 토큰 문법(env.syntax), 토큰 접두사(env.prefix), 리댁션 솔트(redaction.salt)는 프로필에 담기지 않고 import로 바뀌지도 않습니다. import는 로컬 값을 유지하며, 프로필의 문법이나 접두사가 달랐으면 stderr로 알립니다. import한 전역 rewriter 규칙, colormarker 규칙, 저장된 뷰는 이 설치의 카운터로 새 번호를 받으므로, 지워진 로컬 규칙에 대한 프로젝트의 오버라이드가 import한 규칙에 붙지 않습니다.
-o가 실제 사용 중인 settings.json을 가리키면 거부됩니다. export는 스냅샷이 아니므로(기본값 상태인 섹션은 모두 빠지고, env, decoder, oast_providers는 이름을 지정하지 않는 한 빠집니다) 그것을 원본 파일에 되쓰면 해당 섹션이 갱신되는 게 아니라 삭제됩니다.
export가 실제로 그런 섹션을 담게 되면 -o FILE은 0600으로 생성되고, gori가 파일에 무엇이 들어 있는지 이름을 대며 알려줍니다. 자격증명을 export하는 데 동의한 것이 그것을 누구나 읽을 수 있게 두는 데 동의한 것은 아닙니다. 일반 export는 0644로 남고, env 변수가 하나도 없는 설치에서 env를 지정한 export도 일반 export입니다. 권한은 타이핑한 내용이 아니라 문서에 실제로 담긴 것을 따릅니다.
명령을 담은 프로필
다섯 섹션은 데이터가 아니라 명령을 담을 수 있습니다. 다른 설정과 똑같이 export됩니다. 팀이 같은 재서명 훅을 표준으로 쓰는 것이야말로 훅이 존재하는 이유니까요. 대신 양쪽 끝에서 파일에 무엇이 들었는지 말해줍니다.
| 섹션 | 무엇이 담나 | 어떻게 실행되나 |
|---|---|---|
rewriter |
op: pipe인 룰 |
argv, 셸 없음. 매치되는 프록시 트래픽마다 실행 |
scan_rules |
kind: exec인 항목 |
argv, 셸 없음. 분석되는 플로우마다 실행 |
decoder |
exec:…로 쓴 chains 스텝 |
argv, 셸 없음. 체인을 실행할 때 |
statusline |
command |
/bin/sh -c. interval초마다 실행 |
editor |
command |
argv. gori settings --edit와 TUI의 ^E에서 실행 |
앞의 셋은 프로세스 훅입니다. 다섯 중 가장 날카로운 건 statusline입니다. argv exec이 아니라 완전한 셸이고, 같은 섹션에 자기 enabled를 들고 있어 프로필 하나로 바로 무장되며, 트래픽 없이 타이머만으로 실행됩니다. editor는 프로필이 값을 지정했을 때만 보고합니다. 비어 있으면 gori는 받는 쪽의 $VISUAL/$EDITOR/vi로 넘어갑니다.
명령을 실행하지는 않지만 같은 방식으로 나열되고 막히는 모양이 둘 더 있으며, env와 file 행으로 표시됩니다. password_env가 있는 upstream_rules 항목(gori가 내 머신에서 그 변수를 읽어 프로필이 지정한 프록시로 보냅니다)과 body_file이 있는 rewriter short-circuit(매칭된 호스트의 페이지에 로컬 파일을, respond: dir이면 디렉터리를 제공합니다)입니다.
export는 개수를 stderr로 알리고, stdout의 프로필은 깨끗하게 둡니다:
note: 5 entries in this profile run a local command (2 rewriter pipe, 1 scan_rules exec, 1 statusline sh -c, 1 editor exec) — whoever imports it runs them with their own privileges
import는 argv까지 한 줄씩 나열하고, 확인을 받기 전까지 쓰지 않습니다. --dry-run도 같은 목록을 출력하며 어느 쪽이든 아무것도 쓰지 않습니다:
$ gori settings import team-profile.json
5 entries in this profile run a local command here, with your privileges:
rewriter pipe resign body ./resign.sh --key $TOKEN
rewriter pipe (unnamed) /usr/local/bin/hmac [disabled]
scan_rules exec leak detector ./detect.py
statusline sh -c command gori-status --project
editor exec command nvim
importing them is the same trust decision as running the author's script
gori settings import: refused — the 5 entries listed above run a local command with your privileges. Read them, then pass --allow-commands. Nothing was written.
명령을 읽고 나서 --allow-commands를 주세요. 대화형 프롬프트가 없으므로 스크립트에서 실행하는 import는 그대로 스크립트로 남습니다. 그 플래그 자체가 확인 절차입니다. 프로필이 담고는 있지만 꺼둔 항목은 [disabled]로 표시됩니다. 누군가 켜기 전까지는 아무것도 실행하지 않지만, 파일에는 여전히 들어 있습니다. --sections로 범위를 좁히면 이 판단도 함께 좁아집니다. network만 적용하는 import는 아무것도 무장시키지 않으므로 항목을 나열하지도, 플래그를 요구하지도 않습니다.
gori settings tls-fingerprint
gori가 실제로 보내는 ClientHello의 JA3/JA4 지문을 목적지별로 출력합니다. Cloudflare·Akamai·DataDome·PerimeterX 같은 안티봇이 챌린지를 띄울지 판단할 때 읽는 바로 그 offer입니다. outbound_tls의 지문 필드를 검증하는 수단입니다. OpenSSL은 협상 결과만 알려줄 뿐 무엇을 제안했는지는 알려주지 않으므로, 이 명령이 없으면 설정이 먹혔는지 확인할 방법이 없습니다.
gori settings tls-fingerprint # 모든 규칙 + 규칙 없음 기본값
gori settings tls-fingerprint shop.example.com # 그 호스트가 실제로 받는 정책 하나
gori settings tls-fingerprint --json # 원본 목록까지 담은 기계 판독용 출력
# …그리고 per-send 오버라이드가 대신 무엇을 보낼지. Repeater 탭의 ␣Pt나 `--tls-preset`
# 실행이 적용하는 것과 같은 좁히기를, settings.json을 건드리지 않고 미리 봅니다:
gori settings tls-fingerprint shop.example.com --preset curl
shop.example.com (matched rule "shop.example.com")
preset chrome
groups X25519:P-256:P-384
…
tunnelled (gori offers h2) — ALPN h2, http/1.1
JA3 c99e92e692ba483e2602b38b3c0a5645
771,4865-4866-…,65281-0-11-10-35-5-16-22-13-43-45-51-21,29-23-24,0
JA4 t13d1513h2_8daaf6152771_afafd945c4ab
t13d1513h2_002f,0035,…_0005,000a,…_0403,0804,…
정책마다 두 개의 leg를 보여주며, 둘의 차이는 실재합니다. gori는 복호화하는 MITM 연결에서는 h2를 제안하고, 자신이 HTTP/1.1을 말하게 될 leg(포워드 프록시 dial, Repeater, WebSocket)에서는 제안에서 h2를 빼며, alpn을 설정하지 않았다면 ALPN 확장 자체를 보내지 않습니다. 그래서 두 leg의 ClientHello가 다릅니다. 각 다이제스트 아래 줄은 그 다이제스트가 해시한 목록입니다. 어떤 필드가 움직였는지는 거기서만 보이고, 브라우저와 비교할 가치가 있는 쪽도 이쪽입니다.
리포트가 읽는 컨텍스트는 실제 dial이 만드는 것과 같은 객체이므로, gori가 하지 않는 핸드셰이크를 설명할 수 없습니다. 이 OpenSSL이 거부하는 groups/sigalgs 문자열은 해당 규칙에 대해 stderr로 보고하고 나머지는 계속 출력합니다.
--preset NAME은 보고되는 모든 정책을 per-send 오버라이드와 똑같이 좁혀서 출력합니다(전송 단위 TLS 지문 참고). 이미 chrome 규칙이 걸린 호스트에 --tls-preset curl이 실제로 무엇을 실어 보낼지 확인할 때 씁니다. 클라이언트 인증서·프로토콜 범위·permissive는 목적지 것이 그대로 남고, ClientHello 모양만 교체됩니다. 모르는 이름은 빈 hello로 보고하지 않고 거부합니다.
전송 단위 TLS 지문
outbound_tls는 목적지 호스트로만 키가 잡힙니다. 상시 정책에는 맞지만, 지문 기능이 존재하는 이유인 질문, **이 엔드포인트가 chrome일 때와 curl일 때 다르게 답하나?**에는 맞지 않습니다. 그건 같은 호스트에 대한 A/B이고, 전송 사이에 전역 규칙을 고쳐서 하면 두 전송이 비교 불가능해질 뿐 아니라 그 호스트로 가는 다른 모든 탭과 백그라운드 캡처의 핸드셰이크까지 바뀝니다.
per-send 오버라이드는 목적지 테이블을 건드리지 않고 전송 하나 또는 실행 하나에 대해 프리셋을 지정하며, dial 시점에 해석됩니다:
gori run repeater 42 --tls-preset chrome # 캡처한 플로우를 Chrome처럼 재전송
gori run repeater 42 --tls-preset curl # …그리고 curl로 한 번 더, 비교 가능하게
gori run repeater send 7 --tls-preset firefox # 저장된 세션을 이번 전송만 덮어쓰기
gori run repeater create --tls-preset chrome … # 세션에 저장
gori run fuzz --flow 42 --auto --tls-preset chrome # 스윕 전체를 한 핸드셰이크로
TUI에서는 Repeater 탭의 ␣Pt(TARGET 밴드의 ␣Pt:… 칩)이고, 탭과 함께 영속화되므로 다시 연 탭은 이전에 보낸 지문 그대로 보냅니다. Fuzzer는 ^O 고급 카드의 TLS fingerprint 행입니다. MCP는 send_request{tls_preset}와 fuzz_start{tls_preset}이며, 결과 세트가 어느 핸드셰이크에서 나왔는지 말할 수 있도록 그대로 되돌려 줍니다.
오버라이드는 목적지 정책을 통째로 갈아치우는 게 아니라 좁힙니다:
| 필드 | 오버라이드 하에서 |
|---|---|
preset, groups, sigalgs, ciphers, ciphersuites, alpn, session_tickets, ocsp_stapling |
지정한 프리셋 것으로 교체. 이것이 ClientHello 모양이고, 병합하면 목적지 자신의 값이 계속 이기게 됩니다 |
client_cert, client_key |
유지. 오버라이드는 hello가 어떻게 보일지를 말하지 gori가 누구인지를 말하지 않습니다. 인증서를 떨어뜨리면 "chrome vs curl"이 "인증됨 vs 익명"이 됩니다 |
min_version, max_version |
유지. 버전 범위는 그 목적지의 도달 가능성에 대한 사실입니다 |
permissive |
유지. 오버라이드는 security level 0을 줄 수도 뺏을 수도 없습니다 |
오버라이드만 다른 두 전송은 서로 다른 SSL 컨텍스트로 연결하므로 실제로 핸드셰이크도 두 번입니다. https 전용입니다. 평문 구간은 ClientHello를 보내지 않고, gori는 보내지 않은 것을 보고하지 않습니다. 목적지 단위 프리셋과 마찬가지로 이것들은 근사치입니다(확장 순서와 GREASE 배치는 OpenSSL의 것). 가정하지 말고 gori settings tls-fingerprint HOST --preset NAME으로 확인하세요.
--config PATH
--config는 이번 실행에 쓸 설정 파일을 지정합니다. 서브커맨드 앞에 옵니다.
gori --config ./ci-profile.json run capture --for 5m
gori --config ~/profiles/corp.json # 다른 설정으로 TUI 실행
우선순위는 --config → $GORI_CONFIG → $GORI_HOME/settings.json입니다.
이 플래그는 의도적으로 GORI_HOME과 직교합니다. 읽고 쓸 설정 파일만 바꾸고 CA, 프로젝트 DB, 테마, 워드리스트는 그대로 둡니다.
gori wizard
gori wizard
대화형 설정(전역 프록시 바인드 기본값, 테마, Miss Ring 마스코트, 그다음 에디터 키셋과 바꾸는 곳까지 알려 주는 요약)을 실행합니다. 최초 실행 시에도 자동으로 실행됩니다. 바인드 단계는 공유 settings.json 기본값을 기록하며, 선택한 포트에 이미 다른 프로세스가 열려 있으면 경고합니다(Enter를 한 번 더 누르면 그대로 유지). 프로젝트는 Project 탭에서 자체 주소를 고정할 수 있으며, --listen / --port는 이번 실행에 한해서만 오버라이드합니다. Esc를 두 번 누르면 마법사를 건너뛰고, 각 행은 클릭으로도 선택할 수 있습니다.
gori tutorial
gori tutorial
목업 UI에서 TUI를 대화형으로 둘러봅니다: 탭/패널 탐색(서브탭 줄 포함), space 메뉴(Space)와 두 번째 카드, 커맨드 팔레트 검색(Ctrl-P), READ/INS 편집 모드, 클라이언트를 어디로 향하게 하고 CA를 어떻게 신뢰시키는지, 캡처 스위치, 인터셉트(편집기 밖의 i), 도움말과 종료. 목업 메뉴와 투어가 언급하는 모든 키는 설치된 gori에서 읽어 옵니다. 각 레슨은 동작을 시연하고 직접 해 보도록 안내하며, 단계 표시줄의 ✓는 동작을 끝까지 해냈을 때만 켜집니다(◐는 들르기만 한 단계). 연습 단계에는 앞의 네 가지 동작에 걸친 선택적 확인 항목 여섯 개가 있습니다. 마지막 카드는 투어를 마친 뒤 이어질 프로젝트 선택 화면, --db 프로젝트(열기에 실패하면 선택 화면), 셸, 현재 세션에 맞는 다음 단계를 안내하고, 단독으로 실행한 gori tutorial을 마치면 다음에 실행할 명령을 출력합니다. gori wizard 끝에서 제공되고, 세션 안에서는 팔레트 명령 Guided tour(Ctrl-P)로 열 수 있습니다. 실제 프록시 세션 없이도 언제든 안전하게 다시 실행할 수 있습니다. 빠른 시작과 space 메뉴와 팔레트를 참고하세요.
gori update
gori update
gori update --exec # Homebrew/Snap: 패키지 매니저 명령 실행
이 gori 바이너리가 어떻게 설치되었는지 감지하여 그에 맞게 업데이트합니다:
| Install channel | Behavior |
|---|---|
독립 실행 바이너리 (curl 설치, 수동 다운로드, 워크스페이스 빌드, 또는 어떤 패키지 관리자도 소유하지 않은 /usr/bin으로의 수동 복사) |
이 OS/arch에 맞는 최신 GitHub 릴리스 자산을 내려받아 바이너리를 교체 (macOS는 전용 디렉터리의 형제 lib/도 갱신) |
| Homebrew | brew upgrade gori 출력 (--exec로 실행; brew 관리 경로는 절대 덮어쓰지 않음) |
| Snap | snap refresh gori 출력 (--exec로 실행) |
| Chocolatey | choco upgrade gori -y 출력; Windows는 실행 중인 gori.exe를 교체하지 않으므로 gori를 닫고 관리자 셸에서 실행 |
| pacman / AUR | yay / paru / pacman 안내 출력 |
| deb (dpkg) | apt 업그레이드 안내 출력 |
| rpm | dnf / yum / zypper 안내 출력 |
| Nix (스토어 경로) | nix profile upgrade / 플레이크 업데이트 안내 출력; 스토어가 읽기 전용이므로 아무것도 내려받지 않음 |
스토어 경로란 /nix/store/…뿐 아니라 옮겨진 스토어도 포함합니다. 루트 없이 설치하면 스토어가 ~/.local/share/nix/root 아래에 놓이고, NIX_STORE_DIR로 어디로든 옮길 수 있습니다. 기본 접두사를 벗어나면 스토어 해시의 형태로 판별하므로, 사용자가 우연히 nix/store라고 이름 붙인 디렉터리는 그대로 일반 바이너리 설치로 분류됩니다.
/usr/bin 또는 /bin 아래 경로는 패키지 소유권(pacman -Qo, dpkg-query -S, rpm -qf)으로 분류됩니다. 관리자가 파일을 소유하면 gori는 절대 덮어쓰지 않습니다. 프로브가 소유자를 찾지 못하면 바이너리 채널이 자체 업데이트합니다. 패키지 도구가 전혀 없으면 /etc/os-release(ID / ID_LIKE)로 Arch 계열 / Debian 계열 / RHEL 계열 안내를 폴백으로 고릅니다.
릴리스 자산 이름은 설치 가이드와 일치합니다(gori-v*-linux-* 순수 바이너리, gori-v*-osx-*.tar.gz 아카이브). macOS 아카이브 업데이트는 전용 레이아웃(예: curl 설치 프로그램의 PREFIX/opt/gori)을 요구하여 번들된 lib/가 /usr/local/lib 같은 공유 루트 아래에 절대 기록되지 않도록 합니다. 아직 릴리스 자산이 없으면 명령은 릴리스 페이지를 가리키는 명확한 오류로 종료합니다. 조용히 아무 동작도 하지 않는 것이 아닙니다.