-
-
Notifications
You must be signed in to change notification settings - Fork 225
Integrate Gemini API for AI-Powered Response System in OWASP-BLT #4470
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -358,8 +358,14 @@ | |||||||||||||||||||||||
|
||||||||||||||||||||||||
handler404 = "website.views.core.handler404" | ||||||||||||||||||||||||
handler500 = "website.views.core.handler500" | ||||||||||||||||||||||||
from django.contrib import admin | ||||||||||||||||||||||||
from django.urls import path, include | ||||||||||||||||||||||||
from django.conf import settings | ||||||||||||||||||||||||
from django.conf.urls.static import static | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
urlpatterns = [ | ||||||||||||||||||||||||
path('', include('website.urls')), | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify URL routing doesn't conflict with existing patterns. Including 🏁 Script executed: #!/bin/bash
# Description: Check for potential URL conflicts between website.urls and existing patterns
# Expected: Find any URL patterns that might conflict
# Check what URLs are defined in website.urls
echo "=== URLs in website/urls.py ==="
cat website/urls.py
echo -e "\n=== Checking for potential conflicts ==="
# Look for patterns that might conflict with home, api/test-gemini/, ask-gemini/
rg -A 2 -B 2 "(path\(''\)|path\('api/\)|path\('ask-)" blt/urls.py Length of output: 605 🏁 Script executed: #!/bin/bash
set -e
echo "URL prefixes in blt/urls.py:"
# single-quoted
rg -o "path\('[^']*'" blt/urls.py | sed -E "s/path\('([^']*)'/\1/"
# double-quoted
rg -o 'path\("[^"]*"' blt/urls.py | sed -E 's/path\("([^"]*)"/\1/'
echo -e "\nURL prefixes in website/urls.py:"
rg -o "path\('[^']*'" website/urls.py | sed -E "s/path\('([^']*)'/\1/"
rg -o 'path\("[^"]*"' website/urls.py | sed -E 's/path\("([^"]*)"/\1/' Length of output: 4625 Ensure Including
any other URL in your project will 404 instead of matching its intended pattern. Please adjust one of the following:
This will ensure your core app and API routes remain reachable. 🤖 Prompt for AI Agents
|
||||||||||||||||||||||||
path(settings.ADMIN_URL + "/", admin.site.urls), | ||||||||||||||||||||||||
Comment on lines
+361
to
+368
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Remove redundant imports and duplicate admin URL. The imports at lines 361-364 are redundant as they're already imported at the top of the file. Additionally, there's now a duplicate admin URL pattern. Apply this diff to clean up the redundancy: -from django.contrib import admin
-from django.urls import path, include
-from django.conf import settings
-from django.conf.urls.static import static
-
urlpatterns = [
path('', include('website.urls')),
- path(settings.ADMIN_URL + "/", admin.site.urls), The admin URL is already defined at line 528, making the duplicate at line 368 unnecessary. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||
path("simulation/", dashboard, name="dashboard"), | ||||||||||||||||||||||||
path("banned_apps/", BannedAppsView.as_view(), name="banned_apps"), | ||||||||||||||||||||||||
path("api/banned_apps/search/", search_banned_apps, name="search_banned_apps"), | ||||||||||||||||||||||||
|
@@ -1106,3 +1112,7 @@ | |||||||||||||||||||||||
re_path(r"^__debug__/", include(debug_toolbar.urls)), | ||||||||||||||||||||||||
] + urlpatterns | ||||||||||||||||||||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Ask Gemini</title> | ||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"> | ||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | ||
</head> | ||
<body class="bg-white text-gray-800 font-sans"> | ||
<nav class="bg-white shadow-md p-4"> | ||
<div class="text-red-600 font-bold text-2xl">OWASP BLT</div> | ||
</nav> | ||
|
||
<main class="max-w-3xl mx-auto mt-12 px-4"> | ||
<h1 class="text-3xl font-bold text-gray-900 mb-4">Ask Gemini</h1> | ||
<form id="gemini-form" class="mb-6"> | ||
<textarea id="user-input" rows="4" class="w-full border rounded-md p-4 focus:outline-none focus:ring-2 focus:ring-red-500" placeholder="Type your question..."></textarea> | ||
<button type="submit" class="mt-4 bg-red-500 text-white px-6 py-2 rounded-md hover:bg-red-600">Submit</button> | ||
</form> | ||
|
||
<div id="response-container" class="prose max-w-none"></div> | ||
</main> | ||
|
||
<script> | ||
const form = document.getElementById('gemini-form'); | ||
const input = document.getElementById('user-input'); | ||
const responseContainer = document.getElementById('response-container'); | ||
|
||
form.addEventListener('submit', async (e) => { | ||
e.preventDefault(); | ||
const prompt = input.value.trim(); | ||
if (!prompt) return; | ||
|
||
responseContainer.innerHTML = '<p class="text-gray-500">Loading...</p>'; | ||
|
||
const response = await fetch('/api/test-gemini/?prompt=' + encodeURIComponent(prompt)); | ||
const result = await response.json(); | ||
Comment on lines
+37
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Use POST (with JSON body & CSRF token) instead of GET to avoid URL length limits and query-string leakage Long prompts will break on browsers/servers that cap URL size, and sensitive questions end up in logs and browser history. Switch to - const response = await fetch('/api/test-gemini/?prompt=' + encodeURIComponent(prompt));
- const result = await response.json();
+ const response = await fetch('/api/test-gemini/', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRFToken': getCookie('csrftoken') // helper required
+ },
+ body: JSON.stringify({ prompt })
+ });
+ const result = await response.json();
🤖 Prompt for AI Agents
|
||
|
||
if (result.response) { | ||
const formatted = marked.parse(result.response); | ||
responseContainer.innerHTML = formatted; | ||
} else { | ||
Comment on lines
+40
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sanitize Gemini-generated HTML before injecting it into the DOM
- const formatted = marked.parse(result.response);
- responseContainer.innerHTML = formatted;
+ // Sanitize to neutralise any hostile markup returned by the model
+ const formatted = DOMPurify.sanitize(marked.parse(result.response));
+ responseContainer.innerHTML = formatted; Add DOMPurify (or enable <!-- add right after the marked.js import -->
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.5/dist/purify.min.js"
integrity="sha384-8W+bKHSN50dBprVT2z5jc4azlD2r7rSnPv4XLLF9B8g8nMiZxgJ5Bf5+8b5hvx+H"
crossorigin="anonymous"></script> 🤖 Prompt for AI Agents
|
||
responseContainer.innerHTML = '<p class="text-red-600">' + (result.error || 'Something went wrong.') + '</p>'; | ||
} | ||
}); | ||
</script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from django.urls import path | ||
from website.views.core import home | ||
from website.views.gemini import test_gemini, gemini_ui # <-- use gemini_ui, not gemini_input_page | ||
|
||
urlpatterns = [ | ||
path('', home, name='home'), | ||
path('api/test-gemini/', test_gemini, name='test_gemini'), | ||
path('ask-gemini/', gemini_ui, name='gemini_ui'), | ||
] |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -24,6 +24,38 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from openai import OpenAI | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from PIL import Image | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# website/utils.py | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import requests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from django.conf import settings | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def call_gemini(prompt: str) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
headers = {"Content-Type": "application/json"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Add domain-specific context to act like a security assistant | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
context_prompt = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"You are Gemini, a helpful, polite assistant integrated into OWASP BLT, a suite of security tools. " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"You help users understand cybersecurity, bug bounty, appsec, secure coding, OWASP Top 10, and other web security concerns. " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"Provide clear, well-formatted answers suitable for developers and researchers. Respond in markdown format." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
full_prompt = f"{context_prompt}\n\nUser's question: {prompt}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
payload = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"contents": [{"parts": [{"text": full_prompt}]}] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
params = {"key": settings.GEMINI_API_KEY} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
response = requests.post(url, headers=headers, json=payload, params=params) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
data = response.json() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if "candidates" in data: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return data["candidates"][0]["content"]["parts"][0]["text"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return str(data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+31
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve security, error handling, and reliability. Several improvements needed for production readiness:
Apply this diff to address these issues: def call_gemini(prompt: str) -> str:
url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent"
- headers = {"Content-Type": "application/json"}
+ headers = {
+ "Content-Type": "application/json",
+ "x-goog-api-key": settings.GEMINI_API_KEY
+ }
# Add domain-specific context to act like a security assistant
context_prompt = (
"You are Gemini, a helpful, polite assistant integrated into OWASP BLT, a suite of security tools. "
"You help users understand cybersecurity, bug bounty, appsec, secure coding, OWASP Top 10, and other web security concerns. "
"Provide clear, well-formatted answers suitable for developers and researchers. Respond in markdown format."
)
full_prompt = f"{context_prompt}\n\nUser's question: {prompt}"
payload = {
"contents": [{"parts": [{"text": full_prompt}]}]
}
- params = {"key": settings.GEMINI_API_KEY}
- response = requests.post(url, headers=headers, json=payload, params=params)
+ response = requests.post(url, headers=headers, json=payload, timeout=30)
+ response.raise_for_status() # Raise exception for HTTP errors
data = response.json()
- if "candidates" in data:
+ if "candidates" in data and len(data["candidates"]) > 0:
+ parts = data["candidates"][0].get("content", {}).get("parts", [])
+ if parts and len(parts) > 0:
+ return parts[0].get("text", "No response generated")
+ else:
+ return "No response content available"
- return data["candidates"][0]["content"]["parts"][0]["text"]
else:
- return str(data)
+ error_msg = data.get("error", {}).get("message", "Unknown API error")
+ raise Exception(f"Gemini API error: {error_msg}") 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from website.models import DailyStats | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from .models import PRAnalysisReport | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,16 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
from django.shortcuts import render | ||||||||||||||||||||||||||||||||||||||||||||||||||
from django.http import JsonResponse | ||||||||||||||||||||||||||||||||||||||||||||||||||
from website.utils import call_gemini | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
def gemini_ui(request): | ||||||||||||||||||||||||||||||||||||||||||||||||||
return render(request, "gemini_input.html") # <-- match your actual path | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
def test_gemini(request): | ||||||||||||||||||||||||||||||||||||||||||||||||||
prompt = request.GET.get("prompt") | ||||||||||||||||||||||||||||||||||||||||||||||||||
if not prompt: | ||||||||||||||||||||||||||||||||||||||||||||||||||
return JsonResponse({"error": "No prompt provided"}, status=400) | ||||||||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||||||||
response = call_gemini(prompt) | ||||||||||||||||||||||||||||||||||||||||||||||||||
return JsonResponse({"response": response}) | ||||||||||||||||||||||||||||||||||||||||||||||||||
except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||
return JsonResponse({"error": str(e)}, status=500) | ||||||||||||||||||||||||||||||||||||||||||||||||||
Check warningCode scanning / CodeQL Information exposure through an exception Medium Stack trace information Error loading related location Loading
Comment on lines
+8
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix information exposure vulnerability. The exception handling exposes full error details to users, which could leak sensitive information like API keys, internal paths, or system configuration details. Apply this diff to implement secure error handling: def test_gemini(request):
prompt = request.GET.get("prompt")
if not prompt:
return JsonResponse({"error": "No prompt provided"}, status=400)
try:
response = call_gemini(prompt)
return JsonResponse({"response": response})
except Exception as e:
- return JsonResponse({"error": str(e)}, status=500)
+ # Log the full error for debugging but return generic message to user
+ import logging
+ logging.error(f"Gemini API error: {str(e)}")
+ return JsonResponse({"error": "An error occurred while processing your request"}, status=500) 📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Check: CodeQL[warning] 16-16: Information exposure through an exception 🤖 Prompt for AI Agents
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consolidate environment variable loading and add API key validation.
There are multiple approaches to loading environment variables in this file. The existing code uses
environ.Env.read_env()
at lines 14 and 19, and nowload_dotenv()
is added. This creates redundancy and potential confusion.Apply this diff to consolidate and add validation:
The existing
environ.Env.read_env()
calls already handle loading from.env
files, makingload_dotenv()
redundant.📝 Committable suggestion
🤖 Prompt for AI Agents