Author SHA1 Message Date
DavidSosa 9565b4410b docs: documenta el contrato de plugins CMS personalizados (checkPoints en cms/lib/plugins/index.php)
gitleaks / gitleaks (push) Canceled after 0s
gitleaks / gitleaks (pull_request) Canceled after 0s
No estaba documentado en absoluto (solo se mencionaba builder_saas para color). Evita tener que
explorar el servidor por SSH para entender addPlugins/checkPoints/gating/filtros.
2026-09-14 14:52:58 +01:00
DavidSosa 6771918919 docs: corrige API de hooks (CocoDB::getInstance no existe) y documenta modelo de ejecución, escape() y aviso de fiabilidad de write-ops del MCP
gitleaks / gitleaks (push) Canceled after 0s
Hallazgos de fricción real en producción (turismo, onefix-appv1, pisorentado.com):
- CocoDB::getInstance() no existe (fatal), sustituido por DB::getInstance() como última opción
- El enrutado HTTP directo de /hooks/<id>/ da 404 en Forge; documentado el patrón vía filtro | hook
- Modelo de ejecución eval(): __DIR__ vs APP_ROOT_PATH, params por HTTP vs filtro Twig
- DB::escape() ya entrecomilla el literal; patrón seguro documentado
- Nunca delegar hashing de contraseñas en el CMS (create_field no tiene tipo password; SHA1 inadecuado)
- Aviso de que success:true en write-ops/DDL del MCP no garantiza que la operación se aplicó
2026-09-14 14:51:44 +01:00
Jordan DiazandClaude Fable 5.1 05d4be1af0 chore(seguridad): prevención de secretos en un repo público
gitleaks / gitleaks (push) Canceled after 0s
- .gitignore: .claude/settings.local.json (ahí van las claves), .env, *.pem, *.key
- .githooks/pre-push: bloquea el push si lo que sube contiene claves (gitleaks
  si está instalado; si no, grep de los formatos más comunes). Activar con
  git config core.hooksPath .githooks
- .gitea/workflows/gitleaks.yml: escaneo en cada push y pull request
- CLAUDE.md: regla 14, el repo es público y las claves nunca se versionan

Motivo: en 2026-03 se subió una clave de API en .claude/settings.json. Ya está
revocada y el historial reescrito para quitarla.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HPL66q9F33YZxbLPUT7KW8
2026-09-11 15:20:12 +00:00
Jordan Diaz ac6c8e1fbd mas 2026-04-01 15:25:06 +00:00
Jordan Diaz 34db92d5f7 ajustes mios 2026-04-01 15:11:28 +00:00
Jordan Diaz bfb6f8df5b mas 2026-04-01 14:49:19 +00:00
Jordan Diaz db7d85133a mejora mia 2026-04-01 14:39:07 +00:00
Jordan Diaz 730237d1b9 mas cambios codex 2026-04-01 14:14:45 +00:00
Jordan Diaz 586679d84e de codex 2026-04-01 14:08:23 +00:00
Jordan Diaz d00697ca36 ajustes de codex 2026-04-01 13:37:45 +00:00
Jordan Diaz 9526443b4a ajustes codex 2026-04-01 13:31:36 +00:00
Jordan Diaz c51c5222d5 Mas ajustes de codex 2026-04-01 12:57:24 +00:00
Jordan Diaz 555d2cb3c1 Ajusets desde codex 2026-04-01 12:49:26 +00:00
Jordan Diaz 50a0bb9549 fix: update compile and git handlers for permission normalization 2026-04-01 10:42:58 +00:00
Jordan Diaz a90f3e1ef2 fix: update compile and git handlers for permission normalization 2026-04-01 10:39:04 +00:00
Jordan ec98ea6038 Update CLAUDE.md 2026-03-31 13:23:16 +01:00
Jordan 383f4bbda0 Update CLAUDE.md 2026-03-31 13:22:50 +01:00
Jordan 6d3d904aa6 Update builder-fields.md 2026-03-29 12:42:30 +01:00
Jordan 8f38d9ea7d fdsfds 2026-03-28 22:54:31 +00:00
Jordan 14c69ed0d2 otro 2026-03-28 22:45:06 +00:00
Jordan dee931f100 Update CLAUDE.md 2026-03-28 22:12:30 +00:00
Jordan adfbc1ded6 Update mcp-tools-reference.md 2026-03-28 21:54:26 +00:00
Jordan a0a14921cf ajustes 2026-03-28 13:38:38 +00:00
Jordan fe0db50d12 Update settings.json 2026-03-27 18:26:13 +00:00
Jordan 490fe6104a Update settings.json 2026-03-27 18:18:42 +00:00
Jordan 84690ce1d3 Update settings.json 2026-03-27 17:52:43 +00:00
6 changed files with 168 additions and 2 deletions
+27
View File
@@ -0,0 +1,27 @@
# Escaneo de secretos en cada push y pull request. Este repo es PUBLICO.
# Falla si gitleaks encuentra una clave en los commits nuevos.
name: gitleaks
on:
push:
pull_request:
jobs:
gitleaks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Instalar gitleaks
run: |
set -e
VERSION=8.21.2
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${VERSION}/gitleaks_${VERSION}_linux_x64.tar.gz" -o /tmp/gitleaks.tgz
tar -xzf /tmp/gitleaks.tgz -C /tmp gitleaks
sudo install -m 755 /tmp/gitleaks /usr/local/bin/gitleaks
gitleaks version
- name: Escanear el historial
run: gitleaks git --no-banner --redact --verbose .
+49
View File
@@ -0,0 +1,49 @@
#!/bin/sh
# Hook pre-push: bloquea el push si lo que se va a subir contiene secretos.
#
# Activar una vez por clon: git config core.hooksPath .githooks
#
# Este repo es PUBLICO: un secreto que llega al remoto queda expuesto aunque se
# borre en el commit siguiente (sigue en el historial). En 2026-03 se filtro asi
# una clave de API pegada en .claude/settings.json.
#
# Si gitleaks esta instalado (https://github.com/gitleaks/gitleaks) se usa el;
# si no, se aplica un escaneo minimo con grep sobre los formatos mas comunes.
set -u
remote="$1"
z40=0000000000000000000000000000000000000000
fallo=0
while read -r local_ref local_sha remote_ref remote_sha; do
[ "$local_sha" = "$z40" ] && continue # borrado de rama: nada que escanear
if [ "$remote_sha" = "$z40" ]; then
rango="$local_sha" # rama nueva: todo lo alcanzable
else
rango="$remote_sha..$local_sha"
fi
if command -v gitleaks >/dev/null 2>&1; then
if ! gitleaks git --no-banner --redact --log-opts="$rango" . ; then
fallo=1
fi
else
# Escaneo minimo: lineas AÑADIDAS en el rango con formatos de clave conocidos.
hits=$(git log -p --no-color "$rango" -- . ':!*.min.*' ':!*.lock' \
| grep -E '^\+' \
| grep -E 'AIza[0-9A-Za-z_-]{35}|sk_(live|test)_[0-9A-Za-z]{20,}|ghp_[0-9A-Za-z]{30,}|xox[bp]-[0-9A-Za-z-]{10,}|AKIA[0-9A-Z]{16}|eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{10,}|-----BEGIN [A-Z ]*PRIVATE KEY|(ANTHROPIC_AUTH_TOKEN|ANTHROPIC_API_KEY|OPENAI_API_KEY|API_KEY|SECRET|PASSWORD|TOKEN)["'"'"']?[[:space:]]*[:=][[:space:]]*["'"'"'][^"'"'"']{8,}' \
| grep -vE '\$\{?[A-Z_]+\}?|\*\*\*|EJEMPLO|EXAMPLE|placeholder' )
if [ -n "$hits" ]; then
echo "pre-push: posibles secretos en lo que vas a subir ($rango):" >&2
printf '%s\n' "$hits" | cut -c1-120 | sed 's/^/ /' >&2
echo "pre-push: instala gitleaks para un escaneo completo. Si es un falso positivo, --no-verify." >&2
fallo=1
fi
fi
done
if [ "$fallo" -ne 0 ]; then
echo "pre-push: PUSH BLOQUEADO. Quita el secreto del commit (git commit --amend o rebase) y rota la clave si ya la has pegado en algun sitio." >&2
exit 1
fi
exit 0
+14
View File
@@ -0,0 +1,14 @@
# Configuracion LOCAL de Claude Code: aqui van las claves (env con ANTHROPIC_*,
# tokens de proveedores...). NUNCA en .claude/settings.json, que es la plantilla
# versionada y este repo es PUBLICO. En 2026-03 se filtro una clave por ahi.
.claude/settings.local.json
# Secretos y entorno
.env
.env.*
*.pem
*.key
# Ruido de sistema
.DS_Store
Thumbs.db
+2 -1
View File
@@ -129,6 +129,7 @@ Do NOT modify web-base files — they are shared across all projects.
11. Twig concatenation uses `~` operator: `'value=' ~ variable`
12. `enlace` (link) fields already include slashes — **NEVER modify an existing enlace** unless explicitly asked
13. **NEVER modify the `controlador` field** of existing records — it defines whether a page is Builder or Standard
14. **This repository is PUBLIC. NEVER commit secrets** (API keys, `ANTHROPIC_*` tokens, passwords). Local Claude Code config with credentials goes in `.claude/settings.local.json` (git-ignored), never in `.claude/settings.json`. Enable the secret-scanning hook once per clone: `git config core.hooksPath .githooks`. A leaked key stays in git history even after deleting it: rotate it immediately.
## MCP Tools
@@ -147,7 +148,7 @@ Key workflows:
- [docs/modular-system.md](docs/modular-system.md) — Modules, general sections, global variables
- [docs/builder-fields.md](docs/builder-fields.md) — Builder field types, Acai attributes, c-form, components
- [docs/twig-filters.md](docs/twig-filters.md) — Twig filters reference (get, hook, module, queryDB, etc.)
- [docs/hooks-and-api.md](docs/hooks-and-api.md) — PHP hooks, CmsApi, CocoDB, record creation
- [docs/hooks-and-api.md](docs/hooks-and-api.md) — PHP hooks, CmsApi, CocoDB, record creation, custom CMS plugins (checkPoints)
- [docs/css-js-conventions.md](docs/css-js-conventions.md) — CSS/JS/Vue 3, Tailwind, BEM, native components
- [docs/quick-reference.md](docs/quick-reference.md) — Cheat sheet: domain rules, field types, filters
- [docs/production-patterns.md](docs/production-patterns.md) — Real production patterns (header, zigzag, FAQ, forms)
+60 -1
View File
@@ -33,7 +33,9 @@ Regla práctica:
- No uses `exit`
- Para leer parámetros, usa `$_REQUEST[...]` o las variables ya inyectadas por el sistema
- En hooks, usa `CmsApi::get()` o `CocoDB::get()` como primera opción
- No uses `CocoDB::getInstance()` salvo necesidad real muy excepcional
- **`CocoDB::getInstance()` no existe** — llamarlo produce un fatal `Call to undefined method`. Si de
verdad necesitas BD cruda (fuera de `CmsApi`/`CocoDB`), usa `DB::getInstance()`
(`execute`/`executeS`/`fetch`/`escape`/`foundRows`), y solo como última opción
- No escribas SQL manual con `prepare()/bind_param()` salvo que no exista forma razonable de resolverlo con `CmsApi` o `CocoDB`
### Estructura de un Hook
@@ -107,6 +109,63 @@ $mensaje = $result["message"];
**Desde c-form:** Los hooks se ejecutan automáticamente al enviar el formulario si están configurados.
### Diagnóstico: el enrutado HTTP directo de hooks no funciona en Forge
`/hooks/<id>/` da **404 incluso para hooks válidos** cuando se pide por HTTP directo en un entorno
Forge — el único camino fiable es el filtro Twig `| hook` (o `<hook>`/`CmsApi.hook()`, que pasan por el
mismo motor). Si un hook "no responde" y no ves error claro, diagnostica así:
1. Crea un módulo/página temporal que solo llame al hook con `| hook` y vuelque el resultado:
```twig
{% set d = 'hooks/mihook/' | hook({param1: 100}) %}
<pre>{{ d|json_encode(1) }}</pre>
```
2. Si eso funciona pero la llamada por HTTP/JS falla, el problema está en cómo se invoca, no en el hook.
### Modelo de ejecución de los hooks (importante)
Los hooks corren vía `eval()` dentro del motor (`builder_saas/builder_functions.php`), lo que rompe dos
supuestos habituales de PHP:
- **`__DIR__` no es la carpeta del hook** — apunta al motor. Un `require_once __DIR__.'/x.php'` falla con
*"Failed opening required"*. Usa `APP_ROOT_PATH` (con fallback a `$_SERVER['DOCUMENT_ROOT']`) para
construir rutas absolutas dentro del proyecto.
- **Un hook llamado por HTTP (`CmsApi.hook()` desde JS) no recibe los parámetros como variables PHP
sueltas** — eso solo ocurre cuando se invoca por el filtro Twig `'id' | hook({...})`. Por HTTP,
`get_defined_vars()` sale vacío: lee `$_REQUEST[...]` en su lugar.
Cada uno de estos dos puntos, sin saberlo, cuesta una sesión de depuración completa (el hook "falla" con
datos correctos y sin error visible).
### Seguridad en hooks
- **`DB::escape()` devuelve el literal YA entrecomillado.** Envolverlo a mano
(`"campo = '".escape($v)."'"`) produce `''valor''` → error de sintaxis MariaDB. Para `LIKE`, escapa el
patrón completo con sus `%` incluidos. Patrón seguro para filtros que vienen del usuario: lista blanca
de campos permitidos, `(int)` para ids, y devolver a Twig solo lo necesario (p.ej. `num`), nunca el SQL
construido a mano.
- **Nunca deleguen el hashing de contraseñas en el CMS.** `create_field` no tiene tipo contraseña;
`isPasswordField:1` en un textfield **no hashea** (guarda y lee la cadena literal), y el
`editor_password` del CMS usa **SHA1** (inadecuado). En cualquier login propio, hashea en el hook con
`password_hash()` (bcrypt/argon2id) + `password_verify()`, y marca el campo `clave_hash` como
`adminOnly`.
---
## Plugins CMS personalizados (checkPoints)
Para entender **cómo despacha el CMS un plugin custom** (no confundir con hooks de módulo), lee primero
`cms/lib/plugins/index.php` **de ese proyecto** — no hace falta explorar el servidor por SSH ni el
código de `web-base`, el contrato completo está ahí:
- `addPlugins($type, &$var, $fieldtype)` despacha los checkPoints con `include` (no clases/autoload).
- Gating nativo por `adminOnly` + `isAdmin` — un checkPoint puede declararse solo-admin.
- `$var` se pasa **por referencia** — un plugin puede mutar el valor que ven los siguientes.
- Filtros `menuFilter`/`actionFilter` por tabla/action, para que un plugin solo se dispare donde aplica.
- `newFieldType` para registrar tipos de campo nuevos del builder.
- La instalación de un plugin va vía `install.sql` + `schemaPresets` — **no** `schemas/` (ese es el
sistema viejo de `seo_toolkit`, con el `install.php` comentado; no lo repliques).
---
## CmsApi (PHP)
+16
View File
@@ -1,5 +1,21 @@
# MCP Tools Reference
## ⚠️ `success:true` no siempre significa que la operación se aplicó
Las write-ops/DDL (`create_field`, `update_field`, `acai-write`, `acai-line-replace`,
`set_layout_field`, `update_table_metadata`, `create_table`…) pueden devolver `success:true` /
`writeCompleted:true` **aunque el cambio real (ALTER de MySQL, insert, compilación Twig) no se haya
aplicado**. `get_table_schema` refleja el registro/`.ini`, no necesariamente el estado real de MySQL.
**Verifica siempre con una operación real posterior**, nunca te fíes solo de la respuesta del tool:
- Tras escribir/editar un módulo: `compile_module` o recargar la página y comprobar que renderiza.
- Tras crear/editar un campo: hacer un insert/update real en esa tabla y confirmar que no falla.
- Tras un insert, si necesitas el id, no te fíes ciegamente de `lastId`/`insert_id` — si algo no cuadra,
relee por un campo único ya conocido.
Es más lento que confiar en la respuesta, pero el coste de no hacerlo es peor: este es el fallo más
repetido del equipo (bloqueos de insert silenciosos, módulos servidos rotos, registros fantasma).
## Quick Reference
| Tool | Categoría | Acción |