# Project export: Entropy

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: CruzHacks 2026
- Tagline: Entropy is an AI security tester with no setup. All you do is paste a website URL, and an AI agent navigates your website in front of you, testing for security issues like XSS and SQL injection.
- Devpost: https://devpost.com/software/entropy-36yt4l
- GitHub: https://github.com/ChristianHuerta05/Entropy
- Video: https://www.youtube.com/embed/gwKubD6PbPY?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Best AI Hack)
- Team: 1 GitHub contributor(s) — Christian Huerta (3 commits)

## Devpost submission (written by the team)

### Inspiration

I saw that Cybersecurity education has a very steep learning curve and may be boring at times. Students read about various attacks, such as XSS and SQL injection, but never get to see them live in action. Real-world security tools are also very complex and can be overwhelming for beginners. I built Entropy to address this gap. It is an AI-powered learning tool that demonstrates real-world vulnerability testing in real time, making security concepts more real and understandable.

### What it does

Entropy is an interactive security education platform, as a simple-to-use website. All users have to do is paste a URL and watch as an AI agent does these tasks. Visually navigates the website. Students see exactly how an attacker might explore a website, looking for possible attacks. The AI highlights input fields, forms, and interactive elements, identifies common attacks, and performs XSS and SQL injection tests with payloads. At the end, it generates educational reports, explaining what was tested, why it matters, and how to fix vulnerabilities The live terminal view also shows every step the AI takes, acting as a teacher explaining a lesson on web security.

### How we built it

Frontend: React 19 + Vite + Tailwind CSS v4 for the ui Backend: Python FastAPI to help prompt and navigate the AI agent AI Agent: Google Gemini 2.5 Flash using an autonomous browser navigation via the browser-use library. Real-time Streaming is used with Firebase Firestore, as it streams live logs so students can follow along step-by-step Educational Output: Structured reports are provided at the end that explain vulnerabilities in plain language, with advice on how to fix them and how to look out for them.

### Challenges we ran into

Making AI behavior more transparent and easier to understand: We wanted students to understand why the agent was taking each action, so we built a step-by-step logging terminal with action descriptions. Handling model Automation and education: Just feeding the HTML to an AI is boring and doesn't show the steps for looking for vulnerabilities. Students often miss the lesson or lose interest, so using a live browser shows each step in an easy-to-follow view. Handling rate limits: Gemini's API quotas forced us to optimize prompts and add retries to certain steps, while avoiding loops and limiting steps, and also the Gemini Vision input. Parsing AI output: The agent's responses needed easy-to-parse, structured content to make the report easier to understand and more consistent.

### Accomplishments we're proud of

We created a tool that makes security ideas visual and interactive, rather than really abstract Built an experience where students see real attack techniques demonstrated safely and step by step Designed a report that explains vulnerabilities at a level easy for beginners to understand Achieved no setup required, making it very simple for students to start learning Developed a clean UI that makes cybersecurity feel more accessible and not a very complicated tool

### What we learned

How to create educational tools and focus on learning. The importance of showing real-time logs and how that can transform a black-box tool like LLMs into a more understandable and educational experience. How security professionals view vulnerability discovery and finding issues with sites. Techniques for making AI agents behave more predictably and have structured and consistent outputs.

### What's next

More explanation: Add inline tips for each attack type as it occurs, providing live narration. A challenge Mode: Let students try to see what vulnerabilities the agent found before showing the results AI Vision: More use of Gemini Vision so the AI can approach the site more visually, making the explanation more detailed. Safe Practice examples: Create packages or websites that intentionally include vulnerabilities to teach specific concepts.

## README (from the GitHub repository)

# Entropy

**AI-Powered Security Auditor** — Paste a URL, watch an AI agent navigate your site, get a vulnerability report.

---

## Features

- **Zero Configuration** — Just enter a URL and click scan
- **Visual AI Agent** — Watch the browser navigate in real-time
- **Live Terminal Logs** — live streaming output via Firebase
- **Structured Reports** — JSON-based vulnerability reports with risk levels
- **XSS & SQLi Testing** — Automated payload injection and analysis

---

## Tech Stack

| Layer        | Technology                                                                  |
| ------------ | --------------------------------------------------------------------------- |
| **Frontend** | React 19, Vite, Tailwind CSS v4                                             |
| **Backend**  | Python, FastAPI, Uvicorn                                                    |
| **AI Agent** | [browser-use](https://github.com/browser-use/browser-use), Gemini 2.5 Flash |
| **Database** | Firebase Firestore (live logs)                                              |

---

## Quick Start

### Prerequisites

- Python 3.11+
- Node.js 18+
- Google Cloud GeminiAPI Key
- Firebase Project

### Backend Setup

```bash
cd backend

# Create virtual environment
python -m venv venv
source venv/bin/activate

# Install dependencies
pip install -r requirements.txt

# Configure environment
cp .env.example .env
# Add your GOOGLE_API_KEY to .env

# Add Firebase service account
# Place your service-account.json in the backend/ directory

# Set credentials path
export GOOGLE_APPLICATION_CREDENTIALS="./service-account.json"

# Start server
uvicorn app.main:app --reload --port 8000
```

### Frontend Setup

```bash
cd frontend

# Install dependencies
npm install

# Start dev server
npm run dev
```

Open `http://localhost:5173` in your browser.

---

## Environment Variables

### Backend (`.env`)

```
GOOGLE_API_KEY=your_gemini_api_key
```

### Firebase

Place your `service-account.json` in the `backend/` directory.

Update `frontend/src/firebase.js` with your Firebase web config.

---

## 🎯 How It Works

1. **User submits a URL** via the React frontend
2. **Backend spawns an AI agent** using browser-use + Gemini
3. **Agent navigates the site** in a visible Chrome window
4. **Real-time logs stream** to Firestore → React UI
5. **Agent tests for vulnerabilities** (XSS, SQLi, info disclosure)
6. **Structured report generated** and displayed in the UI

---

## 📊 Report Format

```json
{
  "target": "https://example.com",
  "risk_level": "MEDIUM",
  "vulnerabilities": [
    {
      "type": "XSS",
      "location": "search bar",
      "severity": "HIGH",
      "description": "Reflected XSS via script tag"
    }
  ],
  "inputs_tested": [...],
  "pages_visited": [...],
  "recommendations": [...]
}
```

---

## Disclaimer

This tool is for **educational and authorized security testing only**. Only scan websites you own or have explicit permission to test. Unauthorized scanning may violate laws and terms of service.

---

## Hackathon

Built at CruzHacks 2026

---

## License

MIT


## Detected evidence (automated analysis)

Indexed codebase: 16 recognized source files, 37 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- Firebase (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- LangChain (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (21 of 21)

```
.gitignore
backend/.gitignore
backend/app/main.py
backend/app/services/attacker.py
backend/app/services/logger.py
backend/check_models.py
backend/inspect_browser.py
backend/requirements.txt
backend/start_backend.sh
frontend/.gitignore
frontend/eslint.config.js
frontend/index.html
frontend/package.json
frontend/postcss.config.js
frontend/src/App.css
frontend/src/App.jsx
frontend/src/components/ReportViewer.jsx
frontend/src/index.css
frontend/src/main.jsx
frontend/vite.config.js
README.md
```

### Dependencies

- backend/requirements.txt: browser-use, fastapi, firebase-admin, langchain-google-genai, python-dotenv, uvicorn
- frontend/package.json: @eslint/js@^9.39.1, @tailwindcss/postcss@^4.1.18, @types/react@^19.2.5, @types/react-dom@^19.2.3, @vitejs/plugin-react@^5.1.1, autoprefixer@^10.4.23, eslint@^9.39.1, eslint-plugin-react-hooks@^7.0.1, eslint-plugin-react-refresh@^0.4.24, firebase@^12.8.0, globals@^16.5.0, lucide-react@^0.562.0, postcss@^8.5.6, react@^19.2.0, react-dom@^19.2.0, tailwindcss@^4.1.18, vite@^7.2.4

### Recent commits (newest first)

- script to run backend
- fixed parsing issue
- Entropy

## Key source files (fetched from GitHub, selected and truncated for size)

### backend/requirements.txt

```
fastapi
uvicorn
browser-use
langchain-google-genai
firebase-admin
python-dotenv

```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "firebase": "^12.8.0",
    "lucide-react": "^0.562.0",
    "react": "^19.2.0",
    "react-dom": "^19.2.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.39.1",
    "@tailwindcss/postcss": "^4.1.18",
    "@types/react": "^19.2.5",
    "@types/react-dom": "^19.2.3",
    "@vitejs/plugin-react": "^5.1.1",
    "autoprefixer": "^10.4.23",
    "eslint": "^9.39.1",
    "eslint-plugin-react-hooks": "^7.0.1",
    "eslint-plugin-react-refresh": "^0.4.24",
    "globals": "^16.5.0",
    "postcss": "^8.5.6",
    "tailwindcss": "^4.1.18",
    "vite": "^7.2.4"
  }
}

```

### frontend/src/main.jsx

```javascript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

```

### backend/app/main.py

```python
import os
import asyncio
from fastapi import FastAPI, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from dotenv import load_dotenv

load_dotenv()

from app.services.attacker import run_attack
from app.services.logger import initialize_firebase

initialize_firebase()

app = FastAPI(title="Entropy Backend")

origins = [
    "http://localhost:5173",
    "http://127.0.0.1:5173",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

class AttackRequest(BaseModel):
    url: str

@app.post("/attack")
async def start_attack(request: AttackRequest, background_tasks: BackgroundTasks):
    import uuid
    run_id = str(uuid.uuid4())
    
    background_tasks.add_task(run_attack, request.url, run_id)
    
    return {"runId": run_id}

@app.get("/")
def read_root():
    return {"status": "Entropy Backend is running"}

```

### frontend/src/App.jsx

```javascript
import React, { useState, useEffect, useRef } from "react";
import {
  Terminal,
  ShieldAlert,
  Wifi,
  Activity,
  AlertCircle,
  CheckCircle,
  Loader,
  Zap,
  Globe,
  ArrowRight,
  Clock,
  X,
} from "lucide-react";
import { db } from "./firebase";
import { doc, onSnapshot } from "firebase/firestore";
import ReportViewer from "./components/ReportViewer";

function App() {
  const [url, setUrl] = useState("");
  const [isRunning, setIsRunning] = useState(false);
  const [runId, setRunId] = useState(null);
  const [logs, setLogs] = useState([]);
  const [status, setStatus] = useState("IDLE");
  const [currentStep, setCurrentStep] = useState(0);
  const [report, setReport] = useState(null);
  const [showReport, setShowReport] = useState(false);
  const logsEndRef = useRef(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!url) return;

    setIsRunning(true);
    setStatus("INITIALIZING");
    setCurrentStep(0);
    setReport(null);
    setShowReport(false);
    setLogs([
      {
        timestamp: new Date().toISOString(),
        message: `Initializing scan on ${url}...`,
        type: "info",
      },
    ]);

    try {
      const response = await fetch("http://localhost:8000/attack", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ url }),
      });
      const data = await response.json();
      setRunId(data.runId);
      setStatus("SCANNING");
    } catch (error) {
      setLogs((prev) => [
        ...prev,
        {
          timestamp: new Date().toISOString(),
          message: `Connection failed: ${error.message}`,
          type: "error",
        },
      ]);
      setIsRunning(false);
      setStatus("ERROR");
    }
  };

  useEffect(() => {
    if (!runId) return;

    const unsub = onSnapshot(doc(db, "runs", runId), (docSnap) => {
      if (docSnap.exists()) {
        const data = docSnap.data();
        if (data.logs) {
          const processedLogs = [];
          let latestStep = 0;

          data.logs.forEach((log) => {
            if (log.type === "status" && log.message.startsWith("STATUS:")) {
              const newStatus = log.message.replace("STATUS:", "");
              setStatus(newStatus);
              if (newStatus === "COMPLETE" || newStatus === "ERROR") {
                setIsRunning(false);
              }
              return;
            }

            if (log.type === "report" && log.message.startsWith("REPORT:")) {
              try {
                const reportData = JSON.parse(
                  log.message.replace("REPORT:", ""),
                );
                setReport(reportData);
                setShowReport(true);
              } catch (e) {
                console.error("Failed to parse report:", e);
              }
              return;
            }

            if (log.type === "step") {
              const match = log.message.match(/\[Step (\d+)\]/);
              if (match) latestStep = parseInt(match[1]);
            }

            processedLogs.push(log);
          });

          setCurrentStep(latestStep);
          setLogs(processedLogs);
        }
      }
    });

    return () => unsub();
  }, [runId]);

  useEffect(() => {
    logsEndRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [logs]);

  const getLogIcon = (type) => {
    switch (type) {
      case "error":
        return <AlertCircle size={14} className="text-red-500" />;
      case "success":
        return <CheckCircle size={14} className="text-cyan-400" />;
      case "step":
        return <ArrowRight size={14} className="text-purple-400" />;
      default:
        return <Zap size={14} className="text-green-500" />;
    }
  };

  const getLogColor = (type) => {
    switch (type) {
      case "error":
        return "text-red-400";
      case "success":
        return "text-cyan-400";
      case "step":
        return "text-purple-300";
      default:
        return "text-green-400";
    }
  };

  const getStatusDisplay = () => {
    switch (status) {
      case "INITIALIZING":
        return {
          text: "INITIALIZING",
          color: "text-yellow-500",
          icon: <Loader size={14} className="animate-spin" />,
        };
      case "SCANNING":
        return {
          text: "SCANNING",
          color: "text-green-500 animate-pulse",
          icon: <Activity size={14} />,
        };
      case "COMPLETE":
        return {
          text: "COMPLETE",
          color: "text-cyan-400",
          icon: <CheckCircle size={14} />,
        };
      case "ERROR":
        return {
          text: "ERROR",
          color: "text-red-500",
          icon: <AlertCircle size={14} />,
        };
      default:
        return {
          text: "IDLE",
          color: "text-green-700",
          icon: <Activity size={14} />,
        };
    }
  };

  const statusDisplay = getStatusDisplay();

  const handleReset = () => {
    setIsRunning(false);
    setRunId(null);
    setLogs([]);
    setStatus("IDLE");
    setCurrentStep(0);
    setReport(null);
    setShowReport(false);
  };

  return (
    <div className="h-screen w-full flex flex-col p-4 font-mono text-green-500 bg-black overflow-hidden">
      <header className="flex justify-between items-center mb-3 border-b border-green-900 pb-2 flex-shrink-0">
        <div className="flex items-center gap-2">
          <Terminal size={22} className="text-green-400" />
          <h1 className="text-lg font-bold tracking-widest uppercase">
            Entropy <span className="text-green-700 text-xs">v2.0</span>
          </h1>
        </div>
        <div className="flex gap-4 text-xs">
          <div className="flex items-center gap-1 text-green-700">
            <Wifi size={12} />
            <span>CONNECTED</span>
          </div>
          <div className={`flex items-center gap-1 ${statusDisplay.color}`}>
            {statusDisplay.icon}
            <span>{statusDisplay.text}</span>
          </div>
        </div>
[truncated — 5857 more characters]
```

### frontend/postcss.config.js

```javascript
export default {
  plugins: {
    "@tailwindcss/postcss": {},
    autoprefixer: {},
  },
};

```

### frontend/vite.config.js

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
})

```

### backend/inspect_browser.py

```python
from browser_use import Browser
import asyncio

async def main():
    browser = Browser(headless=True)
    print("Attributes of BrowserSession:")
    for attr in dir(browser):
        if not attr.startswith("_"):
            print(attr)
    
if __name__ == "__main__":
    asyncio.run(main())

```

### frontend/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/Terminal.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Entropy</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

```

### backend/check_models.py

```python
import os
import google.generativeai as genai
from dotenv import load_dotenv

load_dotenv()

api_key = os.getenv("GOOGLE_API_KEY")
if not api_key:
    print("No API Key found")
    exit(1)

genai.configure(api_key=api_key)

print("Listing available models...")
try:
    for m in genai.list_models():
        if 'generateContent' in m.supported_generation_methods:
            print(m.name)
except Exception as e:
    print(f"Error listing models: {e}")

```

[7 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]