Claude Skill

finguard

FinGuard CLI로 소스코드 취약점을 점검하고 심각도 기반 보안 게이트와 제한된 수정·재검증 루프를 수행할 때 사용한다. 일반 코드 품질 리뷰나 SCA·모의해킹은 범위 밖이다.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download leeyudok-doksam-skills-skills_finguard-841cccd.zip · 9 KB
Part of leeyudok/doksam-skills — 14 skills

Install

skills CLI npx skills add https://github.com/LeeYudok/doksam-skills/tree/main/skills/finguard
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install leeyudok-doksam-skills@llmmart
Git 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을 명시한다.

워크플로

  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를 따른다.

완료 조건

  • 게이트 명령, 차단 심각도, 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.

No comments yet.

Reviews (0)

No reviews yet.

Related