From f2f83efd6cef7cfe84f89afbccc1cef162b277aa Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Tue, 29 Jul 2025 09:20:04 +0530
Subject: [PATCH 01/32] ffufai.py
---
ffufai.py | 74 +++++++++++++++++++++++++++++++++++++------------------
1 file changed, 50 insertions(+), 24 deletions(-)
diff --git a/ffufai.py b/ffufai.py
index c7ef118..4c13e7e 100755
--- a/ffufai.py
+++ b/ffufai.py
@@ -5,6 +5,7 @@
import subprocess
import requests
import json
+import re
from openai import OpenAI
import anthropic
from urllib.parse import urlparse
@@ -21,7 +22,7 @@ def get_api_key():
def get_headers(url):
try:
- response = requests.head(url, allow_redirects=True)
+ response = requests.head(url, allow_redirects=True, timeout=5)
return dict(response.headers)
except requests.RequestException as e:
print(f"Error fetching headers: {e}")
@@ -29,28 +30,28 @@ def get_headers(url):
def get_ai_extensions(url, headers, api_type, api_key, max_extensions):
prompt = f"""
- Given the following URL and HTTP headers, suggest the most likely file extensions for fuzzing this endpoint.
- Respond with a JSON object containing a list of extensions. The response will be parsed with json.loads(),
- so it must be valid JSON. No preamble or yapping. Use the format: {{"extensions": [".ext1", ".ext2", ...]}}.
- Do not suggest more than {max_extensions}, but only suggest extensions that make sense. For example, if the path is
- /js/ then don't suggest .css as the extension. Also, if limited, prefer the extensions which are more interesting.
- The URL path is great to look at for ideas. For example, if it says presentations, then it's likely there
- are powerpoints or pdfs in there. If the path is /js/ then it's good to use js as an extension.
+Given the following URL and HTTP headers, suggest the most likely file extensions for fuzzing this endpoint.
+Respond with a JSON object containing a list of extensions. The response will be parsed with json.loads(),
+so it must be valid JSON. No preamble or yapping. Use the format: {{"extensions": [".ext1", ".ext2", ...]}}.
+Do not suggest more than {max_extensions}, but only suggest extensions that make sense. For example, if the path is
+/js/ then don't suggest .css as the extension. Also, if limited, prefer the extensions which are more interesting.
+The URL path is great to look at for ideas. For example, if it says presentations, then it's likely there
+are powerpoints or pdfs in there. If the path is /js/ then it's good to use js as an extension.
- Examples:
- 1. URL: https://example.com/presentations/FUZZ
- Headers: {{"Content-Type": "application/pdf", "Content-Length": "1234567"}}
- JSON Response: {{"extensions": [".pdf", ".ppt", ".pptx"]}}
+Examples:
+1. URL: https://example.com/presentations/FUZZ
+ Headers: {{"Content-Type": "application/pdf", "Content-Length": "1234567"}}
+ JSON Response: {{"extensions": [".pdf", ".ppt", ".pptx"]}}
- 2. URL: https://example.com/FUZZ
- Headers: {{"Server": "Microsoft-IIS/10.0", "X-Powered-By": "ASP.NET"}}
- JSON Response: {{"extensions": [".aspx", ".asp", ".exe", ".dll"]}}
+2. URL: https://example.com/FUZZ
+ Headers: {{"Server": "Microsoft-IIS/10.0", "X-Powered-By": "ASP.NET"}}
+ JSON Response: {{"extensions": [".aspx", ".asp", ".exe", ".dll"]}}
- URL: {url}
- Headers: {headers}
+URL: {url}
+Headers: {headers}
- JSON Response:
- """
+JSON Response:
+"""
if api_type == 'openai':
client = OpenAI(api_key=api_key)
@@ -61,7 +62,16 @@ def get_ai_extensions(url, headers, api_type, api_key, max_extensions):
{"role": "user", "content": prompt}
]
)
- return json.loads(response.choices[0].message.content.strip())
+ raw_content = response.choices[0].message.content.strip()
+ print("AI Raw Content:", raw_content)
+
+ # Remove Markdown code block if present
+ if raw_content.startswith("```"):
+ raw_content = re.sub(r"^```(?:json)?\n", "", raw_content)
+ raw_content = re.sub(r"\n```$", "", raw_content)
+
+ return json.loads(raw_content)
+
elif api_type == 'anthropic':
client = anthropic.Anthropic(api_key=api_key)
message = client.messages.create(
@@ -73,7 +83,14 @@ def get_ai_extensions(url, headers, api_type, api_key, max_extensions):
{"role": "user", "content": prompt}
]
)
- return json.loads(message.content[0].text)
+ raw_content = message.content[0].text.strip()
+ print("AI Raw Content:", raw_content)
+
+ if raw_content.startswith("```"):
+ raw_content = re.sub(r"^```(?:json)?\n", "", raw_content)
+ raw_content = re.sub(r"\n```$", "", raw_content)
+
+ return json.loads(raw_content)
def main():
parser = argparse.ArgumentParser(description='ffufai - AI-powered ffuf wrapper')
@@ -101,15 +118,24 @@ def main():
api_type, api_key = get_api_key()
try:
extensions_data = get_ai_extensions(url, headers, api_type, api_key, args.max_extensions)
- print(extensions_data)
+ print("Extensions JSON:", extensions_data)
extensions = ','.join(extensions_data['extensions'][:args.max_extensions])
except (json.JSONDecodeError, KeyError) as e:
- print(f"Error parsing AI response. Try again. Error: {e}")
+ print(f"❌ Error parsing AI response. Try again. Error: {e}")
return
ffuf_command = [args.ffuf_path] + unknown + ['-e', extensions]
- subprocess.run(ffuf_command)
+ # Check if ffuf binary exists
+ if not os.path.isfile(args.ffuf_path):
+ print(f"❌ Error: ffuf binary not found at {args.ffuf_path}")
+ return
+
+ try:
+ print(f"▶️ Running: {' '.join(ffuf_command)}")
+ subprocess.run(ffuf_command)
+ except Exception as e:
+ print(f"❌ Error running ffuf: {e}")
if __name__ == '__main__':
main()
From 1b0c77cd92ce42bdf59ec0089778e86a917699fa Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Tue, 29 Jul 2025 15:36:53 +0530
Subject: [PATCH 02/32] ffufai.py
---
ffufai.py | 83 +++++++++++++++++++++++++++++++++++++------------------
1 file changed, 56 insertions(+), 27 deletions(-)
diff --git a/ffufai.py b/ffufai.py
index 4c13e7e..bbed647 100755
--- a/ffufai.py
+++ b/ffufai.py
@@ -5,20 +5,23 @@
import subprocess
import requests
import json
-import re
from openai import OpenAI
import anthropic
from urllib.parse import urlparse
+import time
def get_api_key():
openai_key = os.getenv('OPENAI_API_KEY')
anthropic_key = os.getenv('ANTHROPIC_API_KEY')
+ hf_key = os.getenv('HUGGINGFACE_API_KEY')
if anthropic_key:
return ('anthropic', anthropic_key)
elif openai_key:
return ('openai', openai_key)
+ elif hf_key:
+ return ('huggingface', hf_key)
else:
- raise ValueError("No API key found. Please set OPENAI_API_KEY or ANTHROPIC_API_KEY.")
+ raise ValueError("No API key found. Please set OPENAI_API_KEY, ANTHROPIC_API_KEY, or HUGGINGFACE_API_KEY.")
def get_headers(url):
try:
@@ -33,19 +36,7 @@ def get_ai_extensions(url, headers, api_type, api_key, max_extensions):
Given the following URL and HTTP headers, suggest the most likely file extensions for fuzzing this endpoint.
Respond with a JSON object containing a list of extensions. The response will be parsed with json.loads(),
so it must be valid JSON. No preamble or yapping. Use the format: {{"extensions": [".ext1", ".ext2", ...]}}.
-Do not suggest more than {max_extensions}, but only suggest extensions that make sense. For example, if the path is
-/js/ then don't suggest .css as the extension. Also, if limited, prefer the extensions which are more interesting.
-The URL path is great to look at for ideas. For example, if it says presentations, then it's likely there
-are powerpoints or pdfs in there. If the path is /js/ then it's good to use js as an extension.
-
-Examples:
-1. URL: https://example.com/presentations/FUZZ
- Headers: {{"Content-Type": "application/pdf", "Content-Length": "1234567"}}
- JSON Response: {{"extensions": [".pdf", ".ppt", ".pptx"]}}
-
-2. URL: https://example.com/FUZZ
- Headers: {{"Server": "Microsoft-IIS/10.0", "X-Powered-By": "ASP.NET"}}
- JSON Response: {{"extensions": [".aspx", ".asp", ".exe", ".dll"]}}
+Do not suggest more than {max_extensions}, but only suggest extensions that make sense.
URL: {url}
Headers: {headers}
@@ -65,13 +56,6 @@ def get_ai_extensions(url, headers, api_type, api_key, max_extensions):
raw_content = response.choices[0].message.content.strip()
print("AI Raw Content:", raw_content)
- # Remove Markdown code block if present
- if raw_content.startswith("```"):
- raw_content = re.sub(r"^```(?:json)?\n", "", raw_content)
- raw_content = re.sub(r"\n```$", "", raw_content)
-
- return json.loads(raw_content)
-
elif api_type == 'anthropic':
client = anthropic.Anthropic(api_key=api_key)
message = client.messages.create(
@@ -86,11 +70,51 @@ def get_ai_extensions(url, headers, api_type, api_key, max_extensions):
raw_content = message.content[0].text.strip()
print("AI Raw Content:", raw_content)
- if raw_content.startswith("```"):
- raw_content = re.sub(r"^```(?:json)?\n", "", raw_content)
- raw_content = re.sub(r"\n```$", "", raw_content)
+ elif api_type == 'huggingface':
+ from transformers import AutoModelForCausalLM, AutoTokenizer
+ import torch
+
+ print("🧠 Loading Qwen model locally...")
+ model_name = "Qwen/Qwen2.5-1.5B-Instruct"
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
+ model = AutoModelForCausalLM.from_pretrained(
+ model_name,
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
+ device_map="auto"
+ )
+
+ # Create chat-style prompt
+ messages = [
+ {"role": "system", "content": "You are a helpful assistant that suggests file extensions for fuzzing based on URL and headers."},
+ {"role": "user", "content": prompt}
+ ]
+
+ text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
+ model_inputs = tokenizer([text], return_tensors="pt").to(model.device)
+
+ generated_ids = model.generate(
+ **model_inputs,
+ max_new_tokens=512,
+ do_sample=False,
+ temperature=0.5
+ )
+
+ generated_ids = [
+ output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
+ ]
+
+ raw_content = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
+ print("AI Raw Content:", raw_content)
+
+ else:
+ raise ValueError("Unsupported API type")
+
+ try:
return json.loads(raw_content)
+ except json.JSONDecodeError:
+ print("❌ Failed to parse AI output. Response:", raw_content)
+ raise
def main():
parser = argparse.ArgumentParser(description='ffufai - AI-powered ffuf wrapper')
@@ -110,7 +134,7 @@ def main():
path_parts = parsed_url.path.split('/')
if 'FUZZ' not in path_parts[-1]:
- print("Warning: FUZZ keyword is not at the end of the URL path. Extension fuzzing may not work as expected.")
+ print("⚠️ Warning: FUZZ keyword is not at the end of the URL path. Extension fuzzing may not work as expected.")
base_url = url.replace('FUZZ', '')
headers = get_headers(base_url)
@@ -119,14 +143,19 @@ def main():
try:
extensions_data = get_ai_extensions(url, headers, api_type, api_key, args.max_extensions)
print("Extensions JSON:", extensions_data)
+
+ if not extensions_data.get('extensions'):
+ print("⚠️ No extensions returned by AI. Using fallback list.")
+ extensions_data = {"extensions": [".php", ".html", ".txt", ".bak"]}
+
extensions = ','.join(extensions_data['extensions'][:args.max_extensions])
+
except (json.JSONDecodeError, KeyError) as e:
print(f"❌ Error parsing AI response. Try again. Error: {e}")
return
ffuf_command = [args.ffuf_path] + unknown + ['-e', extensions]
- # Check if ffuf binary exists
if not os.path.isfile(args.ffuf_path):
print(f"❌ Error: ffuf binary not found at {args.ffuf_path}")
return
From 7e7ad8c5b3974dfe8ad76eb235fc941d2e43efa0 Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Wed, 30 Jul 2025 06:09:40 +0530
Subject: [PATCH 03/32] ffufai.py
---
ffufai.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/ffufai.py b/ffufai.py
index bbed647..afa6056 100755
--- a/ffufai.py
+++ b/ffufai.py
@@ -148,7 +148,11 @@ def main():
print("⚠️ No extensions returned by AI. Using fallback list.")
extensions_data = {"extensions": [".php", ".html", ".txt", ".bak"]}
- extensions = ','.join(extensions_data['extensions'][:args.max_extensions])
+ # extensions = ','.join(extensions_data['extensions'][:args.max_extensions])
+
+ extensions_list = extensions_data['extensions'][:args.max_extensions]
+ # Ensure all entries start with a dot
+ extensions = ','.join(ext if ext.startswith('.') else f'.{ext}' for ext in extensions_list)
except (json.JSONDecodeError, KeyError) as e:
print(f"❌ Error parsing AI response. Try again. Error: {e}")
From 2bc6438ebe98ce00b69bfa28106a54ee977bd182 Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Wed, 30 Jul 2025 08:40:36 +0530
Subject: [PATCH 04/32] ffufai.py
---
ffufai.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ffufai.py b/ffufai.py
index afa6056..9b32de3 100755
--- a/ffufai.py
+++ b/ffufai.py
@@ -74,7 +74,7 @@ def get_ai_extensions(url, headers, api_type, api_key, max_extensions):
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
- print("🧠 Loading Qwen model locally...")
+ print(" メ૦メ૦💋 Loading Qwen model locally...")
model_name = "Qwen/Qwen2.5-1.5B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
From 9baf7027f1c98b6eee54d57c72a59edece811f4c Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Wed, 30 Jul 2025 13:19:20 +0530
Subject: [PATCH 05/32] README.md
---
README.md | 8 --------
1 file changed, 8 deletions(-)
diff --git a/README.md b/README.md
index 6a2f850..5607763 100644
--- a/README.md
+++ b/README.md
@@ -106,14 +106,6 @@ All other ffuf parameters can be used as normal. For a full list of ffuf paramet
- All ffuf parameters are passed through to ffuf, so you can use any ffuf option with ffufai.
- If both OpenAI and Anthropic API keys are set, ffufai will prefer the OpenAI key.
-HUGE Shoutout to zlz, aka Sam Curry, for the amazing idea to make this project. He suggested it and 2 hours later, here it is :)
-
-
-## Troubleshooting
-
-- If you encounter a "command not found" error, make sure you're using `python3 ffufai.py` or that you've correctly set up the symbolic link.
-- If you get an API key error, ensure you've correctly set up your OPENAI_API_KEY or ANTHROPIC_API_KEY environment variable.
-- If you see "import: command not found" errors, it means the script is being interpreted by the shell instead of Python. Make sure you're running it with `python3 ffufai.py` or that the shebang line at the top of the script is correct.
## Contributing
From b1fad8ebd7db75199cacb5a7d6d4b887da9c782d Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Wed, 30 Jul 2025 13:23:13 +0530
Subject: [PATCH 06/32] README.md
---
README.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 5607763..0b90cf9 100644
--- a/README.md
+++ b/README.md
@@ -63,7 +63,11 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
```
export ANTHROPIC_API_KEY='your-api-key-here'
```
-
+ or Hugging Face
+ ```
+ export HUGGINGFACE_API_KEY=your-api-key-here ## no ''
+ ```
+
You can add these lines to your `~/.bashrc` or `~/.zshrc` file to make them permanent.
## Usage
From fb37fc7a9cc0a13ff0dfb1d9eeb117320f12a1c3 Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Wed, 30 Jul 2025 13:24:36 +0530
Subject: [PATCH 07/32] README.md
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 0b90cf9..a159910 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,8 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
```
Replace "/full/path/to/ffufai.py" with the actual full path to where you cloned the repository.
-5. Set up your API key as an environment variable:
+5. Set up your API key as an environment variable :
+
For OpenAI:
```
export OPENAI_API_KEY='your-api-key-here'
From 3f3bed76bfd73b54ca985407fe8d69c4b9ffcc16 Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Wed, 30 Jul 2025 13:25:45 +0530
Subject: [PATCH 08/32] README.md
---
README.md | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
index a159910..ef7ab9d 100644
--- a/README.md
+++ b/README.md
@@ -32,23 +32,23 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
## Installation
-1. Clone this repository:
+1. Clone this repository :
```
git clone https://github.com/jthack/ffufai
cd ffufai
```
-2. Install the required Python packages:
+2. Install the required Python packages :
```
pip install requests openai anthropic
```
-3. Make the script executable:
+3. Make the script executable :
```
chmod +x ffufai.py
```
-4. (Optional) To use ffufai from anywhere, you can create a symbolic link in a directory that's in your PATH. For example:
+4. (Optional) To use ffufai from anywhere, you can create a symbolic link in a directory that's in your PATH. For example :
```
sudo ln -s /full/path/to/ffufai.py /usr/local/bin/ffufai
```
@@ -56,15 +56,15 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
5. Set up your API key as an environment variable :
- For OpenAI:
+ For OpenAI :
```
export OPENAI_API_KEY='your-api-key-here'
```
- Or for Anthropic:
+ Or for Anthropic :
```
export ANTHROPIC_API_KEY='your-api-key-here'
```
- or Hugging Face
+ or Hugging Face :
```
export HUGGINGFACE_API_KEY=your-api-key-here ## no ''
```
@@ -73,13 +73,13 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
## Usage
-Use ffufai just like you would use ffuf, but replace `ffuf` with `python3 ffufai.py` (or just `ffufai` if you've created the symbolic link):
+Use ffufai just like you would use ffuf, but replace `ffuf` with `python3 ffufai.py` (or just `ffufai` if you've created the symbolic link) :
```
python3 ffufai.py -u https://example.com/FUZZ -w /path/to/wordlist.txt
```
-Or if you've created the symbolic link:
+Or if you've created the symbolic link :
```
ffufai -u https://example.com/FUZZ -w /path/to/wordlist.txt
From 66f671e349a5a1dec970d1ca5822d7caee22fdc9 Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Wed, 30 Jul 2025 13:27:24 +0530
Subject: [PATCH 09/32] README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index ef7ab9d..b83b83b 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
-ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automatically suggests file extensions for fuzzing based on the target URL and its headers, using either OpenAI's GPT or Anthropic's Claude AI models.
+ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automatically suggests file extensions for fuzzing based on the target URL and its headers, using either OpenAI's GPT or Anthropic's Claude AI models and Free Hugging Face models on local.
From 25cd89d5ef5bdae318dc7dd1c0fb3ad260d2eb77 Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Wed, 30 Jul 2025 13:28:17 +0530
Subject: [PATCH 10/32] README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index b83b83b..b8fe448 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
- Python 3.6+
- ffuf (installed and accessible in your PATH)
-- An OpenAI API key or Anthropic API key
+- An OpenAI API key or Anthropic API key or Hugging Face API key
## Installation
From fd9c3e2721a7cddc129b6b55a617fcfc59a46beb Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Wed, 30 Jul 2025 13:28:43 +0530
Subject: [PATCH 11/32] README.md
---
README.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/README.md b/README.md
index b8fe448..d604511 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,6 @@
# `ffufai`

-
[](https://opensource.org/licenses/MIT)
From 17f6492eb84b0a4c38e1a2d071f777c336c8c77d Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Wed, 30 Jul 2025 13:29:43 +0530
Subject: [PATCH 12/32] README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index d604511..d291e63 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
- Seamlessly integrates with ffuf
- Automatically suggests relevant file extensions for fuzzing
-- Supports both OpenAI and Anthropic AI models
+- Supports both OpenAI, Anthropic AI models and Free Hugging Face AI models
- Passes through all ffuf parameters
## Prerequisites
From 77c67a67944755a45187e6fc968b1cd705cd8b2b Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Wed, 30 Jul 2025 13:30:31 +0530
Subject: [PATCH 13/32] README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index d291e63..6dba38f 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
1. Clone this repository :
```
- git clone https://github.com/jthack/ffufai
+ git clone [https://github.com/jthack/ffufai](https://github.com/ikajakam/ffufai.git)
cd ffufai
```
From ea192b3b6d523843125f82de9dd4174c8af0c6c0 Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Wed, 30 Jul 2025 13:30:53 +0530
Subject: [PATCH 14/32] README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 6dba38f..1fc8b67 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
1. Clone this repository :
```
- git clone [https://github.com/jthack/ffufai](https://github.com/ikajakam/ffufai.git)
+ git clone https://github.com/ikajakam/ffufai.git
cd ffufai
```
From 8ba9b23f2c888b86db7a351aeb6534c6fea2ce26 Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Wed, 30 Jul 2025 13:31:23 +0530
Subject: [PATCH 15/32] README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 1fc8b67..0196764 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
- Python 3.6+
- ffuf (installed and accessible in your PATH)
-- An OpenAI API key or Anthropic API key or Hugging Face API key
+- An OpenAI API key or Anthropic API key or Free Hugging Face API key
## Installation
From 7b4936f21830d0bff8b92db8017a488935c328a0 Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Wed, 30 Jul 2025 13:51:38 +0530
Subject: [PATCH 16/32] README.md
---
README.md | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 0196764..f0c0124 100644
--- a/README.md
+++ b/README.md
@@ -47,19 +47,25 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
chmod +x ffufai.py
```
-4. (Optional) To use ffufai from anywhere, you can create a symbolic link in a directory that's in your PATH. For example :
+4. To load and run `Qwen/Qwen2.5-1.5B-Instruct` model locally via Hugging Face :
+ ```
+ pip install transformers torch
+ ```
+- Above command will download model, models are cached in `~/.cache/huggingface`.
+
+5. To use ffufai globally from terminal, you can create a symbolic link in a directory that's in your PATH. For example :
```
sudo ln -s /full/path/to/ffufai.py /usr/local/bin/ffufai
```
Replace "/full/path/to/ffufai.py" with the actual full path to where you cloned the repository.
-5. Set up your API key as an environment variable :
+6. Set up your API key as an environment variable :
For OpenAI :
```
export OPENAI_API_KEY='your-api-key-here'
```
- Or for Anthropic :
+ or Anthropic :
```
export ANTHROPIC_API_KEY='your-api-key-here'
```
@@ -68,7 +74,7 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
export HUGGINGFACE_API_KEY=your-api-key-here ## no ''
```
- You can add these lines to your `~/.bashrc` or `~/.zshrc` file to make them permanent.
+ You can add these lines to your `~/.bashrc` or `~/.zshrc` file to make them permanent and reload your `~/.bashrc` or `~/.zshrc`
## Usage
@@ -86,6 +92,7 @@ ffufai -u https://example.com/FUZZ -w /path/to/wordlist.txt
ffufai will automatically suggest extensions based on the URL and add them to the ffuf command.
+
## Parameters
ffufai accepts all the parameters that ffuf does, plus a few additional ones:
@@ -108,7 +115,7 @@ All other ffuf parameters can be used as normal. For a full list of ffuf paramet
- ffufai requires the FUZZ keyword to be at the end of the URL path for accurate extension suggestion. It will warn you if this is not the case.
- All ffuf parameters are passed through to ffuf, so you can use any ffuf option with ffufai.
-- If both OpenAI and Anthropic API keys are set, ffufai will prefer the OpenAI key.
+
## Contributing
From 83848afbb6745ac97a63883ad03d53d0d468f433 Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Wed, 30 Jul 2025 13:53:54 +0530
Subject: [PATCH 17/32] README.md
---
README.md | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index f0c0124..75e948f 100644
--- a/README.md
+++ b/README.md
@@ -42,18 +42,13 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
pip install requests openai anthropic
```
-3. Make the script executable :
- ```
- chmod +x ffufai.py
- ```
-
-4. To load and run `Qwen/Qwen2.5-1.5B-Instruct` model locally via Hugging Face :
+3. To load and run `Qwen/Qwen2.5-1.5B-Instruct` model locally via Hugging Face :
```
pip install transformers torch
```
- Above command will download model, models are cached in `~/.cache/huggingface`.
-5. To use ffufai globally from terminal, you can create a symbolic link in a directory that's in your PATH. For example :
+4. To use ffufai globally from terminal, you can create a symbolic link in a directory that's in your PATH. For example :
```
sudo ln -s /full/path/to/ffufai.py /usr/local/bin/ffufai
```
From ccc68a7a5135170dc9515c85822f047000fa0817 Mon Sep 17 00:00:00 2001
From: Dhaval D
Date: Wed, 30 Jul 2025 15:47:35 +0530
Subject: [PATCH 18/32] update
---
ffufai.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/ffufai.py b/ffufai.py
index 9b32de3..ec17cc7 100755
--- a/ffufai.py
+++ b/ffufai.py
@@ -172,3 +172,5 @@ def main():
if __name__ == '__main__':
main()
+
+# 30/07/2025
\ No newline at end of file
From 8491645a8774aa695ae298245ac0c1e704c7bba4 Mon Sep 17 00:00:00 2001
From: Dhaval D
Date: Wed, 30 Jul 2025 15:50:16 +0530
Subject: [PATCH 19/32] update
---
ffufai.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ffufai.py b/ffufai.py
index ec17cc7..9831f0a 100755
--- a/ffufai.py
+++ b/ffufai.py
@@ -119,7 +119,7 @@ def get_ai_extensions(url, headers, api_type, api_key, max_extensions):
def main():
parser = argparse.ArgumentParser(description='ffufai - AI-powered ffuf wrapper')
parser.add_argument('--ffuf-path', default='ffuf', help='Path to ffuf executable')
- parser.add_argument('--max-extensions', type=int, default=4, help='Maximum number of extensions to suggest')
+ parser.add_argument('--max-extensions', type=int, default=5, help='Maximum number of extensions to suggest')
args, unknown = parser.parse_known_args()
# Find the -u argument in the unknown args
From 04255d95afdb3f687c05c159f880101412c64384 Mon Sep 17 00:00:00 2001
From: Dhaval D
Date: Wed, 30 Jul 2025 15:56:41 +0530
Subject: [PATCH 20/32] update
---
ffufai.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ffufai.py b/ffufai.py
index 9831f0a..9a74045 100755
--- a/ffufai.py
+++ b/ffufai.py
@@ -74,7 +74,7 @@ def get_ai_extensions(url, headers, api_type, api_key, max_extensions):
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
- print(" メ૦メ૦💋 Loading Qwen model locally...")
+ print(" メ૦メ૦ 💋💋💋 Loading Qwen model locally...")
model_name = "Qwen/Qwen2.5-1.5B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
From 2f22b090dd5ca3dcb555367f19ae42d819dcb25c Mon Sep 17 00:00:00 2001
From: Dhaval D
Date: Wed, 30 Jul 2025 16:12:07 +0530
Subject: [PATCH 21/32] update
---
ffufai.py | 42 +++++++++++++++++++++++++++++++-----------
1 file changed, 31 insertions(+), 11 deletions(-)
diff --git a/ffufai.py b/ffufai.py
index 9a74045..39fbada 100755
--- a/ffufai.py
+++ b/ffufai.py
@@ -5,10 +5,11 @@
import subprocess
import requests
import json
+import shutil
from openai import OpenAI
import anthropic
from urllib.parse import urlparse
-import time
+import re
def get_api_key():
openai_key = os.getenv('OPENAI_API_KEY')
@@ -74,7 +75,7 @@ def get_ai_extensions(url, headers, api_type, api_key, max_extensions):
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
- print(" メ૦メ૦ 💋💋💋 Loading Qwen model locally...")
+ print(" メ૦メ૦ 💋💋💋 メ૦メ૦ Loading Qwen model locally...")
model_name = "Qwen/Qwen2.5-1.5B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
@@ -84,7 +85,6 @@ def get_ai_extensions(url, headers, api_type, api_key, max_extensions):
device_map="auto"
)
- # Create chat-style prompt
messages = [
{"role": "system", "content": "You are a helpful assistant that suggests file extensions for fuzzing based on URL and headers."},
{"role": "user", "content": prompt}
@@ -116,13 +116,36 @@ def get_ai_extensions(url, headers, api_type, api_key, max_extensions):
print("❌ Failed to parse AI output. Response:", raw_content)
raise
+def colorize_ffuf_output(line):
+ line = line.decode(errors='ignore').strip()
+ status_match = re.search(r'\[Status: (\d{3})', line)
+ if status_match:
+ status_code = int(status_match.group(1))
+ if status_code == 200:
+ color = '\033[92m' # Green
+ elif status_code == 301:
+ color = '\033[94m' # Blue
+ elif status_code == 403:
+ color = '\033[91m' # Red
+ else:
+ color = '\033[0m' # Default
+ reset = '\033[0m'
+ print(f"{color}{line}{reset}")
+ else:
+ # Optionally parse ffuf's built-in progress:
+ progress_match = re.search(r'Progress: \[(\d+)/(\d+)\]', line)
+ if progress_match:
+ current, total = progress_match.groups()
+ print(f"\033[93m🔄 Progress: {current}/{total} requests\033[0m")
+ else:
+ print(line)
+
def main():
parser = argparse.ArgumentParser(description='ffufai - AI-powered ffuf wrapper')
parser.add_argument('--ffuf-path', default='ffuf', help='Path to ffuf executable')
parser.add_argument('--max-extensions', type=int, default=5, help='Maximum number of extensions to suggest')
args, unknown = parser.parse_known_args()
- # Find the -u argument in the unknown args
try:
url_index = unknown.index('-u') + 1
url = unknown[url_index]
@@ -148,10 +171,7 @@ def main():
print("⚠️ No extensions returned by AI. Using fallback list.")
extensions_data = {"extensions": [".php", ".html", ".txt", ".bak"]}
- # extensions = ','.join(extensions_data['extensions'][:args.max_extensions])
-
extensions_list = extensions_data['extensions'][:args.max_extensions]
- # Ensure all entries start with a dot
extensions = ','.join(ext if ext.startswith('.') else f'.{ext}' for ext in extensions_list)
except (json.JSONDecodeError, KeyError) as e:
@@ -160,17 +180,17 @@ def main():
ffuf_command = [args.ffuf_path] + unknown + ['-e', extensions]
- if not os.path.isfile(args.ffuf_path):
+ if not os.path.isfile(args.ffuf_path) and not shutil.which(args.ffuf_path):
print(f"❌ Error: ffuf binary not found at {args.ffuf_path}")
return
try:
print(f"▶️ Running: {' '.join(ffuf_command)}")
- subprocess.run(ffuf_command)
+ with subprocess.Popen(ffuf_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as proc:
+ for line in proc.stdout:
+ colorize_ffuf_output(line)
except Exception as e:
print(f"❌ Error running ffuf: {e}")
if __name__ == '__main__':
main()
-
-# 30/07/2025
\ No newline at end of file
From 3c79e6fc67b4bb47c0e55194ed6853b4089aaefe Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Thu, 31 Jul 2025 06:53:05 +0530
Subject: [PATCH 22/32] README.md
---
README.md | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 75e948f..4661398 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+sudo apt install python3-poetry
+
+pip install accelerate
+

@@ -44,7 +48,10 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
3. To load and run `Qwen/Qwen2.5-1.5B-Instruct` model locally via Hugging Face :
```
- pip install transformers torch
+ pip install transformers torch accelerate
+ ```
+ ```
+ poetry add accelerate
```
- Above command will download model, models are cached in `~/.cache/huggingface`.
@@ -120,3 +127,4 @@ Contributions are welcome! Please feel free to submit a Pull Request.
## License
This project is licensed under the MIT License - see the LICENSE file for details.
+
From 319d5524bcc4fdd0b4191f53a9b0fd3665183eb2 Mon Sep 17 00:00:00 2001
From: Dhaval D
Date: Thu, 31 Jul 2025 06:54:09 +0530
Subject: [PATCH 23/32] update
---
README.md | 4 ----
1 file changed, 4 deletions(-)
diff --git a/README.md b/README.md
index 4661398..84b89f9 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,3 @@
-sudo apt install python3-poetry
-
-pip install accelerate
-

From 9f7c9cbd6964072f9e0c07ace849bac30b9c77e7 Mon Sep 17 00:00:00 2001
From: Dhaval D
Date: Tue, 5 Aug 2025 21:12:49 +0530
Subject: [PATCH 24/32] update
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 84b89f9..46aeae3 100644
--- a/README.md
+++ b/README.md
@@ -49,7 +49,7 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
```
poetry add accelerate
```
-- Above command will download model, models are cached in `~/.cache/huggingface`.
+- Models are cached in `~/.cache/huggingface`
4. To use ffufai globally from terminal, you can create a symbolic link in a directory that's in your PATH. For example :
```
From f6aaf6d8b3a5a2223201cc57fa332f02858bf12a Mon Sep 17 00:00:00 2001
From: Dhaval D
Date: Tue, 5 Aug 2025 21:13:37 +0530
Subject: [PATCH 25/32] update
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 46aeae3..1c9a25b 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
```
- Models are cached in `~/.cache/huggingface`
-4. To use ffufai globally from terminal, you can create a symbolic link in a directory that's in your PATH. For example :
+4. To use ffufai globally from terminal, create a symbolic link in a directory that's in your PATH.
```
sudo ln -s /full/path/to/ffufai.py /usr/local/bin/ffufai
```
From 4e4cf2bd869a7a133d8d2f2631c215bdf1691a73 Mon Sep 17 00:00:00 2001
From: Dhaval D
Date: Fri, 9 Jan 2026 23:31:53 +0530
Subject: [PATCH 26/32] update
---
ffufai.py | 249 +++++++++++++++++++++++++++++++-----------------------
1 file changed, 141 insertions(+), 108 deletions(-)
diff --git a/ffufai.py b/ffufai.py
index 39fbada..e447d91 100755
--- a/ffufai.py
+++ b/ffufai.py
@@ -6,191 +6,224 @@
import requests
import json
import shutil
+import re
+from urllib.parse import urlparse
+
from openai import OpenAI
import anthropic
-from urllib.parse import urlparse
-import re
+# -------------------------
+# API KEY SELECTION
+# -------------------------
def get_api_key():
- openai_key = os.getenv('OPENAI_API_KEY')
- anthropic_key = os.getenv('ANTHROPIC_API_KEY')
- hf_key = os.getenv('HUGGINGFACE_API_KEY')
- if anthropic_key:
- return ('anthropic', anthropic_key)
- elif openai_key:
- return ('openai', openai_key)
- elif hf_key:
- return ('huggingface', hf_key)
+ if os.getenv("GEMINI_API_KEY"):
+ return ("gemini", os.getenv("GEMINI_API_KEY"))
+ elif os.getenv("ANTHROPIC_API_KEY"):
+ return ("anthropic", os.getenv("ANTHROPIC_API_KEY"))
+ elif os.getenv("OPENAI_API_KEY"):
+ return ("openai", os.getenv("OPENAI_API_KEY"))
+ elif os.getenv("HUGGINGFACE_API_KEY"):
+ return ("huggingface", os.getenv("HUGGINGFACE_API_KEY"))
else:
- raise ValueError("No API key found. Please set OPENAI_API_KEY, ANTHROPIC_API_KEY, or HUGGINGFACE_API_KEY.")
+ raise ValueError(
+ "No API key found. Set one of: GEMINI_API_KEY, ANTHROPIC_API_KEY, "
+ "OPENAI_API_KEY, or HUGGINGFACE_API_KEY."
+ )
+# -------------------------
+# HEADER FETCH
+# -------------------------
def get_headers(url):
try:
response = requests.head(url, allow_redirects=True, timeout=5)
return dict(response.headers)
except requests.RequestException as e:
print(f"Error fetching headers: {e}")
- return {"Header": "Error fetching headers."}
+ return {}
+# -------------------------
+# AI EXTENSION GENERATION
+# -------------------------
def get_ai_extensions(url, headers, api_type, api_key, max_extensions):
prompt = f"""
-Given the following URL and HTTP headers, suggest the most likely file extensions for fuzzing this endpoint.
-Respond with a JSON object containing a list of extensions. The response will be parsed with json.loads(),
-so it must be valid JSON. No preamble or yapping. Use the format: {{"extensions": [".ext1", ".ext2", ...]}}.
-Do not suggest more than {max_extensions}, but only suggest extensions that make sense.
+Given the following URL and HTTP headers, suggest the most likely file extensions
+for fuzzing this endpoint.
+
+Respond with valid JSON only, no commentary.
+Format:
+{{"extensions": [".php", ".json", ".bak"]}}
+
+Limit to at most {max_extensions} extensions.
URL: {url}
Headers: {headers}
-
-JSON Response:
"""
- if api_type == 'openai':
+ system_msg = (
+ "You are a security assistant helping with web fuzzing. "
+ "Return only valid JSON. No prose."
+ )
+
+ # ---------- OpenAI ----------
+ if api_type == "openai":
client = OpenAI(api_key=api_key)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
- {"role": "system", "content": "You are a helpful assistant that suggests file extensions for fuzzing based on URL and headers."},
- {"role": "user", "content": prompt}
- ]
+ {"role": "system", "content": system_msg},
+ {"role": "user", "content": prompt},
+ ],
+ temperature=0,
)
- raw_content = response.choices[0].message.content.strip()
- print("AI Raw Content:", raw_content)
+ raw = response.choices[0].message.content.strip()
- elif api_type == 'anthropic':
+ # ---------- Anthropic ----------
+ elif api_type == "anthropic":
client = anthropic.Anthropic(api_key=api_key)
- message = client.messages.create(
+ msg = client.messages.create(
model="claude-3-5-sonnet-20240620",
- max_tokens=1000,
+ max_tokens=500,
temperature=0,
- system="You are a helpful assistant that suggests file extensions for fuzzing based on URL and headers.",
- messages=[
- {"role": "user", "content": prompt}
- ]
+ system=system_msg,
+ messages=[{"role": "user", "content": prompt}],
+ )
+ raw = msg.content[0].text.strip()
+
+ # ---------- Gemini ----------
+ elif api_type == "gemini":
+ import google.generativeai as genai
+
+ genai.configure(api_key=api_key)
+ model = genai.GenerativeModel(
+ "gemini-1.5-pro",
+ generation_config={
+ "temperature": 0,
+ "response_mime_type": "application/json",
+ },
+ )
+
+ resp = model.generate_content(
+ f"SYSTEM:\n{system_msg}\n\nUSER:\n{prompt}"
)
- raw_content = message.content[0].text.strip()
- print("AI Raw Content:", raw_content)
+ raw = resp.text.strip()
- elif api_type == 'huggingface':
+ # ---------- Local HuggingFace (Qwen) ----------
+ elif api_type == "huggingface":
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
- print(" メ૦メ૦ 💋💋💋 メ૦メ૦ Loading Qwen model locally...")
+ print("🤖 Loading local Qwen model…")
model_name = "Qwen/Qwen2.5-1.5B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
- device_map="auto"
+ device_map="auto",
)
messages = [
- {"role": "system", "content": "You are a helpful assistant that suggests file extensions for fuzzing based on URL and headers."},
- {"role": "user", "content": prompt}
+ {"role": "system", "content": system_msg},
+ {"role": "user", "content": prompt},
]
- text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
- model_inputs = tokenizer([text], return_tensors="pt").to(model.device)
+ text = tokenizer.apply_chat_template(
+ messages, tokenize=False, add_generation_prompt=True
+ )
+ inputs = tokenizer([text], return_tensors="pt").to(model.device)
- generated_ids = model.generate(
- **model_inputs,
- max_new_tokens=512,
+ output = model.generate(
+ **inputs,
+ max_new_tokens=300,
do_sample=False,
- temperature=0.5
)
- generated_ids = [
- output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
- ]
-
- raw_content = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
- print("AI Raw Content:", raw_content)
+ output = output[:, inputs["input_ids"].shape[1]:]
+ raw = tokenizer.decode(output[0], skip_special_tokens=True).strip()
else:
raise ValueError("Unsupported API type")
try:
- return json.loads(raw_content)
+ return json.loads(raw)
except json.JSONDecodeError:
- print("❌ Failed to parse AI output. Response:", raw_content)
+ print("❌ AI returned invalid JSON:")
+ print(raw)
raise
+# -------------------------
+# FFUF OUTPUT COLORIZER
+# -------------------------
def colorize_ffuf_output(line):
- line = line.decode(errors='ignore').strip()
- status_match = re.search(r'\[Status: (\d{3})', line)
- if status_match:
- status_code = int(status_match.group(1))
- if status_code == 200:
- color = '\033[92m' # Green
- elif status_code == 301:
- color = '\033[94m' # Blue
- elif status_code == 403:
- color = '\033[91m' # Red
+ line = line.decode(errors="ignore").strip()
+ m = re.search(r"\[Status: (\d{3})", line)
+ if m:
+ code = int(m.group(1))
+ if code == 200:
+ color = "\033[92m"
+ elif code in (301, 302):
+ color = "\033[94m"
+ elif code == 403:
+ color = "\033[91m"
else:
- color = '\033[0m' # Default
- reset = '\033[0m'
- print(f"{color}{line}{reset}")
+ color = "\033[0m"
+ print(f"{color}{line}\033[0m")
else:
- # Optionally parse ffuf's built-in progress:
- progress_match = re.search(r'Progress: \[(\d+)/(\d+)\]', line)
- if progress_match:
- current, total = progress_match.groups()
- print(f"\033[93m🔄 Progress: {current}/{total} requests\033[0m")
- else:
- print(line)
+ print(line)
+# -------------------------
+# MAIN
+# -------------------------
def main():
- parser = argparse.ArgumentParser(description='ffufai - AI-powered ffuf wrapper')
- parser.add_argument('--ffuf-path', default='ffuf', help='Path to ffuf executable')
- parser.add_argument('--max-extensions', type=int, default=5, help='Maximum number of extensions to suggest')
+ parser = argparse.ArgumentParser(
+ description="ffufai – AI-powered ffuf wrapper (OpenAI / Claude / Gemini / Local)"
+ )
+ parser.add_argument("--ffuf-path", default="ffuf")
+ parser.add_argument("--max-extensions", type=int, default=5)
args, unknown = parser.parse_known_args()
try:
- url_index = unknown.index('-u') + 1
- url = unknown[url_index]
+ url = unknown[unknown.index("-u") + 1]
except (ValueError, IndexError):
- print("Error: -u URL argument is required.")
+ print("❌ You must provide -u URL")
return
- parsed_url = urlparse(url)
- path_parts = parsed_url.path.split('/')
-
- if 'FUZZ' not in path_parts[-1]:
- print("⚠️ Warning: FUZZ keyword is not at the end of the URL path. Extension fuzzing may not work as expected.")
+ parsed = urlparse(url)
+ if "FUZZ" not in parsed.path:
+ print("⚠️ FUZZ keyword not found in URL path")
- base_url = url.replace('FUZZ', '')
+ base_url = url.replace("FUZZ", "")
headers = get_headers(base_url)
api_type, api_key = get_api_key()
- try:
- extensions_data = get_ai_extensions(url, headers, api_type, api_key, args.max_extensions)
- print("Extensions JSON:", extensions_data)
+ print(f"🔌 Using AI backend: {api_type}")
- if not extensions_data.get('extensions'):
- print("⚠️ No extensions returned by AI. Using fallback list.")
- extensions_data = {"extensions": [".php", ".html", ".txt", ".bak"]}
+ try:
+ ext_data = get_ai_extensions(
+ url, headers, api_type, api_key, args.max_extensions
+ )
+ exts = ext_data.get("extensions", [])
+ if not exts:
+ raise ValueError("Empty extension list")
+ except Exception as e:
+ print(f"⚠️ AI failed, using fallback extensions: {e}")
+ exts = [".php", ".html", ".json", ".bak"]
- extensions_list = extensions_data['extensions'][:args.max_extensions]
- extensions = ','.join(ext if ext.startswith('.') else f'.{ext}' for ext in extensions_list)
+ exts = exts[: args.max_extensions]
+ exts = ",".join(e if e.startswith(".") else f".{e}" for e in exts)
- except (json.JSONDecodeError, KeyError) as e:
- print(f"❌ Error parsing AI response. Try again. Error: {e}")
- return
+ ffuf_cmd = [args.ffuf_path] + unknown + ["-e", exts]
- ffuf_command = [args.ffuf_path] + unknown + ['-e', extensions]
-
- if not os.path.isfile(args.ffuf_path) and not shutil.which(args.ffuf_path):
- print(f"❌ Error: ffuf binary not found at {args.ffuf_path}")
+ if not shutil.which(args.ffuf_path):
+ print("❌ ffuf binary not found")
return
- try:
- print(f"▶️ Running: {' '.join(ffuf_command)}")
- with subprocess.Popen(ffuf_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as proc:
- for line in proc.stdout:
- colorize_ffuf_output(line)
- except Exception as e:
- print(f"❌ Error running ffuf: {e}")
+ print("▶️ Running:", " ".join(ffuf_cmd))
+ with subprocess.Popen(
+ ffuf_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
+ ) as proc:
+ for line in proc.stdout:
+ colorize_ffuf_output(line)
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
From 66f66ccef695ebd808b153bcc9a07312c88a951f Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Fri, 9 Jan 2026 23:35:59 +0530
Subject: [PATCH 27/32] added Gemini API README.md
Updated API key requirements and added Gemini API key instructions.
---
README.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 1c9a25b..2ff6f13 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
- Python 3.6+
- ffuf (installed and accessible in your PATH)
-- An OpenAI API key or Anthropic API key or Free Hugging Face API key
+- An OpenAI API key | Anthropic API key | Gemini API key | Free Hugging Face API key
## Installation
@@ -67,6 +67,10 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
```
export ANTHROPIC_API_KEY='your-api-key-here'
```
+ For Gemini :
+ ```
+ export GEMINI_API_KEY="your-api-key-here"
+ ```
or Hugging Face :
```
export HUGGINGFACE_API_KEY=your-api-key-here ## no ''
From 80c390ee42690bf022b357bda2a0971856c0a8a1 Mon Sep 17 00:00:00 2001
From: ikajakam <158202994+ikajakam@users.noreply.github.com>
Date: Fri, 9 Jan 2026 23:37:34 +0530
Subject: [PATCH 28/32] Update README.md
---
README.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 2ff6f13..b976032 100644
--- a/README.md
+++ b/README.md
@@ -59,19 +59,19 @@ ffufai is an AI-powered wrapper for the popular web fuzzer ffuf. It automaticall
6. Set up your API key as an environment variable :
- For OpenAI :
+ OpenAI :
```
export OPENAI_API_KEY='your-api-key-here'
```
- or Anthropic :
+ Anthropic :
```
export ANTHROPIC_API_KEY='your-api-key-here'
```
- For Gemini :
+ Gemini :
```
export GEMINI_API_KEY="your-api-key-here"
```
- or Hugging Face :
+ Hugging Face :
```
export HUGGINGFACE_API_KEY=your-api-key-here ## no ''
```
From d7b7e486f82b5098f5a9b259f3132e0531694af4 Mon Sep 17 00:00:00 2001
From: Dhaval D
Date: Fri, 9 Jan 2026 23:56:05 +0530
Subject: [PATCH 29/32] update
---
ffufai.py | 211 +++++++--------------------------
providers/__init__.py | 4 +
providers/anthropic.py | 41 +++++++
providers/base.py | 7 ++
providers/gemini.py | 44 +++++++
providers/huggingface_local.py | 60 ++++++++++
providers/openai.py | 42 +++++++
7 files changed, 238 insertions(+), 171 deletions(-)
create mode 100644 providers/__init__.py
create mode 100644 providers/anthropic.py
create mode 100644 providers/base.py
create mode 100644 providers/gemini.py
create mode 100644 providers/huggingface_local.py
create mode 100644 providers/openai.py
diff --git a/ffufai.py b/ffufai.py
index e447d91..e123c3f 100755
--- a/ffufai.py
+++ b/ffufai.py
@@ -9,174 +9,52 @@
import re
from urllib.parse import urlparse
-from openai import OpenAI
-import anthropic
+from providers.gemini import GeminiProvider
+from providers.openai import OpenAIProvider
+from providers.anthropic import AnthropicProvider
+from providers.huggingface_local import HuggingFaceLocalProvider
-# -------------------------
-# API KEY SELECTION
-# -------------------------
-def get_api_key():
+
+def get_provider():
if os.getenv("GEMINI_API_KEY"):
- return ("gemini", os.getenv("GEMINI_API_KEY"))
- elif os.getenv("ANTHROPIC_API_KEY"):
- return ("anthropic", os.getenv("ANTHROPIC_API_KEY"))
- elif os.getenv("OPENAI_API_KEY"):
- return ("openai", os.getenv("OPENAI_API_KEY"))
- elif os.getenv("HUGGINGFACE_API_KEY"):
- return ("huggingface", os.getenv("HUGGINGFACE_API_KEY"))
- else:
- raise ValueError(
- "No API key found. Set one of: GEMINI_API_KEY, ANTHROPIC_API_KEY, "
- "OPENAI_API_KEY, or HUGGINGFACE_API_KEY."
- )
+ return GeminiProvider(os.getenv("GEMINI_API_KEY"))
+ if os.getenv("ANTHROPIC_API_KEY"):
+ return AnthropicProvider(os.getenv("ANTHROPIC_API_KEY"))
+ if os.getenv("OPENAI_API_KEY"):
+ return OpenAIProvider(os.getenv("OPENAI_API_KEY"))
+ if os.getenv("HUGGINGFACE_API_KEY"):
+ return HuggingFaceLocalProvider()
+
+ raise RuntimeError("No AI provider API key found")
+
-# -------------------------
-# HEADER FETCH
-# -------------------------
def get_headers(url):
try:
- response = requests.head(url, allow_redirects=True, timeout=5)
- return dict(response.headers)
- except requests.RequestException as e:
- print(f"Error fetching headers: {e}")
+ r = requests.head(url, allow_redirects=True, timeout=5)
+ return dict(r.headers)
+ except Exception:
return {}
-# -------------------------
-# AI EXTENSION GENERATION
-# -------------------------
-def get_ai_extensions(url, headers, api_type, api_key, max_extensions):
- prompt = f"""
-Given the following URL and HTTP headers, suggest the most likely file extensions
-for fuzzing this endpoint.
-
-Respond with valid JSON only, no commentary.
-Format:
-{{"extensions": [".php", ".json", ".bak"]}}
-
-Limit to at most {max_extensions} extensions.
-
-URL: {url}
-Headers: {headers}
-"""
-
- system_msg = (
- "You are a security assistant helping with web fuzzing. "
- "Return only valid JSON. No prose."
- )
-
- # ---------- OpenAI ----------
- if api_type == "openai":
- client = OpenAI(api_key=api_key)
- response = client.chat.completions.create(
- model="gpt-4o",
- messages=[
- {"role": "system", "content": system_msg},
- {"role": "user", "content": prompt},
- ],
- temperature=0,
- )
- raw = response.choices[0].message.content.strip()
-
- # ---------- Anthropic ----------
- elif api_type == "anthropic":
- client = anthropic.Anthropic(api_key=api_key)
- msg = client.messages.create(
- model="claude-3-5-sonnet-20240620",
- max_tokens=500,
- temperature=0,
- system=system_msg,
- messages=[{"role": "user", "content": prompt}],
- )
- raw = msg.content[0].text.strip()
-
- # ---------- Gemini ----------
- elif api_type == "gemini":
- import google.generativeai as genai
-
- genai.configure(api_key=api_key)
- model = genai.GenerativeModel(
- "gemini-1.5-pro",
- generation_config={
- "temperature": 0,
- "response_mime_type": "application/json",
- },
- )
-
- resp = model.generate_content(
- f"SYSTEM:\n{system_msg}\n\nUSER:\n{prompt}"
- )
- raw = resp.text.strip()
-
- # ---------- Local HuggingFace (Qwen) ----------
- elif api_type == "huggingface":
- from transformers import AutoModelForCausalLM, AutoTokenizer
- import torch
-
- print("🤖 Loading local Qwen model…")
- model_name = "Qwen/Qwen2.5-1.5B-Instruct"
- tokenizer = AutoTokenizer.from_pretrained(model_name)
- model = AutoModelForCausalLM.from_pretrained(
- model_name,
- torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
- device_map="auto",
- )
-
- messages = [
- {"role": "system", "content": system_msg},
- {"role": "user", "content": prompt},
- ]
-
- text = tokenizer.apply_chat_template(
- messages, tokenize=False, add_generation_prompt=True
- )
- inputs = tokenizer([text], return_tensors="pt").to(model.device)
-
- output = model.generate(
- **inputs,
- max_new_tokens=300,
- do_sample=False,
- )
-
- output = output[:, inputs["input_ids"].shape[1]:]
- raw = tokenizer.decode(output[0], skip_special_tokens=True).strip()
-
- else:
- raise ValueError("Unsupported API type")
-
- try:
- return json.loads(raw)
- except json.JSONDecodeError:
- print("❌ AI returned invalid JSON:")
- print(raw)
- raise
-
-# -------------------------
-# FFUF OUTPUT COLORIZER
-# -------------------------
def colorize_ffuf_output(line):
line = line.decode(errors="ignore").strip()
m = re.search(r"\[Status: (\d{3})", line)
if m:
code = int(m.group(1))
- if code == 200:
- color = "\033[92m"
- elif code in (301, 302):
- color = "\033[94m"
- elif code == 403:
- color = "\033[91m"
- else:
- color = "\033[0m"
+ color = (
+ "\033[92m" if code == 200 else
+ "\033[94m" if code in (301, 302) else
+ "\033[91m" if code == 403 else
+ "\033[0m"
+ )
print(f"{color}{line}\033[0m")
else:
print(line)
-# -------------------------
-# MAIN
-# -------------------------
+
def main():
parser = argparse.ArgumentParser(
- description="ffufai – AI-powered ffuf wrapper (OpenAI / Claude / Gemini / Local)"
+ description="ffufai – AI-powered ffuf wrapper"
)
parser.add_argument("--ffuf-path", default="ffuf")
parser.add_argument("--max-extensions", type=int, default=5)
@@ -184,46 +62,37 @@ def main():
try:
url = unknown[unknown.index("-u") + 1]
- except (ValueError, IndexError):
+ except Exception:
print("❌ You must provide -u URL")
return
- parsed = urlparse(url)
- if "FUZZ" not in parsed.path:
- print("⚠️ FUZZ keyword not found in URL path")
-
base_url = url.replace("FUZZ", "")
headers = get_headers(base_url)
- api_type, api_key = get_api_key()
- print(f"🔌 Using AI backend: {api_type}")
+ provider = get_provider()
+ print(f"🔌 Using AI backend: {provider.name}")
try:
- ext_data = get_ai_extensions(
- url, headers, api_type, api_key, args.max_extensions
- )
- exts = ext_data.get("extensions", [])
+ data = provider.get_extensions(url, headers, args.max_extensions)
+ exts = data.get("extensions", [])
if not exts:
- raise ValueError("Empty extension list")
- except Exception as e:
- print(f"⚠️ AI failed, using fallback extensions: {e}")
+ raise ValueError
+ except Exception:
exts = [".php", ".html", ".json", ".bak"]
- exts = exts[: args.max_extensions]
- exts = ",".join(e if e.startswith(".") else f".{e}" for e in exts)
-
- ffuf_cmd = [args.ffuf_path] + unknown + ["-e", exts]
+ exts = ",".join(e if e.startswith(".") else f".{e}" for e in exts[:args.max_extensions])
if not shutil.which(args.ffuf_path):
print("❌ ffuf binary not found")
return
- print("▶️ Running:", " ".join(ffuf_cmd))
- with subprocess.Popen(
- ffuf_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
- ) as proc:
+ cmd = [args.ffuf_path] + unknown + ["-e", exts]
+ print("▶️ Running:", " ".join(cmd))
+
+ with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as proc:
for line in proc.stdout:
colorize_ffuf_output(line)
+
if __name__ == "__main__":
main()
diff --git a/providers/__init__.py b/providers/__init__.py
new file mode 100644
index 0000000..3177351
--- /dev/null
+++ b/providers/__init__.py
@@ -0,0 +1,4 @@
+from providers.gemini import GeminiProvider
+from providers.openai import OpenAIProvider
+from providers.anthropic import AnthropicProvider
+from providers.huggingface_local import HuggingFaceLocalProvider
diff --git a/providers/anthropic.py b/providers/anthropic.py
new file mode 100644
index 0000000..d01da37
--- /dev/null
+++ b/providers/anthropic.py
@@ -0,0 +1,41 @@
+# providers/anthropic.py
+
+import json
+import anthropic
+from .base import AIProvider
+
+SYSTEM_MSG = (
+ "You are a security assistant helping with web fuzzing. "
+ "Return only valid JSON. No prose."
+)
+
+class AnthropicProvider(AIProvider):
+ name = "anthropic"
+
+ def __init__(self, api_key):
+ self.client = anthropic.Anthropic(api_key=api_key)
+
+ def get_extensions(self, url, headers, max_extensions):
+ prompt = f"""
+Given the following URL and HTTP headers, suggest the most likely file extensions
+for fuzzing this endpoint.
+
+Respond with valid JSON only.
+Format:
+{{"extensions": [".php", ".json", ".bak"]}}
+
+Limit to at most {max_extensions} extensions.
+
+URL: {url}
+Headers: {headers}
+"""
+
+ msg = self.client.messages.create(
+ model="claude-3-5-sonnet-20240620",
+ max_tokens=500,
+ temperature=0,
+ system=SYSTEM_MSG,
+ messages=[{"role": "user", "content": prompt}],
+ )
+
+ return json.loads(msg.content[0].text.strip())
diff --git a/providers/base.py b/providers/base.py
new file mode 100644
index 0000000..802b5c7
--- /dev/null
+++ b/providers/base.py
@@ -0,0 +1,7 @@
+# providers/base.py
+
+class AIProvider:
+ name = "base"
+
+ def get_extensions(self, url, headers, max_extensions):
+ raise NotImplementedError
diff --git a/providers/gemini.py b/providers/gemini.py
new file mode 100644
index 0000000..4b63774
--- /dev/null
+++ b/providers/gemini.py
@@ -0,0 +1,44 @@
+# providers/gemini.py
+
+import json
+import google.generativeai as genai
+from .base import AIProvider
+
+SYSTEM_MSG = (
+ "You are a security assistant helping with web fuzzing. "
+ "Return only valid JSON. No prose."
+)
+
+class GeminiProvider(AIProvider):
+ name = "gemini"
+
+ def __init__(self, api_key):
+ genai.configure(api_key=api_key)
+ self.model = genai.GenerativeModel(
+ "gemini-1.5-pro",
+ generation_config={
+ "temperature": 0,
+ "response_mime_type": "application/json",
+ },
+ )
+
+ def get_extensions(self, url, headers, max_extensions):
+ prompt = f"""
+Given the following URL and HTTP headers, suggest the most likely file extensions
+for fuzzing this endpoint.
+
+Respond with valid JSON only.
+Format:
+{{"extensions": [".php", ".json", ".bak"]}}
+
+Limit to at most {max_extensions} extensions.
+
+URL: {url}
+Headers: {headers}
+"""
+
+ resp = self.model.generate_content(
+ f"SYSTEM:\n{SYSTEM_MSG}\n\nUSER:\n{prompt}"
+ )
+
+ return json.loads(resp.text.strip())
diff --git a/providers/huggingface_local.py b/providers/huggingface_local.py
new file mode 100644
index 0000000..6b7569f
--- /dev/null
+++ b/providers/huggingface_local.py
@@ -0,0 +1,60 @@
+# providers/huggingface_local.py
+
+import json
+from transformers import AutoModelForCausalLM, AutoTokenizer
+import torch
+from .base import AIProvider
+
+SYSTEM_MSG = (
+ "You are a security assistant helping with web fuzzing. "
+ "Return only valid JSON. No prose."
+)
+
+class HuggingFaceLocalProvider(AIProvider):
+ name = "huggingface"
+
+ def __init__(self):
+ model_name = "Qwen/Qwen2.5-1.5B-Instruct"
+ self.tokenizer = AutoTokenizer.from_pretrained(model_name)
+ self.model = AutoModelForCausalLM.from_pretrained(
+ model_name,
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
+ device_map="auto",
+ )
+
+ def get_extensions(self, url, headers, max_extensions):
+ prompt = f"""
+Given the following URL and HTTP headers, suggest the most likely file extensions
+for fuzzing this endpoint.
+
+Respond with valid JSON only.
+Format:
+{{"extensions": [".php", ".json", ".bak"]}}
+
+Limit to at most {max_extensions} extensions.
+
+URL: {url}
+Headers: {headers}
+"""
+
+ messages = [
+ {"role": "system", "content": SYSTEM_MSG},
+ {"role": "user", "content": prompt},
+ ]
+
+ text = self.tokenizer.apply_chat_template(
+ messages, tokenize=False, add_generation_prompt=True
+ )
+
+ inputs = self.tokenizer([text], return_tensors="pt").to(self.model.device)
+
+ output = self.model.generate(
+ **inputs,
+ max_new_tokens=300,
+ do_sample=False,
+ )
+
+ output = output[:, inputs["input_ids"].shape[1]:]
+ raw = self.tokenizer.decode(output[0], skip_special_tokens=True)
+
+ return json.loads(raw.strip())
diff --git a/providers/openai.py b/providers/openai.py
new file mode 100644
index 0000000..36e4925
--- /dev/null
+++ b/providers/openai.py
@@ -0,0 +1,42 @@
+# providers/openai.py
+
+import json
+from openai import OpenAI
+from .base import AIProvider
+
+SYSTEM_MSG = (
+ "You are a security assistant helping with web fuzzing. "
+ "Return only valid JSON. No prose."
+)
+
+class OpenAIProvider(AIProvider):
+ name = "openai"
+
+ def __init__(self, api_key):
+ self.client = OpenAI(api_key=api_key)
+
+ def get_extensions(self, url, headers, max_extensions):
+ prompt = f"""
+Given the following URL and HTTP headers, suggest the most likely file extensions
+for fuzzing this endpoint.
+
+Respond with valid JSON only.
+Format:
+{{"extensions": [".php", ".json", ".bak"]}}
+
+Limit to at most {max_extensions} extensions.
+
+URL: {url}
+Headers: {headers}
+"""
+
+ resp = self.client.chat.completions.create(
+ model="gpt-4o",
+ messages=[
+ {"role": "system", "content": SYSTEM_MSG},
+ {"role": "user", "content": prompt},
+ ],
+ temperature=0,
+ )
+
+ return json.loads(resp.choices[0].message.content.strip())
From cbff693c76cc021d0fa7c8fcc1903ab2e5a6b00a Mon Sep 17 00:00:00 2001
From: Dhaval D
Date: Sat, 10 Jan 2026 00:17:40 +0530
Subject: [PATCH 30/32] update
---
providers/gemini.py | 51 +++++++++------------------------------------
1 file changed, 10 insertions(+), 41 deletions(-)
diff --git a/providers/gemini.py b/providers/gemini.py
index 4b63774..ac3c4ed 100644
--- a/providers/gemini.py
+++ b/providers/gemini.py
@@ -1,44 +1,13 @@
-# providers/gemini.py
+from google import genai
-import json
-import google.generativeai as genai
-from .base import AIProvider
+class GeminiProvider:
+ def __init__(self, api_key, model="gemini-1.5-pro"):
+ self.client = genai.Client(api_key=api_key)
+ self.model = model
-SYSTEM_MSG = (
- "You are a security assistant helping with web fuzzing. "
- "Return only valid JSON. No prose."
-)
-
-class GeminiProvider(AIProvider):
- name = "gemini"
-
- def __init__(self, api_key):
- genai.configure(api_key=api_key)
- self.model = genai.GenerativeModel(
- "gemini-1.5-pro",
- generation_config={
- "temperature": 0,
- "response_mime_type": "application/json",
- },
+ def generate(self, prompt: str) -> str:
+ response = self.client.models.generate_content(
+ model=self.model,
+ contents=prompt
)
-
- def get_extensions(self, url, headers, max_extensions):
- prompt = f"""
-Given the following URL and HTTP headers, suggest the most likely file extensions
-for fuzzing this endpoint.
-
-Respond with valid JSON only.
-Format:
-{{"extensions": [".php", ".json", ".bak"]}}
-
-Limit to at most {max_extensions} extensions.
-
-URL: {url}
-Headers: {headers}
-"""
-
- resp = self.model.generate_content(
- f"SYSTEM:\n{SYSTEM_MSG}\n\nUSER:\n{prompt}"
- )
-
- return json.loads(resp.text.strip())
+ return response.text.strip()
From 6a09e7f90853bf009f0d2d33b55c4d5e0e407ff8 Mon Sep 17 00:00:00 2001
From: Dhaval D
Date: Sat, 10 Jan 2026 02:15:27 +0530
Subject: [PATCH 31/32] update
---
.../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 462 bytes
.../__pycache__/anthropic.cpython-311.pyc | Bin 0 -> 1882 bytes
providers/__pycache__/base.cpython-311.pyc | Bin 0 -> 606 bytes
providers/__pycache__/gemini.cpython-311.pyc | Bin 0 -> 3466 bytes
.../huggingface_local.cpython-311.pyc | Bin 0 -> 2908 bytes
providers/__pycache__/openai.cpython-311.pyc | Bin 0 -> 1912 bytes
providers/gemini.py | 78 ++++++++++++++++--
7 files changed, 70 insertions(+), 8 deletions(-)
create mode 100644 providers/__pycache__/__init__.cpython-311.pyc
create mode 100644 providers/__pycache__/anthropic.cpython-311.pyc
create mode 100644 providers/__pycache__/base.cpython-311.pyc
create mode 100644 providers/__pycache__/gemini.cpython-311.pyc
create mode 100644 providers/__pycache__/huggingface_local.cpython-311.pyc
create mode 100644 providers/__pycache__/openai.cpython-311.pyc
diff --git a/providers/__pycache__/__init__.cpython-311.pyc b/providers/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9b17f074069ee641243454f946e6a7688294ee90
GIT binary patch
literal 462
zcmZ3^%ge<81Rg1gndgD@V-N=hn4pZ$W!pLuFJcB-hb){A
zv?MXJhy^4pj8L5jv#E#`BqNQG$$(gw2DC0d2V`Fn8&JhchR;Bf;g^nnZeEFgiGF%!
zNpenpUaEdtT4`EhraoN1etbO8n8kc!1^t0Gfe{00000
literal 0
HcmV?d00001
diff --git a/providers/__pycache__/anthropic.cpython-311.pyc b/providers/__pycache__/anthropic.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..bc8a42d4fff169f107d5046582871851edf5c43b
GIT binary patch
literal 1882
zcmZux-ER{|5a08C*f|F~B*7FH%GNDyO%o@A5>&UUMAa5rL5h-8Dz2*2#k+PcIp4Xv
zYap&8<$;Gh@D5Tz;we=X9{5ud6bb7jq)L6_?NI0oPn|u-=S1n;-S1{*cV}m2XZFuy
z$4mrthS<)B8bbd_p-=igv-dSH4-i2_aZ#oBS1Jlg5YUDuH?B`>^84Q6pJykUkS?Yld
zRDOG9fCotQ_W@}+<~zz$WfP>Wfd*Nz6@p;T#4MyGX*V5fg{~G=mIMRAQ0zuvR9%O9
zqL>IYPTd+Cf^jZQVElG+s^N(#F;#a&)%86(RjajXwlf83h*Hd_V&6;6)gWbAj^_x=
zYUgA7{V+@Yv8=TJF2Fi^nLE3iD?a;pJ6GPxm3MRH4efq<1D-t?MpY=Cn50)xHvR?%
z%0}s^e;|_RD1d4w0jNDru4x^#94{GU=}DZCcn}z(6N4m$G0>4zM)(;Z`qSKXYTUfXW6>LM9^9jsb^*ZGun%nS!ah(+k#anY+is@ZJJ?{7(
zH)}rYS2@geIHn$H`i>_`ra4D>(+A5@I}EBm>VlH_ozEJ!D4V|*2IdN7{7a$KTxu33
zaG|u!eJ^4b?Ull>Yo>YKX*l4Lk8Ob?r`lr&e1RPymNIUe=CxiJWgIAGJ70C}7NL_D
zCofHcMURTf3)2@qo1VEa?Yw~^XYwcmt;H#m70Y~=2Fa@LL5u?0OK4Kx{8
zpu4_JI2(^P7m$NM=Yl!SqQQoxPHizaNbVm#XjPNwd
zj@@6`%@kf_iqA8}?r5odGT(jY)XT9m-LvPOeYBnXT)rpDVH#!6zeYyZd?R5){dWY7
zjNflR{NT~2PwYpf-TdT>{Ke<_i#z#CyZK8y*_qw!%yw#q$v&Sg8uC)`>;|>0z_ctl
zo|Y^5VaxihWxM+$n*6g2tYGH={zB2}C_hZ*D*~Blei>jre!A)0`ZuAb=wE~=2IHiD
zIYdXHXH%MkQ#&oKMpvP>XhSi{yN1bIskh&G$;k*JAKs?E>^c4wn*g1>Yq<*$s*0k7
v22zIC)AH$}k?sE5MW?q9zoDV%pM)w>(xG`q0T++LL-V!NzWJY_$eaHFBy!-P
literal 0
HcmV?d00001
diff --git a/providers/__pycache__/base.cpython-311.pyc b/providers/__pycache__/base.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2b0aa251f4d2820eade19fc4e6de6f54a1ce32c9
GIT binary patch
literal 606
zcmZvZJxc>Y5Qb;(F7e10Y8veltE9=5HX;a$qzQuEHOraZ#Dn{YyBCR8LGm9`+9~3n
zv6KZX|3D;G0m0ei5|ud1z4Pu2`|PmySS;oMbEuo`WkF|>Zl*hE#=yZP3|xZ|*JQ>BT#K1_#!|VX=9!4Eea3~dE%X-$uGq1?$7v*
zzpX67rHwL#w^H@Ru1@vft$Ie;lty02DOEX2!&W>#~68WB6zb&bEfF*faZJG0UMSV?+NxxsNh22_ciL
QMG(6#AG-GWPh$@L21QhkK>z>%
literal 0
HcmV?d00001
diff --git a/providers/__pycache__/gemini.cpython-311.pyc b/providers/__pycache__/gemini.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cabd79cc1b605ada3f7e6bf83efdd9e567c7f491
GIT binary patch
literal 3466
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
literal 0
HcmV?d00001
diff --git a/providers/__pycache__/huggingface_local.cpython-311.pyc b/providers/__pycache__/huggingface_local.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d67d80ec46d8f66757cc597918445f2c728038ab
GIT binary patch
literal 2908
zcmZ`)O>7&-6`ox#m(+@)MEymwT45s@P8mBScz{ehQ=&=PPgMf)a3>YYK$ZZYhlvCd<#ia-*
zv%7EKym|BHy*F>Z`TM|tfuJ2H*nXxX^gr3@wD=+OVhNZ#h#;aks38BUqm|DDp2BdVwyG32QM*Xi9P|hmCxOnw_-`lVW^=lt~9h;cHSblFYXD)hX
z#rGJ^72A%ssa#mL7t=uQAc_hKK?Rj4g&0vkK{9fnaiT#?aWSz_U0sD$F5@D-<`uEi
z@mBP&wMR-YQtTL&hGn8)L69D8v3BgnCUx_YoX(wj
zV=i}Q{*9|H<9@Zsg@$3rp&t6V=NH#3!nZ0^7=&)vMQW9CC2RN^*b{>#-z!@cpK>4D
zE+wLmcgn<whhhl}k>!tUt>wsF&sDZW(z0yV=9X1^
z_4|q<*HIaQl~@7D4r-0fHH@c|Cz_LIAJq;f=bMxB4efUN=~%Wo_D(A`{xq9=cXcQ8^x&fa-CQ
zBz+H!-s0HS>u4`3YQp{`WwmDFw@W*^N*bT}e2z<~Ws
zcLro;xHr=6K0eabUk?#l_UnCn{atyN>pix;vBRMoU924#SL_YyntY9#CC_oZO$m%2
zEL}6POU#9pmG=QHF+eIiWimi1%6O<-_JDEhTNHq@WINQPA9Lz5+jE&w^85~>g09U>
z>XM3QyF6zYOO#bSupB~f7hyxh%^3jlWz5eRznSTpn>lCxawb<`j=N=AlX398UWO%BF;|{L%8t
zzn8m#_GPpesgjrQ&Q^#Wl=fS+I#Y6)FJRm>kvHs3?+$9#au#YZ%}X28w`ZLX$ZZ
zaR58QV2t~=gwJs~H&*o>kvQD&b`0H(5c*-@Nx(mbp%jG1VOZ#4KuqXfl|y7A=HpE!
zYtxLHKOxPhS1Gr;25YZT`WRm4`Qo$3~4tGBD84=GVP7d^2M{aNPtr=x#5*329akTz_Dq?H~GV$gXuHQgGY0x&+5cQwGz6dIn`Rl?`&-pS@v
zt~vGQqZ`esxAv#rmhZ&d&CELuy_Fum?LJA*K2Fc>UH*LT-qkPO+)vLQq!*g$g+}bF
z!I9gw-Pylo|C0Ul$-5^H22V5xPwXd8_{SiXi&Yv!T6ljS!1Rjy$+4yS#dYQCsN^{vh
ze-3^k1=qLB07qbb7GB)p_3q2NP-r(XoKcC?{_B#2e)RhGZSs6Orl0zkKt~}zd
zn4&0wj+E3+zkFKg{Qh^nEtJ{`pBDPTe(xLTO8iGq8A^X(KTDOVaLJuWEMWJm16$C_5wYaKQ8_(ETXTO*k
z12IO*fkO@)dZSWPDJR+r`oAQ|646SPD)q!|QK(dL>YH`!ke0rk{mpyx@#eiZ@9q9P
zIA|iEJh7cmG=%m(VV?>jRm(HLRssZ8$lD>jA9#e
zT5U2%tYMpThYQ;mc!j#5k$0q;?;!`
zUI=i=0!~ZSt}R`Z2TR&J0q_t}R8a`3s6?rxhi7>4ofsl>Fig^$D#^4p+1FKakkai|Sj*RzM)adJO9O(^iCB@K_di^6{9`^)v_!i;bvJ0&pbKbc{eJoZe
zt_7|e+?EOY)%DBR_6feYv~&%!)`ryPQ8Ix
z$x?#{e!?u3fXoZAqBC<}W+%4{fFTFf$edy!UM
zu|<@v243h=Ap-#=z^E-~WG>!VT>9dQb!Blr(xeBG?glpDaNJ7ZR6${SUo{((s&p=x
z6BZ3NAf;53xlwxm7NQ}~zGEFkCo-UBybug#5LqsB2Peb^<=&w>6|b#s9sgGe9iAS6
zE&K_!ffnnj{7{@h*mUXjfyZbYm?AZ|PVDAF_ijv()teC8|1Z33tIe_*4vzr}V|2)%w-2{nG_ifS#fGB47pIVtjm;B^H2KZ=edR?m^YVY@6YKr~88YHj
literal 0
HcmV?d00001
diff --git a/providers/gemini.py b/providers/gemini.py
index ac3c4ed..68760e2 100644
--- a/providers/gemini.py
+++ b/providers/gemini.py
@@ -1,13 +1,75 @@
+import json
from google import genai
+from google.genai import types
+from .base import AIProvider
-class GeminiProvider:
- def __init__(self, api_key, model="gemini-1.5-pro"):
+SYSTEM_MSG = (
+ "You are a security assistant helping with web fuzzing. "
+ "Return only valid JSON. No prose."
+)
+
+class GeminiProvider(AIProvider):
+ name = "gemini"
+
+ def __init__(self, api_key):
self.client = genai.Client(api_key=api_key)
- self.model = model
- def generate(self, prompt: str) -> str:
- response = self.client.models.generate_content(
- model=self.model,
- contents=prompt
+ def get_extensions(self, url, headers, max_extensions):
+ prompt = f"""
+Given the following URL and HTTP headers, suggest the most likely file extensions
+for fuzzing this endpoint.
+
+Respond with valid JSON only.
+Format:
+{{"extensions": [".php", ".json", ".bak"]}}
+
+Limit to at most {max_extensions} extensions.
+
+URL: {url}
+Headers: {headers}
+"""
+
+ config = types.GenerateContentConfig(
+ system_instruction=SYSTEM_MSG,
+ response_mime_type="application/json",
+ temperature=0.0
)
- return response.text.strip()
+
+ # Updated based on your diagnostic output
+ # Prioritizing 2.0 Flash as it is fast and available to you
+ model_candidates = [
+ "gemini-2.0-flash",
+ "gemini-2.5-flash",
+ "gemini-flash-latest"
+ ]
+
+ for model_name in model_candidates:
+ try:
+ resp = self.client.models.generate_content(
+ model=model_name,
+ contents=[
+ {"role": "user", "parts": [{"text": prompt}]},
+ ],
+ config=config
+ )
+ return json.loads(resp.text.strip())
+
+ except Exception as e:
+ # If it's the last candidate, we need to handle the error
+ if model_name == model_candidates[-1]:
+ print(f"DEBUG: All model attempts failed. Last error: {e}")
+
+ # SELF-DIAGNOSIS (Kept for future safety)
+ if "404" in str(e):
+ print("\n🔍 DIAGNOSTIC: Fetching list of available models for your key...")
+ try:
+ for m in self.client.models.list():
+ if "generateContent" in (m.supported_actions or []):
+ print(f" - {m.name}")
+ except Exception as list_err:
+ print(f" Could not list models: {list_err}")
+ print("\n👉 Please update the 'model=' line in gemini.py with one of the above.\n")
+
+ return {"extensions": [".php", ".html", ".json"]}
+ else:
+ continue
\ No newline at end of file
From dfdc0ada993ef2999f1f5f4369e5d800ae0caece Mon Sep 17 00:00:00 2001
From: Dhaval D
Date: Sat, 10 Jan 2026 02:18:29 +0530
Subject: [PATCH 32/32] update
---
providers/__pycache__/__init__.cpython-311.pyc | Bin 462 -> 0 bytes
providers/__pycache__/anthropic.cpython-311.pyc | Bin 1882 -> 0 bytes
providers/__pycache__/base.cpython-311.pyc | Bin 606 -> 0 bytes
providers/__pycache__/gemini.cpython-311.pyc | Bin 3466 -> 0 bytes
.../huggingface_local.cpython-311.pyc | Bin 2908 -> 0 bytes
providers/__pycache__/openai.cpython-311.pyc | Bin 1912 -> 0 bytes
6 files changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 providers/__pycache__/__init__.cpython-311.pyc
delete mode 100644 providers/__pycache__/anthropic.cpython-311.pyc
delete mode 100644 providers/__pycache__/base.cpython-311.pyc
delete mode 100644 providers/__pycache__/gemini.cpython-311.pyc
delete mode 100644 providers/__pycache__/huggingface_local.cpython-311.pyc
delete mode 100644 providers/__pycache__/openai.cpython-311.pyc
diff --git a/providers/__pycache__/__init__.cpython-311.pyc b/providers/__pycache__/__init__.cpython-311.pyc
deleted file mode 100644
index 9b17f074069ee641243454f946e6a7688294ee90..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 462
zcmZ3^%ge<81Rg1gndgD@V-N=hn4pZ$W!pLuFJcB-hb){A
zv?MXJhy^4pj8L5jv#E#`BqNQG$$(gw2DC0d2V`Fn8&JhchR;Bf;g^nnZeEFgiGF%!
zNpenpUaEdtT4`EhraoN1etbO8n8kc!1^t0Gfe{00000
diff --git a/providers/__pycache__/anthropic.cpython-311.pyc b/providers/__pycache__/anthropic.cpython-311.pyc
deleted file mode 100644
index bc8a42d4fff169f107d5046582871851edf5c43b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 1882
zcmZux-ER{|5a08C*f|F~B*7FH%GNDyO%o@A5>&UUMAa5rL5h-8Dz2*2#k+PcIp4Xv
zYap&8<$;Gh@D5Tz;we=X9{5ud6bb7jq)L6_?NI0oPn|u-=S1n;-S1{*cV}m2XZFuy
z$4mrthS<)B8bbd_p-=igv-dSH4-i2_aZ#oBS1Jlg5YUDuH?B`>^84Q6pJykUkS?Yld
zRDOG9fCotQ_W@}+<~zz$WfP>Wfd*Nz6@p;T#4MyGX*V5fg{~G=mIMRAQ0zuvR9%O9
zqL>IYPTd+Cf^jZQVElG+s^N(#F;#a&)%86(RjajXwlf83h*Hd_V&6;6)gWbAj^_x=
zYUgA7{V+@Yv8=TJF2Fi^nLE3iD?a;pJ6GPxm3MRH4efq<1D-t?MpY=Cn50)xHvR?%
z%0}s^e;|_RD1d4w0jNDru4x^#94{GU=}DZCcn}z(6N4m$G0>4zM)(;Z`qSKXYTUfXW6>LM9^9jsb^*ZGun%nS!ah(+k#anY+is@ZJJ?{7(
zH)}rYS2@geIHn$H`i>_`ra4D>(+A5@I}EBm>VlH_ozEJ!D4V|*2IdN7{7a$KTxu33
zaG|u!eJ^4b?Ull>Yo>YKX*l4Lk8Ob?r`lr&e1RPymNIUe=CxiJWgIAGJ70C}7NL_D
zCofHcMURTf3)2@qo1VEa?Yw~^XYwcmt;H#m70Y~=2Fa@LL5u?0OK4Kx{8
zpu4_JI2(^P7m$NM=Yl!SqQQoxPHizaNbVm#XjPNwd
zj@@6`%@kf_iqA8}?r5odGT(jY)XT9m-LvPOeYBnXT)rpDVH#!6zeYyZd?R5){dWY7
zjNflR{NT~2PwYpf-TdT>{Ke<_i#z#CyZK8y*_qw!%yw#q$v&Sg8uC)`>;|>0z_ctl
zo|Y^5VaxihWxM+$n*6g2tYGH={zB2}C_hZ*D*~Blei>jre!A)0`ZuAb=wE~=2IHiD
zIYdXHXH%MkQ#&oKMpvP>XhSi{yN1bIskh&G$;k*JAKs?E>^c4wn*g1>Yq<*$s*0k7
v22zIC)AH$}k?sE5MW?q9zoDV%pM)w>(xG`q0T++LL-V!NzWJY_$eaHFBy!-P
diff --git a/providers/__pycache__/base.cpython-311.pyc b/providers/__pycache__/base.cpython-311.pyc
deleted file mode 100644
index 2b0aa251f4d2820eade19fc4e6de6f54a1ce32c9..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 606
zcmZvZJxc>Y5Qb;(F7e10Y8veltE9=5HX;a$qzQuEHOraZ#Dn{YyBCR8LGm9`+9~3n
zv6KZX|3D;G0m0ei5|ud1z4Pu2`|PmySS;oMbEuo`WkF|>Zl*hE#=yZP3|xZ|*JQ>BT#K1_#!|VX=9!4Eea3~dE%X-$uGq1?$7v*
zzpX67rHwL#w^H@Ru1@vft$Ie;lty02DOEX2!&W>#~68WB6zb&bEfF*faZJG0UMSV?+NxxsNh22_ciL
QMG(6#AG-GWPh$@L21QhkK>z>%
diff --git a/providers/__pycache__/gemini.cpython-311.pyc b/providers/__pycache__/gemini.cpython-311.pyc
deleted file mode 100644
index cabd79cc1b605ada3f7e6bf83efdd9e567c7f491..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 3466
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
diff --git a/providers/__pycache__/huggingface_local.cpython-311.pyc b/providers/__pycache__/huggingface_local.cpython-311.pyc
deleted file mode 100644
index d67d80ec46d8f66757cc597918445f2c728038ab..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 2908
zcmZ`)O>7&-6`ox#m(+@)MEymwT45s@P8mBScz{ehQ=&=PPgMf)a3>YYK$ZZYhlvCd<#ia-*
zv%7EKym|BHy*F>Z`TM|tfuJ2H*nXxX^gr3@wD=+OVhNZ#h#;aks38BUqm|DDp2BdVwyG32QM*Xi9P|hmCxOnw_-`lVW^=lt~9h;cHSblFYXD)hX
z#rGJ^72A%ssa#mL7t=uQAc_hKK?Rj4g&0vkK{9fnaiT#?aWSz_U0sD$F5@D-<`uEi
z@mBP&wMR-YQtTL&hGn8)L69D8v3BgnCUx_YoX(wj
zV=i}Q{*9|H<9@Zsg@$3rp&t6V=NH#3!nZ0^7=&)vMQW9CC2RN^*b{>#-z!@cpK>4D
zE+wLmcgn<whhhl}k>!tUt>wsF&sDZW(z0yV=9X1^
z_4|q<*HIaQl~@7D4r-0fHH@c|Cz_LIAJq;f=bMxB4efUN=~%Wo_D(A`{xq9=cXcQ8^x&fa-CQ
zBz+H!-s0HS>u4`3YQp{`WwmDFw@W*^N*bT}e2z<~Ws
zcLro;xHr=6K0eabUk?#l_UnCn{atyN>pix;vBRMoU924#SL_YyntY9#CC_oZO$m%2
zEL}6POU#9pmG=QHF+eIiWimi1%6O<-_JDEhTNHq@WINQPA9Lz5+jE&w^85~>g09U>
z>XM3QyF6zYOO#bSupB~f7hyxh%^3jlWz5eRznSTpn>lCxawb<`j=N=AlX398UWO%BF;|{L%8t
zzn8m#_GPpesgjrQ&Q^#Wl=fS+I#Y6)FJRm>kvHs3?+$9#au#YZ%}X28w`ZLX$ZZ
zaR58QV2t~=gwJs~H&*o>kvQD&b`0H(5c*-@Nx(mbp%jG1VOZ#4KuqXfl|y7A=HpE!
zYtxLHKOxPhS1Gr;25YZT`WRm4`Qo$3~4tGBD84=GVP7d^2M{aNPtr=x#5*329akTz_Dq?H~GV$gXuHQgGY0x&+5cQwGz6dIn`Rl?`&-pS@v
zt~vGQqZ`esxAv#rmhZ&d&CELuy_Fum?LJA*K2Fc>UH*LT-qkPO+)vLQq!*g$g+}bF
z!I9gw-Pylo|C0Ul$-5^H22V5xPwXd8_{SiXi&Yv!T6ljS!1Rjy$+4yS#dYQCsN^{vh
ze-3^k1=qLB07qbb7GB)p_3q2NP-r(XoKcC?{_B#2e)RhGZSs6Orl0zkKt~}zd
zn4&0wj+E3+zkFKg{Qh^nEtJ{`pBDPTe(xLTO8iGq8A^X(KTDOVaLJuWEMWJm16$C_5wYaKQ8_(ETXTO*k
z12IO*fkO@)dZSWPDJR+r`oAQ|646SPD)q!|QK(dL>YH`!ke0rk{mpyx@#eiZ@9q9P
zIA|iEJh7cmG=%m(VV?>jRm(HLRssZ8$lD>jA9#e
zT5U2%tYMpThYQ;mc!j#5k$0q;?;!`
zUI=i=0!~ZSt}R`Z2TR&J0q_t}R8a`3s6?rxhi7>4ofsl>Fig^$D#^4p+1FKakkai|Sj*RzM)adJO9O(^iCB@K_di^6{9`^)v_!i;bvJ0&pbKbc{eJoZe
zt_7|e+?EOY)%DBR_6feYv~&%!)`ryPQ8Ix
z$x?#{e!?u3fXoZAqBC<}W+%4{fFTFf$edy!UM
zu|<@v243h=Ap-#=z^E-~WG>!VT>9dQb!Blr(xeBG?glpDaNJ7ZR6${SUo{((s&p=x
z6BZ3NAf;53xlwxm7NQ}~zGEFkCo-UBybug#5LqsB2Peb^<=&w>6|b#s9sgGe9iAS6
zE&K_!ffnnj{7{@h*mUXjfyZbYm?AZ|PVDAF_ijv()teC8|1Z33tIe_*4vzr}V|2)%w-2{nG_ifS#fGB47pIVtjm;B^H2KZ=edR?m^YVY@6YKr~88YHj