Skip to content

Lo que se empaqueta deja de llevar tests, payloads y rutas que no resuelven - #40

Merged
novanoticia merged 3 commits into
mainfrom
portabilidad-agentskills
Aug 12, 2026
Merged

Lo que se empaqueta deja de llevar tests, payloads y rutas que no resuelven#40
novanoticia merged 3 commits into
mainfrom
portabilidad-agentskills

Conversation

@novanoticia

@novanoticia novanoticia commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Por qué

Exportar email-triage al estándar abierto Agent Skills fallaba con código 3:
la revisión de seguridad del conversor bloqueaba el empaquetado y no se escribía
ningún artefacto.

Los dos hallazgos que bloqueaban resultaron ser falsos positivos con una causa
común
: la defensa anti-inyección de la skill documentándose a sí misma. Un
detector léxico no distingue entre describir un ataque y ejecutarlo.

Dónde Qué había
SKILL.md:273 Un display-name de ejemplo con la formulación literal de una inyección — entrecomillado y declarado como ejemplo, pero ilegible como tal para un detector
triage_helpers.py El PoC completo de descarga-y-ejecución remota, en el comentario que justifica el escape de message-ids

Los dos se reformulan conservando el sentido docente. El payload literal sigue
existiendo donde sirve de algo
: en TestEscaparApplescript.MID_ATAQUE, donde se
ejecuta contra el escape en vez de quedarse como texto en un comentario que además
viajaba dentro del paquete.

Qué más entra

  • Los 4 test_*.py salen de la skill (skills/email-triage/scripts/tests/).
    Eran 217 KB de los 384 KB del paquete y viajaban dentro de cada .zip sin aportar
    nada al agente de destino. El gate de unicidad del árbol se invierte y se le añade
    la invariante que protege el cambio: ni un test_*.py dentro de la skill.
  • description reescrita a mano: 962 → 454 bytes, disparadores delante. Cabe
    entera en el presupuesto más estrecho (490 B, instalación por carpeta), así que el
    conversor ya no recorta ni reordena — y el .zip y la carpeta salen idénticos.
  • base_estado() resuelve el directorio de estado una vez: $EMAIL_TRIAGE_HOME,
    si no ~, si no un temporal. Donde $HOME vale /, ~/.email-triage escribía en
    la raíz. base_estado_es_efimera() permite avisar cuando el estado no persiste.
  • references/paso-1c-hilos.md usaba ${CLAUDE_PLUGIN_ROOT}, que sólo existe
    dentro de un plugin de Claude Code; pasa a <ruta-del-skill>, la convención que ya
    usaba el resto del árbol.

Resultado medido

Antes Después
Código de salida del conversor 3 (bloqueado, 0 artefactos) 2 (artefactos escritos)
Claude Code 🟡 compatible con adaptación 🟢 compatible
Dimensión «comportamiento» 🟠 alto 🟢 bajo
Dimensión «cadena de suministro» 🟡 moderado 🟢 bajo
Hallazgos en ámbito exportado 6 (2 bloqueantes) 1 (🟡, falso positivo)
Adaptaciones automáticas 2 0
Tamaño del .zip 124 KB (23 ficheros, 0 tests)

El único hallazgo que queda dentro de lo que se empaqueta es
adapter_mailapp.py:162subprocess.run(["osascript", "-"], input=script), que
ya es una lista de argumentos explícita sin shell=True, o sea justo lo que pide la
mitigación. Los otros 8 están en tests/ con ámbito «paquete»: no viajan.

309 tests en verde en Python 3.12 y 3.13. Los gates de CI (coherencia de
versiones, changelog, conformidad con Agent Plugins 1.0.0, unicidad del árbol) se
replicaron localmente y pasan. El fuzz se validó con N reducido (400) para
comprobar el cableado del import; la pasada completa de 20.000 la hace CI.

Bump a v3.13.0

El gate sincronia-con-release falló en el primer push, y tenía razón: v3.12.0
ya estaba tagueada y este PR cambia ficheros distribuibles, así que quien instalara
por tag no habría recibido nada de esto. Minor y no patch porque hay capacidad
nueva de usuario: EMAIL_TRIAGE_HOME y base_estado_es_efimera() no existían.
bump-version.sh cubre los 9 sitios; la entrada de changelog va a mano.

Lo que este PR NO arregla

  • ChatGPT, claude.ai, Mistral y Perplexity siguen 🔴 no compatible, y ningún
    cambio de este PR iba a moverlos: dependen de AppleScript, de ejecutar Python o de
    no cortar a los 90 s. Es una limitación real de la skill, no del empaquetado.
  • El código de salida sigue siendo 2, no 0. El nivel global se queda en «alto»
    por los tres 🟠 SEC-EXEC-REMOTO-001 de tests/, que son los payloads de ataque
    contra los que se verifica applescript_quote. Se podría bajar a 0 troceando esos
    literales para que el detector no los case, y deliberadamente no se hace:
    ofuscar un payload para esquivar un escáner es el patrón que vuelve inútiles a los
    escáneres, y contradice el «mecanismo, no confianza» del resto del repo. 2 es el
    estado correcto aquí.

🤖 Generated with Claude Code

novanoticia and others added 3 commits August 12, 2026 11:03
…uelven

Exportar la skill al estandar Agent Skills fallaba con codigo 3: la revision
de seguridad bloqueaba el empaquetado por dos hallazgos de severidad alta que
resultaron ser la propia defensa anti-inyeccion documentandose a si misma.

  - SKILL.md:273 llevaba un display-name de ejemplo con la formulacion
    literal de una inyeccion. Era un ejemplo entrecomillado y declarado como
    tal, pero un detector lexico no puede ver eso: lo lee como instruccion.
  - triage_helpers.py llevaba en un comentario el PoC completo de descarga y
    ejecucion remota que motiva el escape de message-ids.

Los dos se reformulan conservando el sentido docente. El payload literal
sigue existiendo donde sirve de algo: en TestEscaparApplescript.MID_ATAQUE,
donde se EJECUTA contra el escape en vez de quedarse como texto.

Ademas, cuatro cambios que ya no dependian del bloqueo:

  - Los 4 test_*.py salen de skills/email-triage/scripts/ a tests/. Eran
    217 KB de los 384 KB del paquete y viajaban dentro de cada .zip sin
    aportar nada al agente de destino. El gate de unicidad del arbol se
    invierte (test_triage_helpers.py ahora DEBE estar fuera) y se le anade
    la invariante que protege el adelgazamiento: ni un test_*.py dentro de
    la skill. tests/__init__.py pone scripts/ en sys.path.
  - La description del frontmatter se reescribe a mano: 962 -> 454 bytes,
    con los disparadores delante. Cabe entera en el presupuesto mas estrecho
    (490 B, instalacion por carpeta), asi que el conversor ya no recorta ni
    reordena nada y el .zip y la carpeta salen identicos.
  - base_estado() resuelve el directorio de estado una sola vez:
    $EMAIL_TRIAGE_HOME, si no ~, si no un temporal. La tilde no es fiable en
    todas partes: donde $HOME vale "/", ~/.email-triage escribia en la raiz.
    base_estado_es_efimera() permite avisar cuando el estado no persiste, que
    es lo que evita dar por escrito un registro que nunca se escribio.
  - references/paso-1c-hilos.md usaba ${CLAUDE_PLUGIN_ROOT}, que solo existe
    dentro de un plugin de Claude Code; pasa a <ruta-del-skill>, la
    convencion que ya usaba el resto del arbol.

Resultado medido con el conversor: codigo 3 -> 2 (se escriben artefactos),
Claude Code de "compatible con adaptacion" a "compatible" sin adaptaciones,
y la dimension de comportamiento de la revision de seguridad de alto a bajo.
309 tests en verde en 3.12 y 3.13.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…g publicado

El gate sincronia-con-release tenia razon: v3.12.0 ya estaba tagueada y este
PR cambia ficheros distribuibles, asi que quien instalara por tag no recibiria
nada de esto y el marketplace anunciaria 3.12.0 para un codigo que ya no es ese.

Minor y no patch porque hay capacidad nueva de usuario: EMAIL_TRIAGE_HOME y
base_estado_es_efimera() no existian.

bump-version.sh cubre los 9 sitios; la entrada de changelog del README va a
mano, que es justo lo que el otro gate comprueba. 309 tests en verde en 3.12
y 3.13.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PyYAML>=6.0 dejaba que la version instalada hoy no fuera la que se probo, y
config.yaml lo parsea justo esa libreria. Se fija a ==6.0.3, la misma con la
que corre la suite.

El pin no vale de nada si el CI instala por su cuenta: 'pip install pyyaml'
pasa a 'pip install -r requirements.txt', asi lo que se prueba es lo que se
declara.

Sin hashes a proposito, y escrito en el fichero: en cuanto una linea lleva
hash, pip exige --require-hashes para todas y falla si el wheel de la
plataforma del usuario no esta listado. Para un unico dependency opcional con
un 'pip install' documentado en el propio fichero, eso es una trampa, no una
mejora.

Retira SEC-DEP-SIN-FIJAR-002: la dimension de cadena de suministro de la
revision de seguridad baja de moderado a bajo. 309 tests en verde en 3.12 y
3.13.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@novanoticia
novanoticia merged commit b43a58d into main Aug 12, 2026
3 checks passed
@novanoticia
novanoticia deleted the portabilidad-agentskills branch August 12, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant