File size: 3,975 Bytes
245ed4f
 
 
 
 
 
200e3d7
 
245ed4f
 
 
 
 
 
 
 
 
 
 
200e3d7
 
 
 
245ed4f
 
200e3d7
 
 
 
245ed4f
 
 
 
200e3d7
 
245ed4f
200e3d7
 
245ed4f
200e3d7
 
 
 
245ed4f
200e3d7
 
 
 
 
 
 
 
 
 
 
 
245ed4f
200e3d7
245ed4f
200e3d7
 
 
 
 
245ed4f
200e3d7
 
245ed4f
 
 
 
200e3d7
245ed4f
 
200e3d7
245ed4f
200e3d7
 
 
 
 
 
 
 
 
 
245ed4f
200e3d7
245ed4f
200e3d7
 
245ed4f
200e3d7
245ed4f
 
200e3d7
245ed4f
 
 
200e3d7
245ed4f
 
 
 
 
 
200e3d7
245ed4f
200e3d7
245ed4f
 
200e3d7
245ed4f
 
 
200e3d7
245ed4f
 
 
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from pydantic import BaseModel
import re, os, requests, base64
from groq import Groq

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

# ── helpers ──────────────────────────────────────────────────────────────────

def parse_github_url(url):
    url = url.strip().rstrip('/')
    m = re.match(r"https://github\.com/([^/]+)/([^/]+)/blob/[^/]+/(.+)", url)
    if m:
        return m.group(1), m.group(2), m.group(3)
    return None, None, None

def get_file_content(owner, repo, path):
    token = os.getenv("GITHUB_TOKEN", "")
    headers = {"Authorization": f"token {token}"} if token else {}
    r = requests.get(f"https://api.github.com/repos/{owner}/{repo}/contents/{path}", headers=headers, timeout=15)
    if r.status_code != 200:
        return None, f"GitHub API error: {r.status_code}"
    return base64.b64decode(r.json()["content"]).decode("utf-8", errors="replace"), None


def scan_file(owner, repo, file_path):
    code, err = get_file_content(owner, repo, file_path)
    if err:
        return {"error": err}
    if len(code) > 6000:
        code = code[:6000] + "\n... [truncated]"
    groq_key = os.getenv("GROQ_API_KEY", "")
    if not groq_key:
        return {"error": "GROQ_API_KEY not configured"}
    client = Groq(api_key=groq_key)
    prompt = f"""You are a cybersecurity expert. Analyze this code for security vulnerabilities.

File: {file_path}
Repository: {owner}/{repo}

```
{code}
```

Provide a detailed security analysis in this exact markdown format:

# Security Analysis Report

## File Overview
- Repository: {owner}/{repo}
- File: {file_path}
- Language: [detected language]
- Lines analyzed: [count]

## Vulnerabilities Found

### [Vulnerability Name] β€” [CRITICAL/HIGH/MEDIUM/LOW]
- **Line**: [line number]
- **Code**: `[snippet]`
- **Issue**: [explanation]
- **CVE Reference**: [CVE ID if applicable]

## Remediation
[Specific fixes with corrected code]

## Risk Summary
- Critical: [n] | High: [n] | Medium: [n] | Low: [n]
- **Overall Risk**: [CRITICAL/HIGH/MEDIUM/LOW]
"""
    try:
        response = client.chat.completions.create(
            model="llama-3.3-70b-versatile",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.1,
            max_tokens=4096,
        )
        return {"result": response.choices[0].message.content}
    except Exception as e:
        return {"error": f"AI analysis failed: {str(e)}"}


# ── API routes ────────────────────────────────────────────────────────────────

class ScanRequest(BaseModel):
    url: str

@app.get("/api/health")
def health():
    return {"status": "ok", "groq_key_set": bool(os.getenv("GROQ_API_KEY"))}

@app.post("/api/scan")
def scan(req: ScanRequest):
    owner, repo, file_path = parse_github_url(req.url)
    if not owner or not repo or not file_path:
        return {"error": "Invalid GitHub file URL. Use format: github.com/owner/repo/blob/branch/file"}
    return scan_file(owner, repo, file_path)

# ── Serve React frontend ──────────────────────────────────────────────────────

if os.path.exists("static"):
    app.mount("/assets", StaticFiles(directory="static/assets"), name="assets")

    @app.get("/")
    def serve_root():
        return FileResponse("static/index.html")

    @app.get("/{full_path:path}")
    def serve_spa(full_path: str):
        return FileResponse("static/index.html")