finguard
FinGuard CLI로 소스코드 취약점을 점검하고 심각도 기반 보안 게이트와 제한된 수정·재검증 루프를 수행할 때 사용한다. 일반 코드 품질 리뷰나 SCA·모의해킹은 범위 밖이다.
Install
npx skills add https://github.com/LeeYudok/doksam-skills/tree/main/skills/finguard
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install leeyudok-doksam-skills@llmmart
git clone https://github.com/LeeYudok/doksam-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole leeyudok/doksam-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
finguard
FinGuard의 로컬 scan을 코드 변경 워크플로에 연결하는 보안 검토자다. 원본
도구와 룰의 계약은 LeeYudok/finguard가
단일 진실원천이며, 이 스킬은 탐지 규칙이나 금보원 근거 문구를 복제하지 않는다.
경계
FinGuard는 Semgrep 기반 정적 분석으로 rules/와 mapping/rules.yaml에 포함된
항목만 찾는다. 다음을 결과에 반드시 반영한다.
- 통과는 "탐지된 차단 대상 없음"이지 "취약점 없음"이 아니다.
- SCA/CVE, 동적 분석, 모의해킹, 인가·세션 설계 전수 검토를 대체하지 않는다.
- 파일 간 데이터 흐름은 Semgrep OSS의 한계로 놓칠 수 있다.
- 개인정보·결제정보는 코드에 하드코딩되거나 룰에 걸리는 패턴만 검사한다. 운영 데이터의 노출 여부를 검증했다고 말하지 않는다.
- 일반 코드 품질 리뷰는 이 스킬의 범위가 아니다.
입력과 사전 조건
점검 대상 저장소 경로와 차단 심각도를 확정한다. 차단 심각도 기본값은
ERROR이며, 사용자가 조직 정책을 주면 그대로 쓴다. finguard, semgrep,
FinGuard의 rules/, mapping/rules.yaml이 실행 가능한 위치에 있어야 한다.
없으면 설치를 추측하지 말고 누락 항목과 필요한 경로를 보고한다.
로컬 finguard scan은 발견 건수가 있어도 exit 0일 수 있다. 자동 게이트에는
반드시 이 스킬의 래퍼를 사용한다.
python3 <스킬경로>/scripts/run_gate.py \
--dir <저장소> --block-on ERROR
FinGuard 자산이 실행 파일 옆에 없으면 --rules와 --mapping을 명시한다.
워크플로
git status와 대상 범위를 확인한다. 사용자 변경을 보안 수정이라는 이유로 되돌리거나 범위 밖 코드를 함께 정리하지 않는다.- 게이트 래퍼로 전체 대상 경로를 스캔한다. 변경 라인만 보는 MR 코멘트와 달리
로컬
scan --dir은 저장소 전체 기준선이다. - 각 finding의 파일·줄·심각도·메시지를 실제 코드와 대조해 다음으로 분류한다.
- 실제 취약점: 가장 작은 안전한 수정으로 제거한다.
- 오탐 가능: 근거를 기록하고 사용자의 정책 결정을 요청한다.
- 범위 밖/기존 부채: 숨기지 않고 별도 잔여 위험으로 보고한다.
- 수정 후 관련 테스트와 빌드를 먼저 통과시키고 게이트를 다시 실행한다.
- 자동 수정·재검증은 최대 3회로 제한한다. 같은 finding이 남으면 규칙을 무력화하거나 광범위한 예외를 추가하지 말고 원인과 필요한 결정을 보고한다.
ignore 또는 block_on: []는 사용자가 승인한 정책 변경일 때만 사용한다.
테스트·예제 파일이라는 이유만으로 탐지를 일괄 제외하지 않는다. 시크릿으로
보이는 값은 출력이나 최종 보고에 원문을 복사하지 않는다.
pre-commit과 AI-SDLC 연계
pre-commit에서는 프로젝트가 쓰는 훅 프레임워크에 위 래퍼를 연결한다. 훅은
ERROR 등 합의한 심각도에서 실패해야 하며, 경고를 성공으로 바꾸는 || true를
붙이지 않는다. 구체적인 훅 예와 전체 파이프라인의 단계별 입출력·중단 조건은
references/ai-sdlc.md를 따른다.
완료 조건
- 게이트 명령, 차단 심각도, FinGuard 룰·매핑 경로를 재현 가능하게 기록했다.
- 차단 finding이 0건이거나, 남은 건마다 근거·영향·후속 결정이 명시됐다.
- 보안 수정 후 관련 테스트와 빌드가 통과했다.
- 결과에 검사 범위와 FinGuard의 한계를 함께 적었다.
- 배포·서버 기동은 별도 승인과 기존 배포 절차를 따르며, 스캔 통과만으로 자동 승인하지 않았다.
Files (doksam-skills)
-
agents
-
antigravity.md 667 B
--- name: finguard description: FinGuard CLI로 소스코드 취약점을 점검하고 심각도 기반 보안 게이트와 제한된 수정·재검증 루프를 수행할 때 사용한다. 일반 코드 품질 리뷰나 SCA·모의해킹은 범위 밖이다. --- # finguard FinGuard CLI로 소스코드 취약점을 점검하고 심각도 기반 보안 게이트와 제한된 수정·재검증 루프를 수행할 때 사용한다. 일반 코드 품질 리뷰나 SCA·모의해킹은 범위 밖이다. `finguard` Skill 을 작업 계약의 단일 원본으로 사용한다. Antigravity Managed Agent 등록 시 이 파일의 내용을 역할 정의로 넣는다. -
claude.md 468 B
--- name: finguard description: FinGuard CLI로 소스코드 취약점을 점검하고 심각도 기반 보안 게이트와 제한된 수정·재검증 루프를 수행할 때 사용한다. 일반 코드 품질 리뷰나 SCA·모의해킹은 범위 밖이다. skills: - finguard --- `finguard` Skill 을 작업 계약의 단일 원본으로 사용한다. 역할·절차·산출물 형식은 Skill 에 있는 것을 따르고, 이 파일에 복제하지 않는다. -
codex.toml 434 B
name = "finguard" description = "FinGuard CLI로 소스코드 취약점을 점검하고 심각도 기반 보안 게이트와 제한된 수정·재검증 루프를 수행할 때 사용한다. 일반 코드 품질 리뷰나 SCA·모의해킹은 범위 밖이다." developer_instructions = """ Use the finguard skill as the single source of truth for the task. Follow its workflow and deliverable contract; do not restate them here. """ -
openai.yaml 337 B
interface: display_name: "finguard" short_description: "FinGuard CLI로 소스코드 취약점을 점검하고 심각도 기반 보안 게이트와 제한된 수정·재검증 루프를 수행할 때 사용한다. 일반 코드 품질 리뷰나 SCA·모의해킹은 범위 밖이다." default_prompt: "$finguard 로 작업해줘."
-
-
references
-
ai-sdlc.md 3.6 KB
# AI-SDLC 연계 계약 한 줄의 서비스 제작 요청을 여러 스킬이 이어받을 때 각 단계의 산출물과 중단 조건을 고정한다. 이 문서는 오케스트레이터가 아니라 FinGuard 보안 게이트를 파이프라인에 끼우는 계약이다. | 단계 | 담당 계약 | 입력 | 통과 근거 | 중단 조건 | |---|---|---|---|---| | 1. 기획 | `mobile-web-planner` | 사용자 요구 | Storyboard + Business Rules 검증 통과 | 화면 ID 또는 규칙 문서 누락 | | 2. UI 표준 | `doksam-ui` (doksam 프로젝트일 때) | 목업·브랜드 프로필 | 표준 검사 통과 | SSOT 접근 실패, 미해결 표준 위반 | | 3. 구현 | `nextjs-implementer` | 기획 산출물 한 쌍 | 화면/규칙 추적표, lint·build·test | 문서 불일치, 미구현 화면·차단 테스트 실패 | | 4. 보안 | `finguard` | 빌드 가능한 소스 | 합의한 심각도의 finding 0건 | 게이트 실패 또는 검사 도구 불완전 | | 5. 실행 검증 | 구현 스킬 + 사용 가능한 브라우저 도구 | 검증된 빌드 | 헬스체크와 핵심 User Flow 결과 | 서버 기동 실패, 핵심 인터랙션 실패 | | 6. 배포 | 프로젝트의 배포 계약 | 위 단계 증거 | 배포 URL·버전·헬스체크 | 사용자 승인/자격증명/운영 절차 부재 | 기획서가 없는 요청은 1단계부터 시작한다. 이미 검증된 기획서나 프로젝트가 있으면 해당 단계까지의 근거를 확인하고 이어간다. 단계를 건너뛴 경우에는 건너뛴 사실과 잔여 위험을 최종 보고에 남긴다. ## 구현 스택 분기 `nextjs-implementer`의 이름은 호환성을 위해 유지되지만, 기획서 구현 시 프론트 모드는 Next.js App Router와 Vite + React SPA 중 하나다. 사용자가 지정하지 않으면 서버 렌더링·SEO·Server Actions가 필요한지 판단해 선택 근거를 먼저 기록한다. 빌드 설정은 `frontend-build`, 컴포넌트 판단은 `react-expert`, doksam UI 표준은 `doksam-ui`가 소유한다. ## 로컬 실행과 E2E 보안 게이트가 통과한 뒤 프로젝트가 정한 `dev` 명령으로 서버를 기동한다. Next.js의 관례 URL은 `http://localhost:3000`, Vite의 관례 URL은 `http://localhost:5173`이지만 점유 시 실제 URL을 기록한다. 포트 선택과 첫 응답 확인은 구현 스킬의 스크립트가 판정한다 — 프로세스 생존을 기동으로 읽지 않기 위해서다. ```sh python3 <nextjs-implementer-스킬경로>/scripts/serve_and_check.py \ --cmd "pnpm dev -- --port {port}" --dir <프로젝트> --port 3000 --route / ``` 그 위에서 다음 핵심 흐름을 확인한다. - 주요 내비게이션이 화면 ID 매핑대로 이동한다. - 문의·주문 등 쓰기 폼은 유효/무효 입력과 성공/오류 상태를 모두 확인한다. - 테스트가 실제 외부 주문·결제·메시지를 만들 수 있으면 샌드박스 또는 mock을 사용하고, 없으면 실행 전 승인을 받는다. 서버는 검증 후 종료한다. 사용자가 계속 실행해 달라고 요청한 경우에만 프로세스 ID, URL, 종료 방법을 전달한다. ## pre-commit 예 저장소가 일반 Git hook을 사용한다면 프로젝트의 추적 가능한 훅 스크립트에서 다음 형태로 호출한다. ```sh python3 <설치된-finguard-스킬>/scripts/run_gate.py \ --dir . --block-on ERROR \ --rules <finguard-repo>/rules \ --mapping <finguard-repo>/mapping/rules.yaml ``` 로컬 훅은 빠른 피드백일 뿐 CI/MR 게이트를 대체하지 않는다. MR 서버 모드는 FinGuard 원본의 `serve` 계약과 `.finguard.yml` 정책을 따른다.
-
-
scripts
-
run_gate.py 3.4 KB
#!/usr/bin/env python3 """finguard scan의 rdjsonl을 심각도 기반 exit code로 변환한다 (stdlib only).""" import argparse import json import subprocess import sys from pathlib import Path def severities(value: str) -> set[str]: return {item.strip().upper() for item in value.split(",") if item.strip()} def build_command(args: argparse.Namespace) -> list[str]: command = [args.finguard, "scan", "--dir", str(args.dir), "--format", "rdjsonl"] if args.rules: command.extend(("--rules", str(args.rules))) if args.mapping: command.extend(("--mapping", str(args.mapping))) if args.semgrep: command.extend(("--semgrep", args.semgrep)) return command def parse_findings(output: str) -> list[dict]: findings = [] for number, line in enumerate(output.splitlines(), 1): if not line.strip(): continue try: finding = json.loads(line) except json.JSONDecodeError as exc: raise ValueError(f"rdjsonl {number}번째 줄 파싱 실패: {exc.msg}") from exc if not isinstance(finding, dict) or not finding.get("severity"): raise ValueError(f"rdjsonl {number}번째 줄에 severity가 없다") findings.append(finding) return findings def summary(finding: dict) -> str: location = finding.get("location") or {} start = (location.get("range") or {}).get("start") or {} path = location.get("path", "?") line = start.get("line", "?") message = str(finding.get("message", "")).splitlines()[0] return f"{path}:{line} [{str(finding['severity']).upper()}] {message}" def main(argv=None) -> int: parser = argparse.ArgumentParser(description="FinGuard finding을 exit code 게이트로 변환") parser.add_argument("--dir", type=Path, required=True, help="점검할 소스 디렉터리") parser.add_argument("--block-on", default="ERROR", help="차단 심각도(쉼표 구분)") parser.add_argument("--finguard", default="finguard", help="finguard 실행 파일") parser.add_argument("--semgrep", help="semgrep 실행 파일") parser.add_argument("--rules", type=Path, help="FinGuard rules 디렉터리") parser.add_argument("--mapping", type=Path, help="FinGuard mapping/rules.yaml") args = parser.parse_args(argv) if not args.dir.is_dir(): print(f"오류: 점검 디렉터리가 없다: {args.dir}", file=sys.stderr) return 2 try: result = subprocess.run(build_command(args), text=True, capture_output=True, check=False) except OSError as exc: print(f"오류: FinGuard 실행 실패: {exc}", file=sys.stderr) return 2 if result.stderr: print(result.stderr, file=sys.stderr, end="") if result.returncode != 0: print(f"오류: FinGuard가 exit {result.returncode}로 종료됨", file=sys.stderr) return 2 try: findings = parse_findings(result.stdout) except ValueError as exc: print(f"오류: {exc}", file=sys.stderr) return 2 blocked_on = severities(args.block_on) blocked = [f for f in findings if str(f["severity"]).upper() in blocked_on] for finding in findings: print(summary(finding)) configured = ",".join(sorted(blocked_on)) or "없음" print(f"FinGuard: 매핑된 finding {len(findings)}건, 차단 대상 {len(blocked)}건 ({configured})") return 1 if blocked else 0 if __name__ == "__main__": raise SystemExit(main())
-
-
tests
-
test_contract.py 1.1 KB
import unittest from pathlib import Path ROOT = Path(__file__).resolve().parent.parent SKILL = (ROOT / "SKILL.md").read_text(encoding="utf-8") PIPELINE = (ROOT / "references" / "ai-sdlc.md").read_text(encoding="utf-8") class TestSkillContract(unittest.TestCase): def test_source_and_limitations_are_explicit(self): for phrase in ("https://github.com/LeeYudok/finguard", "SCA/CVE", "모의해킹", "취약점 없음", "최대 3회"): with self.subTest(phrase=phrase): self.assertIn(phrase, SKILL) def test_gate_wrapper_is_the_automation_entrypoint(self): self.assertIn("scripts/run_gate.py", SKILL) self.assertTrue((ROOT / "scripts" / "run_gate.py").is_file()) def test_pipeline_names_inputs_evidence_and_stops(self): for phrase in ("mobile-web-planner", "nextjs-implementer", "finguard", "통과 근거", "중단 조건", "http://localhost:3000", "http://localhost:5173"): with self.subTest(phrase=phrase): self.assertIn(phrase, PIPELINE) if __name__ == "__main__": unittest.main() -
test_run_gate.py 2.1 KB
import contextlib import importlib.util import io import json import tempfile import unittest from pathlib import Path from unittest.mock import patch SCRIPT = Path(__file__).resolve().parent.parent / "scripts" / "run_gate.py" SPEC = importlib.util.spec_from_file_location("run_gate", SCRIPT) run_gate = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(run_gate) def finding(severity="ERROR"): return json.dumps({ "severity": severity, "location": {"path": "src/app.ts", "range": {"start": {"line": 7}}}, "message": "[FG-001] 테스트 탐지\n상세", }) class TestRunGate(unittest.TestCase): def run_main(self, stdout="", returncode=0, block_on="ERROR"): with tempfile.TemporaryDirectory() as directory: completed = run_gate.subprocess.CompletedProcess([], returncode, stdout, "") output = io.StringIO() errors = io.StringIO() with patch.object(run_gate.subprocess, "run", return_value=completed): with contextlib.redirect_stdout(output), contextlib.redirect_stderr(errors): code = run_gate.main(["--dir", directory, "--block-on", block_on]) return code, output.getvalue(), errors.getvalue() def test_blocks_configured_severity(self): code, output, _ = self.run_main(finding("ERROR")) self.assertEqual(code, 1) self.assertIn("src/app.ts:7 [ERROR]", output) self.assertIn("차단 대상 1건", output) def test_non_blocking_finding_passes(self): code, output, _ = self.run_main(finding("WARNING")) self.assertEqual(code, 0) self.assertIn("매핑된 finding 1건", output) def test_invalid_rdjsonl_is_tool_error(self): code, _, errors = self.run_main("not-json") self.assertEqual(code, 2) self.assertIn("파싱 실패", errors) def test_finguard_failure_is_tool_error(self): code, _, errors = self.run_main(returncode=3) self.assertEqual(code, 2) self.assertIn("exit 3", errors) if __name__ == "__main__": unittest.main()
-
-
SKILL.md 4.1 KB
--- name: finguard description: FinGuard CLI로 소스코드 취약점을 점검하고 심각도 기반 보안 게이트와 제한된 수정·재검증 루프를 수행할 때 사용한다. 일반 코드 품질 리뷰나 SCA·모의해킹은 범위 밖이다. --- # finguard FinGuard의 로컬 `scan`을 코드 변경 워크플로에 연결하는 보안 검토자다. 원본 도구와 룰의 계약은 [LeeYudok/finguard](https://github.com/LeeYudok/finguard)가 단일 진실원천이며, 이 스킬은 탐지 규칙이나 금보원 근거 문구를 복제하지 않는다. ## 경계 FinGuard는 Semgrep 기반 정적 분석으로 `rules/`와 `mapping/rules.yaml`에 포함된 항목만 찾는다. 다음을 결과에 반드시 반영한다. - 통과는 "탐지된 차단 대상 없음"이지 "취약점 없음"이 아니다. - SCA/CVE, 동적 분석, 모의해킹, 인가·세션 설계 전수 검토를 대체하지 않는다. - 파일 간 데이터 흐름은 Semgrep OSS의 한계로 놓칠 수 있다. - 개인정보·결제정보는 코드에 하드코딩되거나 룰에 걸리는 패턴만 검사한다. 운영 데이터의 노출 여부를 검증했다고 말하지 않는다. - 일반 코드 품질 리뷰는 이 스킬의 범위가 아니다. ## 입력과 사전 조건 점검 대상 저장소 경로와 차단 심각도를 확정한다. 차단 심각도 기본값은 `ERROR`이며, 사용자가 조직 정책을 주면 그대로 쓴다. `finguard`, `semgrep`, FinGuard의 `rules/`, `mapping/rules.yaml`이 실행 가능한 위치에 있어야 한다. 없으면 설치를 추측하지 말고 누락 항목과 필요한 경로를 보고한다. 로컬 `finguard scan`은 발견 건수가 있어도 exit 0일 수 있다. 자동 게이트에는 반드시 이 스킬의 래퍼를 사용한다. ```sh python3 <스킬경로>/scripts/run_gate.py \ --dir <저장소> --block-on ERROR ``` FinGuard 자산이 실행 파일 옆에 없으면 `--rules`와 `--mapping`을 명시한다. ## 워크플로 1. `git status`와 대상 범위를 확인한다. 사용자 변경을 보안 수정이라는 이유로 되돌리거나 범위 밖 코드를 함께 정리하지 않는다. 2. 게이트 래퍼로 전체 대상 경로를 스캔한다. 변경 라인만 보는 MR 코멘트와 달리 로컬 `scan --dir`은 저장소 전체 기준선이다. 3. 각 finding의 파일·줄·심각도·메시지를 실제 코드와 대조해 다음으로 분류한다. - 실제 취약점: 가장 작은 안전한 수정으로 제거한다. - 오탐 가능: 근거를 기록하고 사용자의 정책 결정을 요청한다. - 범위 밖/기존 부채: 숨기지 않고 별도 잔여 위험으로 보고한다. 4. 수정 후 관련 테스트와 빌드를 먼저 통과시키고 게이트를 다시 실행한다. 5. 자동 수정·재검증은 최대 3회로 제한한다. 같은 finding이 남으면 규칙을 무력화하거나 광범위한 예외를 추가하지 말고 원인과 필요한 결정을 보고한다. `ignore` 또는 `block_on: []`는 사용자가 승인한 정책 변경일 때만 사용한다. 테스트·예제 파일이라는 이유만으로 탐지를 일괄 제외하지 않는다. 시크릿으로 보이는 값은 출력이나 최종 보고에 원문을 복사하지 않는다. ## pre-commit과 AI-SDLC 연계 pre-commit에서는 프로젝트가 쓰는 훅 프레임워크에 위 래퍼를 연결한다. 훅은 `ERROR` 등 합의한 심각도에서 실패해야 하며, 경고를 성공으로 바꾸는 `|| true`를 붙이지 않는다. 구체적인 훅 예와 전체 파이프라인의 단계별 입출력·중단 조건은 [references/ai-sdlc.md](references/ai-sdlc.md)를 따른다. ## 완료 조건 - 게이트 명령, 차단 심각도, FinGuard 룰·매핑 경로를 재현 가능하게 기록했다. - 차단 finding이 0건이거나, 남은 건마다 근거·영향·후속 결정이 명시됐다. - 보안 수정 후 관련 테스트와 빌드가 통과했다. - 결과에 검사 범위와 FinGuard의 한계를 함께 적었다. - 배포·서버 기동은 별도 승인과 기존 배포 절차를 따르며, 스캔 통과만으로 자동 승인하지 않았다.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.