---
spec_id: penetration_test_spec_260506
created: 2026-05-06
type: security-spec
status: active
---

# Penetration Test Spec — task-2469

## 1. 목적

task-2467+3에서 발견된 silent corruption 4대 결함:

1. **G3 fail marker 미인식** — `.g3-fail` 존재해도 `.done` 발행 허용
2. **Gemini High severity 은닉** — 시각 priority(High 3건) vs gate high_severity_hits(0건) 불일치
3. **Stale PASS evidence 재사용** — 이전 task/PR/SHA의 PASS evidence를 현재 task에 재사용
4. **bypass/override 무감사** — TASKCTL_BYPASS=1 사용 시 audit log 미생성

task-2468에서 위 결함을 차단하는 Guard 14개(`lifecycle_guards.py`)를 구현했다.
본 spec은 해당 Guard들에 대한 **공격 시뮬레이션을 자동화된 pytest로 박제**한다.

목표: Guard 우회 불가능성을 자동 검증 체계로 남겨, 미래 회귀를 0건으로 유지한다.

---

## 2. Threat Model

### 2-1. Mandatory 시나리오 (A–F, 6개)

#### 시나리오 A: `.done` + `.g3-fail` 동시 존재 우회

| 항목 | 내용 |
|---|---|
| **공격 의도** | G3 verifier가 fail marker를 박제했음에도 `.done`을 강제 발행하려 시도 |
| **공격 방법** | marker 이름을 4가지 변형으로 위장: `g3-fail`, `g3-failed`, `g3_fail`, `g3_failed` |
| **기대 차단 지점** | `lifecycle_guards.check_g3_fail_blocks_done` |
| **Guard 함수** | `check_g3_fail_blocks_done(task_id, events_dir)` |
| **assertion 형식** | `assert r.ok is False` (4변형 모두) |

서브케이스:
- A-1: `g3-fail` marker 위장
- A-2: `g3-failed` marker 위장
- A-3: `g3_fail` marker 위장
- A-4: `g3_failed` marker 위장
- A-5: `.done` + `.g3-fail` 동시 존재 → conflict 차단

#### 시나리오 B: Gemini High/Critical 은닉 우회 (5패턴 계열)

| 항목 | 내용 |
|---|---|
| **공격 의도** | Gemini High/Critical severity를 다양한 표현 방식으로 은닉하여 gate 통과 시도 |
| **공격 방법** | heading 변형 / inline label / emoji / severity-priority keyword / 대소문자 혼용 |
| **기대 차단 지점** | `gemini_severity_parser.count_severities` |
| **Guard 함수** | `count_severities(body)` → `high >= min` |
| **assertion 형식** | `assert sv["high"] >= min_high` |

서브케이스:
- B-1: `## High` / `### Critical` / `#### Blocking` 등 H2~H6 heading 변형 (5+건)
- B-2: `**High:**` / `**[Critical]**` / `**Blocking:**` 등 inline label (4+건)
- B-3: 🔴 🚨 ⛔ ⚠️ ❌ emoji 모두 (5+건)
- B-4: `priority: critical` / `Severity:high` 대소문자 혼용 (4+건)
- B-5: `BLOCKING` / `MUST FIX` / `MUST_FIX` / `HIGH PRIORITY` / `CRITICAL ISSUE` keyword (5+건)

#### 시나리오 C: Approver Spoofing

| 항목 | 내용 |
|---|---|
| **공격 의도** | 시스템 승인자가 아닌데 승인자로 위장하여 auto-approve evidence 인정 요청 |
| **공격 방법** | random-bot, attacker, taskctl-gate-fake, anu-verifier-2, JonghyukJeon(회장=manual) |
| **기대 차단 지점** | `lifecycle_guards.check_approver_identity` |
| **Guard 함수** | `check_approver_identity(approver, allowlist_path)` |
| **assertion 형식** | `assert r.ok is False` (5가지 fake approver 모두) |

서브케이스:
- C-1: `random-bot` — allowlist 비등록
- C-2: `attacker` — allowlist 비등록
- C-3: `taskctl-gate-fake` — 유사 이름 위장
- C-4: `anu-verifier-2` — suffix 변형
- C-5: `JonghyukJeon` — human(manual) 계정, auto evidence 불인정

#### 시나리오 D: merge SHA / base SHA 불일치 우회 (4 cases)

| 항목 | 내용 |
|---|---|
| **공격 의도** | 실제 머지된 commit과 다른 SHA로 done 발행하여 검증 회피 |
| **공격 방법** | empty SHA / null SHA / SHA mismatch / non-main base mismatch |
| **기대 차단 지점** | `lifecycle_guards.check_merge_commit_sha` |
| **Guard 함수** | `check_merge_commit_sha(pr_number, repo, ...)` |
| **assertion 형식** | `assert r.ok is False` (4케이스 모두) |

서브케이스:
- D-1: `empty_sha` — merge_commit_sha="" (빈 문자열)
- D-2: `null_sha` — merge_commit_sha=None
- D-3: `sha_mismatch` — SHA가 origin HEAD와 다름
- D-4: `non_main_base_mismatch` — develop base에서 SHA 불일치

#### 시나리오 E: admin override 무감사 우회 (5 cases)

| 항목 | 내용 |
|---|---|
| **공격 의도** | TASKCTL_BYPASS=1 또는 TASKCTL_PR_AUTHOR_OVERRIDE 사용하면서 audit 누락 |
| **공격 방법** | audit 빈 파일 / 다른 task entry만 존재 / audit 파일 자체 삭제 / production 환경 bypass |
| **기대 차단 지점** | `lifecycle_guards.check_bypass_audit` |
| **Guard 함수** | `check_bypass_audit(task_id, env, audit_path, production)` |
| **assertion 형식** | `assert r.ok is False` (5케이스 모두) |

서브케이스:
- E-1: `bypass_no_audit` — TASKCTL_BYPASS=1, audit 빈 파일
- E-2: `override_no_audit` — TASKCTL_PR_AUTHOR_OVERRIDE, audit 빈 파일
- E-3: `bypass_audit_missing_task` — bypass 있고 audit는 있으나 다른 task entry만
- E-4: `bypass_audit_deleted` — audit 파일 자체 미생성
- E-5: `production_bypass` — PRODUCTION=1 환경에서 bypass fail-fast

#### 시나리오 F: 보고서 본문 조작 (3 sub-cases)

| 항목 | 내용 |
|---|---|
| **공격 의도** | 보고서 메타/evidence를 조작하여 PASS처럼 위장 |
| **공격 방법** | 본문에 High 키워드 산재 / task_id 위조 / SHA 위조 |
| **기대 차단 지점** | `count_severities` + `check_done_g3_pass_evidence` |
| **Guard 함수** | `count_severities(body)`, `check_done_g3_pass_evidence(...)` |
| **assertion 형식** | `sv["high"] >= 1` / `r.ok is False` |

서브케이스:
- F-1: 보고서 메타에 "high=0"이라 적었지만 본문에 High 키워드 산재
- F-2: 다른 task의 g3-pass.json 재사용 (task_id="task-2467+3" 위조)
- F-3: 이전 sha의 PASS evidence 재사용 (force-push 후 stale sha)

---

### 2-2. Optional 시나리오 (G–M, 7개)

#### 시나리오 G: Bot Allowlist 조작 (G-1)
- 공격 의도: allowed_bot_accounts.json에 attacker-bot 추가하여 merge 허용 시도
- 공격 방법: 변조된 allowlist 파일 생성
- 기대 차단 지점: `check_bot_author_allowlist` — 변조 파일 사용 시도 차단

#### 시나리오 H: Approver Allowlist 조작 (H-1)
- 공격 의도: allowed_approvers.json에 attacker 계정 추가
- 공격 방법: 변조된 approver 파일 생성
- 기대 차단 지점: production config와 비교 검증

#### 시나리오 I: state transition 조작
- 공격 의도: FAILED → DONE 등 금지 전이 강제
- 기대 차단 지점: `check_state_transition`

#### 시나리오 J: Empty allowlist fail-closed
- 공격 의도: allowlist 파일 비움으로 fail-open 유도
- 기대 차단 지점: `load_allowed_approvers` 빈 list → fail-closed

#### 시나리오 K: PR evidence pr_number 조작
- 공격 의도: 이전 PR의 PASS evidence를 다른 pr_number로 재사용
- 기대 차단 지점: `check_done_g3_pass_evidence` pr_number mismatch

#### 시나리오 L: TASKCTL_CWD deprecated 경고 우회
- 공격 의도: TASKCTL_CWD로 path 조작
- 기대 차단 지점: `resolve_worktree_path` DeprecationWarning 발생

#### 시나리오 M: audit log 조작
- 공격 의도: audit log에 가짜 bypass 기록 삽입
- 기대 차단 지점: audit entry 존재 확인 (content validation 추가 필요)

---

## 3. Audit Format

audit 파일: `memory/orchestration-audit/penetration-test-2469.jsonl`

각 line (JSON):
```json
{
  "scenario": "A-1",
  "task_id": "task-pentest-A-g3-fail",
  "attack_intent": "g3-fail marker 위장 시도",
  "guard_called": "check_g3_fail_blocks_done",
  "blocked": true,
  "blocking_reasons": ["g3_fail_marker", "task-pentest-A-g3-fail.g3-fail"],
  "ts": "2026-05-06T00:00:00+00:00",
  "test_name": "test_A_done_gfail_concurrent_block_all_marker_variants[g3-fail]"
}
```

필드 설명:
- `scenario`: 시나리오 ID (A-1, A2, B, C, D, E, F1, F2, F3)
- `task_id`: 테스트에 사용된 task id
- `attack_intent`: 공격 의도 설명
- `guard_called`: 호출된 Guard 함수명
- `blocked`: `true` = Guard가 공격 차단 성공 (ok=False)
- `blocking_reasons`: r.blocking 리스트
- `ts`: ISO8601 UTC timestamp
- `test_name`: pytest node id

---

## 4. 합격 조건

| 조건 | 기준 |
|---|---|
| mandatory 시나리오 | A-F 전체 PASS (parametrize 포함 모든 서브케이스) |
| 회귀 | 0건 (기존 test_lifecycle_guards.py 14건 전부 PASS 유지) |
| audit lines | penetration-test-2469.jsonl ≥ 6 lines |
| 우회 성공 케이스 | 0건 (모든 assert r.ok is False 통과) |

---

## 5. 위협 인텔리전스

**fixture가 위협 모델 자체다.**

- `tests/fixtures/task-2469/gemini_reviews/*.md` — Gemini PR 리뷰 공격 패턴 박제
- `tests/fixtures/task-2469/tampered_configs/*.json` — 변조된 config 박제
- `tests/fixtures/task-2469/tampered_evidence/*.json` — 위조된 evidence 박제

각 fixture는 실제 공격 시나리오를 재현한 실물 데이터다.
fixture 자체가 미래 공격자에 대한 사전 방어 지식을 코드화한 것이다.

---

## 6. 참조

- `scripts/lifecycle_guards.py` — Guard 구현 (read-only)
- `scripts/gemini_severity_parser.py` — Severity parser (read-only)
- `tests/taskctl/test_lifecycle_guards.py` — P1 기본 테스트 14건
- `tests/taskctl/test_lifecycle_penetration.py` — 본 spec의 pentest 구현체
- task-2467+3: silent corruption 4대 결함 발생 원인 분석
- task-2468: Guard 구현
