gori는 하나의 프로젝트와 하나의 엔진 위에 세 개의 진입점을 둡니다. gori(사람을 위한 TUI), gori run(스크립트를 위한 헤드리스 CLI), 그리고 gori mcp(AI 에이전트용)입니다. 이 페이지는 스크립팅 경로를 다룹니다.
gori run은 TUI를 얇게 감싼 래퍼가 아닙니다. 같은 Store, Repeater, 스윕 엔진에 터미널 없는 프런트엔드를 붙인 것입니다. 손으로 캡처한 것은 스크립트에서 질의할 수 있고, 스크립트가 캡처한 것은 TUI를 열면 그대로 보입니다.
gori run <subcommand> [verb] [options]
전체 서브커맨드 목록은 gori run -h로, 모든 플래그는 CLI 레퍼런스에서 확인하세요.
프로젝트 선택
각 프로젝트는 자체 SQLite 데이터베이스입니다. 읽기 서브커맨드는 다음 순서로 하나를 고릅니다.
| 선택자 | 의미 |
|---|---|
--db=PATH |
특정 데이터베이스 파일 |
--project=NAME |
짧은 id, 디렉터리 슬러그, 표시 이름, 고유 id 접두사로 매칭(대소문자 무시) |
| (둘 다 없음) | 가장 최근에 사용한 프로젝트 |
두 선택자는 우선순위가 아니라 택일입니다. 둘 다 주면 --db가 조용히 이기는 게 아니라
사용법 오류로 거절합니다. 같은 플래그 짝이 파괴적 동사(history delete, history clear,
project delete)에도 그대로 닿는데, 거기서는 보이지 않는 승자가 어느 프로젝트를 비울지
결정하기 때문입니다.
gori run capture만 한 가지가 다릅니다. 읽기는 이미 존재하는 프로젝트를 요구하지만, capture는 대상을 생성하거나 다시 엽니다.
읽기 서브커맨드는 스토어를 읽기 전용으로 열고 캡처 락을 잡지 않으므로, 라이브 TUI가 캡처 중인 프로젝트를 대상으로 실행해도 안전합니다. SQLite WAL이 읽는 쪽과 쓰는 쪽을 함께 감당합니다. body: 질의는 예외입니다. 검색 인덱스를 비우므로 쓰기입니다.
gori run history --project my-engagement -q 'status:5xx'
gori run issues --db /path/to/project.db --format json
스크립팅 계약
gori run이 뱉는 JSON은 눈으로 보라고 만든 게 아니라 파싱하라고 만든, 안정적이고 문서화된 형태입니다. 다음 네 가지 규칙이 파이프를 깔끔하게 유지합니다.
STDOUT은 데이터, STDERR은 진단. 경고, 개수, 안내, 내보내기 확인 메시지는 모두 STDERR로 갑니다. 그래서 gori run … | jq는 입력에서 잡담을 걸러낼 필요가 없습니다.
--format이 형태를 정합니다. 대부분의 서브커맨드는 text(기본)와 json을 받고, 일부는 jsonl, raw, har, paths, markdown을 더합니다. 실행이 길게 이어지는 곳에서는 두 JSON 형태가 다르고, 그 차이를 알아둘 만합니다.
| 서브커맨드 | --format json |
--format jsonl |
|---|---|---|
capture, history |
한 줄에 JSON 객체 하나 | json의 별칭, 출력 동일 |
fuzz, mine, discover |
버퍼링 후 마지막에 JSON 배열 하나 | 결과가 나올 때마다 한 줄씩 |
긴 스윕을 진행 중에 소비하려면 jsonl을, 끝에 문서 하나를 받으려면 json을 씁니다.
종료 코드에 의미가 있습니다.
| 코드 | 의미 |
|---|---|
0 |
성공 |
1 |
오류: 전송 실패, 열 수 없는 프로젝트, 적용되지 못한 변경 |
3 |
gori run fuzz --fail-if-no-matches가 정상 완료했지만 매칭이 하나도 없음 |
매칭이 없으면서 동시에 모든 전송이 실패한 fuzz 실행(대상 다운, TLS 실패, 스코프 차단)은 3이 아니라 1로 끝납니다. --fail-if-no-matches 없이도 스크립트가 "결과 없음"과 "대상에 닿지도 못함"을 구분할 수 있습니다.
닫힌 파이프는 오류가 아닙니다. gori run history | head -5는 여느 유닉스 필터처럼 조용히 0으로 끝납니다.
# 프로젝트의 모든 5xx를 JSON Lines로 뽑아 jq로
gori run history -q 'status:5xx' --limit 500 --format json | jq -r '.url'
# 5분간 캡처해 이름 붙인 프로젝트에 쌓고, 파일로 스트리밍
gori run capture --project ci-run --for 5m --format jsonl > flows.jsonl
# 퍼저가 반사된 마커를 찾으면 CI 잡을 실패시키기
gori run fuzz 42 --wordlist payloads.txt --mr 'gori-canary' --fail-if-no-matches
스코프 지키기
소켓을 여는 모든 액티브 서브커맨드는 TUI와 MCP가 쓰는 것과 같은 아웃바운드 게이트를 지납니다. 스코프 규칙이 있는 프로젝트는 그 밖의 대상을 거부하며, --allow-unscoped가 의도적인 예외 선언입니다. 샌드박스와 명시적 제외 규칙은 이 플래그와 무관하게 항상 적용됩니다.
--request나 STDIN으로 원시 요청을 퍼징하면서 --project/--db를 주지 않으면 참조할 스코프 자체가 없습니다. 이때 gori는 검사한 척하지 않고 STDERR에 명시적인 unscoped 경고를 출력합니다.
인증이 필요한 스윕
세션 바인딩($BIND.SESSION 같은 것들)은 그것을 관측한 gori 프로세스의 메모리에만 존재하며, 절대 저장되지 않습니다. 복원된 토큰은 이미 낡은 것이기 때문입니다. TUI에서는 한 프로세스가 전송과 뒤이은 스윕을 모두 쥐고 있으니 문제가 없지만, gori run은 프로세스마다 한 번만 실행됩니다.
--bind-from FLOW-ID가 그 빈틈을 메웁니다. 캡처된 플로우 하나를 먼저 재생해서, 그 응답이 같은 프로세스 안에서 fuzz·mine·sequence·discover 템플릿이 읽을 바인딩을 채우게 합니다.
gori run fuzz 42 --bind-from 41 --wordlist ids.txt
바인딩을 정의하는 추출 규칙은 세션 바인딩을 참고하세요.
프로세스 훅
gori에는 플러그인 SDK가 없고 앞으로도 없습니다. 변환을 계산해야 할 때(JWT 재서명, 바디 재압축, 독자 포맷 봉투 복호화, 진짜 탐지기 실행) 이미 가지고 있는 프로그램에 바이트를 넘기면 됩니다. stdin으로 바이트를 주고 stdout으로 교체 바이트를 받습니다. 확장 표면은 이게 전부이고, 같은 원시 도구가 네 군데에 붙어 있습니다.
| 이음매 | 위치 | 하는 일 |
|---|---|---|
Rewriter pipe op |
Rewriter 탭, gori run rewriter add --op=pipe, MCP create_rule |
매치된 구간을 명령에 넘기고 stdout으로 교체. 프록시에서 실시간으로 |
Decoder exec: 스텝 |
Decoder 탭 체인, gori run decoder, §value¦chain§ 마커 |
체인의 한 스텝이 컨버터가 아니라 명령 |
Probe exec 룰 |
Probe 룰, gori run probe rules add --exec |
구간을 명령에 넘겨 exit 0이면 발견, stdout이 근거 |
Miner --hook |
gori run mine --hook, MCP mine_start의 hook |
조립된 요청 전체를 명령에 넘기고 그 stdout이 실제로 나가는 요청. 프로브 하나당 훅 하나 |
# 브라우저에서 나가는 모든 JWT를 내 서명기로 재서명한다.
gori run rewriter add --op=pipe --match=regex --part=body \
--find='eyJ[A-Za-z0-9._-]+' --value='./resign.sh --key dev.pem'
# base64 바디를 디코드해 내 파서에 통과시키고 예쁘게 출력한다.
gori run decoder 'base64-decode > exec:./parse-envelope --json > json-pretty' "$BLOB"
# 정규식 대신 진짜 탐지기가 판정하게 한다.
gori run probe rules add --title 'envelope leak' --exec --pattern './detect-leak --stdin'
# 서명이 걸린 API에서 숨은 파라미터 찾기: 나가기 전에 모든 프로브에 서명한다.
gori run mine 42 --locations=query --hook './sign.sh'
명령은 직접 exec됩니다. 셸이 없습니다. argv는 따옴표와 백슬래시 규칙('a b', "a b",
a\ b)으로만 토큰화되어 execvp에 데이터로 전달됩니다. $FOO, *, `, ;, &&, |,
>는 인자 안의 평범한 문자일 뿐 연산자가 아닙니다. 훅을 통과하는 캡처 바이트가 셸에 해석될
길은 없습니다. Decoder 체인에서는 세 구분자(>, |, ,)가 스텝을 읽기 전에 소비되므로
exec: 스텝의 인자 안에는 들어갈 수 없습니다.
훅은 프록시를 멈추지 못합니다. 모든 실행에 하드 벽시계 타임아웃(settings.json의
hooks.timeout_secs, 기본 5초, 상한 60초)과 32 MiB stdout 상한이 걸립니다. 명령이 타임아웃되거나,
0이 아닌 코드로 종료되거나, 실행 자체에 실패하거나, stdout을 넘치게 쏟으면 원본 바이트가 그대로
통과하고 실패는 프로젝트 이벤트 피드에 알림으로 기록됩니다. 멈춘 훅 때문에 플로우를 잃는 일은
없습니다. Rewriter에서 타임아웃은 한 번의 재작성에 걸린 모든 pipe 룰과 모든 매치가 나눠 쓰는
예산입니다. 400번 매치되는 패턴도, 한 헤드에 걸린 pipe 룰 4개도 예산 한 번만큼만 듭니다. 메시지
하나는 헤드와 바디로 두 번 재작성되므로 메시지가 보는 한계는 그 두 배입니다.
Miner의 훅은 프로브마다 한 번 실행되며, 서명이 걸린 API의 값을 치릅니다. 모든 파라미터가 HMAC이나
서명된 봉투, 요청마다 다른 nonce를 지녀야 하는 앱은 날것의 후보를 반응하기도 전에 거절하므로, 훅이
없으면 캘 것이 없습니다. 모든 프로브가 똑같아 보입니다. --hook은 조립된 요청 하나하나를(후보가
주입되고 세션 바인딩이 이미 해소된 상태로) 지정한 명령에 넘기고, 그 stdout이 실제로 나갑니다. 실행할 수
없는 훅은 이유를 밝히며 그 프로브를 건너뜁니다. 서명 없는 요청을 보내면 앱이 거절하고 마이너는 그것을
깨끗한 음성으로 읽을 것이기 때문입니다. 타임아웃은 같은 hooks.timeout_secs 예산이며 아웃바운드 요청
단위입니다. 마인의 요청 수는 --max-requests와 자체 버킷/이분/확인 트리로 묶여 있으므로 훅 비용 총량도
함께 묶입니다. 마이너는 왕복을 세는 지연 바운드 작업이라, 훅은 그 왕복마다 fork-and-wait 하나를 더합니다.
의도적으로 연결하지 않은 두 곳. MCP decode 툴은 exec: 스텝을 거부합니다(저장된 체인
포함). read-only·unbound로 노출되는 툴이라 순수 계산으로 남깁니다. 훅이 필요한 에이전트는 pipe
rewriter 룰이나 exec probe 룰을 만들면 되고, 둘 다 운영자가 볼 수 있는 게이트된 쓰기입니다. 그리고
Probe exec 룰은 패시브 분석기에서 플로우당 한 번 실행되므로, 느린 탐지기는 라이브 캡처 중 룰
셋 전체의 병목이 됩니다. 명령을 빠르게 유지하세요.
그리는 것은 실행하는 것이 아닙니다. 무언가를 그리려고 체인을 다시 돌리는 표면은 모두
exec: 스텝을 보류하고 그 줄에 held로 표시합니다. Rewriter의 OUTPUT 패널, ^Q 체인 편집기의
미리보기, 템플릿에서 다시 만든 Fuzzer 결과 행(여기 보이는 값은 그 스텝 이전의 페이로드라고
말해 줍니다). Repeater의 Content-Length 반영만은 그 자리에서 말할 곳이 없어 헤더를 아예 고쳐
쓰지 않습니다. ^R이 렌더된 바이트에서 진짜 길이를 계산하고, ^L은 사용자가 그 헤더를 넘겨받는
바로 그 순간 한 번 다시 계산합니다. 복원도 실행이 아닙니다. 프로젝트를 다시 열면 저장된
Repeater 탭과 Decoder 서브탭이 전부 복원되지만 그 명령들은 돌지 않습니다.
실행되는 곳은 둘이고, 둘 다 "전송될 바이트"를 달라는 요청입니다. ^R은 당연하고, 덜
당연하게는 그 바이트를 그대로 건네주는 것들(Repeater의 Copy as… 메뉴와 Comparer 슬롯)
도 실행합니다. 훅을 뺀 curl 한 줄은 자기가 재현한다고 주장하는 그 요청을 재현하지 못하니까요.
Decoder 탭의 체인은 타이핑 중에도 라이브입니다. 그 탭이 exec:가 만들어진 워크벤치라
편집할 때마다 파이프라인이 다시 돕니다. 그리고 타이핑 중인 명령의 각 접두사도 그 자체로 완결된
명령입니다(exec:rm -rf /tmp/x를 치면 도중에 rm -rf /tmp가 실행됩니다). argv는 다른 데서
작성해 붙여넣거나, 디스크에서 편집하는 래퍼 스크립트를 스텝이 가리키게 하세요.
훅은 내 권한으로 실행됩니다. 샌드박스도, 감옥도, 격리도 없습니다. --config 파일이나 다른
Rewriter 룰과 같은 신뢰 수준입니다. gori가 훅을 스스로 만들어내는 일은 없습니다. 전부 사람이 쓴
설정이고, 각각은 자기가 속한 룰 목록에 그대로 보입니다. 열어둔 프로젝트에 다른 세션(에이전트,
두 번째 TUI)이 pipe 룰을 추가하면, 이 세션은 지금부터 나를 대신해 로컬 명령을 실행하게
되었다고 그대로 말해줍니다.
공유하는 프로필도 훅을 실어 나릅니다. rewriter와 scan_rules는 평범한 export 대상 섹션이고,
--sections로 지정한 decoder 체인도 마찬가지입니다. 훅도 다른 룰과 똑같이
프로필에 담겨 이동합니다. 그게 요점입니다. 팀이 같은 재서명 훅을
표준으로 쓰는 것이야말로 훅이 존재하는 이유니까요. 훅 seam 바깥에도 명령을 실행하는 설정이 둘 있고
같은 방식으로 이동합니다: statusline.command(/bin/sh -c로, 타이머마다)와 editor.command입니다.
대신 양쪽 끝에서 말해줍니다. gori settings export는 무엇을 실었는지 stderr에 개수로 알리고,
gori settings import는 명령을 담은 항목을 명령줄까지 하나씩 나열한 뒤 --allow-commands를 주기
전까지 쓰기를 거부합니다. 남의 프로필을 import하는 것은 남의 스크립트를 실행하는 것과 같은 신뢰
결정이니 명령을 먼저 읽으세요. 그 플래그가 확인 절차이고, 대화형 프롬프트가 없으므로 스크립트에서도
그대로 답할 수 있습니다.
무엇을 쓸까
| 할 일 | 서브커맨드 |
|---|---|
| CI에서 헤드리스로 트래픽 캡처 | capture |
| History 질의·내보내기(HAR 포함) | history, show |
| 요청 재전송과 비교 | repeater, compare |
| 페이로드 스윕, 숨은 파라미터 탐색 | fuzz, mine |
| 엔드포인트 크롤링·브루트포스 | discover, sitemap |
| 아이덴티티별 접근 제어 시험 | authorize |
| 스캔과 트리아지 | probe, issues, notes |
| 프로젝트 없이 순수 계산 | decoder, jwt, cookie |
| 프로젝트·스코프·env·규칙 관리 | project, rewriter, colormarker |