From 2dca4fbdfd910c83c376996448912cf6e1c367a3 Mon Sep 17 00:00:00 2001 From: bluecheat Date: Sat, 3 Oct 2026 19:56:15 +0900 Subject: [PATCH] =?UTF-8?q?fix(hooks):=20kv-detect=20=EA=B0=80=20TS=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=84=A0=EC=96=B8(`token:=20string,`)?= =?UTF-8?q?=EC=9D=84=20=EC=8B=9C=ED=81=AC=EB=A6=BF=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9E=A1=EB=8D=98=20=EA=B2=83=20=E2=80=94=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=20=EC=9E=90=EB=A6=AC=EB=A7=8C=20=EC=A7=80=EC=9A=B0=EA=B3=A0=20?= =?UTF-8?q?=EA=B0=92=20=EB=8C=80=EC=9E=85=EC=9D=80=20=EA=B7=B8=EB=8C=80?= =?UTF-8?q?=EB=A1=9C=20=EC=9E=A1=EC=95=84=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kv-detect 는 키 이름 뒤 `:` 를 값의 시작으로 읽어서 타입 자리가 값이 됐어요. 실측(one-tenth): `token: string,` · `messageFor?(target: UserTarget, token: string, platform: Platform)` 이 "CRITICAL 위반 1건" 이 됐고, 그 프로젝트는 매 커밋 오탐을 피하려고 sensors.mode 를 warning 으로 내렸어요 — 시크릿 안전망 전체가 경고로 내려간 셈이에요. ERE 에는 lookahead 가 없어서 표 행 하나로는 "타입이면 빼라" 를 못 써요. 그래서 kv-detect 를 돌리기 전에 줄에서 `키: 타입` 조각만 지워요 (줄 수 불변 — 줄 번호가 원본과 같아요). - ① 원시 타입 (string·number·String·Int …) ② `?:` ③ 타입 이름 + `, ; ) | & < > [` ④ 줄 끝 대문자 낱말(숫자 섞이면 제외). `=` 는 일부러 종결자에서 빼서 `token: Foo = "…"` 의 대입은 남겨요 - `common.sh` 에 `goax_secret_scan_file` — 파일 하나를 `줄번호 TAB 라벨` 로 검출 (내용은 안 내요). critical-rule-grep 은 이 함수 하나로 파일을 읽어요 - 새 프로브 예시 `secret-scan-typed.sh` — 타입 옆 시크릿 값은 막는지(네거티브) + 타입 선언만은 통과하는지(오탐 회귀)를 같이 재요 smoke §41: 타입 선언 픽스처(TS 인터페이스·시그니처·제네릭·유니온·Kotlin/Swift) 통과, 값 대입 6종(`: string = "…"` · `{ token: "…" }` · `= "…"` · YAML 2종 · 같은 줄 타입+값) 차단, 프로브 PASS. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01C8rZ6V3FW3nwYA9T7iygPd --- templates/default/.ax/hooks/README.md | 6 +- .../hooks/pre-commit/critical-rule-grep.sh | 13 +---- templates/default/.ax/scripts/bash/README.md | 2 +- templates/default/.ax/scripts/bash/common.sh | 45 ++++++++++++++ templates/zero/probe/README.md | 3 +- .../zero/probe/examples/secret-scan-typed.sh | 58 +++++++++++++++++++ tests/smoke.sh | 39 +++++++++++++ 7 files changed, 152 insertions(+), 14 deletions(-) create mode 100755 templates/zero/probe/examples/secret-scan-typed.sh diff --git a/templates/default/.ax/hooks/README.md b/templates/default/.ax/hooks/README.md index e0ddabc..f1db128 100644 --- a/templates/default/.ax/hooks/README.md +++ b/templates/default/.ax/hooks/README.md @@ -79,7 +79,7 @@ compaction 뒤엔 4시간 TTL 이 다시 줄 여지를 남겨요. 세션 id 가 ## Secrets 검출 — 패턴의 SSOT 는 `common.sh` 의 표 하나예요 시크릿의 형태를 아는 곳은 `.ax/scripts/bash/common.sh` 의 `goax_secret_rules` 표 **하나** 예요. -`pre-commit/critical-rule-grep.sh` 의 검출은 `goax_secret_patterns`(표의 `use=both|detect` 행)에서, +`pre-commit/critical-rule-grep.sh` 의 검출은 `goax_secret_scan_file`(표의 `use=both|detect` 행)에서, `redact_secrets` 의 마스킹은 같은 표의 `use=both|mask` 행에서 나와요. 형태를 하나 더할 땐 표에만 행을 넣으세요 — 훅에 패턴을 다시 적으면 그 순간 두 곳이 어긋나요. 실제로 어긋나 있었고(웹훅은 마스킹만, `pg_key` 는 마스킹만, `passwd`·`access_key` 는 검출만, AWS·Stripe·JWT 는 정량자가 서로 달랐어요), @@ -88,7 +88,9 @@ compaction 뒤엔 4시간 TTL 이 다시 줄 여지를 남겨요. 세션 id 가 표는 두 갈래를 담아요. ① 발급처가 형식을 정해둔 토큰 — 대소문자를 그대로 봐야 오탐이 안 늘어요. ② `key=value` — 키 이름의 대소문자는 표가 브래킷으로 담고 있어서 `grep -i` 가 필요 없고, 검출 행과 마스킹 행이 키 목록 조각을 공유해요. `${GITHUB_TOKEN}`·`` 같은 참조 표기와 `secret: null`· -`token_count = 0` 은 값 첫 글자와 최소 길이로 걸러요. 검출되면 **파일 이름만** 찍어요 — 매칭된 줄을 +`token_count = 0` 은 값 첫 글자와 최소 길이로 걸러요. TS·Kotlin·Swift 타입 자리(`token: string,` · +`apiKey?: Foo` · `(token: string, …)`)는 `key=value` 를 보기 전에 줄에서 지워요 — 같은 줄의 값 대입은 그대로 +남아 걸려요. 검출되면 **파일 이름만** 찍어요 — 매칭된 줄을 stderr 로 흘리면 검출한 의미가 없어요. `common.sh` 가 없으면 검출할 패턴 자체가 없어요. 그때는 조용히 통과하지 않고 diff --git a/templates/default/.ax/hooks/pre-commit/critical-rule-grep.sh b/templates/default/.ax/hooks/pre-commit/critical-rule-grep.sh index 82618cb..93d340d 100755 --- a/templates/default/.ax/hooks/pre-commit/critical-rule-grep.sh +++ b/templates/default/.ax/hooks/pre-commit/critical-rule-grep.sh @@ -89,21 +89,14 @@ echo "[goax] CRITICAL 룰 검사 (mode=$SENSOR_MODE) — 대상 $(echo "$STAGED" # ② 일반 key=value — 값 첫 글자에서 `$`·`<`·`{`·`%`·`(` 를 빼서 `${GITHUB_TOKEN}`·`` # 같은 참조 표기를 오탐하지 않아요. 값은 6자 이상이라 `secret: null`·`token_count = 0` 도 안 걸려요 # (키 이름의 대소문자는 표가 브래킷으로 담고 있어서 `grep -i` 가 필요 없어요) +# TS·Kotlin·Swift 타입 자리(`token: string,`)는 kv-detect 전에 지워요 — `goax_secret_scan_file` 이 해요 # -# `grep` 의 `-e` 는 필수예요 — PEM 행이 `-----` 로 시작해서, 빼면 옵션으로 읽혀 rc=2 가 나고 -# 그 한 종이 조용히 미탐돼요. -SECRET_PATTERNS=$(goax_secret_patterns) - +# 파일을 읽는 건 `goax_secret_scan_file` 하나예요 (줄번호 + 라벨만 내고 내용은 안 내요). SECRET_FILES="" while IFS= read -r f; do [ -z "$f" ] || [ ! -f "$f" ] && continue - hit="" - while IFS= read -r pat; do - [ -z "$pat" ] && continue - if grep -qIE -e "$pat" "$f" 2>/dev/null; then hit="$pat"; break; fi - done <<< "$SECRET_PATTERNS" # 매칭된 줄은 안 찍어요 — 시크릿을 stderr·로그로 다시 흘리면 검출한 의미가 없어요 - [ -n "$hit" ] && SECRET_FILES="${SECRET_FILES}${f}"$'\n' + [ -n "$(goax_secret_scan_file "$f")" ] && SECRET_FILES="${SECRET_FILES}${f}"$'\n' done <<< "$STAGED" if [ -n "$SECRET_FILES" ]; then SECRET_N=$(printf '%s' "$SECRET_FILES" | grep -c . || true) diff --git a/templates/default/.ax/scripts/bash/README.md b/templates/default/.ax/scripts/bash/README.md index 4157534..01a7e24 100644 --- a/templates/default/.ax/scripts/bash/README.md +++ b/templates/default/.ax/scripts/bash/README.md @@ -7,7 +7,7 @@ | 스크립트 | 용도 | 호출하는 skill | |---|---|---| -| `common.sh` | 공통 함수 (find_project_root, json_output, [goax] log, `goax_inject_fresh` 세션 내 중복 주입 제거, `goax_lock`/`goax_unlock`/`goax_unlock_all` 원장 락, `goax_mktemp` 폴백 임시 파일, `goax_git_hook_path` git 없이도 도는 훅 경로, `goax_resolve_spec` `--spec` 축약 해석, `goax_secret_rules`/`goax_secret_patterns`/`redact_secrets` 시크릿 패턴 SSOT — 검출과 마스킹이 같은 표에서 나와요, `goax_hook_enabled`/`goax_hook_profile` 훅 끄기·프로필, `goax_session_mark`/`goax_session_marked`/`goax_session_count` 세션 마커, `goax_shell_scan` 따옴표·heredoc 을 셸처럼 읽는 명령 판정(bypass·destructive), `goax_module_rules_matching`·`goax_imported_paths` 주입 훅과 게이트가 공유하는 룰 매칭, `goax_doc_id`/`goax_doc_key`/`goax_doc_sort` spec·ADR ID) | (sourced by all) | +| `common.sh` | 공통 함수 (find_project_root, json_output, [goax] log, `goax_inject_fresh` 세션 내 중복 주입 제거, `goax_lock`/`goax_unlock`/`goax_unlock_all` 원장 락, `goax_mktemp` 폴백 임시 파일, `goax_git_hook_path` git 없이도 도는 훅 경로, `goax_resolve_spec` `--spec` 축약 해석, `goax_secret_rules`/`goax_secret_patterns`/`goax_secret_scan_file`/`redact_secrets` 시크릿 패턴 SSOT — 검출과 마스킹이 같은 표에서 나와요 (검출은 kv-detect 전에 TS·Kotlin·Swift 타입 자리를 지워요), `goax_hook_enabled`/`goax_hook_profile` 훅 끄기·프로필, `goax_session_mark`/`goax_session_marked`/`goax_session_count` 세션 마커, `goax_shell_scan` 따옴표·heredoc 을 셸처럼 읽는 명령 판정(bypass·destructive), `goax_module_rules_matching`·`goax_imported_paths` 주입 훅과 게이트가 공유하는 룰 매칭, `goax_doc_id`/`goax_doc_key`/`goax_doc_sort` spec·ADR ID) | (sourced by all) | | `detect-model.sh` | 지금 돌고 있는 모델 식별 — override → `$GOAX_MODEL` → transcript 스캔 → unknown | (진단·로깅용) | | `next-spec-num.sh` | 새 spec/ADR ID 발급 — `YYYY-MM-DD-<4hex>` (`--kind spec\|adr`, `--reserve --slug` 로 실물까지 O_EXCL 생성). 순번이 아니라 브랜치끼리 안 겹쳐요. `--check-duplicates` 는 옛 순번(`NNN`/`NNNN`) 중복 진단 | `spec`, `adr`, `doctor` | | `tier-from-state.sh` | current-task.json + config.yml → tier 결정 + evaluator 필수 여부 + spec_review 필수 여부(Size 축만) (`--reset` 는 `reset-task.sh` 경유) | `spec`, `tasks-gate.sh`, `spec-review.sh`, `update-state.sh` | diff --git a/templates/default/.ax/scripts/bash/common.sh b/templates/default/.ax/scripts/bash/common.sh index f560a02..16c9777 100755 --- a/templates/default/.ax/scripts/bash/common.sh +++ b/templates/default/.ax/scripts/bash/common.sh @@ -158,6 +158,7 @@ goax_hook_exit() { # # goax_secret_rules() 표 자체. 한 행 = TAB