Upload firmware images, unpack them, explore the filesystem, analyze binaries, and conduct security assessments — all powered by AI analysis via Model Context Protocol (MCP).
Connect any MCP-compatible AI agent to Wairz's 172 analysis tools — Claude Code, Claude Desktop, OpenCode, Codex, Cursor, VS Code + Copilot, Gemini CLI, Windsurf, and more.
- Firmware Unpacking — Automatic extraction of SquashFS, JFFS2, UBIFS, CramFS, ext, CPIO, and Intel HEX filesystems via binwalk3 and unblob, with multi-partition support
- File Explorer — Browse extracted filesystems with a virtual tree, view text/binary/hex content, and search across files
- Binary Analysis — Disassemble and decompile binaries using radare2 and Ghidra headless, with cross-reference, taint analysis, and capability detection (capa)
- Component Map — Interactive dependency graph showing binaries, libraries, scripts, and their relationships
- Security Assessment — Detect hardcoded credentials, crypto material, hardcoded IPs, setuid binaries, insecure configs, weak permissions, and network dependencies
- Attack Surface Scoring — Automated 0-100 risk scoring across network exposure, CGI, setuid, dangerous functions, and known daemons
- SAST — ShellCheck for shell scripts and Bandit for Python scripts, with CWE mapping
- cwe_checker — Binary vulnerability pattern detection (17 CWEs) via Docker sidecar with ARM/MIPS/x86 support
- YARA Scanning — Custom rules + ~5000 YARA Forge community rules, with on-demand updates
- Threat Intelligence — ClamAV malware scanning, VirusTotal hash lookups (privacy-first, no file upload), abuse.ch suite (MalwareBazaar, ThreatFox, URLhaus, YARAify), and CIRCL Hashlookup for known-good binary identification via NSRL
- SBOM & CVE Scanning — Generate Software Bill of Materials (CycloneDX 1.7, SPDX 2.3, CycloneDX VEX) with generic binary version detection fallback, CPE enrichment via NVD dictionary, and vulnerability scanning against the NVD
- Firmware Emulation — User-mode (QEMU) for single binaries, system-mode (FirmAE) for full OS boot in isolated containers, with GDB, pcap capture, and web endpoint interaction
- Network Protocol Analysis — Capture and analyze traffic from emulated firmware: protocol breakdown, insecure protocol detection, DNS queries, TLS metadata
- Fuzzing — AFL++ with QEMU mode for cross-architecture binary fuzzing, with automatic dictionary/corpus generation and crash triage
- Firmware Comparison — Diff filesystem trees, binaries, and decompiled functions across firmware versions
- RTOS & Bare-Metal Support — Detection of FreeRTOS, VxWorks, Zephyr, ThreadX and companion components (lwIP, FatFs, etc.)
- UEFI Firmware Support — UEFIExtract for firmware volumes, module listing, NVRAM variable extraction, and PE32+ scanning
- Android Firmware — Multi-phase APK security scanning: 18 manifest security checks (MobSF-equivalent), DEX bytecode pattern detection (~30 insecure API patterns), jadx decompilation + mobsfscan SAST (43 rules), with firmware-aware severity adjustment for system/priv-app APKs. Includes batch scanning of all APKs, decompiled source viewer, permission analysis, and signature verification
- Device Acquisition — Pull firmware directly from ADB-connected Android devices via a host-side bridge
- Firmware Update Detection — Identify SWUpdate, RAUC, Mender, opkg, U-Boot, and custom update mechanisms with security gap analysis
- CRA Compliance — EU Cyber Resilience Act Annex I assessment (20 requirements), auto-populate from existing findings, Article 14 notification export
- Live Device UART — Connect to physical devices via a host-side serial bridge for interactive console access
- AI Analysis via MCP — 172 analysis tools (across 21 categories) exposed to any MCP-compatible AI agent for autonomous security research
- Findings & Reports — Record security findings with severity ratings and evidence, export as Markdown, with full assessment orchestration
Claude Code / Claude Desktop / OpenCode
│
│ MCP (stdio)
▼
┌─────────────────┐ ┌──────────────────────────────────┐
│ wairz-mcp │────▶│ FastAPI Backend │
│ (MCP server) │ │ │
│ 172 tools │ │ Services: firmware, analysis, │
│ │ │ emulation, fuzzing, sbom, uart │
└─────────────────┘ │ │
│ Ghidra headless · QEMU · AFL++ │
└──────────┬───────────────────────┘
│
┌──────────────┐ ┌──────────────┼──────────────┐
│ React SPA │───▶│ PostgreSQL │ Redis │
│ (Frontend) │ │ │ │
└──────────────┘ └──────────────┴──────────────┘
Optional:
wairz-uart-bridge.py (host) ←─ TCP:9999 ─→ Docker backend
WAIRZ is currently in public beta. You may encounter bugs or rough edges. If you run into any issues, please open an issue on GitHub or reach out at andrew@digitalandrew.io.
WAIRZ supports embedded Linux, RTOS/bare-metal (FreeRTOS, VxWorks, Zephyr, ThreadX), UEFI, and Android firmware.
git clone https://github.com/digitalandrew/wairz.git
cd wairz
cp .env.example .env
# REQUIRED before the next step: edit .env and uncomment either
# WAIRZ_ALLOW_NO_AUTH=true (recommended for local-only single-user)
# or set
# API_KEY=<a-strong-random-key> (production / multi-user)
# If neither is set, the backend container will exit-loop with:
# "ERROR: api_key is required. Set API_KEY in .env or
# WAIRZ_ALLOW_NO_AUTH=true for local-only deployments."
docker compose up --build- Frontend: http://localhost:3000
- API docs: http://localhost:8000/docs
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -dBackend Python changes are picked up automatically via uvicorn --reload. Frontend uses Vite dev server with HMR. No rebuild needed for code changes — only rebuild when dependencies change (pyproject.toml or package.json).
# Start PostgreSQL and Redis
docker compose up -d postgres redis
# Backend
cd backend
uv sync
uv run alembic upgrade head
uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
# Frontend (separate terminal)
cd frontend
npm install
npm run devOr use the helper script:
./launch.shWairz uses MCP to give AI agents access to firmware analysis tools. After starting the backend, register the MCP server with your preferred client:
claude mcp add wairz -- docker exec -i wairz-backend-1 uv run wairz-mcp --project-id <PROJECT_ID>Add to your Claude Desktop config (~/.config/Claude/claude_desktop_config.json on Linux, ~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"wairz": {
"command": "docker",
"args": [
"exec", "-i", "wairz-backend-1",
"uv", "run", "wairz-mcp",
"--project-id", "<PROJECT_ID>"
]
}
}
}Add to your opencode.json (project root or ~/.config/opencode/opencode.json):
{
"mcp": {
"wairz": {
"type": "local",
"command": ["docker", "exec", "-i", "wairz-backend-1", "uv", "run", "wairz-mcp", "--project-id", "<PROJECT_ID>"],
"timeout": 30000,
"enabled": true
}
}
}Note: The
timeoutmust be increased from the default 5000ms because Wairz registers 172 tools.
Once connected, your AI agent can autonomously explore firmware, analyze binaries, run emulation, fuzz targets, and generate security findings. The MCP server supports dynamic project switching via the switch_project tool — no restart needed to change projects.
| Category | Count | Tools |
|---|---|---|
| Security | 26 | check_known_cves, analyze_config_security, check_setuid_binaries, analyze_init_scripts, check_filesystem_permissions, analyze_certificate, check_kernel_hardening, scan_with_yara, extract_kernel_config, check_kernel_config, analyze_selinux_policy, check_selinux_enforcement, check_compliance, scan_scripts, shellcheck_scan, bandit_scan, check_secure_boot, update_yara_rules, detect_network_dependencies, detect_update_mechanisms, analyze_update_config, create_cra_assessment, auto_populate_cra, update_cra_requirement, export_cra_checklist, generate_article14_notification |
| Threat Intelligence | 10 | scan_with_clamav, scan_firmware_clamav, check_virustotal, scan_firmware_virustotal, check_malwarebazaar_hash, check_threatfox_ioc, check_urlhaus_url, enrich_firmware_threat_intel, check_known_good_hash, scan_firmware_known_good |
| Emulation | 25 | start_emulation, run_command_in_emulation, stop_emulation, check_emulation_status, get_emulation_logs, diagnose_emulation_environment, troubleshoot_emulation, enumerate_emulation_services, get_crash_dump, run_gdb_command, save_emulation_preset, list_emulation_presets, start_emulation_from_preset, emulate_with_qiling, check_qiling_rootfs, start_system_emulation, system_emulation_status, list_firmware_services, run_command_in_firmware, stop_system_emulation, capture_network_traffic, get_nvram_state, interact_web_endpoint, list_available_kernels, download_kernel |
| Binary Analysis | 23 | list_functions, disassemble_function, decompile_function, list_imports, list_exports, xrefs_to, xrefs_from, get_binary_info, analyze_binary_format, check_binary_protections, check_all_binary_protections, find_string_refs, resolve_import, find_callers, search_binary_content, get_stack_layout, get_global_layout, trace_dataflow, cross_binary_dataflow, detect_capabilities, list_binary_capabilities, detect_rtos, analyze_raw_binary |
| Fuzzing | 9 | analyze_fuzzing_target, generate_fuzzing_dictionary, generate_seed_corpus, generate_fuzzing_harness, start_fuzzing_campaign, check_fuzzing_status, stop_fuzzing_campaign, triage_fuzzing_crash, diagnose_fuzzing_campaign |
| SBOM | 9 | generate_sbom, get_sbom_components, check_component_cves, run_vulnerability_scan, list_vulnerabilities_for_assessment, export_sbom, push_to_dependency_track, assess_vulnerabilities, set_vulnerability_status |
| Filesystem | 8 | list_directory, read_file, search_files, file_info, find_files_by_type, get_component_map, get_firmware_metadata, extract_bootloader_env |
| UART | 8 | uart_connect, uart_send_command, uart_read, uart_send_break, uart_send_raw, uart_disconnect, uart_status, uart_get_transcript |
| Reporting | 6 | add_finding, list_findings, update_finding, generate_assessment_report, generate_executive_summary, run_full_assessment |
| Documents | 6 | read_scratchpad, update_scratchpad, save_document, read_project_instructions, list_project_documents, read_project_document |
| Strings | 5 | extract_strings, search_strings, find_crypto_material, find_hardcoded_credentials, find_hardcoded_ips |
| Network | 5 | analyze_network_traffic, get_protocol_breakdown, identify_insecure_protocols, get_dns_queries, get_network_conversations |
| UEFI | 5 | list_firmware_volumes, list_uefi_modules, extract_nvram_variables, identify_uefi_module, read_uefi_module |
| Comparison | 4 | list_firmware_versions, diff_firmware, diff_binary, diff_decompilation |
| Project | 3 | get_project_info, switch_project, list_projects |
| Android | 3 | analyze_apk, list_apk_permissions, check_apk_signatures |
| cwe_checker | 3 | cwe_check_status, cwe_check_binary, cwe_check_firmware |
| VulHunt | 3 | vulhunt_scan_binary, vulhunt_scan_firmware, vulhunt_check_available |
| Attack Surface | 2 | detect_input_vectors, analyze_binary_attack_surface |
For live device access via UART, run the bridge on the host machine (USB serial adapters can't easily pass through to Docker):
pip install pyserial
python3 scripts/wairz-uart-bridge.py --bind 0.0.0.0 --port 9999The bridge is a TCP server — the serial device path and baud rate are specified via the uart_connect MCP tool, not on the command line.
On Linux, allow Docker traffic to reach the bridge and ensure .env is configured correctly:
sudo iptables -I INPUT -p tcp --dport 9999 -j ACCEPTUART_BRIDGE_HOST in .env must be host.docker.internal (not localhost). Restart the backend after changing .env: docker compose restart backend.
See UART Console docs for full setup details.
For pulling firmware directly from ADB-connected Android devices, run the device bridge on the host:
python3 scripts/wairz-device-bridge.py --bind 0.0.0.0 --port 9998For development without a real device, use mock mode:
python3 scripts/wairz-device-bridge.py --mock --port 9998Setup is the same pattern as the UART bridge: set DEVICE_BRIDGE_HOST=host.docker.internal in .env and allow Docker traffic on port 9998.
| Layer | Technology |
|---|---|
| Frontend | React 19, Vite, TypeScript, Tailwind CSS, shadcn/ui |
| Code Viewer | Monaco Editor |
| Component Graph | ReactFlow + Dagre |
| Terminal | xterm.js |
| State Management | Zustand |
| Backend | Python 3.12, FastAPI, SQLAlchemy 2.0 (async), Alembic |
| Database | PostgreSQL 16 |
| Cache | Redis 7 |
| Firmware Extraction | binwalk3, unblob, sasquatch, jefferson, ubi_reader, cramfs-tools, UEFIExtract |
| Binary Analysis | radare2 (r2pipe), pyelftools, LIEF, capa |
| Decompilation | Ghidra 11.3.1 (headless) with custom analysis scripts |
| Vulnerability Detection | cwe_checker (17 CWEs), VulHunt, YARA (~5000 Forge rules), ShellCheck, Bandit |
| Threat Intelligence | ClamAV, VirusTotal, abuse.ch (MalwareBazaar, ThreatFox, URLhaus, YARAify), CIRCL Hashlookup (NSRL) |
| Emulation | QEMU user-mode + system-mode, FirmAE, Qiling (ARM, MIPS, MIPSel, AArch64) |
| Network Analysis | Scapy (pcap capture + protocol analysis from emulated firmware) |
| Fuzzing | AFL++ with QEMU mode |
| SBOM | CycloneDX 1.7, SPDX 2.3, CycloneDX VEX, NVD API (nvdlib), Grype, Syft |
| Android | Androguard (APK analysis), ADB (device acquisition) |
| UART | pyserial (host-side bridge) |
| Compliance | EU CRA Annex I (20 requirements), ETSI EN 303 645 |
| AI Integration | MCP (Model Context Protocol) |
| Containers | Docker + Docker Compose |
wairz/
├── backend/
│ ├── app/
│ │ ├── main.py # FastAPI application
│ │ ├── config.py # Settings (pydantic-settings)
│ │ ├── database.py # Async SQLAlchemy engine/session
│ │ ├── mcp_server.py # MCP server with dynamic project switching
│ │ ├── models/ # SQLAlchemy ORM models
│ │ ├── schemas/ # Pydantic request/response schemas
│ │ ├── routers/ # REST API endpoints
│ │ ├── services/ # Business logic
│ │ ├── ai/ # MCP tool registry + 172 tool implementations
│ │ │ └── tools/ # 21 category files (filesystem, binary, security, emulation, hardware_firmware, cwe_checker, vulhunt, attack_surface, network, uefi, taint_llm, etc.)
│ │ └── utils/ # Path sandboxing, output truncation
│ ├── alembic/ # Database migrations
│ └── pyproject.toml
├── frontend/
│ ├── src/
│ │ ├── pages/ # Route pages (explorer, emulation, fuzzing, SBOM, etc.)
│ │ ├── components/ # UI components (file tree, hex viewer, component map, etc.)
│ │ ├── api/ # API client functions
│ │ ├── stores/ # Zustand state management
│ │ └── types/ # TypeScript type definitions
│ └── package.json
├── ghidra/
│ ├── Dockerfile # Ghidra headless container
│ └── scripts/ # Custom Java analysis scripts
├── emulation/
│ ├── Dockerfile # QEMU container (ARM, MIPS, MIPSel, AArch64)
│ └── scripts/ # Emulation helper scripts
├── fuzzing/
│ └── Dockerfile # AFL++ container with QEMU mode
├── scripts/
│ ├── wairz-uart-bridge.py # Host-side UART serial bridge
│ └── wairz-device-bridge.py # Host-side ADB device acquisition bridge
├── docker-compose.yml
├── launch.sh # Local development launcher
├── .env.example
└── CLAUDE.md
All settings are configured via environment variables or .env file:
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgresql+asyncpg://wairz:wairz@postgres:5432/wairz |
PostgreSQL connection |
REDIS_URL |
redis://redis:6379/0 |
Redis connection |
STORAGE_ROOT |
/data/firmware |
Firmware storage directory |
MAX_UPLOAD_SIZE_MB |
2048 |
Maximum firmware upload size |
MAX_TOOL_OUTPUT_KB |
30 |
MCP tool output truncation limit |
GHIDRA_PATH |
/opt/ghidra |
Ghidra installation path |
GHIDRA_TIMEOUT |
300 |
Ghidra decompilation timeout (seconds) |
FUZZING_IMAGE |
wairz-fuzzing |
Fuzzing container image name |
FUZZING_TIMEOUT_MINUTES |
120 |
Max fuzzing campaign duration |
FUZZING_MAX_CAMPAIGNS |
1 |
Max concurrent fuzzing campaigns |
UART_BRIDGE_HOST |
host.docker.internal |
UART bridge hostname |
UART_BRIDGE_PORT |
9999 |
UART bridge TCP port |
DEVICE_BRIDGE_HOST |
host.docker.internal |
Device acquisition bridge hostname |
DEVICE_BRIDGE_PORT |
9998 |
Device acquisition bridge TCP port |
NVD_API_KEY |
(empty) | Optional NVD API key for higher rate limits |
VIRUSTOTAL_API_KEY |
(empty) | Optional VirusTotal API key (hash-only lookups, no file upload) |
ABUSECH_AUTH_KEY |
(empty) | Optional abuse.ch auth key for higher rate limits |
CLAMAV_HOST |
clamav |
ClamAV daemon hostname (Docker service) |
CLAMAV_PORT |
3310 |
ClamAV daemon TCP port |
API_KEY |
(empty) | Optional API key for REST endpoint authentication |
LOG_LEVEL |
INFO |
Logging level |
Wairz ingests and analyses untrusted firmware binaries; the deployment surface itself also needs care. The following rules are enforced by the default docker-compose.yml:
Required secrets. POSTGRES_PASSWORD and FIRMAE_DB_PASSWORD are mandatory — docker compose up errors out if they are not set in .env:
$ docker compose config
error while interpolating services.postgres.environment.POSTGRES_PASSWORD:
required variable POSTGRES_PASSWORD is missing a value
Generate strong values with:
python3 -c 'import secrets; print(secrets.token_urlsafe(32))'Do not commit .env. Use .env.example as a template only.
Binding defaults. Backend (:8000) and frontend (:3000) default to 127.0.0.1 — local access only. The /ws WebSocket endpoint is not yet authenticated, so exposing the backend to LAN is unsafe until API_KEY is set and the WebSocket is auth-gated. To allow LAN access after you understand the tradeoffs:
# .env
API_KEY=<strong-random-key>
BACKEND_HOST_BIND=0.0.0.0
FRONTEND_HOST_BIND=0.0.0.0Postgres (:5432) and Redis (:6379) are always loopback-bound — there is no override. Use docker compose exec postgres psql ... for host-side DB access, not a network socket.
Rotating credentials. Edit .env, then recreate the affected containers:
docker compose up -dFor postgres specifically, changing POSTGRES_PASSWORD against an existing pgdata volume requires either ALTER USER wairz WITH PASSWORD ... inside the running container, or a fresh volume. The pg-backup service (nightly pg_dump into ${BACKUP_DIR:-./backups}) makes rotation-via-dump-and-restore safe to experiment with.
Production. For production deployments, consider an external secret manager (HashiCorp Vault, AWS Secrets Manager, SOPS-encrypted .env files) rather than plaintext .env. A docker-compose.prod.yml Docker-secrets variant is on the roadmap but not yet in-tree.
Good firmware images for testing:
