File size: 3,565 Bytes
92c4ae6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import argparse
import asyncio
from pathlib import Path
import sys
from atom_security.analyzers.llm import LLMAnalyzer
from atom_security.analyzers.static import StaticAnalyzer
from atom_security.core.models import Severity


async def run_scan(args):
    target_path = Path(args.path)
    if not target_path.exists():
        print(f"Error: Path not found: {target_path}")
        sys.exit(1)
        
    print(f"Scanning {target_path}...")
    
    findings = []
    
    # 1. Static Analysis (Always runs)
    static_analyzer = StaticAnalyzer()
    if target_path.is_file():
        findings.extend(static_analyzer.scan_file(target_path))
    else:
        result = static_analyzer.scan_directory(target_path)
        findings.extend(result.findings)
        
    # 2. LLM Analysis (Optional)
    if args.llm != "none":
        print(f"Running LLM Analysis ({args.llm} mode)...")
        llm_analyzer = LLMAnalyzer(
            mode=args.llm,
            model=args.llm_model,
            api_key=args.llm_api_key,
            provider=args.llm_provider
        )
        
        # For simplicity in CLI, we analyze the main instructions or whole folder summary
        # In a real tool, we might iterate over all files, but that's expensive.
        # We'll analyze the whole context for directory scans.
        if target_path.is_file():
            content = target_path.read_text()
            llm_findings = await llm_analyzer.analyze(target_path.name, content)
            findings.extend(llm_findings)
        else:
            # Aggregate contents for directory (limited)
            all_content = ""
            for p in target_path.rglob("*"):
                if p.is_file() and p.suffix in [".md", ".py", ".json"]:
                    all_content += f"\n--- {p.name} ---\n{p.read_text()[:1000]}"
            llm_findings = await llm_analyzer.analyze(target_path.name, all_content)
            findings.extend(llm_findings)

    # Output results
    if args.format == "text":
        print(f"\nScan Complete ({len(findings)} issues found)")
        print("=" * 60)
        
        # Group by severity
        sorted_findings = sorted(findings, key=lambda x: x.severity, reverse=True)
        
        for f in sorted_findings:
            print(f"[{f.severity.value}] {f.category} ({f.analyzer})")
            print(f"  File: {f.file_path}:{f.line_number}")
            print(f"  Msg:  {f.description}")
            if f.line_content:
                print(f"  Code: {f.line_content.strip()}")
            print("-" * 60)
            
    elif args.format == "json":
        import json
        output = [f.dict() for f in findings]
        print(json.dumps(output, indent=2, default=str))
        
    if findings:
        sys.exit(1)
    else:
        print("No issues found.")
        sys.exit(0)

def main():
    parser = argparse.ArgumentParser(description="Atom Security Scanner")
    parser.add_argument("path", help="Path to file or directory to scan")
    parser.add_argument("--format", choices=["text", "json"], default="text", help="Output format")
    
    # LLM Options
    parser.add_argument("--llm", choices=["none", "local", "byok"], default="none", help="LLM analysis mode")
    parser.add_argument("--llm-provider", choices=["openai", "anthropic"], default="openai", help="LLM provider (for byok)")
    parser.add_argument("--llm-model", help="LLM model name")
    parser.add_argument("--llm-api-key", help="LLM API Key")
    
    args = parser.parse_args()
    asyncio.run(run_scan(args))

if __name__ == "__main__":
    main()