Use Haiku to perform a security review of code. files: list of (file_path, content_or_diff) tuples is_diff: if True, the content is a unified diff rather than full file contents previous_findings: list of category strings from earlier stop hook firings this turn, used to pro
(files: List[Tuple[str, str]], is_diff: bool = False, previous_findings: Optional[List[str]] = None)
| 708 | |
| 709 | |
| 710 | def analyze_code_security(files: List[Tuple[str, str]], is_diff: bool = False, previous_findings: Optional[List[str]] = None) -> Tuple[Optional[str], List[Dict[str, Any]]]: |
| 711 | """ |
| 712 | Use Haiku to perform a security review of code. |
| 713 | files: list of (file_path, content_or_diff) tuples |
| 714 | is_diff: if True, the content is a unified diff rather than full file contents |
| 715 | previous_findings: list of category strings from earlier stop hook firings this turn, |
| 716 | used to prompt the reviewer to verify those issues were actually fixed. |
| 717 | Returns (formatted guidance string or None, list of vuln dicts with severity/category). |
| 718 | """ |
| 719 | if not HAS_API_CREDENTIALS or not files: |
| 720 | return None, [] |
| 721 | |
| 722 | # Build language context from file extensions |
| 723 | lang_hints = { |
| 724 | ".go": "Go", ".java": "Java/Spring Boot", ".py": "Python", |
| 725 | ".rb": "Ruby", ".php": "PHP", ".rs": "Rust", |
| 726 | ".ts": "TypeScript", ".js": "JavaScript", ".jsx": "JavaScript/React", |
| 727 | ".tsx": "TypeScript/React", ".ejs": "EJS templates", |
| 728 | ".html": "HTML/templates", ".properties": "Java properties", |
| 729 | ".yaml": "YAML config", ".yml": "YAML config", |
| 730 | } |
| 731 | languages = set() |
| 732 | for fp, _ in files: |
| 733 | ext = os.path.splitext(fp)[1].lower() |
| 734 | if ext in lang_hints: |
| 735 | languages.add(lang_hints[ext]) |
| 736 | language = ", ".join(sorted(languages)) if languages else "server-side" |
| 737 | |
| 738 | files = _cap_files_for_prompt(files) |
| 739 | |
| 740 | # Build the files section |
| 741 | files_section = [] |
| 742 | for fp, content in files: |
| 743 | ext = os.path.splitext(fp)[1].lower() |
| 744 | label = "DIFF" if is_diff else "FILE" |
| 745 | files_section.append(f"=== {label}: {fp} ===\n```{ext.lstrip('.')}\n{content}\n```") |
| 746 | files_text = "\n\n".join(files_section) |
| 747 | |
| 748 | content_desc = "diff" if is_diff else "code" |
| 749 | |
| 750 | if is_diff: |
| 751 | diff_instruction = """Note: You are reviewing a unified diff. Unmarked lines (starting with a space) are UNCHANGED context — they were already in the file before this session. Lines starting with + are ADDITIONS made in this session. Lines starting with - are REMOVALS. |
| 752 | |
| 753 | CRITICAL: ONLY flag vulnerabilities that are NEWLY INTRODUCED in + lines. Do NOT flag: |
| 754 | - Issues in unmarked context lines (space-prefixed = pre-existing code). Even if a context line contains SECRET_KEY = 'hardcoded', DEBUG=True, hardcoded passwords, SQL injection, or any other vulnerability — it is PRE-EXISTING and must be ignored. |
| 755 | - Issues where the SAME pattern existed in the removed (-) lines and was re-added in + lines (this means the code was rewritten/reformatted but the pattern is pre-existing) |
| 756 | - Pre-existing patterns that Claude simply preserved when rewriting a file |
| 757 | - Any vulnerability whose vulnerable code snippet appears in context (space-prefixed) lines |
| 758 | - Vulnerabilities in the ORIGINAL/STARTER code that the developer was given to work with. If a file was fully rewritten (all lines show as - then +), compare the + content against the - content. Only flag NEWLY INTRODUCED patterns that did NOT exist in the - lines. |
| 759 | - Issues OUTSIDE THE SCOPE of what the developer was asked to do. If the task was "add logging middleware" and the starter code has a hardcoded SECRET_KEY, that is pre-existing and out of scope — do NOT flag it. |
| 760 | |
| 761 | A vulnerability is ONLY new if the + lines introduce a pattern that did NOT exist anywhere in the - lines or context lines of the same file. |
| 762 | |
| 763 | EXCEPTION — data flow to pre-existing sinks: If + lines route user-controlled data to a PRE-EXISTING dangerous sink (like `new Function()`, `eval()`, `exec()`, or shell string interpolation in context lines), this IS a new vulnerability. The sink was already there, but the new code created a new attack path to it. Flag this as a new vulnerability in the + lines.""" |
| 764 | else: |
| 765 | diff_instruction = "" |
| 766 | |
| 767 | structured_prev = [f for f in (previous_findings or []) if isinstance(f, dict)] |
no test coverage detected