Repository navigation
09 Desarrollo
Guía para contribuir código a Blackwire. Para entender cómo encajan las piezas antes de tocar nada, lee primero 08 — Arquitectura. El proyecto fue refactorizado de monolito a modular: el backend es una app factory con un router por dominio y la UI es una app de escritorio nativa PySide6/Qt (un shell main.py con un widget por herramienta bajo widgets/). Respeta esa estructura.
- Python 3.9+
-
PySide6 + Qt — necesarios para ejecutar la app de escritorio (se instalan vía pip/
requirements.txt). - Git
- Un editor de código (VS Code recomendado).
git clone https://github.com/Glitchboi-sudo/Blackwire.git
cd Blackwire
make install # crea el venv, instala dependencias y genera el certificado de mitmproxymake serve # arranca el backend en primer plano (Ctrl-C para detener)make serve corre uvicorn desde backend/ en primer plano; no transpila ningún frontend (la UI es una app nativa). Para trabajar en la interfaz, lanza la app de escritorio en otra terminal con python desktop/run.py (y python desktop/run.py --selftest para un smoke test headless).
No uses
python backend/main.py. Los imports asumen que el directorio de trabajo esbackend/; arrancar así rompe la resolución de módulos. Usa siempremake serve(omake start).
| Comando | Qué hace |
|---|---|
make install |
venv + dependencias + certificado de mitmproxy. |
make serve |
Backend en primer plano. |
make start |
Backend en segundo plano. |
make stop |
Detiene el backend. |
make restart |
Reinicia el backend (necesario tras cambios de backend o de extensiones). |
make run |
Instala, arranca el backend y lanza la app de escritorio. |
make verify |
Verifica dependencias. |
make clean |
Borra el venv, cachés y artefactos de runtime. |
make help |
Lista todos los objetivos. |
python desktop/run.py |
Lanza la app de escritorio (GUI). |
python desktop/run.py --selftest |
Smoke test headless de la app de escritorio. |
Importante: no hay hot-reload del backend. FastAPI descubre las extensiones en el arranque, no en runtime. Tras cualquier cambio en backend/ o en una extensión: make restart.
Blackwire/
├── backend/
│ ├── main.py # app factory FastAPI (registra routers)
│ ├── config.py # paths, constantes, validadores
│ ├── schemas.py # modelos Pydantic + catálogo de ops Chepy
│ ├── db.py # acceso SQLite por proyecto
│ ├── mitm_addon.py # addon de mitmproxy (el proxy)
│ ├── chepy_compat.py # motor Cipher
│ ├── routes/ # 1 router FastAPI por dominio
│ ├── services/ # state.py + proxy_control.py
│ ├── utils/ # httpql, scope, git_manager, extensions_loader, ...
│ └── extensions/ # extensiones (.py)
├── desktop/ # app de escritorio nativa PySide6/Qt (la única UI)
│ ├── run.py # lanzador
│ └── blackwire_desktop/
│ ├── api.py # cliente REST (única frontera HTTP)
│ ├── ws_client.py # cliente WebSocket (/ws)
│ ├── httpql.py
│ ├── sitemap.py
│ ├── diff.py
│ ├── intruder.py
│ ├── sensitive.py
│ ├── jwt_tool.py
│ ├── rawhttp.py
│ ├── themes.py # 17 temas + QSS + densidad
│ ├── settings.py # QSettings
│ ├── commands.py # paleta de comandos (Ctrl+K)
│ ├── docker_runtime.py # gestiona el contenedor del backend
│ ├── backend_server.py # uvicorn en dev
│ ├── widgets/ # 1 widget por herramienta
│ └── main.py # shell
├── mcp_server/ # blackwire-mcp (stdio)
├── assets/ # banner, icono, blackwire.desktop
├── packaging/ # build de escritorio (PyInstaller + Docker)
├── projects/ # 1 DB SQLite por proyecto (blackwire.db + config.json; gitignored)
├── .github/workflows/desktop.yml
├── Makefile
├── pyproject.toml
├── requirements.txt
├── package.json
├── README.md
└── CLAUDE.md
Antes de tocar main.py, evalúa en este orden — casi todo cabe en un módulo nuevo sin engordar los archivos núcleo:
-
Lógica de proxy/HTTP → una extensión en
backend/extensions/. -
Nuevos endpoints de API →
backend/routes/{feature}.py, registrado enmain.py. -
Nuevo widget de UI →
desktop/blackwire_desktop/widgets/{feature}.py, cableado enmain.py. -
Helper compartido →
backend/utils/{util}.pyo un módulo endesktop/blackwire_desktop/. -
Sin alternativa → edita
main.pycon el diff mínimo posible.
No añadas @app.get(...) en main.py. El patrón correcto es crear (o ampliar) un router de dominio y registrarlo.
Paso 1 — crea/edita backend/routes/mi_feature.py:
from fastapi import APIRouter
from pydantic import BaseModel
router = APIRouter()
class CustomRequest(BaseModel):
field1: str
field2: int
@router.get("/api/custom")
async def custom_endpoint():
"""Documentación del endpoint."""
return {"status": "ok", "data": []}
@router.post("/api/custom")
async def create_custom(data: CustomRequest):
# accede vía data.field1, data.field2
return {"status": "ok"}Paso 2 — regístralo en backend/main.py:
from routes.mi_feature import router as mi_feature_router
# ...
for _router in (..., mi_feature_router):
app.include_router(_router)Crear un router en
backend/routes/sin registrarlo enmain.pyes un error silencioso: FastAPI no lo descubre solo.
Si tu router necesita acceso a la DB u otras dependencias, sigue el patrón de inyección que usan los routers existentes (init_*_routes(get_db=...)), llamado en main.py antes del registro.
El acceso a SQLite por proyecto está centralizado en backend/db.py:
from db import get_db
async def query_db():
db = await get_db() # conexión a la DB del proyecto activo
async with db:
async with db.execute("SELECT * FROM requests") as cursor:
return await cursor.fetchall()Usa get_db_with_regex() si necesitas el operador regex de HTTPQL.
La lógica que toca el tráfico va en backend/mitm_addon.py (el addon de mitmproxy), nunca en main.py:
class BlackwireAddon:
def request(self, flow: http.HTTPFlow):
# ... lógica propia
self._apply_extensions("on_request", flow) # delega a las extensionesUna herramienta nueva es un QWidget en desktop/blackwire_desktop/widgets/{feature}.py, cableado en main.py. No metas la lógica de la herramienta dentro de main.py.
El widget solo habla con el backend a través de api.py (cliente REST, única frontera HTTP) y escucha eventos en vivo mediante ws_client.py (WebSocket /ws). No hagas llamadas HTTP crudas desde el widget ni mantengas tu propia conexión.
Nunca bloquees el hilo de la UI. Toda operación de red o cómputo largo va a un QThreadPool (worker en segundo plano) y los resultados se devuelven al hilo de la UI vía señales/slots. Un api.py llamado de forma síncrona en el hilo principal congela la GUI.
El theming es dirigido por tokens vía QSS, generado en themes.py (17 temas, conmutables en caliente, más densidad: compacta/cómoda/amplia). Para estilar tus widgets usa object names y roles (p. ej. setObjectName(...)) que el QSS global resuelve; no uses estilos inline codificados a mano, porque rompen el cambio de tema en caliente.
Resumen — la guía completa con el esquema de UI y la referencia de hooks está en 07 — Sistema de Extensiones.
Paso 1 — backend/extensions/my_extension.py:
EXTENSION_META = {
"name": "my_extension",
"title": "My Extension",
"description": "Lo que hace",
"ui_schema": {
"type": "schema-driven",
"fields": [
{"name": "setting1", "label": "Setting 1", "type": "text", "default": ""}
]
},
"default_config": {"enabled": False, "setting1": ""}
}
from mitmproxy import http
class MyExtension:
name = "my_extension"
def on_request(self, flow: http.HTTPFlow, cfg: dict, full_config: dict):
if not cfg.get("enabled"):
return
if cfg.get("setting1"):
flow.request.headers["X-Custom"] = cfg["setting1"]
def register():
return MyExtension()Paso 2 — reinicia para que se descubra:
make restartPaso 3 — pruébala: habilita "My Extension" en la app de escritorio → configura → envía un request de prueba → verifica la modificación.
El estándar actual es la verificación manual contra una sesión de proxy en marcha:
- Inicia Blackwire (
make serve). - Crea un proyecto de prueba.
- Configura el proxy en tu navegador/cliente.
- Envía requests y verifica el comportamiento.
Para arreglos de bugs, reproduce primero el fallo y deja constancia del paso de reproducción antes de corregir.
Aún no hay suite automatizada. Contribuciones bienvenidas:
- Tests unitarios de la API del backend.
- Tests de integración del proxy.
- Tests E2E del frontend.
Python:
- PEP 8:
snake_casepara funciones/variables,PascalCasepara clases,UPPER_SNAKE_CASEpara constantes. - Type hints y docstrings en el código del backend (las extensiones pueden omitir type hints estrictos por compatibilidad con mitmproxy).
- Límite de línea: 88 caracteres (convención Black/Ruff).
UI / PySide6:
- PEP 8, igual que el backend.
- Cada herramienta es una subclase de
QWidget. - Theming vía QSS dirigido por tokens (
themes.py); nada de estilos inline codificados a mano.
feat: añade analizador JWT
fix: corrige refresh del token de webhook
ext: rate limiter
refactor: divide el router de colecciones
docs: actualiza la wiki de arquitectura
No hagas commit directo a main. Crea siempre una rama: feat/nombre o fix/nombre.
- Haz fork del repositorio.
- Crea una rama:
git checkout -b feat/mi-feature. - Implementa y commitea.
- Haz push a tu fork.
- Abre el PR con: descripción clara, capturas (si hay cambios de UI) y pasos de prueba.
El empaquetado de escritorio (PyInstaller + Docker) vive en el directorio packaging/ del repositorio y lo construye el workflow .github/workflows/desktop.yml (disparado por un tag v*). El backend corre en un contenedor Docker aislado gestionado por la propia GUI. (El antiguo repositorio separado Blackwire-compile quedó obsoleto.)