@saulwade/swl-ses 1.3.8 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/CLAUDE.md +15 -6
  2. package/README.md +15 -14
  3. package/agentes/nemesis-auditor-swl.md +161 -0
  4. package/bin/swl-mcp-server.js +187 -187
  5. package/bin/swl-webhook-server.js +198 -0
  6. package/comandos/swl/.evolved.json +22 -22
  7. package/comandos/swl/adoptar-proyecto.md +21 -1
  8. package/comandos/swl/claudemd.md +14 -1
  9. package/comandos/swl/contribuir.md +233 -233
  10. package/comandos/swl/exportar-vault.md +108 -0
  11. package/comandos/swl/nemesis.md +122 -0
  12. package/comandos/swl/nuevo-proyecto.md +24 -2
  13. package/comandos/swl/salud.md +34 -0
  14. package/comandos/swl/verificar.md +45 -0
  15. package/gateway/adapters/base.js +109 -0
  16. package/gateway/adapters/discord.js +167 -0
  17. package/gateway/adapters/email.js +221 -0
  18. package/gateway/adapters/slack.js +192 -0
  19. package/gateway/adapters/telegram.js +183 -0
  20. package/gateway/adapters/webhook.js +113 -0
  21. package/gateway/adapters/whatsapp.js +214 -0
  22. package/gateway/agent-executor.js +322 -0
  23. package/gateway/command-relay.js +271 -0
  24. package/gateway/cron/jobs.js +263 -0
  25. package/gateway/cron/scheduler.js +322 -0
  26. package/gateway/cron/store.js +335 -0
  27. package/gateway/index.js +320 -0
  28. package/gateway/lib/event-channel.js +191 -0
  29. package/gateway/session.js +131 -0
  30. package/gateway/webhook-server.js +324 -0
  31. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  32. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  33. package/habilidades/build-errors-nextjs/SKILL.md +55 -1
  34. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  35. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  36. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  37. package/habilidades/eval-framework/SKILL.md +212 -212
  38. package/habilidades/extractor-de-aprendizajes/SKILL.md +20 -10
  39. package/habilidades/feynman-auditor-swl/SKILL.md +123 -0
  40. package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -0
  41. package/habilidades/harness-claude-code/SKILL.md +299 -299
  42. package/habilidades/infra-github-actions/SKILL.md +166 -166
  43. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  44. package/habilidades/manejo-errores/.evolved.json +8 -8
  45. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  46. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  47. package/habilidades/nextjs-testing/SKILL.md +89 -5
  48. package/habilidades/node-experto/SKILL.md +37 -1
  49. package/habilidades/patrones-python/SKILL.md +229 -229
  50. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  51. package/habilidades/planear-fase/SKILL.md +319 -319
  52. package/habilidades/react-experto/SKILL.md +45 -4
  53. package/habilidades/release-semver/.evolved.json +8 -8
  54. package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -0
  55. package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -0
  56. package/habilidades/tdd-workflow/SKILL.md +36 -4
  57. package/habilidades/testing-python/SKILL.md +340 -340
  58. package/habilidades/web-fetcher-routing/SKILL.md +75 -0
  59. package/hooks/claudemd-bloat-detector.js +161 -161
  60. package/hooks/inyeccion-contexto.js +8 -3
  61. package/hooks/lib/agent-routing.js +107 -107
  62. package/hooks/lib/auto-consolidator.js +335 -335
  63. package/hooks/lib/error-classifier.js +308 -308
  64. package/hooks/lib/merkle-audit.js +96 -96
  65. package/hooks/lib/provenance-tracker.js +191 -191
  66. package/hooks/lib/rate-limit-ip.js +177 -0
  67. package/hooks/lib/rate-limit-tracker.js +253 -253
  68. package/hooks/lib/resource-quota.js +122 -122
  69. package/hooks/lib/retry-jitter.js +165 -165
  70. package/hooks/lib/security-net.js +201 -0
  71. package/hooks/lib/skill-auditor.js +588 -588
  72. package/hooks/lib/sync-status.js +228 -228
  73. package/hooks/lib/taint-tracker.js +107 -107
  74. package/hooks/lib/text-similarity.js +241 -241
  75. package/hooks/lib/toon-compressor.js +245 -245
  76. package/hooks/lib/webhook-dedup.js +184 -0
  77. package/hooks/lib/webhook-verify.js +123 -0
  78. package/hooks/proteccion-rutas.js +120 -15
  79. package/hooks/registro-turnos.js +209 -209
  80. package/hooks/sugerir-regenerar-inventario.js +170 -170
  81. package/hooks/validar-formato-post-subagente.js +140 -140
  82. package/hooks/validar-memoria-hook.js +218 -218
  83. package/instintos/prompt-appendices.yaml +57 -57
  84. package/manifiestos/agent-output-schemas.json +57 -57
  85. package/manifiestos/modulos.json +31 -0
  86. package/manifiestos/skills-lock.json +1114 -1093
  87. package/package.json +6 -4
  88. package/plantillas/auditor-veto-template.md +105 -105
  89. package/plantillas/github-workflows/README.md +47 -47
  90. package/plantillas/github-workflows/release-please.yml +44 -44
  91. package/plantillas/github-workflows/swl-ci.yml +107 -107
  92. package/plantillas/github-workflows/swl-security.yml +51 -51
  93. package/plugin.json +2 -2
  94. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  95. package/reglas/arreglar-al-detectar.md +147 -147
  96. package/reglas/fragmentos-compartidos.md +152 -152
  97. package/reglas/harness-claude-code.md +213 -213
  98. package/reglas/usar-context7.md +226 -226
  99. package/reglas/usar-sistema-swl.md +251 -0
  100. package/schemas/diary-entry.schema.json +80 -80
  101. package/scripts/audit-tools/audit-history.js +330 -0
  102. package/scripts/audit-tools/bundle-tracker.js +290 -0
  103. package/scripts/audit-tools/canary-monitor.js +352 -0
  104. package/scripts/audit-tools/code-profiler.js +605 -0
  105. package/scripts/audit-tools/dep-doctor.js +320 -0
  106. package/scripts/audit-tools/env-validator.js +206 -0
  107. package/scripts/audit-tools/lib/fs-walk.js +48 -0
  108. package/scripts/audit-tools/lib/output.js +23 -0
  109. package/scripts/audit-tools/migration-checker.js +392 -0
  110. package/scripts/audit-tools/pentest-scanner.js +1436 -0
  111. package/scripts/benchmark-memoria.js +167 -167
  112. package/scripts/comandos/skills.js +251 -2
  113. package/scripts/configurar-branch-protection.js +418 -418
  114. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  115. package/scripts/field-report.js +199 -199
  116. package/scripts/generar-checklists-consolidados.js +273 -273
  117. package/scripts/generar-inventario.js +420 -420
  118. package/scripts/generar-matriz-lenguajes.js +271 -271
  119. package/scripts/lib/artefactos-python.js +43 -43
  120. package/scripts/lib/benchmark-metrics.js +160 -160
  121. package/scripts/lib/budget-enforcer.js +252 -252
  122. package/scripts/lib/configurar-ci.js +380 -380
  123. package/scripts/lib/contadores-inventario.js +217 -217
  124. package/scripts/lib/detectar-stack-detallado.js +307 -307
  125. package/scripts/lib/diary-entry.js +234 -234
  126. package/scripts/lib/eval-metrics-store.js +218 -218
  127. package/scripts/lib/eval-quality.js +171 -171
  128. package/scripts/lib/eval-schemas.js +144 -144
  129. package/scripts/lib/eval-self-correct.js +106 -106
  130. package/scripts/lib/eval-validator.js +185 -185
  131. package/scripts/lib/jaccard-similarity.js +98 -98
  132. package/scripts/lib/longmemeval-runner.js +125 -125
  133. package/scripts/lib/npm-version.js +261 -261
  134. package/scripts/lib/paquetes-conocidos.js +50 -50
  135. package/scripts/lib/prompt-builder.js +264 -264
  136. package/scripts/lib/rrf-fusion.js +175 -175
  137. package/scripts/lib/scoring-instintos.js +277 -277
  138. package/scripts/lib/semantic-search.js +252 -252
  139. package/scripts/limpiar-artefactos-python.js +131 -131
  140. package/scripts/mcp-server/README.md +128 -128
  141. package/scripts/mcp-server/handlers.js +206 -206
  142. package/scripts/migrar-csv-a-array.js +168 -168
  143. package/scripts/migrar-fase-dominio.js +201 -201
  144. package/scripts/publicar.js +511 -511
  145. package/scripts/run-eval.js +141 -141
  146. package/scripts/validar-manifest.js +195 -195
  147. package/scripts/validar-userland-vacio.js +110 -110
  148. package/scripts/verificar-release.js +110 -0
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * webhook-verify.js — Verificación de firmas HMAC en webhooks entrantes.
5
+ *
6
+ * Provee dos funciones independientes:
7
+ *
8
+ * - verifyGithubSignature(payloadBody, signatureHeader, secret)
9
+ * GitHub envía la firma como header `X-Hub-Signature-256: sha256=<hex>`.
10
+ * Calcula HMAC SHA-256 sobre el body crudo y compara timing-safe.
11
+ *
12
+ * - verifyBearer(authorizationHeader, secret)
13
+ * Genérico: header `Authorization: Bearer <token>` comparado timing-safe
14
+ * contra el secreto compartido.
15
+ *
16
+ * Ambas funciones:
17
+ * - Aceptan body como Buffer o string. Buffer es lo correcto para HMAC
18
+ * porque GitHub firma los bytes literales del request, no la cadena
19
+ * decodificada.
20
+ * - Retornan boolean. Nunca lanzan excepciones — falla = false.
21
+ * - Usan `crypto.timingSafeEqual` para evitar ataques de timing.
22
+ * - Zero-deps (solo `crypto` nativo).
23
+ *
24
+ * Origen: port de `temp/claude-code-telegram-main/src/api/auth.py` (Python, 61 LOC).
25
+ *
26
+ * @module hooks/lib/webhook-verify
27
+ */
28
+
29
+ const crypto = require('crypto');
30
+
31
+ /**
32
+ * Verifica firma HMAC SHA-256 al estilo GitHub.
33
+ *
34
+ * @param {Buffer|string} payloadBody Body crudo del request.
35
+ * @param {string|undefined|null} signatureHeader Valor del header X-Hub-Signature-256.
36
+ * @param {string} secret Secreto compartido configurado en GitHub.
37
+ * @returns {boolean} true si la firma es válida; false en cualquier otro caso.
38
+ */
39
+ function verifyGithubSignature(payloadBody, signatureHeader, secret) {
40
+ if (!signatureHeader || typeof signatureHeader !== 'string') {
41
+ return false;
42
+ }
43
+ if (!signatureHeader.startsWith('sha256=')) {
44
+ return false;
45
+ }
46
+ if (!secret || typeof secret !== 'string') {
47
+ return false;
48
+ }
49
+ if (payloadBody === undefined || payloadBody === null) {
50
+ return false;
51
+ }
52
+
53
+ const bodyBuffer = Buffer.isBuffer(payloadBody)
54
+ ? payloadBody
55
+ : Buffer.from(String(payloadBody), 'utf8');
56
+
57
+ const expectedDigest = crypto
58
+ .createHmac('sha256', secret)
59
+ .update(bodyBuffer)
60
+ .digest('hex');
61
+ const expectedHeader = 'sha256=' + expectedDigest;
62
+
63
+ return constantTimeEqual(expectedHeader, signatureHeader);
64
+ }
65
+
66
+ /**
67
+ * Verifica un Bearer token simple en el header Authorization.
68
+ *
69
+ * @param {string|undefined|null} authorizationHeader Valor del header Authorization.
70
+ * @param {string} secret Token esperado (sin el prefijo "Bearer ").
71
+ * @returns {boolean} true si el token coincide; false en cualquier otro caso.
72
+ */
73
+ function verifyBearer(authorizationHeader, secret) {
74
+ if (!authorizationHeader || typeof authorizationHeader !== 'string') {
75
+ return false;
76
+ }
77
+ if (!authorizationHeader.startsWith('Bearer ')) {
78
+ return false;
79
+ }
80
+ if (!secret || typeof secret !== 'string') {
81
+ return false;
82
+ }
83
+
84
+ const token = authorizationHeader.slice('Bearer '.length);
85
+ return constantTimeEqual(token, secret);
86
+ }
87
+
88
+ /**
89
+ * Comparación timing-safe de dos strings.
90
+ *
91
+ * `crypto.timingSafeEqual` requiere buffers de la misma longitud. Si las
92
+ * longitudes difieren, el comparador termina temprano y eso filtra
93
+ * información de longitud por timing. Para evitarlo:
94
+ *
95
+ * 1. Si longitudes difieren, retornar false sin invocar timingSafeEqual.
96
+ * 2. Si coinciden, comparar.
97
+ *
98
+ * El filtrado de longitud es aceptable aquí porque la longitud de la firma
99
+ * HMAC SHA-256 hex es fija (71 caracteres con prefijo "sha256=") y la
100
+ * longitud del Bearer es controlada por el secreto del servidor — ninguna
101
+ * es información sensible derivable del input del atacante.
102
+ *
103
+ * @param {string} a
104
+ * @param {string} b
105
+ * @returns {boolean}
106
+ */
107
+ function constantTimeEqual(a, b) {
108
+ if (typeof a !== 'string' || typeof b !== 'string') {
109
+ return false;
110
+ }
111
+ if (a.length !== b.length) {
112
+ return false;
113
+ }
114
+ const bufA = Buffer.from(a, 'utf8');
115
+ const bufB = Buffer.from(b, 'utf8');
116
+ return crypto.timingSafeEqual(bufA, bufB);
117
+ }
118
+
119
+ module.exports = {
120
+ verifyGithubSignature,
121
+ verifyBearer,
122
+ constantTimeEqual,
123
+ };
@@ -208,8 +208,10 @@ function verificarDentroDeCwd(rutaRaw, cwdActual) {
208
208
 
209
209
  /**
210
210
  * Rutas permitidas fuera del CWD.
211
- * Estas rutas son legítimas para el funcionamiento de Claude Code
212
- * (memoria persistente, configuración de proyecto, etc.).
211
+ * Estas rutas son legítimas para el funcionamiento de Claude Code y SWL
212
+ * (memoria persistente, configuración global, skills/agentes/comandos/reglas
213
+ * del usuario que SWL instala globalmente y que se modifican durante backports
214
+ * entre proyectos).
213
215
  *
214
216
  * Se comparan con startsWith case-insensitive en Windows.
215
217
  */
@@ -218,21 +220,100 @@ const RUTAS_PERMITIDAS_EXTERNAS = [
218
220
  path.join(os.homedir(), '.claude', 'projects'),
219
221
  // Configuración global de Claude Code
220
222
  path.join(os.homedir(), '.claude', 'settings.json'),
223
+ // Skills globales del usuario (instaladas por SWL, modificables durante backport)
224
+ path.join(os.homedir(), '.claude', 'skills'),
225
+ // Agentes globales del usuario
226
+ path.join(os.homedir(), '.claude', 'agents'),
227
+ // Comandos globales del usuario
228
+ path.join(os.homedir(), '.claude', 'commands'),
229
+ // Reglas globales del usuario (modificables durante consolidación cross-proyecto)
230
+ path.join(os.homedir(), '.claude', 'rules'),
221
231
  ];
222
232
 
223
233
  /**
224
- * Verifica si una ruta resuelta está en la lista de rutas externas permitidas.
234
+ * Parsea la variable de entorno SWL_PROTECCION_RUTAS_PERMITIR (CSV de rutas
235
+ * absolutas) y devuelve un array de paths normalizados.
236
+ *
237
+ * Patrón de uso: el usuario define la variable ANTES de iniciar Claude Code
238
+ * cuando necesita permitir writes legítimos a destinos fuera del CWD que no
239
+ * están cubiertos por el whitelist por defecto. Ej:
240
+ *
241
+ * SWL_PROTECCION_RUTAS_PERMITIR="D:\Python\sigaf,F:\vault\notas"
242
+ *
243
+ * Cada ruta se resuelve con path.resolve para normalizar separadores y
244
+ * eliminar referencias relativas. Rutas vacías o solo whitespace se descartan.
245
+ *
246
+ * @returns {string[]} array de paths absolutos resueltos (puede estar vacío)
247
+ */
248
+ function parsearRutasPermitidasUsuario() {
249
+ const raw = process.env.SWL_PROTECCION_RUTAS_PERMITIR;
250
+ if (!raw || typeof raw !== 'string') return [];
251
+ return raw
252
+ .split(',')
253
+ .map(r => r.trim())
254
+ .filter(r => r.length > 0)
255
+ .map(r => path.resolve(r));
256
+ }
257
+
258
+ /**
259
+ * Cache módulo-level del parseo de SWL_PROTECCION_RUTAS_PERMITIR.
260
+ *
261
+ * `process.env` no cambia durante la vida del proceso (los hooks se ejecutan
262
+ * como subproceso de Claude Code, un proceso nuevo por cada Write/Edit), por
263
+ * lo que el parseo solo necesita hacerse una vez al cargar el módulo. Esto
264
+ * evita re-parsear el CSV y volver a llamar `path.resolve` en cada call a
265
+ * `esRutaExternaPermitida()`, que se ejecuta por cada operación de escritura.
266
+ *
267
+ * Para tests que necesitan re-parsear (cambian la variable después de cargar
268
+ * el módulo), exportar la función `_resetCacheRutasPermitidas()` permitiría
269
+ * forzar el refresh — los tests existentes usan spawnSync, por lo que cada
270
+ * subproceso carga el módulo fresco con su propio entorno.
271
+ */
272
+ const RUTAS_PERMITIDAS_USUARIO = parsearRutasPermitidasUsuario();
273
+
274
+ /**
275
+ * Normaliza separadores y elimina trailing slash para comparación de prefijo
276
+ * con boundary check. Garantiza que la comparación sea sobre componentes de
277
+ * path completos, no sobre substrings.
278
+ *
279
+ * Ejemplo: ambas variantes de `~/.claude/skills/` y `~/.claude/skills` se
280
+ * normalizan a `.../.claude/skills` (sin trailing).
281
+ *
282
+ * @param {string} p
283
+ * @returns {string}
284
+ */
285
+ function normalizarParaComparacion(p) {
286
+ return normalizarSeparadores(p).replace(/\/+$/, '');
287
+ }
288
+
289
+ /**
290
+ * Verifica si una ruta resuelta está en la lista de rutas externas permitidas
291
+ * (whitelist por defecto + rutas opt-in del usuario vía variable de entorno).
292
+ *
293
+ * **Boundary check obligatorio**: usa match exacto O prefijo seguido de '/'
294
+ * para evitar que paths siblings con el mismo prefijo textual pasen la
295
+ * verificación. Ejemplos de hostiles que DEBEN ser bloqueados:
296
+ * - whitelist `~/.claude/skills` → `~/.claude/skills-evil/x.md` (no es subdir)
297
+ * - whitelist `~/.claude/settings.json` → `~/.claude/settings.json.bak`
298
+ * - whitelist `D:/Proyecto` → `D:/Proyecto-otro/file.md`
299
+ *
225
300
  * @param {string} rutaResuelta
226
301
  * @returns {boolean}
227
302
  */
228
303
  function esRutaExternaPermitida(rutaResuelta) {
229
- const rutaNorm = normalizarSeparadores(rutaResuelta);
230
- for (const permitida of RUTAS_PERMITIDAS_EXTERNAS) {
231
- const permitidaNorm = normalizarSeparadores(permitida);
232
- const coincide = process.platform === 'win32'
233
- ? rutaNorm.toLowerCase().startsWith(permitidaNorm.toLowerCase())
234
- : rutaNorm.startsWith(permitidaNorm);
235
- if (coincide) return true;
304
+ const rutaNorm = normalizarParaComparacion(rutaResuelta);
305
+ const rutaCmp = process.platform === 'win32' ? rutaNorm.toLowerCase() : rutaNorm;
306
+
307
+ const combinadas = RUTAS_PERMITIDAS_EXTERNAS.concat(RUTAS_PERMITIDAS_USUARIO);
308
+ for (const permitida of combinadas) {
309
+ const permitidaNorm = normalizarParaComparacion(permitida);
310
+ const permitidaCmp = process.platform === 'win32' ? permitidaNorm.toLowerCase() : permitidaNorm;
311
+
312
+ // Match exacto (cubre archivos como settings.json) O prefijo seguido de '/'
313
+ // (cubre subdirectorios como skills/foo dentro del directorio skills).
314
+ if (rutaCmp === permitidaCmp || rutaCmp.startsWith(permitidaCmp + '/')) {
315
+ return true;
316
+ }
236
317
  }
237
318
  return false;
238
319
  }
@@ -270,7 +351,8 @@ function extraerRuta(toolInput) {
270
351
  * @returns {string}
271
352
  */
272
353
  function construirMensajeBloqueo(tipoViolacion, rutaRaw, rutaResuelta, cwdActual) {
273
- return [
354
+ const esTraversal = /traversal/i.test(tipoViolacion);
355
+ const lineas = [
274
356
  `Escritura bloqueada por política de seguridad de rutas.`,
275
357
  ``,
276
358
  `Violación: ${tipoViolacion}`,
@@ -282,10 +364,33 @@ function construirMensajeBloqueo(tipoViolacion, rutaRaw, rutaResuelta, cwdActual
282
364
  ` - La ruta de destino debe estar dentro del directorio de trabajo actual.`,
283
365
  ` - No se permiten secuencias de path traversal (../, ~/, %2e%2e, etc.).`,
284
366
  ` - No se permiten rutas absolutas fuera del proyecto.`,
285
- ``,
286
- `Si necesitas escribir fuera del CWD, ajusta el directorio de trabajo`,
287
- `o utiliza una ruta relativa dentro del proyecto.`,
288
- ].join('\n');
367
+ ];
368
+
369
+ if (!esTraversal) {
370
+ lineas.push(
371
+ ``,
372
+ `Rutas externas permitidas por defecto:`,
373
+ ` - ~/.claude/projects, settings.json, skills, agents, commands, rules`,
374
+ ``,
375
+ `Para destinos legítimos no cubiertos (otros proyectos del usuario, vault,`,
376
+ `backports cross-proyecto), define la variable de entorno ANTES de iniciar`,
377
+ `Claude Code con un CSV de rutas absolutas:`,
378
+ ``,
379
+ ` PowerShell: $env:SWL_PROTECCION_RUTAS_PERMITIR = "D:\\Python\\sigaf,F:\\vault"`,
380
+ ` Bash: export SWL_PROTECCION_RUTAS_PERMITIR="/d/Python/sigaf,/f/vault"`,
381
+ ``,
382
+ `Alternativa: usar Bash con cp/heredoc (no pasa por este hook) cuando el`,
383
+ `destino es seguro y el cambio es puntual.`,
384
+ );
385
+ } else {
386
+ lineas.push(
387
+ ``,
388
+ `Path traversal NO se levanta con SWL_PROTECCION_RUTAS_PERMITIR — la variable`,
389
+ `solo afecta el check de "dentro del CWD", no las secuencias ../, ~/, %2e%2e.`,
390
+ );
391
+ }
392
+
393
+ return lineas.join('\n');
289
394
  }
290
395
 
291
396
  // ---------------------------------------------------------------------------