Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions MobSF_AI/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 AshishSecDev

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
101 changes: 101 additions & 0 deletions MobSF_AI/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# MobSF AI Sidecar

This project adds an AI-powered chat assistant to the [Mobile Security Framework (MobSF)](https://github.com/MobSF/Mobile-Security-Framework-MobSF). It allows security analysts to ask questions about the analysis report generated by MobSF directly within the interface, leveraging OpenAI's model.

## Features

- **Seamless Integration**: Injects a specialized chat interface directly into MobSF reports using an Nginx reverse proxy.
- **Context-Aware**: The AI assistant automatically retrieves the current analysis report (static analysis) and uses it as context to answer your questions.
- **Interactive Q&A**: Ask about vulnerabilities, code snippets, remediation steps or general security concepts related to your app.

## Architecture & How It Works

The solution is composed of three Docker services working together:

1. **MobSF (`mobsf`)**: The standard Mobile Security Framework application running on port 8000.
2. **Sidecar (`chat`)**: A Python Flask application that runs on port 5000.
- Serves the chat UI.
- Integrates the user, the MobSF API and OpenAI API.
- Fetches the report JSON from MobSF using the unique scan hash.
- Sends the relevant parts of the report along with the user's query to the OpenAI API.
3. **Gateway (`gateway`)**: An Nginx reverse proxy (listening on port 8080) that coordinates everything:
- Proxies standard traffic to the MobSF container.
- Routes `/chat/` and `/api/chat` requests to the Sidecar container.

### Data Flow
1. User uploads the Mobile App in MobSF.
2. User opens the view report in MobSF when mobile app analysis completed.
3. User clicks the Chat button.
4. Browser opens a separate chat window.
5. User can integract with this particular app report data.
6. Sidecar fetches the report from MobSF API then sends prompt + report + question to OpenAI and returns answer.

## Prerequisites

- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/)
- An [OpenAI API Key](https://platform.openai.com/api-keys)

## Setup & Run

1. **Clone the repository:**
```bash
git clone <repository-url>
cd Mobsf_Side_Car
```

2. **Configure Environment:**
Open `docker-compose-mobsfai.yml` and check the environment variables.

You generally need to provide your OpenAI API Key. You can set it directly in the file (not recommended for committed code) or export it in your shell:
```bash
export MOBSF_OPENAI_API_KEY="sk-..."
```

*Note: The `MOBSF_API_KEY` is currently hardcoded to `1234567890` in the docker-compose file for both the MobSF instance and the Sidecar to communicate. If you change it in one place, ensure you update it in both services. You can the API in MobSF GUI as well.*

3. **Build and Start:**
Run the following command to build the chat container and start the stack:
```bash
docker-compose -f docker-compose-mobsfai.yml up --build
```
OR
```bash
docker-compose -f docker-compose-mobsfai.yml up -d --build
```
OR
```bash
docker-compose -f docker-compose-mobsfai.yml down -v
```


4. **Access the Application:**
Open your browser and navigate to:
**http://localhost:8080**

*(Note: Do not use port 8000, as that bypasses the Nginx gateway and the chat button will not appear.)*

## Usage

1. Go to **http://localhost:8080**.
2. Upload an Android (APK) or iOS (IPA) application for analysis.
3. Wait for the scan to complete.
4. Once the "Static Analysis" report loads, look for a **floating Chat button (💬)** in the bottom-right corner.
5. Click the button to open the AI assistant.
6. Ask questions like:
- "Summarize the critical vulnerabilities."
- "How do I fix the high-severity issues found in the manifest?"
- "Explain the 'Application is debuggable' finding."

## Troubleshooting

- **Chat button not appearing?**
- Ensure you are accessing via `http://localhost:8080` and not port 8000.
- Check the browser console for any JavaScript errors.
- Ensure the URL contains `/StaticAnalyzer/` or matches the pattern expected by the injected script.
- **AI Error / OpenAI Issues?**
- Check the logs of the chat container: `docker logs mobsf_chat`.
- Verify your `OPENAI_API_KEY` is valid and has access to the model.
- **MobSF Communication Errors?**
- Ensure the `MOBSF_API_KEY` matches in both the `mobsf` and `chat` service definitions in `docker-compose-mobsfai.yml`.

## AshishSecDev
41 changes: 41 additions & 0 deletions MobSF_AI/docker-compose-mobsfai.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
version: '3'

services:
mobsf:
image: opensecurity/mobile-security-framework-mobsf:latest
container_name: mobsf_core
expose:
- "8000"
environment:
- MOBSF_API_KEY=1234567890
networks:
- mobsf_net

chat:
build: ./sidecar
container_name: mobsf_chat
expose:
- "5000"
environment:
- MOBSF_URL=http://mobsf:8000
- MOBSF_API_KEY=1234567890
- OPENAI_API_KEY=${MOBSF_OPENAI_API_KEY}
networks:
- mobsf_net

gateway:
image: nginx:alpine
container_name: mobsf_gateway
ports:
- "8080:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
networks:
- mobsf_net
depends_on:
- mobsf
- chat

networks:
mobsf_net:
driver: bridge
55 changes: 55 additions & 0 deletions MobSF_AI/nginx/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
events {}

http {
server {
listen 80;
client_max_body_size 1024M;
proxy_read_timeout 3600s;
proxy_connect_timeout 3600s;
proxy_send_timeout 3600s;
send_timeout 3600s;

location / {
proxy_pass http://mobsf:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;

# Ensure gzipped responses from upstream are decompressed so sub_filter works
proxy_set_header Accept-Encoding "";

# 1. Capture scan_hash from URL if present
# Pattern: /StaticAnalyzer/hash/ or /DynamicAnalyzer/hash/
# We use map or regex in location?
# Creating a variable $scan_hash is tricky in pure nginx without Lua.
# But we can use a simple JS injection that parses URL client-side.

# Inject Chat Button
sub_filter '</body>' '<script>
(function() {
// Parse hash from URL (MobSF standard: /StaticAnalyzer/hash/ or similar)
const match = window.location.pathname.match(/\/([a-f0-9]{32})\//);
if (match) {
const hash = match[1];
const btn = document.createElement("a");
btn.href = "/chat/" + hash + "/";
btn.target = "_blank";
btn.style.cssText = "position:fixed;bottom:20px;right:20px;z-index:9999;width:60px;height:60px;background:#28a745;border-radius:50%;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 6px rgba(0,0,0,0.1);text-decoration:none;";
btn.innerHTML = "<span style=\\"color:white;font-size:24px;\\">💬</span>";
document.body.appendChild(btn);
}
})();
</script></body>';
sub_filter_once on;
}

# Route /chat/ to sidecar
location /chat/ {
proxy_pass http://chat:5000;
}

# Route /api/chat to sidecar
location /api/chat {
proxy_pass http://chat:5000;
}
}
}
9 changes: 9 additions & 0 deletions MobSF_AI/sidecar/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM python:3.9-slim

WORKDIR /app

RUN pip install flask requests openai

COPY . .

CMD ["python", "app.py"]
78 changes: 78 additions & 0 deletions MobSF_AI/sidecar/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from flask import Flask, render_template, request, jsonify
import requests
import os
from openai import OpenAI
import json

app = Flask(__name__)

MOBSF_URL = os.environ.get('MOBSF_URL', 'http://mobsf:8000')
MOBSF_API_KEY = os.environ.get('MOBSF_API_KEY', '')
OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY', '')

client = OpenAI(api_key=OPENAI_API_KEY)


@app.route('/chat/<scan_hash>/')
def chat_ui(scan_hash):
# We can pass scan_hash to the template
return render_template('chat.html', scan_hash=scan_hash)


@app.route('/api/chat', methods=['POST'])
def chat_api():
data = request.json
scan_hash = data.get('hash')
message = data.get('message')
history = data.get('history', [])

if not scan_hash or not message:
return jsonify({'error': 'Missing hash or message'}), 400

headers = {'Authorization': MOBSF_API_KEY}
try:
# Fetch report from MobSF
resp = requests.post(f"{MOBSF_URL}/api/v1/report_json",
data={'hash': scan_hash}, headers=headers)
if resp.status_code != 200:
return jsonify({'error': f"MobSF Error: {resp.status_code} {resp.text}"}), 500

report_data = resp.json()
except Exception as e:
return jsonify({'error': str(e)}), 500

system_instruction = f"""
You are a security analyst assistant for MobSF.
Report Context:
{json.dumps(report_data, default=str)[:50000]}

Answer the user's question based on this report.
"""

messages = [{"role": "system", "content": system_instruction}]
for turn in history:
# Gemini format adaptation if needed, but sidecar uses new format
role = turn.get('role', 'user')
if role == 'model':
role = 'assistant'

content = turn.get('content', '')
if not content and 'parts' in turn:
content = turn['parts'][0]
messages.append({"role": role, "content": str(content)})

messages.append({"role": "user", "content": message})

try:
completion = client.chat.completions.create(
model="gpt-4o",
messages=messages
)
reply = completion.choices[0].message.content
return jsonify({'response': reply})
except Exception as e:
return jsonify({'error': str(e)}), 500


if __name__ == '__main__':
app.run()
Binary file added MobSF_AI/sidecar/static/img/mobsf_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading