-
-
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?
Conversation
… flash model with the context of security tools, and to test the api is ask-gemini
WalkthroughThe changes introduce Gemini AI integration into the Django project. They add environment variable loading for the Gemini API key, new URL routes and view functions for interacting with Gemini, a utility to call the Gemini API, and a user-facing HTML template for submitting prompts. Minor settings and URL configuration adjustments are also included. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Browser
participant DjangoServer
participant GeminiAPI
User->>Browser: Open /ask-gemini/
Browser->>DjangoServer: GET /ask-gemini/
DjangoServer->>Browser: Render gemini_input.html
User->>Browser: Enter prompt and submit form
Browser->>DjangoServer: GET /api/test-gemini/?prompt=...
DjangoServer->>GeminiAPI: POST prompt to Gemini API
GeminiAPI-->>DjangoServer: Return generated response
DjangoServer-->>Browser: JSON response with Gemini output
Browser->>User: Render response in UI
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~18 minutes Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
response = call_gemini(prompt) | ||
return JsonResponse({"response": response}) | ||
except Exception as e: | ||
return JsonResponse({"error": str(e)}, status=500) |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Stack trace information
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.
Actionable comments posted: 7
🧹 Nitpick comments (4)
website/utils.py (1)
27-30
: Remove duplicate imports.The imports for
requests
anddjango.conf.settings
are already present at the top of the file (lines 15 and 18).-# website/utils.py -import requests -from django.conf import settings -website/templates/gemini_input.html (3)
30-46
: Add robust fetch error handling
await response.json()
will throw on network errors or non-JSON responses, crashing the handler. Guard for!response.ok
and wrap the call intry / catch
.- const response = await fetch('/api/test-gemini/?prompt=' + encodeURIComponent(prompt)); - const result = await response.json(); + let result; + try { + const response = await fetch('/api/test-gemini/?prompt=' + encodeURIComponent(prompt)); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + result = await response.json(); + } catch (err) { + responseContainer.innerHTML = `<p class="text-red-600">Request failed: ${err.message}</p>`; + return; + }
7-8
: Add Sub-Resource Integrity (SRI) &crossorigin
for CDN assetsIncluding external scripts without SRI is a supply-chain risk. Pin exact versions and add integrity hashes. Same applies to Tailwind.
18-19
: Disable the submit button while awaiting a response to prevent duplicate requestsUI/UX nit – users can spam the endpoint by repeatedly clicking Submit.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Knowledge Base: Disabled due to Reviews > Disable Knowledge Base setting
📒 Files selected for processing (6)
blt/settings.py
(3 hunks)blt/urls.py
(2 hunks)website/templates/gemini_input.html
(1 hunks)website/urls.py
(1 hunks)website/utils.py
(1 hunks)website/views/gemini.py
(1 hunks)
🧰 Additional context used
🪛 GitHub Check: CodeQL
website/views/gemini.py
[warning] 16-16: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: auto-assign
- GitHub Check: Run Tests
- GitHub Check: docker-test
- GitHub Check: Analyze (python)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
blt/settings.py (1)
169-169
: LGTM!Adding 'templates' to the DIRS list properly enables Django to locate templates in the dedicated templates directory for the new Gemini feature.
website/views/gemini.py (1)
5-6
: LGTM!The UI view correctly renders the Gemini input template.
website/urls.py (1)
1-9
: LGTM!The URL configuration is well-structured with clear routing for the home page, Gemini API endpoint, and UI interface. The import structure and URL patterns follow Django best practices.
from dotenv import load_dotenv | ||
load_dotenv() | ||
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY") |
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 now load_dotenv()
is added. This creates redundancy and potential confusion.
Apply this diff to consolidate and add validation:
-from dotenv import load_dotenv
-load_dotenv()
-GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
+GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
+if not GEMINI_API_KEY:
+ raise Exception("GEMINI_API_KEY environment variable is required for Gemini integration.")
The existing environ.Env.read_env()
calls already handle loading from .env
files, making load_dotenv()
redundant.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
from dotenv import load_dotenv | |
load_dotenv() | |
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY") | |
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY") | |
if not GEMINI_API_KEY: | |
raise Exception("GEMINI_API_KEY environment variable is required for Gemini integration.") |
🤖 Prompt for AI Agents
In blt/settings.py around lines 634 to 636, remove the redundant load_dotenv()
call and the import from dotenv since environ.Env.read_env() is already used
earlier to load environment variables. Then, add validation to ensure
GEMINI_API_KEY is set by checking if the environment variable is None or empty
and raise an appropriate error if it is missing. This consolidates environment
loading and ensures the API key is validated.
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), |
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
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
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
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), | |
urlpatterns = [ | |
path('', include('website.urls')), | |
] |
🤖 Prompt for AI Agents
In blt/urls.py around lines 361 to 368, remove the redundant imports of admin,
path, include, settings, and static since they are already imported at the top
of the file. Also, delete the duplicate admin URL pattern at line 368 because
the admin URL is already defined at line 528, avoiding duplication in
urlpatterns.
|
||
urlpatterns = [ | ||
path('', include('website.urls')), |
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.
💡 Verification agent
🧩 Analysis chain
Verify URL routing doesn't conflict with existing patterns.
Including website.urls
at the root path could potentially conflict with existing URL patterns defined later in the file.
🏁 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 website.urls
doesn’t override all other routes
Including website.urls
at the empty prefix as the first entry in blt/urls.py
(line 367) will capture every request and, since website/urls.py
only defines:
''
(home)api/test-gemini/
ask-gemini/
any other URL in your project will 404 instead of matching its intended pattern.
Please adjust one of the following:
- Move
blt/urls.py:367 - path('', include('website.urls')),
-
…other patterns here…
- path('', include('website.urls')), # move below existing routes
- Or mount under its own prefix, e.g.:
- path('', include('website.urls')),
- path('website/', include('website.urls')),
This will ensure your core app and API routes remain reachable.
🤖 Prompt for AI Agents
In blt/urls.py at line 367, including 'website.urls' at the root path causes it
to override all other routes, leading to 404 errors for URLs not defined there.
To fix this, either move this include statement below other route definitions so
those routes take precedence, or mount 'website.urls' under a specific prefix
(e.g., 'website/') to isolate its URL space and prevent it from capturing all
requests.
const response = await fetch('/api/test-gemini/?prompt=' + encodeURIComponent(prompt)); | ||
const result = await response.json(); |
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
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 POST
with a JSON payload (and pass Django’s csrftoken
).
- 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();
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In website/templates/gemini_input.html around lines 37 to 38, the code uses a
GET request with query parameters to send the prompt, which risks URL length
limits and leaks sensitive data in logs and history. Change the fetch call to
use POST instead, sending the prompt as a JSON body. Also, include the CSRF
token from Django in the request headers to ensure security. Update the
server-side endpoint to accept POST requests with JSON if needed.
if (result.response) { | ||
const formatted = marked.parse(result.response); | ||
responseContainer.innerHTML = formatted; | ||
} else { |
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.
Sanitize Gemini-generated HTML before injecting it into the DOM
marked.parse()
will happily render any HTML the model returns – including <script>
tags – leading to a trivial stored/ reflected XSS vector. Never trust LLM output.
- 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 marked
’s sanitize
option, but DOMPurify is battle-tested):
<!-- add right after the marked.js import -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js"
integrity="sha384-8W+bKHSN50dBprVT2z5jc4azlD2r7rSnPv4XLLF9B8g8nMiZxgJ5Bf5+8b5hvx+H"
crossorigin="anonymous"></script>
🤖 Prompt for AI Agents
In website/templates/gemini_input.html around lines 40 to 43, the code uses
marked.parse() to convert model output to HTML and injects it directly into the
DOM, which can lead to XSS vulnerabilities if the output contains malicious
scripts. To fix this, include the DOMPurify library by adding its script tag
after the marked.js import, then sanitize the HTML returned by marked.parse()
using DOMPurify.sanitize() before assigning it to responseContainer.innerHTML.
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) | ||
|
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
Improve security, error handling, and reliability.
Several improvements needed for production readiness:
- API key should be in headers, not query parameters
- Missing error handling for API response status
- No timeout specified for HTTP request
- Response parsing could fail if structure is unexpected
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
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
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) | |
def call_gemini(prompt: str) -> str: | |
url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent" | |
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}]}] | |
} | |
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 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" | |
else: | |
error_msg = data.get("error", {}).get("message", "Unknown API error") | |
raise Exception(f"Gemini API error: {error_msg}") |
🤖 Prompt for AI Agents
In website/utils.py lines 31 to 56, the call_gemini function has multiple
issues: the API key should be moved from query parameters to the request headers
for better security; add error handling to check the HTTP response status and
raise or handle errors if the call fails; specify a timeout parameter in the
requests.post call to avoid hanging requests; and add robust parsing of the JSON
response to safely handle unexpected or missing fields, using try-except blocks
or conditional checks to prevent runtime errors.
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) |
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.
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
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
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) | |
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: | |
# Log the full error for debugging but return a generic message to the user | |
import logging | |
logging.error(f"Gemini API error: {e}") | |
return JsonResponse( | |
{"error": "An error occurred while processing your request"}, | |
status=500 | |
) |
🧰 Tools
🪛 GitHub Check: CodeQL
[warning] 16-16: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
🤖 Prompt for AI Agents
In website/views/gemini.py around lines 8 to 16, the current exception handling
returns the full error message to the user, risking exposure of sensitive
information. Modify the except block to log the detailed exception internally
for debugging but return a generic error message to the user in the
JsonResponse, such as "An internal error occurred," with a 500 status code to
prevent leaking sensitive details.
@atharva9980 can you please run
Also the issue you have linked is related to one of the GSoC milestones , I suggest you take a look on some other issues , or collaborate with @SahilDhillon21 , if he's fine with it. |
Closes #4378
🔧 Feature: Gemini API Integration
✅ What this PR does
gemini-2.5-flash
) using secure key from.env
./ask-gemini/
matching the OWASP BLT design.marked.js
.📸 Screenshots
📂 Files Changed
website/views/gemini.py
website/utils.py
website/templates/gemini_input.html
website/urls.py
.env
usage andsettings.py
🚀 How to Test
/ask-gemini/
🔒 Notes
further: It will be grateful if you carefully evaluate the work and give me suggestions , I am ready to to further enhance it by modifying its usage to RAG architecture , or agentic AI architecture , generative AI or ML model etc.
Summary by CodeRabbit
New Features
Configuration
Improvements