np_auditor ← volver
Caso de estudio · Fase E
1 de agosto, 2026 · run v2 · ~45 s total

Tres repos públicos, un pipeline: qué salió (y qué no)

Corremos NP Auditor sobre aws-cli (AWS), Django (DSF) y Metaflow (Netflix): clones shallow, scans acotados, sin ejecutar el target y sin tocar el banco de firmas. Aquí va el resultado con números reales — para mortales y para quien quiera el detalle técnico.

Léelo así

Esto no es “hackeamos AWS / Django / Netflix”. Es una pasada SAST + cuarentena heurística sobre subdirectorios públicos. Hay señal útil y también ruido. Lo honesto es mostrar ambos.

105Capa1 conocidos
919Cuarentena
727Match objetivo
192Candidatos nuevos

Método: qué es Fase E

Fase E es el experimento de Capa2 sobre código real (no solo fixtures). El pipeline va en tres capas de lectura:

01
Capa1 — conocidos Firmas del banco con CWE. Hit = archivo + línea + firma. Aquí sí hay etiqueta de vulnerabilidad conocida.
02
Cuarentena (staging) Sinks AST sospechosos sin CWE inventado. Se guardan para análisis; no se promocionan al banco.
03
Match de objetivos Cada track de cuarentena se cruza contra objetivos Capa2 (obj_cmd_injection, obj_pickle_rce, etc.). Sale score + familia — pendiente de revisión humana / oráculo.

Reglas del run (sin atajos)

Runner: run_capa2_fase_e_v2.py · ts 2026-08-01T21:48:39Z · cerebro aislado en repos_reales/ para no pisar fixtures.

Números agregados

Conteos por repo · Fase E v2
Repo Org Capa1 Cuarentena Match obj Nuevos .py ≈ s
aws-cli AWS 14 138 99 39 219 26.4
django DSF 23 462 400 62 319 8.5
metaflow Netflix 68 319 228 91 418 9.7
Total 105 919 727 192 956 44.6

Top objetivos globales (match): obj_sqli_concat 192 · obj_rce_eval 188 · obj_mass_assignment 158 · obj_cmd_injection 91 · obj_ssti 29. Una parte de eso es ruido de familia (ver más abajo).

Más allá de las métricas

Si las tablas se sienten frías: en cada blog por repo hay auditoría con cerebro — impacto, lo no etiquetado (candidato_nuevo), ruido vs señal y veredicto P0/P1/P2 de investigación (threat-model defensivo, sin PoCs). Empieza por aws-cli, Django o Metaflow. Y si quieres el lado producto — Claude en el IDE leyendo el pack, no el motor — está el caso Claude + Fase E.

aws-cli — Amazon Web Services

Auditoría completa de aws-cli →

Para mortales

Miramos el paquete awscli/ (≈219 archivos Python) en un clone shallow de develop (4b39e80…). Capa1 marcó 14 patrones conocidos (rutas, logs, plantillas). La cuarentena metió 138 sinks; 99 pegaron a un objetivo (sobre todo command injection y “eval-like”) y 39 quedaron como candidatos nuevos. Significa: el motor encuentra lugares donde el CLI habla con el shell, abre zips o arma rutas — candidatos a revisión. No significa: que AWS tenga 99 bugs explotables listos para CVE.

aws-cli · métricas del run
MétricaValor
URL / branch / SHAaws/aws-cli · develop · 4b39e80d
Scan pathawscli/ (sin tests/ completo como foco)
Capa1 conocidos14
Cuarentena / match / nuevos138 / 99 / 39
Oráculo pass/fail0 / 0
Top objetivoscmd_injection 32 · rce_eval 27 · mass_assignment 16
CWE Capa1 (top)CWE-117 ×5 · CWE-22 ×4 · CWE-1336 ×1

Detalle técnico — ejemplos del scan

CWE-22 awscli/compat.py:487 · ruta_compuesta_sin_verificar_contencion Capa1
También en cloudformation/artifact_exporter.py:448 y helpers de testutils.py. Path traversal flag — revisar contención real del path.
CWE-117 awscli/customizations/cloudtrail/validation.py · mensaje_log_sin_sanear Capa1 ×5
Logs sin sanear (también EMR). Útil como higiene; no implica RCE.
obj_cmd_injection awscli/help.py:190 · Popen + shell=True Capa2 match 0.9
# piping to a pager to work, we need to use shell=True.
kwargs['shell'] = True
return Popen(*args, **kwargs)
También alias.py:293 (compat_shell_quote(..., shell=True)) e invoker con shell=True. Señal real de superficie shell — contexto de pager/alias, no “proof of exploit”.
obj_zip_slip awscli/customizations/awslambda.py:94 · zipfile.ZipFile Capa2 match 0.8
with closing(zipfile.ZipFile(fileobj)) as f:
    f.infolist()
ZipFile en el camino de empaquetado Lambda / CloudFormation. Objetivo zip_slip = “mira este sink”; no confirma traversal en extract.
obj_mass_assignment awscli/alias.py:243 · setattr Ruido típico
setattr → familia mass_assignment es heurística floja en CLIs (namespaces de argparse). Ejemplo claro de match semántico ruidoso.

Django — Django Software Foundation

Auditoría completa de Django →

Para mortales

Escaneamos subdirs del core: core, db, http, utils, views, forms (≈319 .py) en main (60121939…). Capa1 encontró 23 hits conocidos — el más llamativo: pickle en backends de cache (CWE-502). La cuarentena explotó a 462 tracks; 400 hicieron match (muchísimo obj_sqli_concat por cursor.execute parametrizado — ruido). Significa: el framework usa patrones que un SAST tiene que ver (pickle, SQL helpers, templates). No significa: que Django esté “roto” en prod; varios hits son diseño documentado o FP de familia.

django · métricas del run
MétricaValor
URL / branch / SHAdjango/django · main · 60121939
Scan pathscore · db · http · utils · views · forms
Capa1 conocidos23
Cuarentena / match / nuevos462 / 400 / 62
Oráculo pass/fail0 / 0
Top objetivossqli_concat 180 · mass_assignment 87 · rce_eval 85
CWE Capa1 (top)CWE-502 ×6 · CWE-347 ×5 · CWE-829 ×2 · CWE-89 ×2

Detalle técnico — ejemplos del scan

CWE-502 django/core/cache/backends/filebased.py:38 · pickle.loads Capa1
if not self._is_expired(f):
    return pickle.loads(zlib.decompress(f.read()))
También locmem.py, redis.py y en cuarentena db.py:102 (pickle.loads(base64…)obj_pickle_rce 0.9). Pickle en cache es un patrón clásico de Django (confianza en el store). Señal correcta del motor; exploitabilidad depende de quién escribe el cache.
CWE-89 django/db/backends/base/creation.py:215 · sql_armado_por_interpolacion Capa1
SQL armado en helpers de creación de DB (también Oracle). Contexto de tooling, no request path típico.
obj_sqli_concat django/core/cache/backends/db.py:75 · cursor.execute Ruido heurístico
with connection.cursor() as cursor:
    cursor.execute(
        "SELECT %s …"   # placeholders — no concat de user input crudo
180 matches de obj_sqli_concat en Django: muchos son cursor.execute parametrizado. El sink existe; la familia “concat” se pasa de lanza. Caso de estudio del FP semántico.
obj_cmd_injection django/utils/version.py:93 · shell=True Capa2 match
"git log --pretty=format:%ct --quiet -1 HEAD",
    capture_output=True,
    shell=True,
Git log para versión en desarrollo — shell=True real, superficie acotada.
obj_rce_eval django/db/… · compiler.compile Nombre engañoso
Muchos hits “alto score” son compiler.compile del SQL compiler de Django — no es compile() de bytecode Python. La familia obj_rce_eval se confunde con el nombre del método.

Metaflow — Netflix

Auditoría completa de Metaflow →

Para mortales

Paquete metaflow/ (≈418 .py) en master (3d6fa453…). Aquí Capa1 fue el más cargado: 68 conocidos (pickle, paths, HTML, eval, permisos…). Cuarentena 319 · match 228 · nuevos 91. Aparece mucho subprocess / shell=True (bootstraps de pypi/uv y vendor click). Significa: un orquestador de ML toca shell, serialización y vendors — superficie grande, el scanner la ve. No significa: 68 CVEs nuevas; varios hits viven en _vendor/ o son diseño del runner.

metaflow · métricas del run
MétricaValor
URL / branch / SHANetflix/metaflow · master · 3d6fa453
Scan pathmetaflow/
Capa1 conocidos68
Cuarentena / match / nuevos319 / 228 / 91
Oráculo pass/fail0 / 0
Top objetivosrce_eval 76 · mass_assignment 55 · cmd_injection 50
CWE Capa1 (top)CWE-502 ×11 · CWE-916 ×8 · CWE-347 ×7 · CWE-22 ×6 · CWE-79 ×5

Detalle técnico — ejemplos del scan

CWE-502 metaflow/plugins/datastores/serializers/pickle_serializer.py:70 Capa1
Pickle también en env_escape/data_transferer.py (varios sitios). Misma lógica que Django cache: deserialización explícita — el motor la etiqueta bien; el riesgo depende del trust boundary.
CWE-79 metaflow/_vendor/packaging/*.py · html_armado_sin_escapar Capa1 ×5
Hits en packaging vendorizado (_parser.py, markers.py, version.py…). Tratar con escepticismo: firma HTML sobre librería de versiones — candidato a FP de firma, no XSS web clásico.
obj_cmd_injection metaflow/cmd_with_io.py:12 · subprocess.call(shell=True) Capa2 match 0.9
if subprocess.call(cmdline, shell=True):
    raise ExternalCommandFailed(
        "Command '%s' returned a non-zero exit code." % cmdline)
También plugins/pypi/bootstrap.py y plugins/uv/bootstrap.py con subprocess.run(..., shell=True), y vendor click (_termui_impl.py con os.system / Popen(..., shell=True)).
CWE-22 metaflow/plugins/pypi/bootstrap.py:310 · ruta_compuesta… Capa1
Path flags + permisos amplios (CWE-732) en bootstraps de entorno — superficie de install/runtime.

Ruido, oráculo y límites

Lo que el número grande no te dice

match_objetivo = 727 no es “727 bugs confirmados”. Es “727 sinks que la heurística cruzó con una familia de objetivo”. El oráculo (oraculo_pass = 0) solo corre sobre fixtures propias — en código real queda pendiente revisión. descartado_fp = 0 en estos runs: el filtro FP mínimo casi no aplicó.

Ruido que vimos en vivo

Limitaciones del experimento

Conclusión (honesta)

El pipeline sí produce señal en código real grande: pickle en cache Django, shell=True / Popen en aws-cli y Metaflow, ZipFile en caminos de empaquetado, paths y logs en Capa1. Eso es exactamente lo que quieres de un SAST + cuarentena: superficie visible, con archivo y línea.

Y también produce ruido heurístico: familias que se pasan de lanza, vendors, nombres de método engañosos. Por eso Fase E deja los matches en cuarentena y no escribe el banco — Store → analyze; humano (o fixtures) decide.

Si te llevas una frase: corrimos el motor sobre tres repos que la gente sí usa, en ~45 segundos, sin ejecutar su código, y salió un mapa útil de sinks — no un trophy wall de “hackeamos AWS”.

Artefactos del run

Agregado: fase_e_repos_reales_latest.json · por repo bajo cerebro/capa2_objetivos/repos_reales/{aws-cli,django,metaflow}/ · tmp: /tmp/np-fase-e-repos/reporte_agregado.json.

Desglose por repo: aws-cli · Django · Metaflow. Cómo Claude revisa esos packs en el IDE: caso Claude + Fase E. ¿Quieres el producto, no solo el caso? Vuelve al home o lee cómo jala NP Auditor.