A full-stack security scanner that combines Bandit + Semgrep static analysis with Google Gemini AI-powered explanations, a tamper-evident Evidence Chain audit trail, and PDF/Excel report export.
Upload any code file and hit Start Security Scan — VulNex immediately begins a 3-stage pipeline.
Watch Static Analysis → AI Analysis → CVE Mapping complete live.
Paginated, filterable, searchable table of every finding.
Executive Summary, Severity Breakdown, Vulnerability Summary table, and per-finding detail pages.
Screenshots were captured from a live run scanning
sample_vulnerable_app.py(31 findings, August 2026).
| Feature | Details |
|---|---|
| Multi-language scanning | .py .js .ts .java .cpp .c .cs .rb .php .go .rs + ZIP archives |
| Static analysis | Bandit (Python) + Semgrep (multi-language) |
| AI analysis | Google Gemini — per-finding explanations, suggested code fixes, confidence scores |
| CVE/CWE mapping | Automatic mapping to CWE IDs and OWASP Top 10 categories |
| Evidence Chain | Tamper-evident SHA-256 hash-chained audit trail per finding (see below) |
| Verification endpoint | Walk the chain and recompute all hashes to confirm integrity |
| Report export | PDF (ReportLab) and Excel (openpyxl) with chain-of-custody summary |
| Mark as reviewed | Per-finding review flag, also logged to the evidence chain |
| Real-time progress | Live 3-stage pipeline progress with polling |
Every vulnerability finding gets a tamper-evident audit trail. Each lifecycle event (detected, reviewed, exported, status changed) appends an immutable EvidenceLog entry that cryptographically links to the previous one — no update or delete operations are ever allowed.
EvidenceLog {
id UUID
findingId → vulnerabilities.id
eventType "detected" | "reviewed" | "exported" | "status_changed"
timestamp ISO 8601, UTC
actor user/session id ("system" for automated events)
fileHash SHA-256 of the scanned file at detection time
previousLogHash SHA-256 of prior entry (null for genesis entry)
currentLogHash SHA-256( findingId | eventType | timestamp | actor | fileHash | previousLogHash )
}
| Event | Trigger |
|---|---|
detected |
Vulnerability created during scan |
reviewed |
GET /api/vulnerabilities/:id (detail view) or PATCH .../review |
exported |
POST /api/scans/:id/report (for every included finding) |
status_changed |
Future: any other status mutation |
GET /api/vulnerabilities/:id/evidence → full ordered log chain
GET /api/vulnerabilities/:id/evidence/verify → walk chain, recompute hashes, return valid/invalid
Verify response:
{
"findingId": "...",
"valid": true,
"entriesChecked": 4,
"brokenEntryId": null,
"brokenEntryIndex": null,
"reason": null
}Open any vulnerability detail and click the Evidence Chain tab:
- Timeline of all events with color-coded badges (detected = red, reviewed = blue, exported = purple)
- Each entry shows actor, timestamp, file hash, previous hash, current hash
- Verify Integrity button calls the verify endpoint and shows a green Chain Valid or red Chain Tampered (entry N) badge inline
The report generator includes a per-finding chain-of-custody summary:
- First detected timestamp
- Last modified timestamp
- Verification status (
valid/invalid/no_chain) - Total event count
Enable it via the "Include chain-of-custody summary per finding" checkbox in the Report Generator UI.
- React 18 + TypeScript
- Vite 5
- shadcn/ui + Radix UI + Tailwind CSS
- TanStack Query v5 (data fetching + mutations)
- Wouter (routing)
- Lucide React (icons)
- Express (TypeScript, Node 20, run via
tsx) - In-memory storage (
MemStorage— Maps, resets on restart) - Drizzle ORM schema (PostgreSQL-compatible, ready to connect)
- Multer (file uploads, up to 50 MB)
| Script | Purpose |
|---|---|
server/services/scanner.py |
Runs Bandit + Semgrep, returns JSON findings |
server/services/ai_analyzer.py |
Calls Google Gemini / HuggingFace per finding |
server/services/report_generator.py |
Builds PDF (ReportLab) and Excel (openpyxl) reports |
- Node.js built-in
crypto—createHash('sha256')for all evidence chain hashing - Hash input:
findingId|eventType|timestamp|actor|fileHash|previousLogHash(pipe-delimited)
VulNex/
├── client/ React frontend
│ └── src/
│ ├── components/
│ │ ├── evidence-chain.tsx ← NEW: Evidence Chain tab UI
│ │ ├── vulnerability-detail.tsx ← Updated: tabbed with Evidence Chain
│ │ ├── report-generator.tsx ← Updated: chain-of-custody option
│ │ ├── vulnerability-table.tsx
│ │ ├── file-upload.tsx
│ │ ├── scan-progress.tsx
│ │ └── header.tsx
│ └── pages/home.tsx
├── server/
│ ├── index.ts Express app entry
│ ├── routes.ts ← Updated: evidence log hooks + 2 new endpoints
│ ├── storage.ts ← Updated: EvidenceLog store + hash utilities
│ └── services/
│ ├── scanner.py
│ ├── ai_analyzer.py
│ └── report_generator.py ← Updated: @file arg support (ENAMETOOLONG fix)
├── shared/
│ └── schema.ts ← Updated: evidenceLogs table + new types
├── sample_vulnerable_app.py Demo file with 15 vuln categories (31 findings)
├── requirements.txt
├── package.json
└── run.txt Full setup guide
- Node.js 20+
- Python 3.11+
- npm 10+
npm install
pip install -r requirements.txtCreate a .env file in the project root:
GEMINI_API_KEY=your_gemini_api_key
GEMINI_MODEL=gemini-flash-lite-latest
USE_GEMINI=true
HUGGINGFACE_API_KEY=your_hf_key
PYTHONUTF8=1
PYTHONIOENCODING=utf-8Get a free Gemini key at aistudio.google.com/app/apikey.
mkdir uploads
mkdir reportsnpm run devFull setup guide (including Windows-specific tips, Defender exclusions, troubleshooting) is in
run.txt.
sample_vulnerable_app.py is included for demo purposes — it deliberately contains 31 vulnerabilities across 15 categories:
| # | Category | CWE |
|---|---|---|
| 1 | Hardcoded credentials (DB password, JWT key, AWS keys) | CWE-798 |
| 2 | SQL injection (string concatenation) | CWE-89 |
| 3 | OS command injection (os.system, subprocess with shell=True) |
CWE-78 |
| 4 | Path traversal | CWE-22 |
| 5 | Insecure deserialization (pickle.loads, yaml.load) |
CWE-502 |
| 6 | Weak cryptography (MD5 password hash, SHA-1) | CWE-327 |
| 7 | Insecure random (random.randint for tokens) |
CWE-338 |
| 8 | SSRF (unvalidated requests.get) |
CWE-918 |
| 9 | XXE-prone XML parsing | CWE-611 |
| 10 | Open redirect | CWE-601 |
| 11 | Sensitive data in logs (password logged) | CWE-532 |
| 12 | Insecure temp file (tempfile.mktemp) |
CWE-377 |
| 13 | Assert for security check | CWE-617 |
| 14 | exec() / eval() on user input |
CWE-95 |
| 15 | ReDoS-prone regex | CWE-400 |
Upload this file to see VulNex in action.
The following was verified against a live server run:
Scan: sample_vulnerable_app.py → 31 findings
Finding: start_process_with_a_shell (os.system shell injection, line 42, High/CWE-78)
Evidence chain after full lifecycle:
[0] DETECTED actor=system hash=e147785c... prevHash=(genesis)
[1] REVIEWED actor=anonymous hash=a3f9912b... prevHash=e147785c...
[2] REVIEWED actor=anonymous hash=8cb21d4e... prevHash=a3f9912b...
[3] EXPORTED actor=system hash=7f304aa1... prevHash=8cb21d4e...
Verify: { "valid": true, "entriesChecked": 4, "brokenEntryId": null }
POST /api/scans Create a new scan
POST /api/scans/:id/upload Upload files (multipart/form-data)
POST /api/scans/:id/start Start the scan pipeline
GET /api/scans/:id Get scan + vulnerabilities + progress
GET /api/scans List all scans
POST /api/scans/:id/report Generate PDF or Excel report
GET /api/reports/download/:id Download the generated report
GET /api/vulnerabilities/:id Get full vulnerability detail
PATCH /api/vulnerabilities/:id/review Mark as reviewed
GET /api/vulnerabilities/:id/evidence Full ordered log chain
GET /api/vulnerabilities/:id/evidence/verify Recompute all hashes, return valid/invalid
- Storage is in-memory — all data resets on server restart. To persist, connect the Drizzle schema to a PostgreSQL database (schema is already defined in
shared/schema.ts). - No authentication — the app has no login/session system. The
actorfield in evidence logs defaults to"anonymous"for browser requests. Add anx-actorrequest header to identify users. - Reports larger than ~32 KB of scan data use a temp file to pass data to the Python process (Windows
ENAMETOOLONGfix applied).
MIT