# Redaction gates

> redaction-scan 범위·exit, REDACTION_REQUIRE_EXTRA, inventory 역검사, gitleaks 설정, 스테이징 전제와 pre-push 훅.

- Repository: local/mogui-ADE-orchestrator

- Human docs: https://grok-wiki.com/public/docs/local-mogui-ade-orchestrator-97afe791d5ac
- Complete Markdown: https://grok-wiki.com/public/docs/local-mogui-ade-orchestrator-97afe791d5ac/llms-full.txt

## Source Files

- `scripts/redaction-scan.sh`
- `scripts/redaction-inventory`
- `scripts/redaction-allowlist.txt`
- `config/gitleaks.toml`
- `docs/internal/tooling/redaction-scan.md`
- `hooks/pre-push`
- `tests/test_redaction_scan_native_script.py`
- `SECURITY.md`

---

---
title: "Redaction gates"
description: "redaction-scan 범위·exit, REDACTION_REQUIRE_EXTRA, inventory 역검사, gitleaks 설정, 스테이징 전제와 pre-push 훅."
---

`scripts/redaction-scan.sh`는 gitleaks를 매칭 엔진으로 쓰는 fail-closed 게이트다. 스캔 범위를 tracked·staged·commit range로 한정하고, 커밋 메시지를 별도 루프로 읽으며, 조직 규칙(`REDACTION_EXTRA_PATTERNS`)을 런타임에 병합한 뒤 실제로 무엇을 읽었는지 한 줄로 선언한다. 역검사 도구 `scripts/redaction-inventory`와 선택 훅 `hooks/pre-push`가 같은 방어면을 보완한다.

## 구성 요소

| 경로 | 역할 |
| --- | --- |
| `scripts/redaction-scan.sh` | 범위 선택, org 규칙 번역, 엔진 자가검사, 파일·커밋 메시지 스캔, exit 계약 |
| `config/gitleaks.toml` | 커밋된 규칙 세트 (`useDefault = true` 확장) + 클래스 단위 allowlist |
| `scripts/redaction-inventory` | 규칙이 커버하지 않는 토큰 역검사 (blind-spot 좁히기) |
| `scripts/redaction-allowlist.txt` | 구 포맷 allowlist — **비어 있어야 함**. 항목이 있으면 exit 2 |
| `hooks/pre-push` | push 직전 range 스캔 (클론마다 opt-in) |
| `.github/workflows/gates.yml` | PR/main에서 커밋된 규칙만으로 scan 실행 |

<Note>
매칭 엔진은 gitleaks다. 스크립트는 gitleaks가 하지 않는 일을 한다: tracked 범위 강제, 커밋 메시지 스캔, org 규칙 로더, 커버리지 선언, fail-closed exit.
</Note>

## 명령과 모드

```bash
scripts/redaction-scan.sh                 # tracked 전체 (기본)
scripts/redaction-scan.sh --staged        # index / staged only
scripts/redaction-scan.sh --range A..B    # range로 변경된 파일 + 해당 커밋 메시지
scripts/redaction-scan.sh --commit-messages A..B
scripts/redaction-scan.sh --require-extra # 또는 REDACTION_REQUIRE_EXTRA=1
scripts/redaction-scan.sh -v|--verbose
scripts/redaction-scan.sh --help
```

| 모드 | 파일 열거 | 커밋 메시지 |
| --- | --- | --- |
| `tracked` (기본) | `git ls-files` | `not-scanned` |
| `staged` | `git diff --cached --name-only --diff-filter=ACMR` | `not-scanned` |
| `range` | `git diff --name-only --diff-filter=ACMR A..B` | range와 동일 구간 자동 스캔 |

`--range` 없이 `--commit-messages`만 주면 메시지 스캔을 임의의 모드에 추가할 수 있다. 파일은 경로당 한 번 `gitleaks dir`로 스캔한다(복수 경로 인자가 디렉터리 전체로 확장되며 untracked를 끌어들이는 것을 피함).

### 출력 계약 (scope 선언)

성공 시 stdout 한 줄이 범위를 명시한다.

```console
$ scripts/redaction-scan.sh
redaction-scan: OK — 0 findings (mode=tracked, files=144, commit-messages=not-scanned, org-rules=10)
```

| 필드 | 의미 |
| --- | --- |
| `mode` | `tracked` / `staged` / `range` |
| `files` | 실제로 연 파일 수 |
| `commit-messages` | 스캔한 메시지 수, 또는 `not-scanned` |
| `org-rules` | **로드되어 엔진에 들어간** 조직 규칙 수 (파일 줄 수가 아님) |

<Warning>
초록 결과가 “전체 감사 완료”를 의미하지 않는다. org 규칙이 없으면 stderr에 generic-only 경고가 뜨고, `org-rules=0`으로 끝난다. 공개 push/CI 게이트에서는 `REDACTION_REQUIRE_EXTRA=1`로 그 갭을 실패로 바꿔야 한다.
</Warning>

## Exit 코드

| 코드 | 의미 | 대표 원인 |
| --- | --- | --- |
| `0` | clean | 매칭 0건 |
| `1` | findings (fail-closed) | 면제되지 않은 시크릿/식별자 |
| `2` | cannot decide | `gitleaks` 없음, `config/gitleaks.toml` 없음, 필수 org 규칙 미로드, 구 allowlist 항목 잔존, range 미해석, RE2 비호환 병합 설정, 엔진 오류, 사용법 오류 |

판정 불가(2)와 발견(1)을 합치지 않는다. 엔진 실패를 clean으로 읽히게 두지 않기 위해 `gitleaks`에 `--exit-code 0`을 쓰고, 엔진 non-zero는 스크립트가 2로 승격한다.

## 조직 규칙 (`REDACTION_EXTRA_PATTERNS`)

공개 저장소이므로 회사·제품·개인 식별자 패턴은 커밋하지 않는다. 체크아웃마다 외부 파일로 공급한다.

```bash
cat > ~/.config/redaction-extra.txt <<'RULES'
company_acme|Company identifier acme|(?i)acme
personal_handle|Personal handle|(?i)myhandle
person_x_ko|Personal identifier native|가나다
RULES

export REDACTION_EXTRA_PATTERNS=~/.config/redaction-extra.txt
```

### 라인 형식

```text
id|description|regex
```

- 빈 줄과 `#` 주석 무시
- 구분자는 **앞 두 개의** `|`만 사용 (regex 안 `|` 허용)
- Python `re.compile`로 1차 검증 후 gitleaks RE2로 병합
- 형식 오류·컴파일 실패 줄은 건너뛰고 WARNING (`N of M organization rule lines are unusable`)

### 필수화

```bash
export REDACTION_REQUIRE_EXTRA=1
# 또는
scripts/redaction-scan.sh --require-extra
```

org 규칙이 0개면:

1. WARNING: generic patterns only  
2. `REQUIRE_EXTRA=1`이면 FAIL + exit **2**

### RE2 엔진 자가검사

병합 설정을 쓰기 전에 `gitleaks stdin` canary를 돌린다. lookaround 등 RE2 비호환 구문은 설정 로드 패닉 → 전 스캔 no-op → 거짓 초록이 된다. canary 실패 시 규칙 **id만** 나열하고 exit 2한다 (패턴 본문은 출력하지 않음).

### 네이티브 스크립트 경고

로드된 org 규칙에 ASCII 초과 문자가 하나도 없으면:

```text
WARNING — organization rules contain no native script pattern; romanization only identifier rules miss native spellings
```

리터럴 비ASCII만 인정한다. `\u…`, `\p{Hangul}` 같은 이스케이프/속성 클래스는 로더 또는 canary에서 걸러진다.

## `config/gitleaks.toml`

커밋된 베이스 설정. `[extend] useDefault = true`로 gitleaks 기본 시크릿 세트를 포함하고, 저장소 규칙을 추가한다.

| `id` | 대상 |
| --- | --- |
| `private_key` | PEM/OpenSSH private key 헤더 |
| `aws_access_key` | `AKIA…` |
| `github_token` | `gh[pousr]_…` |
| `slack_token` | `xox[baprs]-…` |
| `openai_sk` / `anthropic_key` | `sk-…` / `sk-ant-…` |
| `bearer_token` | `Bearer …` |
| `assignment_secret` / `dotenv_export` | 하드코딩 할당·export |
| `home_path` | `/Users/<name>` (fixture 접두사 일부 allow) |
| `internal_ip` | RFC1918 |
| `jira_hf` | `HF-*` |
| `slack_url` / `internal_host` | Slack URL, `*.internal`/`*.corp`/`*.local` |

전역 allowlist는 placeholder 라인 regex(`example.com`, `YOUR_API_KEY`, `sk-test-` 등)와 경로 제외(`scripts/redaction-scan.sh`, `config/gitleaks.toml`, `__pycache__/`, `.orca/` 등)를 담는다.

### 면제 (exemption)

| 방법 | 용도 |
| --- | --- |
| `.gitleaksignore` fingerprint | 단일 finding |
| `config/gitleaks.toml` allowlist | 규칙/클래스 단위 |
| ~~`scripts/redaction-allowlist.txt`~~ | **퇴역**. 주석이 아닌 항목이 있으면 exit 2 |

진짜 시크릿은 allowlist 하지 말고 로테이션 후 히스토리에서 제거한다.

## 스테이징 전제

스캐너는 **tracked(또는 staged/range에 포함된) 파일만** 읽는다.

- 아직 `git add` 하지 않은 신규 파일은 보이지 않고, 결과만 보면 clean이다.
- 워크트리에만 있는 시크릿·식별자는 게이트를 통과할 수 있다.
- CONTRIBUTING 운영 순서: **먼저 stage, 그다음 scan**.

```bash
git add <paths>
scripts/redaction-scan.sh --staged
# 또는 push 전 full tracked
scripts/redaction-scan.sh --require-extra
```

SECURITY.md가 문서화한 알려진 한계와 동일하다: unstaged 신규 파일 비가시, inventory의 바이너리 무음 스킵.

## pre-push 훅

커밋된 훅: `hooks/pre-push`. husky/lefthook 없음. 클론마다 한 번 켠다.

```bash
git config core.hooksPath hooks
```

동작 요약:

1. stdin의 각 push ref에 대해 local/remote SHA를 읽는다.
2. remote 삭제(`local_sha` = zero)는 스킵.
3. 가능하면 base를 잡고 `scripts/redaction-scan.sh --range "$base..$local_sha"` 실행 (중간 커밋·커밋 메시지 포함 — tip-only 스캔이 놓치는 형태를 막기 위함).
4. range를 하나도 못 잡으면 tracked 전체 스캔으로 fall back (silent pass 방지).
5. 호출자 env를 그대로 사용: `REDACTION_EXTRA_PATTERNS` / `REDACTION_REQUIRE_EXTRA`가 있으면 적용. **훅 자체가 org 규칙을 강제하지 않는다** (공개 저장소에 커밋된 훅이 비공개 규칙을 요구할 수 없음).
6. 테스트 스위트는 훅에 넣지 않는다 (느린 훅 → `--no-verify` 우회).

## redaction-inventory (역검사)

`redaction-scan`이 “규칙이 이름 붙인 것”을 찾는다면, inventory는 **규칙이 전혀 건드리지 않는 토큰**을 보고한다. 빈 결과 ≠ 안전 증명. blind-spot을 좁힐 뿐이다.

```bash
REDACTION_EXTRA_PATTERNS=~/.config/redaction-extra.txt scripts/redaction-inventory
scripts/redaction-inventory --baseline .redaction-inventory-baseline
scripts/redaction-inventory --json
```

| 항목 | 값 |
| --- | --- |
| 범위 | 현재 커밋의 tracked 파일 (+ 파일명 결합) |
| 필수 env | `REDACTION_EXTRA_PATTERNS` (없으면 exit 2) |
| 후보 종류 | kebab-case, 한글 인접 Latin 단어, `/Users/` 사용자명, 이메일 도메인 |
| baseline | 기본 `.redaction-inventory-baseline` — 검토 완료 토큰 제외 |
| 바이너리 | 앞 8KB에 NUL이면 스킵 (출력에 “skip” 고지 없음) |

| Exit | 의미 |
| --- | --- |
| `0` | uncovered 후보 0 |
| `1` | 후보 발견 (시크릿 판정이 아님) |
| `2` | 규칙 파일 없음/비파싱, git 아님, tracked 파일 0 |

한국어 인명은 자동 탐지하지 않는다. org 규칙에 `person_*` 등으로 직접 넣는다.

## 스캔이 보지 않는 것

저장소 밖 표면은 범위 밖이다.

- PR 제목/본문, 리뷰 코멘트, 이슈, 릴리스 노트
- untracked / unstaged 신규 파일
- inventory 기준 바이너리 파일 내용

외부로 나가는 산문은 게시 전 별도 grep이 필요하다.

## CI

`.github/workflows/gates.yml`의 `redaction` job:

- `./scripts/redaction-scan.sh` — **커밋된 규칙만** (org 파일은 저장소에 없음)
- inventory는 워크플로에서 비차단일 수 있음; 전체 org 스캔은 로컬 pre-push 책임

gitleaks는 워크플로에서 버전 핀·체크섬 검증 후 설치한다. 테스트 job에도 gitleaks를 깔아 `redaction-scan.sh` 실측 테스트를 돌린다.

## 운영 체크리스트

<Steps>
  <Step title="도구 준비">
    `gitleaks`, `bash`, `git`, `python3`를 PATH에 둔다. macOS 예: `brew install gitleaks`.
  </Step>
  <Step title="조직 규칙 파일">
    `REDACTION_EXTRA_PATTERNS`를 VCS 밖 파일로 설정한다. 로마자 + 네이티브 표기를 같이 넣는다.
  </Step>
  <Step title="스테이징 후 스캔">
    변경을 stage한 뒤 `scripts/redaction-scan.sh --staged` 또는 full tracked + `--require-extra`.
  </Step>
  <Step title="pre-push 활성화">
    `git config core.hooksPath hooks`
  </Step>
  <Step title="역검사">
    `scripts/redaction-inventory`로 규칙 blind-spot 후보를 고르고, 필요한 것만 org 규칙 또는 baseline에 반영한다.
  </Step>
</Steps>

### 실패 대응

| 증상 | 조치 |
| --- | --- |
| `gitleaks is not on PATH` (exit 2) | gitleaks 설치 |
| `required organization rules were not loaded` | `REDACTION_EXTRA_PATTERNS` 경로·내용 확인 |
| `retired format` allowlist | `.gitleaksignore` 또는 `config/gitleaks.toml`로 이전 후 allowlist 파일 비우기 |
| `not supported by RE2` | lookaround 등 제거 후 규칙 재작성 |
| `range does not resolve` | 로컬에 없는 SHA/range — fetch 또는 base 재설정 |
| finding (exit 1) | 제거·로테이션, 또는 의도적 fixture만 fingerprint 면제 |
| 초록인데 unstaged 파일에 시크릿 | stage 후 재스캔 (스코프 한계) |

## Related pages

<CardGroup>
  <Card title="방어 인벤토리" href="/defense-inventory">
    redaction scan·inventory를 포함한 런타임 가드 표
  </Card>
  <Card title="Contributing" href="/contributing">
    tracked-only 전제, hooksPath, pytest, exit 규약
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    preflight·gate 실패 복구 프로브
  </Card>
  <Card title="CLI 레퍼런스" href="/cli-reference">
    scripts/ 공개 명령 표와 옵션 경계
  </Card>
</CardGroup>
