AdityaDevx commited on
Commit
245ed4f
Β·
1 Parent(s): efde21b

File-only scan: remove repo scan, remove TRY buttons

Browse files
Files changed (4) hide show
  1. Dockerfile +4 -3
  2. api.py +0 -296
  3. app.py +59 -254
  4. requirements.txt +3 -1
Dockerfile CHANGED
@@ -1,12 +1,13 @@
1
- ο»ΏFROM python:3.11-slim
2
 
3
  WORKDIR /app
4
 
5
  COPY requirements.txt .
6
  RUN pip install --no-cache-dir -r requirements.txt
7
 
8
- COPY api.py .
 
9
 
10
  EXPOSE 7860
11
 
12
- CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "7860", "--log-level", "info"]
 
1
+ FROM python:3.11-slim
2
 
3
  WORKDIR /app
4
 
5
  COPY requirements.txt .
6
  RUN pip install --no-cache-dir -r requirements.txt
7
 
8
+ COPY app.py .
9
+ COPY static/ ./static/
10
 
11
  EXPOSE 7860
12
 
13
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
api.py DELETED
@@ -1,296 +0,0 @@
1
- ο»Ώfrom fastapi import FastAPI
2
- from fastapi.middleware.cors import CORSMiddleware
3
- from pydantic import BaseModel
4
- import re, os, requests, asyncio, concurrent.futures
5
- from datetime import datetime
6
- from dotenv import load_dotenv
7
- from groq import Groq
8
- import logging
9
-
10
- # Configure logging
11
- logging.basicConfig(
12
- level=logging.INFO,
13
- format='%(asctime)s - %(levelname)s - %(message)s'
14
- )
15
- logger = logging.getLogger(__name__)
16
-
17
- load_dotenv()
18
-
19
- app = FastAPI()
20
-
21
- @app.on_event("startup")
22
- async def startup_event():
23
- logger.info("===== Application Startup =====")
24
- logger.info(f"GROQ_API_KEY set: {bool(os.getenv('GROQ_API_KEY'))}")
25
- logger.info(f"GITHUB_TOKEN set: {bool(os.getenv('GITHUB_TOKEN'))}")
26
- logger.info("API is ready to accept requests")
27
- print(f"===== Application Startup at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} =====")
28
-
29
- app.add_middleware(
30
- CORSMiddleware,
31
- allow_origins=["*"],
32
- allow_credentials=True,
33
- allow_methods=["*"],
34
- allow_headers=["*"],
35
- )
36
-
37
- def parse_github_url(url):
38
- url = url.strip().rstrip('/')
39
- # File URL: https://github.com/owner/repo/blob/branch/path
40
- m = re.match(r"https://github\.com/([^/]+)/([^/]+)/blob/[^/]+/(.+)", url)
41
- if m:
42
- return m.group(1), m.group(2), m.group(3), False
43
- # Repo URL: https://github.com/owner/repo
44
- m = re.match(r"https://github\.com/([^/]+)/([^/]+)/?$", url)
45
- if m:
46
- return m.group(1), m.group(2), None, True
47
- return None, None, None, False
48
-
49
- def get_file_content(owner, repo, path):
50
- token = os.getenv("GITHUB_TOKEN", "")
51
- headers = {"Authorization": f"token {token}"} if (token and token != "your_github_personal_access_token_here") else {}
52
- url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}"
53
- r = requests.get(url, headers=headers, timeout=15)
54
- if r.status_code != 200:
55
- return None, f"GitHub API error: {r.status_code}"
56
- import base64
57
- data = r.json()
58
- content = base64.b64decode(data["content"]).decode("utf-8", errors="replace")
59
- return content, None
60
-
61
- def get_repo_files(owner, repo, path="", max_files=20):
62
- """Recursively get code files from repo"""
63
- token = os.getenv("GITHUB_TOKEN", "")
64
- headers = {"Authorization": f"token {token}"} if (token and token != "your_github_personal_access_token_here") else {}
65
- url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}"
66
-
67
- try:
68
- r = requests.get(url, headers=headers, timeout=15)
69
- if r.status_code != 200:
70
- return []
71
-
72
- items = r.json()
73
- files = []
74
-
75
- for item in items:
76
- if len(files) >= max_files:
77
- break
78
-
79
- if item['type'] == 'file':
80
- # Only scan code files
81
- ext = item['name'].split('.')[-1].lower()
82
- if ext in ['py', 'js', 'jsx', 'ts', 'tsx', 'java', 'cpp', 'c', 'php', 'rb', 'go', 'rs', 'sql', 'sh']:
83
- files.append(item['path'])
84
- elif item['type'] == 'dir' and item['name'] not in ['.git', 'node_modules', '__pycache__', 'dist', 'build']:
85
- # Recursively scan directories
86
- files.extend(get_repo_files(owner, repo, item['path'], max_files - len(files)))
87
-
88
- return files
89
- except:
90
- return []
91
-
92
- def run_repo_scan(owner, repo):
93
- """Scan entire repository"""
94
- logger.info(f"Starting repo scan for {owner}/{repo}")
95
-
96
- # Get list of code files
97
- files = get_repo_files(owner, repo)
98
- if not files:
99
- return {"error": "No code files found or repo is private"}
100
-
101
- logger.info(f"Found {len(files)} files to scan")
102
-
103
- # Scan each file and collect results
104
- all_vulnerabilities = []
105
- scanned_count = 0
106
-
107
- for file_path in files[:15]: # Limit to 15 files to avoid timeout
108
- logger.info(f"Scanning file {scanned_count + 1}/{len(files[:15])}: {file_path}")
109
- code, err = get_file_content(owner, repo, file_path)
110
-
111
- if err:
112
- continue
113
-
114
- # Truncate large files
115
- if len(code) > 4000:
116
- code = code[:4000] + "\n... [truncated]"
117
-
118
- # Quick scan for this file
119
- client = Groq(api_key=os.getenv("GROQ_API_KEY", ""))
120
-
121
- prompt = f"""Analyze this code file for security vulnerabilities. Be concise.
122
-
123
- File: {file_path}
124
-
125
- ```
126
- {code}
127
- ```
128
-
129
- List only CRITICAL and HIGH severity vulnerabilities found. Format:
130
- - **[Vulnerability]** in `{file_path}` line X: [brief issue]
131
-
132
- If no critical/high issues, respond: "No critical issues found."
133
- """
134
-
135
- try:
136
- response = client.chat.completions.create(
137
- model="llama-3.3-70b-versatile",
138
- messages=[{"role": "user", "content": prompt}],
139
- temperature=0.1,
140
- max_tokens=800,
141
- )
142
- result = response.choices[0].message.content.strip()
143
- if "no critical issues" not in result.lower():
144
- all_vulnerabilities.append(f"### {file_path}\n{result}\n")
145
- scanned_count += 1
146
- except Exception as e:
147
- logger.error(f"Error scanning {file_path}: {str(e)}")
148
- continue
149
-
150
- # Generate final report
151
- if not all_vulnerabilities:
152
- report = f"""# Repository Security Scan Report
153
-
154
- ## Repository Overview
155
- - Repository: {owner}/{repo}
156
- - Files Scanned: {scanned_count}
157
- - Status: βœ… No critical vulnerabilities detected
158
-
159
- ## Summary
160
- All scanned files passed security checks. No CRITICAL or HIGH severity issues found.
161
-
162
- ## Risk Summary
163
- - Critical: 0 | High: 0 | Medium: 0 | Low: 0
164
- - **Overall Risk**: LOW
165
- """
166
- else:
167
- report = f"""# Repository Security Scan Report
168
-
169
- ## Repository Overview
170
- - Repository: {owner}/{repo}
171
- - Files Scanned: {scanned_count}
172
- - Vulnerabilities Found: {len(all_vulnerabilities)} files with issues
173
-
174
- ## Vulnerabilities by File
175
-
176
- {''.join(all_vulnerabilities)}
177
-
178
- ## Recommendations
179
- 1. Review and fix all CRITICAL and HIGH severity issues immediately
180
- 2. Implement input validation and sanitization
181
- 3. Use parameterized queries for database operations
182
- 4. Keep dependencies updated
183
-
184
- ## Risk Summary
185
- - Critical: {sum(1 for v in all_vulnerabilities if 'CRITICAL' in v)} | High: {sum(1 for v in all_vulnerabilities if 'HIGH' in v)} | Medium: 0 | Low: 0
186
- - **Overall Risk**: {'CRITICAL' if any('CRITICAL' in v for v in all_vulnerabilities) else 'HIGH'}
187
- """
188
-
189
- logger.info(f"Repo scan completed: {scanned_count} files scanned")
190
- return {"result": report}
191
-
192
- def run_scan(owner, repo, file_path):
193
- """Scan single file"""
194
- code, err = get_file_content(owner, repo, file_path)
195
- if err:
196
- return {"error": err}
197
-
198
- # Truncate large files
199
- if len(code) > 6000:
200
- code = code[:6000] + "\n... [truncated]"
201
-
202
- client = Groq(api_key=os.getenv("GROQ_API_KEY", ""))
203
-
204
- prompt = f"""You are a cybersecurity expert. Analyze this code for security vulnerabilities.
205
-
206
- File: {file_path}
207
- Repository: {owner}/{repo}
208
-
209
- ```
210
- {code}
211
- ```
212
-
213
- Provide a detailed security analysis in this exact markdown format:
214
-
215
- # Security Analysis Report
216
-
217
- ## File Overview
218
- - Repository: {owner}/{repo}
219
- - File: {file_path}
220
- - Language: [detected language]
221
- - Lines analyzed: [count]
222
-
223
- ## Vulnerabilities Found
224
-
225
- [For each vulnerability found:]
226
- ### [Vulnerability Name] β€” [CRITICAL/HIGH/MEDIUM/LOW]
227
- - **Line**: [line number or range]
228
- - **Code**: `[vulnerable snippet]`
229
- - **Issue**: [clear explanation]
230
- - **CVE Reference**: [relevant CVE ID if applicable]
231
-
232
- ## Remediation
233
- [Specific fix for each vulnerability with corrected code]
234
-
235
- ## Risk Summary
236
- - Critical: [n] | High: [n] | Medium: [n] | Low: [n]
237
- - **Overall Risk**: [CRITICAL/HIGH/MEDIUM/LOW]
238
- """
239
-
240
- try:
241
- response = client.chat.completions.create(
242
- model="llama-3.3-70b-versatile",
243
- messages=[{"role": "user", "content": prompt}],
244
- temperature=0.1,
245
- max_tokens=4096,
246
- )
247
- result = response.choices[0].message.content
248
- return {"result": result}
249
- except Exception as e:
250
- return {"error": f"AI analysis failed: {str(e)}"}
251
-
252
-
253
- class ScanRequest(BaseModel):
254
- url: str
255
-
256
-
257
- @app.post("/api/scan")
258
- async def scan(req: ScanRequest):
259
- logger.info(f"Received scan request for URL: {req.url}")
260
- owner, repo, file_path, is_repo = parse_github_url(req.url)
261
-
262
- if not owner or not repo:
263
- logger.warning(f"Invalid URL format: {req.url}")
264
- return {"error": "Invalid GitHub URL. Use github.com/owner/repo or .../blob/branch/file"}
265
-
266
- if is_repo:
267
- # Full repository scan
268
- logger.info(f"Starting repository scan: {owner}/{repo}")
269
- loop = asyncio.get_running_loop()
270
- with concurrent.futures.ThreadPoolExecutor() as pool:
271
- result = await loop.run_in_executor(pool, run_repo_scan, owner, repo)
272
- else:
273
- # Single file scan
274
- logger.info(f"Scanning file: {owner}/{repo}/{file_path}")
275
- loop = asyncio.get_running_loop()
276
- with concurrent.futures.ThreadPoolExecutor() as pool:
277
- result = await loop.run_in_executor(pool, run_scan, owner, repo, file_path)
278
-
279
- if "error" in result:
280
- logger.error(f"Scan failed: {result['error']}")
281
- else:
282
- logger.info("Scan completed successfully")
283
-
284
- return result
285
-
286
-
287
- @app.get("/api/health")
288
- def health():
289
- logger.info("Health check requested")
290
- return {"status": "ok", "groq_key_set": bool(os.getenv("GROQ_API_KEY"))}
291
-
292
-
293
- @app.get("/")
294
- def root():
295
- logger.info("Root endpoint accessed")
296
- return {"status": "Vulnerability Scanner API running"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -1,87 +1,48 @@
1
- import gradio as gr
2
- import re
3
- import os
4
- import requests
5
- import base64
 
6
  from groq import Groq
7
 
 
 
 
 
 
 
 
 
 
 
 
8
  def parse_github_url(url):
9
- """Parse GitHub URL to extract owner, repo, and file path"""
10
  url = url.strip().rstrip('/')
11
- # File URL
12
  m = re.match(r"https://github\.com/([^/]+)/([^/]+)/blob/[^/]+/(.+)", url)
13
  if m:
14
- return m.group(1), m.group(2), m.group(3), False
15
- # Repo URL
16
- m = re.match(r"https://github\.com/([^/]+)/([^/]+)/?$", url)
17
- if m:
18
- return m.group(1), m.group(2), None, True
19
- return None, None, None, False
20
 
21
  def get_file_content(owner, repo, path):
22
- """Fetch file content from GitHub"""
23
  token = os.getenv("GITHUB_TOKEN", "")
24
  headers = {"Authorization": f"token {token}"} if token else {}
25
- url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}"
26
-
27
- try:
28
- r = requests.get(url, headers=headers, timeout=15)
29
- if r.status_code != 200:
30
- return None, f"GitHub API error: {r.status_code}"
31
-
32
- data = r.json()
33
- content = base64.b64decode(data["content"]).decode("utf-8", errors="replace")
34
- return content, None
35
- except Exception as e:
36
- return None, f"Error fetching file: {str(e)}"
37
 
38
- def get_repo_files(owner, repo, path="", max_files=15):
39
- """Get list of code files from repository"""
40
- token = os.getenv("GITHUB_TOKEN", "")
41
- headers = {"Authorization": f"token {token}"} if token else {}
42
- url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}"
43
-
44
- try:
45
- r = requests.get(url, headers=headers, timeout=15)
46
- if r.status_code != 200:
47
- return []
48
-
49
- items = r.json()
50
- files = []
51
-
52
- for item in items:
53
- if len(files) >= max_files:
54
- break
55
-
56
- if item['type'] == 'file':
57
- ext = item['name'].split('.')[-1].lower()
58
- if ext in ['py', 'js', 'jsx', 'ts', 'tsx', 'java', 'cpp', 'c', 'php', 'rb', 'go', 'rs', 'sql', 'sh']:
59
- files.append(item['path'])
60
- elif item['type'] == 'dir' and item['name'] not in ['.git', 'node_modules', '__pycache__', 'dist', 'build']:
61
- files.extend(get_repo_files(owner, repo, item['path'], max_files - len(files)))
62
-
63
- return files
64
- except:
65
- return []
66
 
67
- def scan_file(owner, repo, file_path, progress=gr.Progress()):
68
- """Scan a single file for vulnerabilities"""
69
- progress(0.2, desc="Fetching file content...")
70
  code, err = get_file_content(owner, repo, file_path)
71
  if err:
72
- return f"❌ Error: {err}"
73
-
74
  if len(code) > 6000:
75
  code = code[:6000] + "\n... [truncated]"
76
-
77
- progress(0.5, desc="Analyzing with AI...")
78
-
79
  groq_key = os.getenv("GROQ_API_KEY", "")
80
  if not groq_key:
81
- return "❌ Error: GROQ_API_KEY not configured"
82
-
83
  client = Groq(api_key=groq_key)
84
-
85
  prompt = f"""You are a cybersecurity expert. Analyze this code for security vulnerabilities.
86
 
87
  File: {file_path}
@@ -93,31 +54,29 @@ Repository: {owner}/{repo}
93
 
94
  Provide a detailed security analysis in this exact markdown format:
95
 
96
- # πŸ›‘οΈ Security Analysis Report
97
 
98
- ## πŸ“ File Overview
99
  - Repository: {owner}/{repo}
100
  - File: {file_path}
101
  - Language: [detected language]
102
  - Lines analyzed: [count]
103
 
104
- ## 🚨 Vulnerabilities Found
105
 
106
- [For each vulnerability found:]
107
  ### [Vulnerability Name] β€” [CRITICAL/HIGH/MEDIUM/LOW]
108
- - **Line**: [line number or range]
109
- - **Code**: `[vulnerable snippet]`
110
- - **Issue**: [clear explanation]
111
- - **CVE Reference**: [relevant CVE ID if applicable]
112
 
113
- ## πŸ› οΈ Remediation
114
- [Specific fix for each vulnerability with corrected code]
115
 
116
- ## πŸ“Š Risk Summary
117
  - Critical: [n] | High: [n] | Medium: [n] | Low: [n]
118
  - **Overall Risk**: [CRITICAL/HIGH/MEDIUM/LOW]
119
  """
120
-
121
  try:
122
  response = client.chat.completions.create(
123
  model="llama-3.3-70b-versatile",
@@ -125,190 +84,36 @@ Provide a detailed security analysis in this exact markdown format:
125
  temperature=0.1,
126
  max_tokens=4096,
127
  )
128
- progress(1.0, desc="Complete!")
129
- return response.choices[0].message.content
130
  except Exception as e:
131
- return f"❌ AI analysis failed: {str(e)}"
132
-
133
- def scan_repository(owner, repo, progress=gr.Progress()):
134
- """Scan entire repository"""
135
- progress(0.1, desc="Fetching repository files...")
136
- files = get_repo_files(owner, repo)
137
-
138
- if not files:
139
- return "❌ No code files found or repository is private"
140
-
141
- progress(0.2, desc=f"Found {len(files)} files to scan...")
142
-
143
- all_vulnerabilities = []
144
- scanned_count = 0
145
-
146
- groq_key = os.getenv("GROQ_API_KEY", "")
147
- if not groq_key:
148
- return "❌ Error: GROQ_API_KEY not configured"
149
-
150
- client = Groq(api_key=groq_key)
151
-
152
- for i, file_path in enumerate(files[:15]):
153
- progress((0.2 + (i / 15) * 0.7), desc=f"Scanning {i+1}/{min(len(files), 15)}: {file_path}")
154
-
155
- code, err = get_file_content(owner, repo, file_path)
156
- if err:
157
- continue
158
-
159
- if len(code) > 4000:
160
- code = code[:4000] + "\n... [truncated]"
161
-
162
- prompt = f"""Analyze this code file for security vulnerabilities. Be concise.
163
-
164
- File: {file_path}
165
 
166
- ```
167
- {code}
168
- ```
169
-
170
- List only CRITICAL and HIGH severity vulnerabilities found. Format:
171
- - **[Vulnerability]** in `{file_path}` line X: [brief issue]
172
 
173
- If no critical/high issues, respond: "No critical issues found."
174
- """
175
-
176
- try:
177
- response = client.chat.completions.create(
178
- model="llama-3.3-70b-versatile",
179
- messages=[{"role": "user", "content": prompt}],
180
- temperature=0.1,
181
- max_tokens=800,
182
- )
183
- result = response.choices[0].message.content.strip()
184
- if "no critical issues" not in result.lower():
185
- all_vulnerabilities.append(f"### {file_path}\n{result}\n")
186
- scanned_count += 1
187
- except:
188
- continue
189
-
190
- progress(1.0, desc="Generating report...")
191
-
192
- if not all_vulnerabilities:
193
- report = f"""# πŸ›‘οΈ Repository Security Scan Report
194
 
195
- ## πŸ“¦ Repository Overview
196
- - Repository: {owner}/{repo}
197
- - Files Scanned: {scanned_count}
198
- - Status: βœ… No critical vulnerabilities detected
199
 
200
- ## πŸ“Š Summary
201
- All scanned files passed security checks. No CRITICAL or HIGH severity issues found.
 
202
 
203
- ## πŸ“ˆ Risk Summary
204
- - Critical: 0 | High: 0 | Medium: 0 | Low: 0
205
- - **Overall Risk**: LOW
206
- """
207
- else:
208
- report = f"""# πŸ›‘οΈ Repository Security Scan Report
209
 
210
- ## πŸ“¦ Repository Overview
211
- - Repository: {owner}/{repo}
212
- - Files Scanned: {scanned_count}
213
- - Vulnerabilities Found: {len(all_vulnerabilities)} files with issues
214
 
215
- ## 🚨 Vulnerabilities by File
216
-
217
- {''.join(all_vulnerabilities)}
218
-
219
- ## πŸ› οΈ Recommendations
220
- 1. Review and fix all CRITICAL and HIGH severity issues immediately
221
- 2. Implement input validation and sanitization
222
- 3. Use parameterized queries for database operations
223
- 4. Keep dependencies updated
224
-
225
- ## πŸ“Š Risk Summary
226
- - Critical: {sum(1 for v in all_vulnerabilities if 'CRITICAL' in v)} | High: {sum(1 for v in all_vulnerabilities if 'HIGH' in v)} | Medium: 0 | Low: 0
227
- - **Overall Risk**: {'CRITICAL' if any('CRITICAL' in v for v in all_vulnerabilities) else 'HIGH'}
228
- """
229
-
230
- return report
231
-
232
- def analyze_github_url(url, progress=gr.Progress()):
233
- """Main analysis function"""
234
- if not url or not url.strip():
235
- return "❌ Please provide a GitHub URL"
236
-
237
- owner, repo, file_path, is_repo = parse_github_url(url)
238
-
239
- if not owner or not repo:
240
- return "❌ Invalid GitHub URL. Use:\n- File: github.com/owner/repo/blob/branch/file.py\n- Repo: github.com/owner/repo"
241
-
242
- if is_repo:
243
- return scan_repository(owner, repo, progress)
244
- else:
245
- return scan_file(owner, repo, file_path, progress)
246
-
247
- # Gradio Interface
248
- CSS = """
249
- .gradio-container {
250
- font-family: 'Inter', sans-serif;
251
- }
252
- .cyber-title {
253
- text-align: center;
254
- color: #00ff41;
255
- font-size: 2em;
256
- font-weight: bold;
257
- margin-bottom: 10px;
258
- }
259
- """
260
 
261
- with gr.Blocks(css=CSS, title="πŸ›‘οΈ Vulnerability Scanner") as demo:
262
- gr.HTML("""
263
- <div class="cyber-title">
264
- πŸ›‘οΈ VULNERABILITY SCANNER
265
- </div>
266
- <p style="text-align:center;color:#888;">
267
- AI-powered security analysis for GitHub repositories and files
268
- </p>
269
- """)
270
-
271
- with gr.Row():
272
- with gr.Column():
273
- url_input = gr.Textbox(
274
- label="GitHub URL",
275
- placeholder="https://github.com/owner/repo/blob/main/file.py or https://github.com/owner/repo",
276
- lines=2
277
- )
278
-
279
- with gr.Row():
280
- scan_btn = gr.Button("πŸ” Scan for Vulnerabilities", variant="primary")
281
- clear_btn = gr.Button("πŸ—‘οΈ Clear")
282
-
283
- gr.Examples(
284
- examples=[
285
- ["https://github.com/ayushmittal62/vunreability_scanner_testing/blob/master/python/database.py"],
286
- ["https://github.com/ayushmittal62/vunreability_scanner_testing"],
287
- ],
288
- inputs=url_input,
289
- label="πŸ“ Example URLs"
290
- )
291
-
292
- with gr.Row():
293
- output = gr.Markdown(label="Analysis Results")
294
-
295
- scan_btn.click(
296
- fn=analyze_github_url,
297
- inputs=[url_input],
298
- outputs=[output]
299
- )
300
-
301
- clear_btn.click(
302
- fn=lambda: ("", ""),
303
- outputs=[url_input, output]
304
- )
305
-
306
- gr.HTML("""
307
- <div style="text-align:center;margin-top:20px;color:#666;font-size:0.9em;">
308
- Built by <a href="https://github.com/AdityaDev-X" target="_blank">AdityaDev-X</a> |
309
- Powered by Groq LLaMA 3.3 70B
310
- </div>
311
- """)
312
 
313
- if __name__ == "__main__":
314
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.staticfiles import StaticFiles
4
+ from fastapi.responses import FileResponse
5
+ from pydantic import BaseModel
6
+ import re, os, requests, base64
7
  from groq import Groq
8
 
9
+ app = FastAPI()
10
+
11
+ app.add_middleware(
12
+ CORSMiddleware,
13
+ allow_origins=["*"],
14
+ allow_methods=["*"],
15
+ allow_headers=["*"],
16
+ )
17
+
18
+ # ── helpers ──────────────────────────────────────────────────────────────────
19
+
20
  def parse_github_url(url):
 
21
  url = url.strip().rstrip('/')
 
22
  m = re.match(r"https://github\.com/([^/]+)/([^/]+)/blob/[^/]+/(.+)", url)
23
  if m:
24
+ return m.group(1), m.group(2), m.group(3)
25
+ return None, None, None
 
 
 
 
26
 
27
  def get_file_content(owner, repo, path):
 
28
  token = os.getenv("GITHUB_TOKEN", "")
29
  headers = {"Authorization": f"token {token}"} if token else {}
30
+ r = requests.get(f"https://api.github.com/repos/{owner}/{repo}/contents/{path}", headers=headers, timeout=15)
31
+ if r.status_code != 200:
32
+ return None, f"GitHub API error: {r.status_code}"
33
+ return base64.b64decode(r.json()["content"]).decode("utf-8", errors="replace"), None
 
 
 
 
 
 
 
 
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
+ def scan_file(owner, repo, file_path):
 
 
37
  code, err = get_file_content(owner, repo, file_path)
38
  if err:
39
+ return {"error": err}
 
40
  if len(code) > 6000:
41
  code = code[:6000] + "\n... [truncated]"
 
 
 
42
  groq_key = os.getenv("GROQ_API_KEY", "")
43
  if not groq_key:
44
+ return {"error": "GROQ_API_KEY not configured"}
 
45
  client = Groq(api_key=groq_key)
 
46
  prompt = f"""You are a cybersecurity expert. Analyze this code for security vulnerabilities.
47
 
48
  File: {file_path}
 
54
 
55
  Provide a detailed security analysis in this exact markdown format:
56
 
57
+ # Security Analysis Report
58
 
59
+ ## File Overview
60
  - Repository: {owner}/{repo}
61
  - File: {file_path}
62
  - Language: [detected language]
63
  - Lines analyzed: [count]
64
 
65
+ ## Vulnerabilities Found
66
 
 
67
  ### [Vulnerability Name] β€” [CRITICAL/HIGH/MEDIUM/LOW]
68
+ - **Line**: [line number]
69
+ - **Code**: `[snippet]`
70
+ - **Issue**: [explanation]
71
+ - **CVE Reference**: [CVE ID if applicable]
72
 
73
+ ## Remediation
74
+ [Specific fixes with corrected code]
75
 
76
+ ## Risk Summary
77
  - Critical: [n] | High: [n] | Medium: [n] | Low: [n]
78
  - **Overall Risk**: [CRITICAL/HIGH/MEDIUM/LOW]
79
  """
 
80
  try:
81
  response = client.chat.completions.create(
82
  model="llama-3.3-70b-versatile",
 
84
  temperature=0.1,
85
  max_tokens=4096,
86
  )
87
+ return {"result": response.choices[0].message.content}
 
88
  except Exception as e:
89
+ return {"error": f"AI analysis failed: {str(e)}"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
 
 
 
 
 
 
91
 
92
+ # ── API routes ────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
+ class ScanRequest(BaseModel):
95
+ url: str
 
 
96
 
97
+ @app.get("/api/health")
98
+ def health():
99
+ return {"status": "ok", "groq_key_set": bool(os.getenv("GROQ_API_KEY"))}
100
 
101
+ @app.post("/api/scan")
102
+ def scan(req: ScanRequest):
103
+ owner, repo, file_path = parse_github_url(req.url)
104
+ if not owner or not repo or not file_path:
105
+ return {"error": "Invalid GitHub file URL. Use format: github.com/owner/repo/blob/branch/file"}
106
+ return scan_file(owner, repo, file_path)
107
 
108
+ # ── Serve React frontend ──────────────────────────────────────────────────────
 
 
 
109
 
110
+ if os.path.exists("static"):
111
+ app.mount("/assets", StaticFiles(directory="static/assets"), name="assets")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
+ @app.get("/")
114
+ def serve_root():
115
+ return FileResponse("static/index.html")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
+ @app.get("/{full_path:path}")
118
+ def serve_spa(full_path: str):
119
+ return FileResponse("static/index.html")
requirements.txt CHANGED
@@ -1,4 +1,6 @@
1
- gradio
 
 
2
  groq
3
  requests
4
  python-dotenv
 
1
+ fastapi
2
+ uvicorn
3
+ pydantic
4
  groq
5
  requests
6
  python-dotenv